Compare commits

..

11 Commits

Author SHA1 Message Date
Anthony Stirling
c2062c19ab Merge remote-tracking branch 'origin/main' into bug/remember-me 2024-11-15 21:30:14 +00:00
Anthony Stirling
6760fddd6f Merge branch 'bug/remember-me' of
git@github.com:Stirling-Tools/Stirling-PDF.git into bug/remember-me
2024-11-15 21:26:51 +00:00
a
20a75d577d Merge branch 'bug/remember-me' of git@github.com:Stirling-Tools/Stirling-PDF.git into bug/remember-me 2024-11-15 13:13:34 +00:00
Anthony Stirling
559581c59d Merge branch 'bug/remember-me' of
git@github.com:Stirling-Tools/Stirling-PDF.git into bug/remember-me
2024-11-15 13:12:20 +00:00
Anthony Stirling
e7356a1d38 Merge branch 'main' into bug/remember-me 2024-11-07 20:39:19 +00:00
a
1405e4f5ee Merge branch 'bug/remember-me' of git@github.com:Stirling-Tools/Stirling-PDF.git into bug/remember-me 2024-11-07 20:38:54 +00:00
Anthony Stirling
322e7dee0d exe no longer disable CLI 2024-11-07 20:38:32 +00:00
github-actions[bot]
918e977c6a Update translation files (#2185)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-05 14:26:24 +00:00
Anthony Stirling
a31633e5b8 Merge branch 'main' into bug/remember-me 2024-11-05 14:24:44 +00:00
Anthony Stirling
ed551cec91 remove uselss comment 2024-11-05 14:24:28 +00:00
Anthony Stirling
3f14e77725 fix remmeber me 2024-11-05 14:22:55 +00:00
95 changed files with 1712 additions and 3778 deletions

View File

@@ -9,9 +9,8 @@ The script also provides functionality to update the translation files to match
adjusting the format. adjusting the format.
Usage: Usage:
python check_language_properties.py --reference-file <path_to_reference_file> --branch <branch_name> [--actor <actor_name>] [--files <list_of_changed_files>] python script_name.py --reference-file <path_to_reference_file> --branch <branch_name> [--files <list_of_changed_files>]
""" """
import copy import copy
import glob import glob
import os import os
@@ -19,10 +18,6 @@ import argparse
import re import re
# Maximum size for properties files (e.g., 200 KB)
MAX_FILE_SIZE = 200 * 1024
def parse_properties_file(file_path): def parse_properties_file(file_path):
"""Parses a .properties file and returns a list of objects (including comments, empty lines, and line numbers).""" """Parses a .properties file and returns a list of objects (including comments, empty lines, and line numbers)."""
properties_list = [] properties_list = []
@@ -100,7 +95,7 @@ def write_json_file(file_path, updated_properties):
def update_missing_keys(reference_file, file_list, branch=""): def update_missing_keys(reference_file, file_list, branch=""):
reference_properties = parse_properties_file(reference_file) reference_properties = parse_properties_file(reference_file)
for file_path in file_list: for file_path in file_list:
basename_current_file = os.path.basename(os.path.join(branch, file_path)) basename_current_file = os.path.basename(branch + file_path)
if ( if (
basename_current_file == os.path.basename(reference_file) basename_current_file == os.path.basename(reference_file)
or not file_path.endswith(".properties") or not file_path.endswith(".properties")
@@ -108,7 +103,7 @@ def update_missing_keys(reference_file, file_list, branch=""):
): ):
continue continue
current_properties = parse_properties_file(os.path.join(branch, file_path)) current_properties = parse_properties_file(branch + file_path)
updated_properties = [] updated_properties = []
for ref_entry in reference_properties: for ref_entry in reference_properties:
ref_entry_copy = copy.deepcopy(ref_entry) ref_entry_copy = copy.deepcopy(ref_entry)
@@ -119,79 +114,60 @@ def update_missing_keys(reference_file, file_list, branch=""):
if ref_entry_copy["key"] == current_entry["key"]: if ref_entry_copy["key"] == current_entry["key"]:
ref_entry_copy["value"] = current_entry["value"] ref_entry_copy["value"] = current_entry["value"]
updated_properties.append(ref_entry_copy) updated_properties.append(ref_entry_copy)
write_json_file(os.path.join(branch, file_path), updated_properties) write_json_file(branch + file_path, updated_properties)
def check_for_missing_keys(reference_file, file_list, branch): def check_for_missing_keys(reference_file, file_list, branch):
update_missing_keys(reference_file, file_list, branch) update_missing_keys(reference_file, file_list, branch + "/")
def read_properties(file_path): def read_properties(file_path):
if os.path.isfile(file_path) and os.path.exists(file_path): with open(file_path, "r", encoding="utf-8") as file:
with open(file_path, "r", encoding="utf-8") as file: return file.read().splitlines()
return file.read().splitlines()
return [""]
def check_for_differences(reference_file, file_list, branch, actor): def check_for_differences(reference_file, file_list, branch):
reference_branch = reference_file.split("/")[0] reference_branch = reference_file.split("/")[0]
basename_reference_file = os.path.basename(reference_file) basename_reference_file = os.path.basename(reference_file)
report = [] report = []
report.append(f"#### 🔄 Reference Branch: `{reference_branch}`") report.append(
f"### 📋 Checking with the file `{basename_reference_file}` from the `{reference_branch}` - Checking the `{branch}`"
)
reference_lines = read_properties(reference_file) reference_lines = read_properties(reference_file)
has_differences = False has_differences = False
only_reference_file = True only_reference_file = True
file_arr = file_list for file_path in file_list:
basename_current_file = os.path.basename(branch + "/" + file_path)
if len(file_list) == 1:
file_arr = file_list[0].split()
base_dir = os.path.abspath(os.path.join(os.getcwd(), "src", "main", "resources"))
for file_path in file_arr:
absolute_path = os.path.abspath(file_path)
# Verify that file is within the expected directory
if not absolute_path.startswith(base_dir):
raise ValueError(f"Unsafe file found: {file_path}")
# Verify file size before processing
if os.path.getsize(os.path.join(branch, file_path)) > MAX_FILE_SIZE:
raise ValueError(
f"The file {file_path} is too large and could pose a security risk."
)
basename_current_file = os.path.basename(os.path.join(branch, file_path))
if ( if (
basename_current_file == basename_reference_file basename_current_file == basename_reference_file
or not file_path.startswith(
os.path.join("src", "main", "resources", "messages_")
)
or not file_path.endswith(".properties") or not file_path.endswith(".properties")
or not basename_current_file.startswith("messages_") or not basename_current_file.startswith("messages_")
): ):
continue continue
only_reference_file = False only_reference_file = False
report.append(f"#### 📃 **File Check:** `{basename_current_file}`") report.append(f"#### 🗂️ **Checking File:** `{basename_current_file}`...")
current_lines = read_properties(os.path.join(branch, file_path)) current_lines = read_properties(branch + "/" + file_path)
reference_line_count = len(reference_lines) reference_line_count = len(reference_lines)
current_line_count = len(current_lines) current_line_count = len(current_lines)
if reference_line_count != current_line_count: if reference_line_count != current_line_count:
report.append("") report.append("")
report.append("1. **Test Status:** ❌ **_Failed_**") report.append("- **Test 1 Status:** ❌ Failed")
report.append(" - **Issue:**")
has_differences = True has_differences = True
if reference_line_count > current_line_count: if reference_line_count > current_line_count:
report.append( report.append(
f" - **_Mismatched line count_**: {reference_line_count} (reference) vs {current_line_count} (current). Comments, empty lines, or translation strings are missing." f" - **Issue:** Missing lines! Comments, empty lines, or translation strings are missing. Details: {reference_line_count} (reference) vs {current_line_count} (current)."
) )
elif reference_line_count < current_line_count: elif reference_line_count < current_line_count:
report.append( report.append(
f" - **_Too many lines_**: {reference_line_count} (reference) vs {current_line_count} (current). Please verify if there is an additional line that needs to be removed." f" - **Issue:** Too many lines! Check your translation files! Details: {reference_line_count} (reference) vs {current_line_count} (current)."
) )
# update_missing_keys(reference_file, [file_path], branch + "/")
else: else:
report.append("1. **Test Status:** ✅ **_Passed_**") report.append("- **Test 1 Status:** ✅ Passed")
# Check for missing or extra keys # Check for missing or extra keys
current_keys = [] current_keys = []
@@ -216,42 +192,32 @@ def check_for_differences(reference_file, file_list, branch, actor):
has_differences = True has_differences = True
missing_keys_str = "`, `".join(missing_keys_list) missing_keys_str = "`, `".join(missing_keys_list)
extra_keys_str = "`, `".join(extra_keys_list) extra_keys_str = "`, `".join(extra_keys_list)
report.append("2. **Test Status:** ❌ **_Failed_**") report.append("- **Test 2 Status:** ❌ Failed")
report.append(" - **Issue:**")
if missing_keys_list: if missing_keys_list:
spaces_keys_list = []
for key in missing_keys_list:
if " " in key:
spaces_keys_list.append(key)
if spaces_keys_list:
spaces_keys_str = "`, `".join(spaces_keys_list)
report.append(
f" - **_Keys containing unnecessary spaces_**: `{spaces_keys_str}`!"
)
report.append( report.append(
f" - **_Extra keys in `{basename_current_file}`_**: `{missing_keys_str}` that are not present in **_`{basename_reference_file}`_**." f" - **Issue:** There are keys in ***{basename_current_file}*** `{missing_keys_str}` that are not present in ***{basename_reference_file}***!"
) )
if extra_keys_list: if extra_keys_list:
report.append( report.append(
f" - **_Missing keys in `{basename_reference_file}`_**: `{extra_keys_str}` that are not present in **_`{basename_current_file}`_**." f" - **Issue:** There are keys in ***{basename_reference_file}*** `{extra_keys_str}` that are not present in ***{basename_current_file}***!"
) )
# update_missing_keys(reference_file, [file_path], branch + "/")
else: else:
report.append("2. **Test Status:** ✅ **_Passed_**") report.append("- **Test 2 Status:** ✅ Passed")
# if has_differences:
# report.append("")
# report.append(f"#### 🚧 ***{basename_current_file}*** will be corrected...")
report.append("") report.append("")
report.append("---") report.append("---")
report.append("") report.append("")
# update_file_list = glob.glob(branch + "/src/**/messages_*.properties", recursive=True)
# update_missing_keys(reference_file, update_file_list)
# report.append("---")
# report.append("")
if has_differences: if has_differences:
report.append("## ❌ Overall Check Status: **_Failed_**") report.append("## ❌ Overall Check Status: **_Failed_**")
report.append("")
report.append(
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)"
)
else: else:
report.append("## ✅ Overall Check Status: **_Success_**") report.append("## ✅ Overall Check Status: **_Success_**")
report.append("")
report.append(
f"Thanks @{actor} for your help in keeping the translations up to date."
)
if not only_reference_file: if not only_reference_file:
print("\n".join(report)) print("\n".join(report))
@@ -259,11 +225,6 @@ def check_for_differences(reference_file, file_list, branch, actor):
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Find missing keys") parser = argparse.ArgumentParser(description="Find missing keys")
parser.add_argument(
"--actor",
required=False,
help="Actor from PR.",
)
parser.add_argument( parser.add_argument(
"--reference-file", "--reference-file",
required=True, required=True,
@@ -283,21 +244,11 @@ if __name__ == "__main__":
) )
args = parser.parse_args() args = parser.parse_args()
# Sanitize --actor input to avoid injection attacks
if args.actor:
args.actor = re.sub(r"[^a-zA-Z0-9_\\-]", "", args.actor)
# Sanitize --branch input to avoid injection attacks
if args.branch:
args.branch = re.sub(r"[^a-zA-Z0-9\\-]", "", args.branch)
file_list = args.files file_list = args.files
if file_list is None: if file_list is None:
file_list = glob.glob( file_list = glob.glob(
os.path.join( os.getcwd() + "/src/**/messages_*.properties", recursive=True
os.getcwd(), "src", "main", "resources", "messages_*.properties"
)
) )
update_missing_keys(args.reference_file, file_list) update_missing_keys(args.reference_file, file_list)
else: else:
check_for_differences(args.reference_file, file_list, args.branch, args.actor) check_for_differences(args.reference_file, file_list, args.branch)

View File

@@ -1,179 +0,0 @@
name: PR Deployment via Comment
on:
issue_comment:
types: [created]
jobs:
check-comment:
runs-on: ubuntu-latest
if: |
github.event.issue.pull_request &&
(
contains(github.event.comment.body, 'prdeploy') ||
contains(github.event.comment.body, 'deploypr')
)
&&
(
github.event.comment.user.login == 'frooodle' ||
github.event.comment.user.login == 'sf298' ||
github.event.comment.user.login == 'Ludy87' ||
github.event.comment.user.login == 'LaserKaspar' ||
github.event.comment.user.login == 'sbplat' ||
github.event.comment.user.login == 'reecebrowne'
)
outputs:
pr_number: ${{ steps.get-pr.outputs.pr_number }}
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
steps:
- name: Get PR data
id: get-pr
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.issue.number;
console.log(`PR Number: ${prNumber}`);
core.setOutput('pr_number', prNumber);
- name: Get PR repository and ref
id: get-pr-info
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const prNumber = context.payload.issue.number;
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber,
});
// For forks, use the full repository name, for internal PRs use the current repo
const repository = pr.head.repo.fork ? pr.head.repo.full_name : `${owner}/${repo}`;
console.log(`PR Repository: ${repository}`);
console.log(`PR Branch: ${pr.head.ref}`);
core.setOutput('repository', repository);
core.setOutput('ref', pr.head.ref);
deploy-pr:
needs: check-comment
runs-on: ubuntu-latest
steps:
- name: Checkout PR
uses: actions/checkout@v4
with:
repository: ${{ needs.check-comment.outputs.pr_repository }}
ref: ${{ needs.check-comment.outputs.pr_ref }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Run Gradle Command
run: ./gradlew clean build
env:
DOCKER_ENABLE_SECURITY: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push PR-specific image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
sudo chmod 600 ../private.key
- name: Deploy to VPS
run: |
# First create the docker-compose content locally
cat > docker-compose.yml << 'EOF'
version: '3.3'
services:
stirling-pdf:
container_name: stirling-pdf-pr-${{ needs.check-comment.outputs.pr_number }}
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
ports:
- "${{ needs.check-comment.outputs.pr_number }}:8080"
volumes:
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/data:/usr/share/tessdata:rw
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/config:/configs:rw
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "false"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-GB
UI_APPNAME: "Stirling-PDF PR#${{ needs.check-comment.outputs.pr_number }}"
UI_HOMEDESCRIPTION: "PR#${{ needs.check-comment.outputs.pr_number }} for Stirling-PDF Latest"
UI_APPNAMENAVBAR: "PR#${{ needs.check-comment.outputs.pr_number }}"
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "false"
restart: on-failure:5
EOF
# Then copy the file and execute commands
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/docker-compose.yml
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
# Create PR-specific directories
mkdir -p /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/{data,config,logs}
# Move docker-compose file to correct location
mv /tmp/docker-compose.yml /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/docker-compose.yml
# Start or restart the container
cd /stirling/PR-${{ needs.check-comment.outputs.pr_number }}
docker-compose pull
docker-compose up -d
ENDSSH
- name: Post deployment URL to PR
if: success()
uses: actions/github-script@v7
with:
script: |
const { GITHUB_REPOSITORY } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const prNumber = ${{ needs.check-comment.outputs.pr_number }};
const deploymentUrl = `http://${{ secrets.VPS_HOST }}:${prNumber}`;
const commentBody = `## 🚀 PR Test Deployment\n\n` +
`Your PR has been deployed for testing!\n\n` +
`🔗 **Test URL:** [${deploymentUrl}](${deploymentUrl})\n\n` +
`This deployment will be automatically cleaned up when the PR is closed.\n\n`;
await github.rest.issues.createComment({
owner: repoOwner,
repo: repoName,
issue_number: prNumber,
body: commentBody
});

View File

@@ -1,78 +0,0 @@
name: PR Deployment cleanup
on:
pull_request:
types: [opened, synchronize, reopened, closed]
permissions:
contents: write
pull-requests: write
env:
SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets
CLEANUP_PERFORMED: 'false' # Add flag to track if cleanup occurred
jobs:
cleanup:
runs-on: ubuntu-latest
if: github.event.action == 'closed'
steps:
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
sudo chmod 600 ../private.key
- name: Cleanup PR deployment
id: cleanup
run: |
CLEANUP_STATUS=$(ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
if [ -d "/stirling/PR-${{ github.event.pull_request.number }}" ]; then
echo "Found PR directory, proceeding with cleanup..."
# Stop and remove containers
cd /stirling/PR-${{ github.event.pull_request.number }}
docker-compose down || true
# Go back to root before removal
cd /
# Remove PR-specific directories
rm -rf /stirling/PR-${{ github.event.pull_request.number }}
# Remove the Docker image
docker rmi --no-prune ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ github.event.pull_request.number }} || true
echo "PERFORMED_CLEANUP"
else
echo "PR directory not found, nothing to clean up"
echo "NO_CLEANUP_NEEDED"
fi
ENDSSH
)
if [[ $CLEANUP_STATUS == *"PERFORMED_CLEANUP"* ]]; then
echo "cleanup_performed=true" >> $GITHUB_OUTPUT
else
echo "cleanup_performed=false" >> $GITHUB_OUTPUT
fi
- name: Post cleanup notice to PR
if: steps.cleanup.outputs.cleanup_performed == 'true'
uses: actions/github-script@v7
with:
script: |
const { GITHUB_REPOSITORY } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const prNumber = context.issue.number;
const commentBody = `## 🧹 Deployment Cleanup\n\n` +
`The test deployment for this PR has been cleaned up.`;
await github.rest.issues.createComment({
owner: repoOwner,
repo: repoName,
issue_number: prNumber,
body: commentBody
});

View File

@@ -6,22 +6,18 @@ on:
paths: paths:
- "src/main/resources/messages_*.properties" - "src/main/resources/messages_*.properties"
push: push:
branches: ["main"]
paths: paths:
- "src/main/resources/messages_en_GB.properties" - "src/main/resources/messages_en_GB.properties"
permissions:
contents: write
pull-requests: write
jobs: jobs:
check-files: check-files:
if: github.event_name == 'pull_request_target' if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout main branch first
uses: actions/checkout@v4
with:
ref: main
path: main-branch
fetch-depth: 0
- name: Checkout PR branch - name: Checkout PR branch
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@@ -30,6 +26,13 @@ jobs:
path: pr-branch path: pr-branch
fetch-depth: 0 fetch-depth: 0
- name: Checkout main branch
uses: actions/checkout@v4
with:
ref: main
path: main-branch
fetch-depth: 0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
@@ -46,73 +49,56 @@ jobs:
echo "Fetching PR changed files..." echo "Fetching PR changed files..."
cd pr-branch cd pr-branch
gh repo set-default ${{ github.repository }} gh repo set-default ${{ github.repository }}
# Store files in a safe way, only allowing valid properties files gh pr view ${{ github.event.pull_request.number }} --json files -q ".files[].path" > ../changed_files.txt
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
cd .. cd ..
echo $(cat changed_files.txt)
echo "Processing changed files..." BRANCH_PATH="pr-branch"
mapfile -t CHANGED_FILES < changed_files.txt echo "BRANCH_PATH=${BRANCH_PATH}" >> $GITHUB_ENV
CHANGED_FILES=$(cat changed_files.txt | tr '\n' ' ')
CHANGED_FILES_STR="${CHANGED_FILES[*]}" echo "CHANGED_FILES=${CHANGED_FILES}" >> $GITHUB_ENV
echo "CHANGED_FILES=${CHANGED_FILES_STR}" >> $GITHUB_ENV echo "Changed files: ${CHANGED_FILES}"
echo "Branch: ${BRANCH_PATH}"
echo "Changed files: ${CHANGED_FILES_STR}"
- name: Determine reference file - name: Determine reference file
id: determine-file id: determine-file
run: | run: |
echo "Determining reference file..." echo "Determining reference file..."
if grep -Fxq "src/main/resources/messages_en_GB.properties" changed_files.txt; then if echo "${{ env.CHANGED_FILES }}" | grep -q 'src/main/resources/messages_en_GB.properties'; then
echo "Using PR branch reference file"
echo "REFERENCE_FILE=pr-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV echo "REFERENCE_FILE=pr-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV
else else
echo "Using main branch reference file"
echo "REFERENCE_FILE=main-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV echo "REFERENCE_FILE=main-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV
fi fi
echo "REFERENCE_FILE=${{ env.REFERENCE_FILE }}"
- name: Show REFERENCE_FILE - name: Show REFERENCE_FILE
run: echo "Reference file is set to ${REFERENCE_FILE}" run: echo "Reference file is set to ${{ env.REFERENCE_FILE }}"
- name: Run Python script to check files - name: Run Python script to check files
id: run-check id: run-check
run: | run: |
echo "Running Python script to check files..." python main-branch/.github/scripts/check_language_properties.py --reference-file ${{ env.REFERENCE_FILE }} --branch ${{ env.BRANCH_PATH }} --files ${{ env.CHANGED_FILES }} > failure.txt || true
python main-branch/.github/scripts/check_language_properties.py \
--actor ${{ github.event.pull_request.user.login }} \
--reference-file "${REFERENCE_FILE}" \
--branch pr-branch \
--files "${CHANGED_FILES[@]}" > result.txt || true
- name: Capture output - name: Capture output
id: capture-output id: capture-output
run: | run: |
if [ -f result.txt ] && [ -s result.txt ]; then if [ -f failure.txt ] && [ -s failure.txt ]; then
echo "Test, capturing output..." echo "Test failed, capturing output..."
SCRIPT_OUTPUT=$(cat result.txt) ERROR_OUTPUT=$(cat failure.txt)
echo "SCRIPT_OUTPUT<<EOF" >> $GITHUB_ENV echo "ERROR_OUTPUT<<EOF" >> $GITHUB_ENV
echo "$SCRIPT_OUTPUT" >> $GITHUB_ENV echo "$ERROR_OUTPUT" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV
echo "${SCRIPT_OUTPUT}" echo $ERROR_OUTPUT
# Set FAIL_JOB to true if SCRIPT_OUTPUT contains ❌
if [[ "$SCRIPT_OUTPUT" == *"❌"* ]]; then
echo "FAIL_JOB=true" >> $GITHUB_ENV
else
echo "FAIL_JOB=false" >> $GITHUB_ENV
fi
else else
echo "No update found." echo "No errors found."
echo "SCRIPT_OUTPUT=" >> $GITHUB_ENV echo "ERROR_OUTPUT=" >> $GITHUB_ENV
echo "FAIL_JOB=false" >> $GITHUB_ENV
fi fi
- name: Post comment on PR - name: Post comment on PR
if: env.SCRIPT_OUTPUT != '' if: env.ERROR_OUTPUT != ''
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
script: | script: |
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env; const { GITHUB_REPOSITORY, ERROR_OUTPUT } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/'); const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const prNumber = context.issue.number; const prNumber = context.issue.number;
@@ -134,7 +120,7 @@ jobs:
owner: repoOwner, owner: repoOwner,
repo: repoName, repo: repoName,
comment_id: comment.id, comment_id: comment.id,
body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n` body: `## 🚀 Translation Verification Summary\n\n\n${ERROR_OUTPUT}\n`
}); });
console.log("Updated existing comment."); console.log("Updated existing comment.");
} else if (!comment) { } else if (!comment) {
@@ -143,24 +129,33 @@ jobs:
owner: repoOwner, owner: repoOwner,
repo: repoName, repo: repoName,
issue_number: prNumber, issue_number: prNumber,
body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n` body: `## 🚀 Translation Verification Summary\n\n\n${ERROR_OUTPUT}\n`
}); });
console.log("Created new comment."); console.log("Created new comment.");
} else { } else {
console.log("Comment update attempt denied. Actor does not match."); console.log("Comment update attempt denied. Actor does not match.");
} }
- name: Fail job if errors found # - name: Set up git config
if: env.FAIL_JOB == 'true' # run: |
run: | # git config --global user.name "github-actions[bot]"
echo "Failing the job because errors were detected." # git config --global user.email "github-actions[bot]@users.noreply.github.com"
exit 1
# - name: Add translation keys
# run: |
# cd ${{ env.BRANCH_PATH }}
# git add src/main/resources/messages_*.properties
# git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
# git commit -m "Update translation files" || echo "No changes to commit"
# - name: Push
# if: env.CHANGES_DETECTED == 'true'
# run: |
# cd pr-branch
# git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.event.pull_request.head.repo.full_name }}.git
# git push origin ${{ github.head_ref }} || echo "Push failed: possibly no changes to push"
update-translations-main: update-translations-main:
if: github.event_name == 'push' if: github.event_name == 'push'
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -174,10 +169,7 @@ jobs:
- name: Run Python script to check files - name: Run Python script to check files
id: run-check id: run-check
run: | run: |
echo "Running Python script to check files..." python .github/scripts/check_language_properties.py --reference-file src/main/resources/messages_en_GB.properties --branch main
python .github/scripts/check_language_properties.py \
--reference-file src/main/resources/messages_en_GB.properties \
--branch main
- name: Set up git config - name: Set up git config
run: | run: |
@@ -192,7 +184,7 @@ jobs:
- name: Create Pull Request - name: Create Pull Request
id: cpr id: cpr
if: env.CHANGES_DETECTED == 'true' if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v6
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update translation files" commit-message: "Update translation files"
@@ -201,8 +193,6 @@ jobs:
signoff: true signoff: true
branch: update_translation_files branch: update_translation_files
title: "Update translation files" title: "Update translation files"
add-paths: |
src/main/resources/messages_*.properties
body: | body: |
Auto-generated by [create-pull-request][1] Auto-generated by [create-pull-request][1]
@@ -210,4 +200,3 @@ jobs:
labels: Translation labels: Translation
draft: false draft: false
delete-branch: true delete-branch: true
sign-commits: true

View File

@@ -67,7 +67,6 @@ jobs:
images: | images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf ${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: | tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
@@ -96,7 +95,6 @@ jobs:
images: | images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf ${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: | tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
@@ -124,7 +122,6 @@ jobs:
images: | images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf ${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: | tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }}

View File

@@ -114,7 +114,7 @@ These files provide pre-configured setups for different scenarios. For example,
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Security container_name: Stirling-PDF-Security
image: stirlingtools/stirling-pdf:latest image: frooodle/s-pdf:latest
deploy: deploy:
resources: resources:
limits: limits:
@@ -173,20 +173,20 @@ Stirling-PDF uses different Docker images for various configurations. The build
For the latest version: For the latest version:
```bash ```bash
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile . docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest -f ./Dockerfile .
``` ```
For the ultra-lite version: For the ultra-lite version:
```bash ```bash
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile-ultra-lite . docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest-ultra-lite -f ./Dockerfile-ultra-lite .
``` ```
For the fat version (with security enabled): For the fat version (with security enabled):
```bash ```bash
export DOCKER_ENABLE_SECURITY=true export DOCKER_ENABLE_SECURITY=true
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile-fat . docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest-fat -f ./Dockerfile-fat .
``` ```
Note: The `--no-cache` and `--pull` flags ensure that the build process uses the latest base images and doesn't use cached layers, which is useful for testing and ensuring reproducible builds. however to improve build times these can often be removed depending on your usecase Note: The `--no-cache` and `--pull` flags ensure that the build process uses the latest base images and doesn't use cached layers, which is useful for testing and ensuring reproducible builds. however to improve build times these can often be removed depending on your usecase

View File

@@ -1,5 +1,5 @@
# Build the application # Build the application
FROM gradle:8.11-jdk17 AS build FROM gradle:8.7-jdk17 AS build
# Set the working directory # Set the working directory
WORKDIR /app WORKDIR /app

45
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,45 @@
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'chmod 755 gradlew'
sh './gradlew build'
}
}
stage('Docker Build') {
steps {
script {
def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim()
def image = "frooodle/s-pdf:$appVersion"
sh "docker build -t $image ."
}
}
}
stage('Docker Push') {
steps {
script {
def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim()
def image = "frooodle/s-pdf:$appVersion"
withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) {
sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN"
sh "docker push $image"
}
}
}
}
stage('Helm Push') {
steps {
script {
//TODO: Read chartVersion from Chart.yaml
def chartVersion = '1.0.0'
withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) {
sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN"
sh "helm package chart/stirling-pdf"
sh "helm push stirling-pdf-chart-1.0.0.tgz oci://registry-1.docker.io/frooodle"
}
}
}
}
}
}

View File

@@ -120,13 +120,13 @@ Please view the [LocalRunGuide](https://github.com/Stirling-Tools/Stirling-PDF/b
### Docker / Podman ### Docker / Podman
> [!NOTE] > [!NOTE]
> <https://hub.docker.com/r/stirlingtools/stirling-pdf> > <https://hub.docker.com/r/frooodle/s-pdf>
Stirling-PDF has three different versions: a full version, an ultra-lite version, and a 'fat' version. Depending on the types of features you use, you may want a smaller image to save on space. To see what the different versions offer, please look at our [version mapping](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md). For people that don't mind space optimization, just use the latest tag. Stirling-PDF has three different versions: a full version, an ultra-lite version, and a 'fat' version. Depending on the types of features you use, you may want a smaller image to save on space. To see what the different versions offer, please look at our [version mapping](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md). For people that don't mind space optimization, just use the latest tag.
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/stirlingtools/stirling-pdf/latest?label=Stirling-PDF%20Full) ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest?label=Stirling-PDF%20Full)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/stirlingtools/stirling-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite) ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/stirlingtools/stirling-pdf/latest-fat?label=Stirling-PDF%20Fat) ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-fat?label=Stirling-PDF%20Fat)
Please note in the examples below, you may need to change the volume paths as needed, e.g., `./extraConfigs:/configs` to `/opt/stirlingpdf/extraConfigs:/configs`. Please note in the examples below, you may need to change the volume paths as needed, e.g., `./extraConfigs:/configs` to `/opt/stirlingpdf/extraConfigs:/configs`.
@@ -144,7 +144,7 @@ docker run -d \
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \ -e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
-e LANGS=en_GB \ -e LANGS=en_GB \
--name stirling-pdf \ --name stirling-pdf \
stirlingtools/stirling-pdf:latest frooodle/s-pdf:latest
``` ```
### Docker Compose ### Docker Compose
@@ -153,7 +153,7 @@ docker run -d \
version: '3.3' version: '3.3'
services: services:
stirling-pdf: stirling-pdf:
image: stirlingtools/stirling-pdf:latest image: frooodle/s-pdf:latest
ports: ports:
- '8080:8080' - '8080:8080'
volumes: volumes:
@@ -186,47 +186,46 @@ Certain functionality like `Sign` supports pre-saved files stored at `/customFil
## Supported Languages ## Supported Languages
Stirling-PDF currently supports 37 languages! Stirling-PDF currently supports 36 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![99%](https://geps.dev/progress/99) | | Arabic (العربية) (ar_AR) | ![98%](https://geps.dev/progress/98) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![76%](https://geps.dev/progress/76) | | Basque (Euskara) (eu_ES) | ![55%](https://geps.dev/progress/55) |
| Basque (Euskara) (eu_ES) | ![54%](https://geps.dev/progress/54) | | Bulgarian (Български) (bg_BG) | ![97%](https://geps.dev/progress/97) |
| Bulgarian (Български) (bg_BG) | ![95%](https://geps.dev/progress/95) | | Catalan (Català) (ca_CA) | ![90%](https://geps.dev/progress/90) |
| Catalan (Català) (ca_CA) | ![89%](https://geps.dev/progress/89) | | Croatian (Hrvatski) (hr_HR) | ![98%](https://geps.dev/progress/98) |
| Croatian (Hrvatski) (hr_HR) | ![96%](https://geps.dev/progress/96) | | Czech (Česky) (cs_CZ) | ![98%](https://geps.dev/progress/98) |
| Czech (Česky) (cs_CZ) | ![96%](https://geps.dev/progress/96) | | Danish (Dansk) (da_DK) | ![97%](https://geps.dev/progress/97) |
| Danish (Dansk) (da_DK) | ![95%](https://geps.dev/progress/95) | | Dutch (Nederlands) (nl_NL) | ![96%](https://geps.dev/progress/96) |
| Dutch (Nederlands) (nl_NL) | ![94%](https://geps.dev/progress/94) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![98%](https://geps.dev/progress/98) | | French (Français) (fr_FR) | ![97%](https://geps.dev/progress/97) |
| German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) | | German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) |
| Greek (Ελληνικά) (el_GR) | ![96%](https://geps.dev/progress/96) | | Greek (Ελληνικά) (el_GR) | ![98%](https://geps.dev/progress/98) |
| Hindi (हिंदी) (hi_IN) | ![93%](https://geps.dev/progress/93) | | Hindi (हिंदी) (hi_IN) | ![95%](https://geps.dev/progress/95) |
| Hungarian (Magyar) (hu_HU) | ![96%](https://geps.dev/progress/96) | | Hungarian (Magyar) (hu_HU) | ![98%](https://geps.dev/progress/98) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![96%](https://geps.dev/progress/96) | | Indonesian (Bahasa Indonesia) (id_ID) | ![98%](https://geps.dev/progress/98) |
| Irish (Gaeilge) (ga_IE) | ![86%](https://geps.dev/progress/86) | | Irish (Gaeilge) (ga_IE) | ![88%](https://geps.dev/progress/88) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) |
| Japanese (日本語) (ja_JP) | ![84%](https://geps.dev/progress/84) | | Japanese (日本語) (ja_JP) | ![86%](https://geps.dev/progress/86) |
| Korean (한국어) (ko_KR) | ![94%](https://geps.dev/progress/94) | | Korean (한국어) (ko_KR) | ![96%](https://geps.dev/progress/96) |
| Norwegian (Norsk) (no_NB) | ![86%](https://geps.dev/progress/86) | | Norwegian (Norsk) (no_NB) | ![88%](https://geps.dev/progress/88) |
| Polish (Polski) (pl_PL) | ![95%](https://geps.dev/progress/95) | | Polish (Polski) (pl_PL) | ![97%](https://geps.dev/progress/97) |
| Portuguese (Português) (pt_PT) | ![96%](https://geps.dev/progress/96) | | Portuguese (Português) (pt_PT) | ![98%](https://geps.dev/progress/98) |
| Portuguese Brazilian (Português) (pt_BR) | ![96%](https://geps.dev/progress/96) | | Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) |
| Romanian (Română) (ro_RO) | ![89%](https://geps.dev/progress/89) | | Romanian (Română) (ro_RO) | ![90%](https://geps.dev/progress/90) |
| Russian (Русский) (ru_RU) | ![95%](https://geps.dev/progress/95) | | Russian (Русский) (ru_RU) | ![97%](https://geps.dev/progress/97) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![69%](https://geps.dev/progress/69) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![70%](https://geps.dev/progress/70) |
| Simplified Chinese (简体中文) (zh_CN) | ![90%](https://geps.dev/progress/90) | | Simplified Chinese (简体中文) (zh_CN) | ![91%](https://geps.dev/progress/91) |
| Slovakian (Slovensky) (sk_SK) | ![81%](https://geps.dev/progress/81) | | Slovakian (Slovensky) (sk_SK) | ![82%](https://geps.dev/progress/82) |
| Spanish (Español) (es_ES) | ![96%](https://geps.dev/progress/96) | | Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) |
| Swedish (Svenska) (sv_SE) | ![95%](https://geps.dev/progress/95) | | Swedish (Svenska) (sv_SE) | ![97%](https://geps.dev/progress/97) |
| Thai (ไทย) (th_TH) | ![94%](https://geps.dev/progress/94) | | Thai (ไทย) (th_TH) | ![96%](https://geps.dev/progress/96) |
| Traditional Chinese (繁體中文) (zh_TW) | ![97%](https://geps.dev/progress/97) | | Traditional Chinese (繁體中文) (zh_TW) | ![98%](https://geps.dev/progress/98) |
| Turkish (Türkçe) (tr_TR) | ![90%](https://geps.dev/progress/90) | | Turkish (Türkçe) (tr_TR) | ![92%](https://geps.dev/progress/92) |
| Ukrainian (Українська) (uk_UA) | ![79%](https://geps.dev/progress/79) | | Ukrainian (Українська) (uk_UA) | ![80%](https://geps.dev/progress/80) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![87%](https://geps.dev/progress/87) | | Vietnamese (Tiếng Việt) (vi_VN) | ![89%](https://geps.dev/progress/89) |
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.) ## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)

View File

@@ -1,6 +1,6 @@
plugins { plugins {
id "java" id "java"
id "org.springframework.boot" version "3.4.0" id "org.springframework.boot" version "3.3.5"
id "io.spring.dependency-management" version "1.1.6" id "io.spring.dependency-management" version "1.1.6"
id "org.springdoc.openapi-gradle-plugin" version "1.8.0" id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
id "io.swagger.swaggerhub" version "1.3.2" id "io.swagger.swaggerhub" version "1.3.2"
@@ -10,21 +10,19 @@ plugins {
//id "nebula.lint" version "19.0.3" //id "nebula.lint" version "19.0.3"
} }
import com.github.jk1.license.render.* import com.github.jk1.license.render.*
ext { ext {
springBootVersion = "3.4.0" springBootVersion = "3.3.5"
pdfboxVersion = "3.0.3" pdfboxVersion = "3.0.3"
logbackVersion = "1.5.7" logbackVersion = "1.5.7"
imageioVersion = "3.12.0" imageioVersion = "3.12.0"
lombokVersion = "1.18.36" lombokVersion = "1.18.34"
bouncycastleVersion = "1.79" bouncycastleVersion = "1.78.1"
} }
group = "stirling.software" group = "stirling.software"
version = "0.34.0" version = "0.32.0"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21
@@ -121,7 +119,7 @@ configurations.all {
} }
dependencies { dependencies {
//security updates //security updates
implementation "org.springframework:spring-webmvc:6.2.0" implementation "org.springframework:spring-webmvc:6.1.14"
implementation("io.github.pixee:java-security-toolkit:1.2.0") implementation("io.github.pixee:java-security-toolkit:1.2.0")
@@ -143,10 +141,11 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.4.1' implementation 'org.springframework.security:spring-security-saml2-service-provider:6.3.4'
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5' implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
// Don't upgrade h2database //2.2.x requires rebuild of DB file.. need migration path
runtimeOnly "com.h2database:h2:2.3.232" runtimeOnly "com.h2database:h2:2.1.214"
// implementation "com.h2database:h2:2.2.224"
constraints { constraints {
implementation "org.opensaml:opensaml-core" implementation "org.opensaml:opensaml-core"
implementation "org.opensaml:opensaml-saml-api" implementation "org.opensaml:opensaml-saml-api"
@@ -202,19 +201,12 @@ dependencies {
exclude group: "commons-logging", module: "commons-logging" exclude group: "commons-logging", module: "commons-logging"
} }
// https://mvnrepository.com/artifact/technology.tabula/tabula
implementation ('technology.tabula:tabula:1.0.5') {
exclude group: "org.slf4j", module: "slf4j-simple"
exclude group: "org.bouncycastle", module: "bcprov-jdk15on"
exclude group: "com.google.code.gson", module: "gson"
}
implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4' implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4'
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.14.1" implementation "io.micrometer:micrometer-core:1.13.6"
implementation group: "com.google.zxing", name: "core", version: "3.5.3" implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.24.0" implementation "org.commonmark:commonmark:0.24.0"

View File

@@ -1,7 +1,7 @@
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Security-Fat container_name: Stirling-PDF-Security-Fat
image: stirlingtools/stirling-pdf:latest-fat image: frooodle/s-pdf:latest-fat
deploy: deploy:
resources: resources:
limits: limits:

View File

@@ -1,7 +1,7 @@
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Security container_name: Stirling-PDF-Security
image: stirlingtools/stirling-pdf:latest image: frooodle/s-pdf:latest
deploy: deploy:
resources: resources:
limits: limits:

View File

@@ -1,7 +1,7 @@
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Security container_name: Stirling-PDF-Security
image: stirlingtools/stirling-pdf:latest image: frooodle/s-pdf:latest
deploy: deploy:
resources: resources:
limits: limits:

View File

@@ -1,7 +1,7 @@
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Ultra-Lite-Security container_name: Stirling-PDF-Ultra-Lite-Security
image: stirlingtools/stirling-pdf:latest-ultra-lite image: frooodle/s-pdf:latest-ultra-lite
deploy: deploy:
resources: resources:
limits: limits:

View File

@@ -1,7 +1,7 @@
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Ultra-Lite container_name: Stirling-PDF-Ultra-Lite
image: stirlingtools/stirling-pdf:latest-ultra-lite image: frooodle/s-pdf:latest-ultra-lite
deploy: deploy:
resources: resources:
limits: limits:

View File

@@ -1,7 +1,7 @@
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF container_name: Stirling-PDF
image: stirlingtools/stirling-pdf:latest image: frooodle/s-pdf:latest
deploy: deploy:
resources: resources:
limits: limits:

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -3,11 +3,6 @@ ignore = [
'language.direction', 'language.direction',
] ]
[az_AZ]
ignore = [
'language.direction',
]
[bg_BG] [bg_BG]
ignore = [ ignore = [
'language.direction', 'language.direction',
@@ -37,12 +32,12 @@ ignore = [
ignore = [ ignore = [
'AddStampRequest.alphabet', 'AddStampRequest.alphabet',
'AddStampRequest.position', 'AddStampRequest.position',
'home.pipeline.title'
'PDFToBook.selectText.1', 'PDFToBook.selectText.1',
'PDFToText.tags', 'PDFToText.tags',
'addPageNumbers.selectText.3', 'addPageNumbers.selectText.3',
'alphabet', 'alphabet',
'certSign.name', 'certSign.name',
'home.pipeline.title',
'language.direction', 'language.direction',
'licenses.version', 'licenses.version',
'pipeline.title', 'pipeline.title',
@@ -51,6 +46,7 @@ ignore = [
'sponsor', 'sponsor',
'text', 'text',
'watermark.type.1', 'watermark.type.1',
'certSign.name',
] ]
[el_GR] [el_GR]

View File

@@ -452,7 +452,7 @@ public class SecurityConfiguration {
RelyingPartyRegistration rp = RelyingPartyRegistration rp =
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId()) RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
.signingX509Credentials((c) -> c.add(signingCredential)) .signingX509Credentials((c) -> c.add(signingCredential))
.assertingPartyMetadata( .assertingPartyDetails(
(details) -> (details) ->
details.entityId(samlConf.getIdpIssuer()) details.entityId(samlConf.getIdpIssuer())
.singleSignOnServiceLocation( .singleSignOnServiceLocation(

View File

@@ -34,12 +34,6 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
@Value("${spring.datasource.url}") @Value("${spring.datasource.url}")
private String url; private String url;
@Value("${spring.datasource.username}")
private String databaseUsername;
@Value("${spring.datasource.password}")
private String databasePassword;
private Path backupPath = Paths.get("configs/db/backup/"); private Path backupPath = Paths.get("configs/db/backup/");
@Override @Override
@@ -140,8 +134,7 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
this.getBackupFilePath("backup_" + dateNow.format(myFormatObj) + ".sql"); this.getBackupFilePath("backup_" + dateNow.format(myFormatObj) + ".sql");
String query = "SCRIPT SIMPLE COLUMNS DROP to ?;"; String query = "SCRIPT SIMPLE COLUMNS DROP to ?;";
try (Connection conn = try (Connection conn = DriverManager.getConnection(url, "sa", "");
DriverManager.getConnection(url, databaseUsername, databasePassword);
PreparedStatement stmt = conn.prepareStatement(query)) { PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, insertOutputFilePath.toString()); stmt.setString(1, insertOutputFilePath.toString());
stmt.execute(); stmt.execute();
@@ -154,8 +147,7 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
// Retrieves the H2 database version. // Retrieves the H2 database version.
public String getH2Version() { public String getH2Version() {
String version = "Unknown"; String version = "Unknown";
try (Connection conn = try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
DriverManager.getConnection(url, databaseUsername, databasePassword)) {
try (Statement stmt = conn.createStatement(); try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) { ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) {
if (rs.next()) { if (rs.next()) {
@@ -197,8 +189,7 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
private boolean executeDatabaseScript(Path scriptPath) { private boolean executeDatabaseScript(Path scriptPath) {
String query = "RUNSCRIPT from ?;"; String query = "RUNSCRIPT from ?;";
try (Connection conn = try (Connection conn = DriverManager.getConnection(url, "sa", "");
DriverManager.getConnection(url, databaseUsername, databasePassword);
PreparedStatement stmt = conn.prepareStatement(query)) { PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, scriptPath.toString()); stmt.setString(1, scriptPath.toString());
stmt.execute(); stmt.execute();

View File

@@ -1,18 +1,25 @@
package stirling.software.SPDF.config.security.saml2; package stirling.software.SPDF.config.security.saml2;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Scanner;
import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader; import org.bouncycastle.util.io.pem.PemReader;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CertificateUtils { public class CertificateUtils {
public static X509Certificate readCertificate(Resource certificateResource) throws Exception { public static X509Certificate readCertificate(Resource certificateResource) throws Exception {
@@ -39,4 +46,84 @@ public class CertificateUtils {
.generatePrivate(new PKCS8EncodedKeySpec(decodedKey)); .generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
} }
} }
public static X509Certificate getIdPCertificate(Resource certificateResource) throws Exception {
if (certificateResource instanceof UrlResource) {
return extractCertificateFromMetadata(certificateResource);
} else {
// Treat as file resource
return readCertificate(certificateResource);
}
}
private static X509Certificate extractCertificateFromMetadata(Resource metadataResource) throws Exception {
log.info("Attempting to extract certificate from metadata resource: {}", metadataResource.getDescription());
try (InputStream is = metadataResource.getInputStream()) {
String content = new String(is.readAllBytes(), StandardCharsets.UTF_8);
log.info("Retrieved metadata content, length: {}", content.length());
// Find the certificate data
int startIndex = content.indexOf("<ds:X509Certificate>");
int endIndex = content.indexOf("</ds:X509Certificate>");
if (startIndex == -1 || endIndex == -1) {
log.error("Certificate tags not found in metadata");
throw new Exception("Certificate tags not found in metadata");
}
// Extract certificate data
String certData = content.substring(
startIndex + "<ds:X509Certificate>".length(),
endIndex
).trim();
log.info("Found certificate data, length: {}", certData.length());
// Remove any whitespace and newlines from cert data
certData = certData.replaceAll("\\s+", "");
// Reconstruct PEM format with proper line breaks
StringBuilder pemBuilder = new StringBuilder();
pemBuilder.append("-----BEGIN CERTIFICATE-----\n");
// Insert line breaks every 64 characters
int lineLength = 64;
for (int i = 0; i < certData.length(); i += lineLength) {
int end = Math.min(i + lineLength, certData.length());
pemBuilder.append(certData, i, end).append('\n');
}
pemBuilder.append("-----END CERTIFICATE-----");
String pemCert = pemBuilder.toString();
log.debug("Reconstructed PEM certificate:\n{}", pemCert);
try {
ByteArrayInputStream pemStream = new ByteArrayInputStream(pemCert.getBytes(StandardCharsets.UTF_8));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(pemStream);
log.info("Successfully parsed certificate. Subject: {}", cert.getSubjectX500Principal());
// Optional: check validity dates
cert.checkValidity(); // Throws CertificateExpiredException if expired
log.info("Certificate is valid (not expired)");
return cert;
} catch (Exception e) {
log.error("Failed to parse certificate", e);
throw new Exception("Failed to parse X509 certificate from metadata", e);
}
} catch (Exception e) {
log.error("Error processing metadata resource", e);
throw e;
}
}
} }

View File

@@ -16,23 +16,35 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override @Override
public void onAuthenticationFailure( public void onAuthenticationFailure(
HttpServletRequest request, HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
AuthenticationException exception) AuthenticationException exception)
throws IOException, ServletException { throws IOException, ServletException {
if (exception instanceof Saml2AuthenticationException) {
Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error(); if (exception instanceof Saml2AuthenticationException saml2Exception) {
getRedirectStrategy() Saml2Error error = saml2Exception.getSaml2Error();
.sendRedirect(request, response, "/login?erroroauth=" + error.getErrorCode());
} else if (exception instanceof ProviderNotFoundException) { // Log detailed information about the SAML error
getRedirectStrategy() log.error("SAML Authentication failed with error code: {}", error.getErrorCode());
.sendRedirect( log.error("Error description: {}", error.getDescription());
request,
response, // Redirect to login with specific error code
"/login?erroroauth=not_authentication_provider_found"); getRedirectStrategy()
} .sendRedirect(request, response, "/login?erroroauth=" + error.getErrorCode());
log.error("AuthenticationException: " + exception); } else if (exception instanceof ProviderNotFoundException) {
} log.error("Authentication failed: No authentication provider found");
getRedirectStrategy()
.sendRedirect(
request,
response,
"/login?erroroauth=not_authentication_provider_found");
} else {
log.error("Unknown AuthenticationException: {}", exception.getMessage());
getRedirectStrategy().sendRedirect(request, response, "/login?erroroauth=unknown_error");
}
}
} }

View File

@@ -0,0 +1,67 @@
package stirling.software.SPDF.config.security.saml2;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.saml2.core.Saml2ErrorCodes;
public class LoggingSamlAuthenticationProvider implements AuthenticationProvider {
private static final Logger log = LoggerFactory.getLogger(LoggingSamlAuthenticationProvider.class);
private final OpenSaml4AuthenticationProvider delegate;
public LoggingSamlAuthenticationProvider(OpenSaml4AuthenticationProvider delegate) {
this.delegate = delegate;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication instanceof Saml2AuthenticationToken token) {
String samlResponse = token.getSaml2Response();
// Log the raw SAML response
log.info("Raw SAML Response (Base64): {}", samlResponse);
// Decode and log the SAML response XML
try {
String decodedResponse = new String(Base64.getDecoder().decode(samlResponse), StandardCharsets.UTF_8);
log.info("Decoded SAML Response XML:\n{}", decodedResponse);
} catch (IllegalArgumentException e) {
// If decoding fails, its likely already plain XML
log.warn("SAML Response appears to be different format, not Base64-encoded.");
log.debug("SAML Response XML:\n{}", samlResponse);
}
// Delegate the actual authentication to the wrapped OpenSaml4AuthenticationProvider
try {
return delegate.authenticate(authentication);
} catch (Saml2AuthenticationException e) {
log.error("SAML authentication failed: {}");
log.error("Detailed error message: {}", e);
throw e;
}
}
return null;
}
@Override
public boolean supports(Class<?> authentication) {
// Only support Saml2AuthenticationToken
return Saml2AuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@@ -1,12 +1,12 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.QuoteMode;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.ContentDisposition; import org.springframework.http.ContentDisposition;
@@ -18,36 +18,79 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.opencsv.CSVWriter;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.controller.api.CropController; import stirling.software.SPDF.controller.api.CropController;
import stirling.software.SPDF.controller.api.strippers.PDFTableStripper;
import stirling.software.SPDF.model.api.extract.PDFFilePage; import stirling.software.SPDF.model.api.extract.PDFFilePage;
import stirling.software.SPDF.pdf.FlexibleCSVWriter;
import technology.tabula.ObjectExtractor;
import technology.tabula.Page;
import technology.tabula.Table;
import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
import technology.tabula.writers.Writer;
@RestController @RestController
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
@Tag(name = "Convert", description = "Convert APIs") @Tag(name = "Convert", description = "Convert APIs")
public class ExtractCSVController { public class ExtractCSVController {
private static final Logger logger = LoggerFactory.getLogger(ExtractCSVController.class); private static final Logger logger = LoggerFactory.getLogger(CropController.class);
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data") @PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
@Operation(summary = "Extracts a CSV document from a PDF", description = "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO") @Operation(
summary = "Extracts a CSV document from a PDF",
description =
"This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception { public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {
StringWriter writer = new StringWriter();
ArrayList<String> tableData = new ArrayList<>();
int columnsCount = 0;
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) { try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
CSVFormat format = CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build(); final double res = 72; // PDF units are at 72 DPI
Writer csvWriter = new FlexibleCSVWriter(format); PDFTableStripper stripper = new PDFTableStripper();
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm(); PDPage pdPage = document.getPage(form.getPageId() - 1);
try (ObjectExtractor extractor = new ObjectExtractor(document)) { stripper.extractTable(pdPage);
Page page = extractor.extract(form.getPageId()); columnsCount = stripper.getColumns();
List<Table> tables = sea.extract(page); for (int c = 0; c < columnsCount; ++c) {
csvWriter.write(writer, tables); for (int r = 0; r < stripper.getRows(); ++r) {
tableData.add(stripper.getText(r, c));
}
}
}
ArrayList<String> notEmptyColumns = new ArrayList<>();
for (String item : tableData) {
if (!item.trim().isEmpty()) {
notEmptyColumns.add(item);
} else {
columnsCount--;
}
}
List<String> fullTable =
notEmptyColumns.stream()
.map(
(entity) ->
entity.replace('\n', ' ')
.replace('\r', ' ')
.trim()
.replaceAll("\\s{2,}", "|"))
.toList();
int rowsCount = fullTable.get(0).split("\\|").length;
ArrayList<String> headersList = getTableHeaders(columnsCount, fullTable);
ArrayList<String> recordList = getRecordsList(rowsCount, fullTable);
if (headersList.size() == 0 && recordList.size() == 0) {
throw new Exception("No table detected, no headers or records found");
}
StringWriter writer = new StringWriter();
try (CSVWriter csvWriter = new CSVWriter(writer)) {
csvWriter.writeNext(headersList.toArray(new String[0]));
for (String record : recordList) {
csvWriter.writeNext(record.split("\\|"));
} }
} }
@@ -56,12 +99,41 @@ public class ExtractCSVController {
ContentDisposition.builder("attachment") ContentDisposition.builder("attachment")
.filename( .filename(
form.getFileInput() form.getFileInput()
.getOriginalFilename() .getOriginalFilename()
.replaceFirst("[.][^.]+$", "") .replaceFirst("[.][^.]+$", "")
+ "_extracted.csv") + "_extracted.csv")
.build()); .build());
headers.setContentType(MediaType.parseMediaType("text/csv")); headers.setContentType(MediaType.parseMediaType("text/csv"));
return ResponseEntity.ok().headers(headers).body(writer.toString()); return ResponseEntity.ok().headers(headers).body(writer.toString());
} }
private ArrayList<String> getRecordsList(int rowsCounts, List<String> items) {
ArrayList<String> recordsList = new ArrayList<>();
for (int b = 1; b < rowsCounts; b++) {
StringBuilder strbldr = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
String[] parts = items.get(i).split("\\|");
strbldr.append(parts[b]);
if (i != items.size() - 1) {
strbldr.append("|");
}
}
recordsList.add(strbldr.toString());
}
return recordsList;
}
private ArrayList<String> getTableHeaders(int columnsCount, List<String> items) {
ArrayList<String> resultList = new ArrayList<>();
for (int i = 0; i < columnsCount; i++) {
String[] parts = items.get(i).split("\\|");
resultList.add(parts[0]);
}
return resultList;
}
} }

View File

@@ -98,10 +98,10 @@ public class CertSignController {
public CreateSignature(KeyStore keystore, char[] pin) public CreateSignature(KeyStore keystore, char[] pin)
throws KeyStoreException, throws KeyStoreException,
UnrecoverableKeyException, UnrecoverableKeyException,
NoSuchAlgorithmException, NoSuchAlgorithmException,
IOException, IOException,
CertificateException { CertificateException {
super(keystore, pin); super(keystore, pin);
ClassPathResource resource = new ClassPathResource("static/images/signature.png"); ClassPathResource resource = new ClassPathResource("static/images/signature.png");
try (InputStream is = resource.getInputStream()) { try (InputStream is = resource.getInputStream()) {
@@ -160,7 +160,8 @@ public class CertSignController {
extState.setNonStrokingAlphaConstant(0.5f); extState.setNonStrokingAlphaConstant(0.5f);
cs.setGraphicsStateParameters(extState); cs.setGraphicsStateParameters(extState);
cs.transform(Matrix.getScaleInstance(0.08f, 0.08f)); cs.transform(Matrix.getScaleInstance(0.08f, 0.08f));
PDImageXObject img = PDImageXObject.createFromFileByExtension(logoFile, doc); PDImageXObject img =
PDImageXObject.createFromFileByExtension(logoFile, doc);
cs.drawImage(img, 100, 0); cs.drawImage(img, 100, 0);
cs.restoreGraphicsState(); cs.restoreGraphicsState();
} }
@@ -208,7 +209,10 @@ public class CertSignController {
} }
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign") @PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
@Operation(summary = "Sign PDF with a Digital Certificate", description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO") @Operation(
summary = "Sign PDF with a Digital Certificate",
description =
"This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
throws Exception { throws Exception {
MultipartFile pdf = request.getFileInput(); MultipartFile pdf = request.getFileInput();
@@ -238,7 +242,7 @@ public class CertSignController {
PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password); PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password);
Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes()); Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes());
ks.setKeyEntry( ks.setKeyEntry(
"alias", privateKey, password.toCharArray(), new Certificate[] { cert }); "alias", privateKey, password.toCharArray(), new Certificate[] {cert});
break; break;
case "PKCS12": case "PKCS12":
ks = KeyStore.getInstance("PKCS12"); ks = KeyStore.getInstance("PKCS12");
@@ -310,19 +314,22 @@ public class CertSignController {
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password) private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
throws IOException, OperatorCreationException, PKCSException { throws IOException, OperatorCreationException, PKCSException {
try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) { try (PEMParser pemParser =
new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
Object pemObject = pemParser.readObject(); Object pemObject = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
PrivateKeyInfo pkInfo; PrivateKeyInfo pkInfo;
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) { if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder() InputDecryptorProvider decProv =
.build(password.toCharArray()); new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv); pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv);
} else if (pemObject instanceof PEMEncryptedKeyPair) { } else if (pemObject instanceof PEMEncryptedKeyPair) {
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); PEMDecryptorProvider decProv =
pkInfo = ((PEMEncryptedKeyPair) pemObject) new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
.decryptKeyPair(decProv) pkInfo =
.getPrivateKeyInfo(); ((PEMEncryptedKeyPair) pemObject)
.decryptKeyPair(decProv)
.getPrivateKeyInfo();
} else { } else {
pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo(); pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();
} }

View File

@@ -0,0 +1,327 @@
package stirling.software.SPDF.controller.api.strippers;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.fontbox.util.BoundingBox;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType3Font;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.PDFTextStripperByArea;
import org.apache.pdfbox.text.TextPosition;
/**
* Class to extract tabular data from a PDF. Works by making a first pass of the page to group all
* nearby text items together, and then inferring a 2D grid from these regions. Each table cell is
* then extracted using a PDFTextStripperByArea object.
*
* <p>Works best when headers are included in the detected region, to ensure representative text in
* every column.
*
* <p>Based upon DrawPrintTextLocations PDFBox example
* (https://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/util/DrawPrintTextLocations.java)
*
* @author Beldaz
*/
public class PDFTableStripper extends PDFTextStripper {
/**
* This will print the documents data, for each table cell.
*
* @param args The command line arguments.
* @throws IOException If there is an error parsing the document.
*/
/*
* Used in methods derived from DrawPrintTextLocations
*/
private AffineTransform flipAT;
private AffineTransform rotateAT;
/** Regions updated by calls to writeString */
private Set<Rectangle2D> boxes;
// Border to allow when finding intersections
private double dx = 1.0; // This value works for me, feel free to tweak (or add setter)
private double dy = 0.000; // Rows of text tend to overlap, so need to extend
/** Region in which to find table (otherwise whole page) */
private Rectangle2D regionArea;
/** Number of rows in inferred table */
private int nRows = 0;
/** Number of columns in inferred table */
private int nCols = 0;
/** This is the object that does the text extraction */
private PDFTextStripperByArea regionStripper;
/**
* 1D intervals - used for calculateTableRegions()
*
* @author Beldaz
*/
public static class Interval {
double start;
double end;
public Interval(double start, double end) {
this.start = start;
this.end = end;
}
public void add(Interval col) {
if (col.start < start) start = col.start;
if (col.end > end) end = col.end;
}
public static void addTo(Interval x, LinkedList<Interval> columns) {
int p = 0;
Iterator<Interval> it = columns.iterator();
// Find where x should go
while (it.hasNext()) {
Interval col = it.next();
if (x.end >= col.start) {
if (x.start <= col.end) { // overlaps
x.add(col);
it.remove();
}
break;
}
++p;
}
while (it.hasNext()) {
Interval col = it.next();
if (x.start > col.end) break;
x.add(col);
it.remove();
}
columns.add(p, x);
}
}
/**
* Instantiate a new PDFTableStripper object.
*
* @throws IOException If there is an error loading the properties.
*/
public PDFTableStripper() throws IOException {
super.setShouldSeparateByBeads(false);
regionStripper = new PDFTextStripperByArea();
regionStripper.setSortByPosition(true);
}
/**
* Define the region to group text by.
*
* @param rect The rectangle area to retrieve the text from.
*/
public void setRegion(Rectangle2D rect) {
regionArea = rect;
}
public int getRows() {
return nRows;
}
public int getColumns() {
return nCols;
}
/**
* Get the text for the region, this should be called after extractTable().
*
* @return The text that was identified in that region.
*/
public String getText(int row, int col) {
return regionStripper.getTextForRegion("el" + col + "x" + row);
}
public void extractTable(PDPage pdPage) throws IOException {
setStartPage(getCurrentPageNo());
setEndPage(getCurrentPageNo());
boxes = new HashSet<Rectangle2D>();
// flip y-axis
flipAT = new AffineTransform();
flipAT.translate(0, pdPage.getBBox().getHeight());
flipAT.scale(1, -1);
// page may be rotated
rotateAT = new AffineTransform();
int rotation = pdPage.getRotation();
if (rotation != 0) {
PDRectangle mediaBox = pdPage.getMediaBox();
switch (rotation) {
case 90:
rotateAT.translate(mediaBox.getHeight(), 0);
break;
case 270:
rotateAT.translate(0, mediaBox.getWidth());
break;
case 180:
rotateAT.translate(mediaBox.getWidth(), mediaBox.getHeight());
break;
default:
break;
}
rotateAT.rotate(Math.toRadians(rotation));
}
// Trigger processing of the document so that writeString is called.
try (Writer dummy = new OutputStreamWriter(new ByteArrayOutputStream())) {
super.output = dummy;
super.processPage(pdPage);
}
Rectangle2D[][] regions = calculateTableRegions();
// System.err.println("Drawing " + nCols + "x" + nRows + "="+ nRows*nCols + "
// regions");
for (int i = 0; i < nCols; ++i) {
for (int j = 0; j < nRows; ++j) {
final Rectangle2D region = regions[i][j];
regionStripper.addRegion("el" + i + "x" + j, region);
}
}
regionStripper.extractRegions(pdPage);
}
/**
* Infer a rectangular grid of regions from the boxes field.
*
* @return 2D array of table regions (as Rectangle2D objects). Note that some of these regions
* may have no content.
*/
private Rectangle2D[][] calculateTableRegions() {
// Build up a list of all table regions, based upon the populated
// regions of boxes field. Treats the horizontal and vertical extents
// of each box as distinct
LinkedList<Interval> columns = new LinkedList<Interval>();
LinkedList<Interval> rows = new LinkedList<Interval>();
for (Rectangle2D box : boxes) {
Interval x = new Interval(box.getMinX(), box.getMaxX());
Interval y = new Interval(box.getMinY(), box.getMaxY());
Interval.addTo(x, columns);
Interval.addTo(y, rows);
}
nRows = rows.size();
nCols = columns.size();
Rectangle2D[][] regions = new Rectangle2D[nCols][nRows];
int i = 0;
// Label regions from top left, rather than the transformed orientation
for (Interval column : columns) {
int j = 0;
for (Interval row : rows) {
regions[nCols - i - 1][nRows - j - 1] =
new Rectangle2D.Double(
column.start,
row.start,
column.end - column.start,
row.end - row.start);
++j;
}
++i;
}
return regions;
}
/**
* Register each character's bounding box, updating boxes field to maintain a list of all
* distinct groups of characters.
*
* <p>Overrides the default functionality of PDFTextStripper. Most of this is taken from
* DrawPrintTextLocations.java, with extra steps at end of main loop
*/
@Override
protected void writeString(String string, List<TextPosition> textPositions) throws IOException {
for (TextPosition text : textPositions) {
// glyph space -> user space
// note: text.getTextMatrix() is *not* the Text Matrix, it's the Text Rendering Matrix
AffineTransform at = text.getTextMatrix().createAffineTransform();
PDFont font = text.getFont();
BoundingBox bbox = font.getBoundingBox();
// advance width, bbox height (glyph space)
float xadvance =
font.getWidth(text.getCharacterCodes()[0]); // todo: should iterate all chars
Rectangle2D.Float rect =
new Rectangle2D.Float(0, bbox.getLowerLeftY(), xadvance, bbox.getHeight());
if (font instanceof PDType3Font) {
// bbox and font matrix are unscaled
at.concatenate(font.getFontMatrix().createAffineTransform());
} else {
// bbox and font matrix are already scaled to 1000
at.scale(1 / 1000f, 1 / 1000f);
}
Shape s = at.createTransformedShape(rect);
s = flipAT.createTransformedShape(s);
s = rotateAT.createTransformedShape(s);
//
// Merge character's bounding box with boxes field
//
Rectangle2D bounds = s.getBounds2D();
// Pad sides to detect almost touching boxes
Rectangle2D hitbox = bounds.getBounds2D();
hitbox.add(bounds.getMinX() - dx, bounds.getMinY() - dy);
hitbox.add(bounds.getMaxX() + dx, bounds.getMaxY() + dy);
// Find all overlapping boxes
List<Rectangle2D> intersectList = new ArrayList<Rectangle2D>();
for (Rectangle2D box : boxes) {
if (box.intersects(hitbox)) {
intersectList.add(box);
}
}
// Combine all touching boxes and update
// (NOTE: Potentially this could leave some overlapping boxes un-merged,
// but it's sufficient for now and get's fixed up in calculateTableRegions)
for (Rectangle2D box : intersectList) {
bounds.add(box);
boxes.remove(box);
}
boxes.add(bounds);
}
}
/**
* This method does nothing in this derived class, because beads and regions are incompatible.
* Beads are ignored when stripping by area.
*
* @param aShouldSeparateByBeads The new grouping of beads.
*/
@Override
public final void setShouldSeparateByBeads(boolean aShouldSeparateByBeads) {}
/** Adapted from PDFTextStripperByArea {@inheritDoc} */
@Override
protected void processTextPosition(TextPosition text) {
if (regionArea != null && !regionArea.contains(text.getX(), text.getY())) {
// skip character
} else {
super.processTextPosition(text);
}
}
}

View File

@@ -34,9 +34,7 @@ public class DatabaseWebController {
} }
List<FileInfo> backupList = databaseBackupHelper.getBackupList(); List<FileInfo> backupList = databaseBackupHelper.getBackupList();
model.addAttribute("backupFiles", backupList); model.addAttribute("systemUpdate", backupList);
model.addAttribute("databaseVersion", databaseBackupHelper.getH2Version());
return "database"; return "database";
} }

View File

@@ -20,6 +20,7 @@ import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import lombok.Data; import lombok.Data;
import lombok.Getter; import lombok.Getter;
@@ -30,6 +31,7 @@ import stirling.software.SPDF.model.provider.GithubProvider;
import stirling.software.SPDF.model.provider.GoogleProvider; import stirling.software.SPDF.model.provider.GoogleProvider;
import stirling.software.SPDF.model.provider.KeycloakProvider; import stirling.software.SPDF.model.provider.KeycloakProvider;
import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.model.provider.UnsupportedProviderException;
import stirling.software.SPDF.utils.GeneralUtils;
@Configuration @Configuration
@ConfigurationProperties(prefix = "") @ConfigurationProperties(prefix = "")
@@ -134,44 +136,20 @@ public class ApplicationProperties {
private String privateKey; private String privateKey;
private String spCert; private String spCert;
public InputStream getIdpMetadataUri() throws IOException { public Resource getIdpMetadataUri() throws IOException {
if (idpMetadataUri.startsWith("classpath:")) { return GeneralUtils.filePathToResource(idpMetadataUri);
return new ClassPathResource(idpMetadataUri.substring("classpath".length()))
.getInputStream();
}
try {
URI uri = new URI(idpMetadataUri);
URL url = uri.toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
return connection.getInputStream();
} catch (URISyntaxException e) {
throw new IOException("Invalid URI format: " + idpMetadataUri, e);
}
} }
public Resource getSpCert() { public Resource getSpCert() {
if (spCert.startsWith("classpath:")) { return GeneralUtils.filePathToResource(spCert);
return new ClassPathResource(spCert.substring("classpath:".length()));
} else {
return new FileSystemResource(spCert);
}
} }
public Resource getidpCert() { public Resource getidpCert() {
if (idpCert.startsWith("classpath:")) { return GeneralUtils.filePathToResource(idpCert);
return new ClassPathResource(idpCert.substring("classpath:".length()));
} else {
return new FileSystemResource(idpCert);
}
} }
public Resource getPrivateKey() { public Resource getPrivateKey() {
if (privateKey.startsWith("classpath:")) { return GeneralUtils.filePathToResource(privateKey);
return new ClassPathResource(privateKey.substring("classpath:".length()));
} else {
return new FileSystemResource(privateKey);
}
} }
} }

View File

@@ -1,16 +0,0 @@
package stirling.software.SPDF.pdf;
import org.apache.commons.csv.CSVFormat;
import technology.tabula.writers.CSVWriter;
public class FlexibleCSVWriter extends CSVWriter {
public FlexibleCSVWriter() {
super();
}
public FlexibleCSVWriter(CSVFormat csvFormat) {
super(csvFormat);
}
}

View File

@@ -24,7 +24,7 @@ public class MetricsAggregatorService {
this.postHogService = postHogService; this.postHogService = postHogService;
} }
@Scheduled(fixedRate = 1800000) // Run every 30 minutes @Scheduled(fixedRate = 900000) // Run every 15 minutes
public void aggregateAndSendMetrics() { public void aggregateAndSendMetrics() {
Map<String, Object> metrics = new HashMap<>(); Map<String, Object> metrics = new HashMap<>();
Search.in(meterRegistry) Search.in(meterRegistry)
@@ -32,19 +32,11 @@ public class MetricsAggregatorService {
.counters() .counters()
.forEach( .forEach(
counter -> { counter -> {
String method = counter.getId().getTag("method"); String key =
String uri = counter.getId().getTag("uri"); String.format(
"http_requests_%s_%s",
// Skip if either method or uri is null counter.getId().getTag("method"),
if (method == null || uri == null) { counter.getId().getTag("uri").replace("/", "_"));
return;
}
String key = String.format(
"http_requests_%s_%s",
method,
uri.replace("/", "_")
);
double currentCount = counter.count(); double currentCount = counter.count();
double lastCount = lastSentMetrics.getOrDefault(key, 0.0); double lastCount = lastSentMetrics.getOrDefault(key, 0.0);

View File

@@ -15,7 +15,6 @@ import java.util.TimeZone;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.posthog.java.PostHog; import com.posthog.java.PostHog;
@@ -27,25 +26,19 @@ import stirling.software.SPDF.model.ApplicationProperties;
public class PostHogService { public class PostHogService {
private final PostHog postHog; private final PostHog postHog;
private final String uniqueId; private final String uniqueId;
private final String appVersion;
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private final UserServiceInterface userService; private final UserServiceInterface userService;
private final Environment env;
@Autowired @Autowired
public PostHogService( public PostHogService(
PostHog postHog, PostHog postHog,
@Qualifier("UUID") String uuid, @Qualifier("UUID") String uuid,
@Qualifier("appVersion") String appVersion,
ApplicationProperties applicationProperties, ApplicationProperties applicationProperties,
@Autowired(required = false) UserServiceInterface userService, @Autowired(required = false) UserServiceInterface userService) {
Environment env) {
this.postHog = postHog; this.postHog = postHog;
this.uniqueId = uuid; this.uniqueId = uuid;
this.appVersion = appVersion;
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
this.userService = userService; this.userService = userService;
this.env = env;
captureSystemInfo(); captureSystemInfo();
} }
@@ -71,16 +64,6 @@ public class PostHogService {
Map<String, Object> metrics = new HashMap<>(); Map<String, Object> metrics = new HashMap<>();
try { try {
//Application version
metrics.put("app_version", appVersion);
String deploymentType = "JAR"; // default
if ("true".equalsIgnoreCase(env.getProperty("BROWSER_OPEN"))) {
deploymentType = "EXE";
} else if (isRunningInDocker()) {
deploymentType = "DOCKER";
}
metrics.put("deployment_type", deploymentType);
// System info // System info
metrics.put("os_name", System.getProperty("os.name")); metrics.put("os_name", System.getProperty("os.name"));
metrics.put("os_version", System.getProperty("os.version")); metrics.put("os_version", System.getProperty("os.version"));
@@ -149,6 +132,7 @@ public class PostHogService {
// Docker detection and stats // Docker detection and stats
boolean isDocker = isRunningInDocker(); boolean isDocker = isRunningInDocker();
metrics.put("is_docker", isDocker);
if (isDocker) { if (isDocker) {
metrics.put("docker_metrics", getDockerMetrics()); metrics.put("docker_metrics", getDockerMetrics());
} }

View File

@@ -29,6 +29,10 @@ import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions; import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.fathzer.soft.javaluator.DoubleEvaluator; import com.fathzer.soft.javaluator.DoubleEvaluator;
@@ -349,4 +353,23 @@ public class GeneralUtils {
return "GenericID"; return "GenericID";
} }
} }
public static Resource filePathToResource(String resourceFile) {
if (resourceFile == null) {
throw new IllegalStateException("file is not configured");
}
if (resourceFile.startsWith("classpath:")) {
return new ClassPathResource(resourceFile.substring("classpath:".length()));
} else if (resourceFile.startsWith("http://") || resourceFile.startsWith("https://")) {
try {
return new UrlResource(resourceFile);
} catch (Exception e) {
throw new RuntimeException("Failed to create URL resource: " + resourceFile, e);
}
} else {
return new FileSystemResource(resourceFile);
}
}
} }

View File

@@ -35,7 +35,7 @@ spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000}
#spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/ #spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/
#spring.thymeleaf.cache=false #spring.thymeleaf.cache=false
spring.datasource.url=jdbc:h2:file:./configs/stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE spring.datasource.url=jdbc:h2:file:./configs/stirling-pdf-DB;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driver-class-name=org.h2.Driver spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa spring.datasource.username=sa
spring.datasource.password= spring.datasource.password=

View File

@@ -81,11 +81,10 @@ page=صفحة
pages=صفحات pages=صفحات
loading=جارٍ التحميل... loading=جارٍ التحميل...
addToDoc=إضافة إلى المستند addToDoc=إضافة إلى المستند
reset=إعداة ضبط
legal.privacy=سياسة الخصوصية legal.privacy=سياسة الخصوصية
legal.terms=شروط الاستخدام legal.terms=شروط الاستخدام
legal.accessibility=إمكانية الوصول legal.accessibility=Accessibility
legal.cookie=سياسة ملفات تعريف الارتباط legal.cookie=سياسة ملفات تعريف الارتباط
legal.impressum=بيان الهوية legal.impressum=بيان الهوية
@@ -119,8 +118,8 @@ pipelineOptions.validateButton=تحقق
######################## ########################
enterpriseEdition.button=ترقية إلى محترف enterpriseEdition.button=ترقية إلى محترف
enterpriseEdition.warning=هذه الخاصية متوفرة فقط للمستخدمين المحترفين. enterpriseEdition.warning=هذه الخاصية متوفرة فقط للمستخدمين المحترفين.
enterpriseEdition.yamlAdvert=يدعم Stirling PDF Pro ملفات الإعدادات YAML وميزات SSO أخرى enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
enterpriseEdition.ssoAdvert=هل تبحث عن المزيد من ميزات إدارة المستخدمين؟ اطلع على Stirling PDF Pro enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
################# #################
@@ -142,7 +141,7 @@ navbar.language=اللغات
navbar.settings=إعدادات navbar.settings=إعدادات
navbar.allTools=أدوات navbar.allTools=أدوات
navbar.multiTool=أدوات متعددة navbar.multiTool=أدوات متعددة
navbar.search=البحث navbar.search=Search
navbar.sections.organize=تنظيم navbar.sections.organize=تنظيم
navbar.sections.convertTo=تحويل الى PDF navbar.sections.convertTo=تحويل الى PDF
navbar.sections.convertFrom=تحويل من PDF navbar.sections.convertFrom=تحويل من PDF
@@ -247,8 +246,8 @@ database.fileNotFound=لم يتم العثور على الملف
database.fileNullOrEmpty=يجب ألا يكون الملف فارغًا أو خاليًا database.fileNullOrEmpty=يجب ألا يكون الملف فارغًا أو خاليًا
database.failedImportFile=فشل استيراد الملف database.failedImportFile=فشل استيراد الملف
session.expired=لقد انتهت جلستك. يرجى تحديث الصفحة والمحاولة مرة أخرى session.expired=Your session has expired. Please refresh the page and try again.
session.refreshPage=تحديث الصفحة session.refreshPage=Refresh Page
############# #############
# HOME-PAGE # # HOME-PAGE #
@@ -513,15 +512,15 @@ home.splitPdfByChapters.desc=قسم مستند PDF إلى ملفات متعدد
splitPdfByChapters.tags=تجزئة، فصول، علامات تبويب، تنظيم splitPdfByChapters.tags=تجزئة، فصول، علامات تبويب، تنظيم
#replace-invert-color #replace-invert-color
replace-color.title=إستبدال-عكس اللون replace-color.title=Replace-Invert-Color
replace-color.header=استبدال-عكس لون PDF replace-color.header=استبدال-إلغاء مirro لون PDF
home.replaceColorPdf.title=إستبدال و عكس الألوان home.replaceColorPdf.title=Replace and Invert Color
home.replaceColorPdf.desc=استبدال الألوان للنصوص والخلفيات في المستندات PDF وإلغاء تعكير اللون الكامل للمستند لتقليل حجم الملف home.replaceColorPdf.desc=استبدال الألوان للنصوص والخلفيات في المستندات PDF وإلغاء تعكير اللون الكامل للمستند لتقليل حجم الملف
replaceColorPdf.tags=استبدال اللون، عمليات الصفحة، الخلفية، جانب الخادم replaceColorPdf.tags=استبدال اللون، عمليات الصفحة، الخلفية، جانب الخادم
replace-color.selectText.1=خيارات استبدال أو عكس الألوان replace-color.selectText.1=خيارات استبدال-إلغاء مirro لون
replace-color.selectText.2=افتراضي(ألوان التباين العالي الافتراضية) replace-color.selectText.2=Default(Default high contrast colors)
replace-color.selectText.3=خصيصة (ألوان شخصية) replace-color.selectText.3=خصيصة (ألوان شخصية)
replace-color.selectText.4=عكس كامل(عكس جميع الألوان) replace-color.selectText.4=Full-Invert(Invert all colors)
replace-color.selectText.5=خيارات ألوان التباين العالي replace-color.selectText.5=خيارات ألوان التباين العالي
replace-color.selectText.6=نص أبيض على خلفية سوداء replace-color.selectText.6=نص أبيض على خلفية سوداء
replace-color.selectText.7=نص أسود على خلفية بيضاء replace-color.selectText.7=نص أسود على خلفية بيضاء
@@ -818,12 +817,7 @@ sign.save=حفظ توقيع
sign.personalSigs=توقيعات شخصية sign.personalSigs=توقيعات شخصية
sign.sharedSigs=توقيعات مشتركة sign.sharedSigs=توقيعات مشتركة
sign.noSavedSigs=لم يتم العثور على توقيعات محفوظة sign.noSavedSigs=لم يتم العثور على توقيعات محفوظة
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=إصلاح repair.title=إصلاح
@@ -940,27 +934,17 @@ pdfOrganiser.placeholder=(مثال: 1,3,2 أو 4-8,2,10-12 أو 2n-1)
multiTool.title=أداة متعددة PDF multiTool.title=أداة متعددة PDF
multiTool.header=أداة متعددة PDF multiTool.header=أداة متعددة PDF
multiTool.uploadPrompts=اسم الملف multiTool.uploadPrompts=اسم الملف
multiTool.selectAll=تحديد الكل multiTool.selectAll=Select All
multiTool.deselectAll=إلغاء تحديد الكل multiTool.deselectAll=Deselect All
multiTool.selectPages=تحديد الصفحة multiTool.selectPages=Page Select
multiTool.selectedPages=الصفحات المحددة multiTool.selectedPages=Selected Pages
multiTool.page=صفحة multiTool.page=Page
multiTool.deleteSelected=حذف المحدد multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=تصدير multiTool.downloadAll=Export
multiTool.downloadSelected=تصدير المحدد multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=إدراج فاصل صفحات
multiTool.addFile=إضافة ملف
multiTool.rotateLeft=تدوير إلى اليسار
multiTool.rotateRight=تدوير إلى اليمين
multiTool.split=تقسيم
multiTool.moveLeft=تحريك إلى اليسار
multiTool.moveRight=تحريك إلى اليمين
multiTool.delete=حذف
multiTool.dragDropMessage=الصفحات المحددة
#multiTool-advert #multiTool-advert
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
#view pdf #view pdf
viewPdf.title=عرض PDF viewPdf.title=عرض PDF
@@ -1254,9 +1238,9 @@ splitByChapters.title=تجزئة المستند حسب الفصول
splitByChapters.header=تجزئة المستند حسب الفصول splitByChapters.header=تجزئة المستند حسب الفصول
splitByChapters.bookmarkLevel=مستوى العلامات التذكارية splitByChapters.bookmarkLevel=مستوى العلامات التذكارية
splitByChapters.includeMetadata=شامل البيانات المرفقة splitByChapters.includeMetadata=شامل البيانات المرفقة
splitByChapters.allowDuplicates=السماح بالتكرار splitByChapters.allowDuplicates=Allow Duplicates
splitByChapters.desc.1=هذه الأداة تقوم بتقسيم ملف PDF إلى عدة ملفات PDF استناداً إلى بنية فصوله splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
splitByChapters.desc.2=مستوى الإشارة المرجعية: اختر مستوى الإشارات المرجعية التي تريد استخدامها للتقسيم (0 للمستوى الأعلى، 1 للمستوى الثاني، وما إلى ذلك) splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
splitByChapters.desc.3=تمثيل البيانات الأصلية: إذا تم اختيارها، سترمز البيانات المرجعية الأصلية إلى كل PDF مجزأ. splitByChapters.desc.3=تمثيل البيانات الأصلية: إذا تم اختيارها، سترمز البيانات المرجعية الأصلية إلى كل PDF مجزأ.
splitByChapters.desc.4=سماح بالتكرار: إذا تم اختياره، يسمح بوجود معاينات متعددة في الصفحة نفسها لخلق ملفات PDF منفصلة. splitByChapters.desc.4=سماح بالتكرار: إذا تم اختياره، يسمح بوجود معاينات متعددة في الصفحة نفسها لخلق ملفات PDF منفصلة.
splitByChapters.submit=تقطيع ملف PDF splitByChapters.submit=تقطيع ملف PDF

File diff suppressed because it is too large Load Diff

View File

@@ -81,7 +81,6 @@ page=Страница
pages=Страници pages=Страници
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Политика за поверителност legal.privacy=Политика за поверителност
legal.terms=Правила и условия legal.terms=Правила и условия
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Поправи repair.title=Поправи
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Pàgina
pages=Pàgines pages=Pàgines
loading=Carregant... loading=Carregant...
addToDoc=Afegeix al document addToDoc=Afegeix al document
reset=Reset
legal.privacy=Política de Privacitat legal.privacy=Política de Privacitat
legal.terms=Termes i condicions legal.terms=Termes i condicions
@@ -818,12 +817,7 @@ sign.save=Desa Signatura
sign.personalSigs=Signatures Personals sign.personalSigs=Signatures Personals
sign.sharedSigs=Signatures Compartides sign.sharedSigs=Signatures Compartides
sign.noSavedSigs=No s'han trobat signatures desades sign.noSavedSigs=No s'han trobat signatures desades
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Reparar repair.title=Reparar
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Strana
pages=Strany pages=Strany
loading=Načítání... loading=Načítání...
addToDoc=Přidat do dokumentu addToDoc=Přidat do dokumentu
reset=Reset
legal.privacy=Politika soukromí legal.privacy=Politika soukromí
legal.terms=Podmínky použití legal.terms=Podmínky použití
@@ -818,12 +817,7 @@ sign.save=Uložit podpis
sign.personalSigs=Osobní podpisy sign.personalSigs=Osobní podpisy
sign.sharedSigs=Sdílené podpisy sign.sharedSigs=Sdílené podpisy
sign.noSavedSigs=Nenašly se žádné uložené podpisy sign.noSavedSigs=Nenašly se žádné uložené podpisy
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Opravit repair.title=Opravit
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Sidenummer
pages=Sideantal pages=Sideantal
loading=Laster... loading=Laster...
addToDoc=Tilføj til Dokument addToDoc=Tilføj til Dokument
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Vilkår og betingelser legal.terms=Vilkår og betingelser
@@ -818,12 +817,7 @@ sign.save=Gem Signatur
sign.personalSigs=Personlige Signaturer sign.personalSigs=Personlige Signaturer
sign.sharedSigs=Delte Signaturer sign.sharedSigs=Delte Signaturer
sign.noSavedSigs=Ingen Gemte Signaturer Fundet sign.noSavedSigs=Ingen Gemte Signaturer Fundet
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Reparér repair.title=Reparér
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Seite
pages=Seiten pages=Seiten
loading=Laden... loading=Laden...
addToDoc=In Dokument hinzufügen addToDoc=In Dokument hinzufügen
reset=Zurücksetzen
legal.privacy=Datenschutz legal.privacy=Datenschutz
legal.terms=AGB legal.terms=AGB
@@ -818,12 +817,7 @@ sign.save=Signature speichern
sign.personalSigs=Persönliche Signaturen sign.personalSigs=Persönliche Signaturen
sign.sharedSigs=Geteilte Signaturen sign.sharedSigs=Geteilte Signaturen
sign.noSavedSigs=Es wurden keine gespeicherten Signaturen gefunden sign.noSavedSigs=Es wurden keine gespeicherten Signaturen gefunden
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Reparieren repair.title=Reparieren
@@ -949,18 +943,8 @@ multiTool.deleteSelected=Auswahl löschen
multiTool.downloadAll=Downloaden multiTool.downloadAll=Downloaden
multiTool.downloadSelected=Auswahl downloaden multiTool.downloadSelected=Auswahl downloaden
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
#view pdf #view pdf
viewPdf.title=PDF anzeigen viewPdf.title=PDF anzeigen

View File

@@ -81,7 +81,6 @@ page=Σελίδα
pages=Σελίδες pages=Σελίδες
loading=Φόρτωση... loading=Φόρτωση...
addToDoc=Πρόσθεση στο Εκπομπώματο addToDoc=Πρόσθεση στο Εκπομπώματο
reset=Reset
legal.privacy=Πολιτική Προνομίους legal.privacy=Πολιτική Προνομίους
legal.terms=Φράσεις Υποχρεωτικότητας legal.terms=Φράσεις Υποχρεωτικότητας
@@ -818,12 +817,7 @@ sign.save=Αποθήκευση Αλιάσης
sign.personalSigs=Προσωπικές Αλιάσεις sign.personalSigs=Προσωπικές Αλιάσεις
sign.sharedSigs=Μεταδότες Αλιάσεις sign.sharedSigs=Μεταδότες Αλιάσεις
sign.noSavedSigs=Δεν βρέθηκαν αποθηκευμένες αλιάσεις sign.noSavedSigs=Δεν βρέθηκαν αποθηκευμένες αλιάσεις
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Επιδιόρθωση repair.title=Επιδιόρθωση
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -818,12 +818,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Repair repair.title=Repair
@@ -949,16 +944,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Terms and Conditions legal.terms=Terms and Conditions
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Repair repair.title=Repair
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Página
pages=Páginas pages=Páginas
loading=Cargando... loading=Cargando...
addToDoc=Agregar al Documento addToDoc=Agregar al Documento
reset=Reset
legal.privacy=Política de Privacidad legal.privacy=Política de Privacidad
legal.terms=Términos y Condiciones legal.terms=Términos y Condiciones
@@ -818,12 +817,7 @@ sign.save=Guardar Firma
sign.personalSigs=Firmas Personales sign.personalSigs=Firmas Personales
sign.sharedSigs=Firmas compartidas sign.sharedSigs=Firmas compartidas
sign.noSavedSigs=No se encontraron firmas guardadas sign.noSavedSigs=No se encontraron firmas guardadas
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Reparar repair.title=Reparar
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Terms and Conditions legal.terms=Terms and Conditions
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Konpondu repair.title=Konpondu
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -56,8 +56,8 @@ userNotFoundMessage=Utilisateur non trouvé.
incorrectPasswordMessage=Le mot de passe actuel est incorrect. incorrectPasswordMessage=Le mot de passe actuel est incorrect.
usernameExistsMessage=Le nouveau nom d'utilisateur existe déjà. usernameExistsMessage=Le nouveau nom d'utilisateur existe déjà.
invalidUsernameMessage=Nom d'utilisateur invalide, le nom d'utilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide. invalidUsernameMessage=Nom d'utilisateur invalide, le nom d'utilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide.
invalidPasswordMessage=Le mot de passe ne peut pas être vide et ne doit pas contenir d'espaces au début ou à la fin. invalidPasswordMessage=Le mot de passe ne peut pas être vide et ne doit pas contenir d'espaces au début ou en fin.
confirmPasswordErrorMessage=Le nouveau mot de passe et sa confirmation doivent être identiques. confirmPasswordErrorMessage=Nouveau Mot de passe et Confirmer le Nouveau Mot de passe doivent correspondre.
deleteCurrentUserMessage=Impossible de supprimer l'utilisateur actuellement connecté. deleteCurrentUserMessage=Impossible de supprimer l'utilisateur actuellement connecté.
deleteUsernameExistsMessage=Le nom d'utilisateur n'existe pas et ne peut pas être supprimé. deleteUsernameExistsMessage=Le nom d'utilisateur n'existe pas et ne peut pas être supprimé.
downgradeCurrentUserMessage=Impossible de rétrograder le rôle de l'utilisateur actuel. downgradeCurrentUserMessage=Impossible de rétrograder le rôle de l'utilisateur actuel.
@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Chargement... loading=Chargement...
addToDoc=Ajouter au Document addToDoc=Ajouter au Document
reset=Réinitialiser
legal.privacy=Politique de Confidentialité legal.privacy=Politique de Confidentialité
legal.terms=Conditions Générales legal.terms=Conditions Générales
@@ -142,7 +141,7 @@ navbar.language=Langues
navbar.settings=Paramètres navbar.settings=Paramètres
navbar.allTools=Outils navbar.allTools=Outils
navbar.multiTool=Outils Multiples navbar.multiTool=Outils Multiples
navbar.search=Rechercher navbar.search=Search
navbar.sections.organize=Organisation navbar.sections.organize=Organisation
navbar.sections.convertTo=Convertir en PDF navbar.sections.convertTo=Convertir en PDF
navbar.sections.convertFrom=Convertir depuis PDF navbar.sections.convertFrom=Convertir depuis PDF
@@ -813,17 +812,12 @@ sign.draw=Dessiner une signature
sign.text=Saisir de texte sign.text=Saisir de texte
sign.clear=Effacer sign.clear=Effacer
sign.add=Ajouter sign.add=Ajouter
sign.saved=Sceaux enregistrées sign.saved=Saved Signatures
sign.save=Enregistrer le sceau sign.save=Enregistrer le sceau
sign.personalSigs=Sceaux personnels sign.personalSigs=Sceaux personnels
sign.sharedSigs=Sceaux partagés sign.sharedSigs=Sceaux partagés
sign.noSavedSigs=Aucun sceau enregistré trouvé sign.noSavedSigs=Aucun sceau enregistré trouvé
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Réparer repair.title=Réparer
@@ -940,27 +934,17 @@ pdfOrganiser.placeholder=(par exemple 1,3,2 ou 4-8,2,10-12 ou 2n-1)
multiTool.title=Outil multifonction PDF multiTool.title=Outil multifonction PDF
multiTool.header=Outil multifonction PDF multiTool.header=Outil multifonction PDF
multiTool.uploadPrompts=Nom du fichier multiTool.uploadPrompts=Nom du fichier
multiTool.selectAll=Tout sélectionner multiTool.selectAll=Select All
multiTool.deselectAll=Tout déselectionner multiTool.deselectAll=Deselect All
multiTool.selectPages=Sélection des pages multiTool.selectPages=Page Select
multiTool.selectedPages=Pages sélectionnées multiTool.selectedPages=Selected Pages
multiTool.page=Page multiTool.page=Page
multiTool.deleteSelected=Supprimer la sélection multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Exporter multiTool.downloadAll=Export
multiTool.downloadSelected=Exporter la sélection multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insérer un saut de page
multiTool.addFile=Ajouter un fichier
multiTool.rotateLeft=Rotation vers la gauche
multiTool.rotateRight=Rotation vers la droite
multiTool.split=Diviser
multiTool.moveLeft=Déplacer vers la gauche
multiTool.moveRight=Déplacer vers la droite
multiTool.delete=Supprimer
multiTool.dragDropMessage=Page(s) sélectionnées
#multiTool-advert #multiTool-advert
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles ! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
#view pdf #view pdf
viewPdf.title=Visualiser un PDF viewPdf.title=Visualiser un PDF

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Terms and Conditions legal.terms=Terms and Conditions
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Deisiúchán repair.title=Deisiúchán
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=पृष्ठ
pages=पृष्ठों pages=पृष्ठों
loading=डालिंग... loading=डालिंग...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=गुप्तता सूचना legal.privacy=गुप्तता सूचना
legal.terms=शर्तें और प्रवाह legal.terms=शर्तें और प्रवाह
@@ -818,12 +817,7 @@ sign.save=प्रदर्शन बचाएं
sign.personalSigs=मौजूदा प्रदर्शन sign.personalSigs=मौजूदा प्रदर्शन
sign.sharedSigs=साझेदार प्रदर्शन sign.sharedSigs=साझेदार प्रदर्शन
sign.noSavedSigs=कोई भी संवर्तित प्रदर्शन नहीं मौजूद है sign.noSavedSigs=कोई भी संवर्तित प्रदर्शन नहीं मौजूद है
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=मरम्मत repair.title=मरम्मत
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Stranica
pages=Stranice pages=Stranice
loading=Učitavanje... loading=Učitavanje...
addToDoc=Dodaj u dokument addToDoc=Dodaj u dokument
reset=Reset
legal.privacy=Politika privatnosti legal.privacy=Politika privatnosti
legal.terms=Uspe sodržine legal.terms=Uspe sodržine
@@ -818,12 +817,7 @@ sign.save=Sačuvaj potpisnu oznaku
sign.personalSigs=Osobni potpisi sign.personalSigs=Osobni potpisi
sign.sharedSigs=Dijeljeni potpisi sign.sharedSigs=Dijeljeni potpisi
sign.noSavedSigs=Nema sacuvanih potpisa pronađenih sign.noSavedSigs=Nema sacuvanih potpisa pronađenih
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Popravi repair.title=Popravi
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Oldal
pages=Oldalak pages=Oldalak
loading=Betöltés... loading=Betöltés...
addToDoc=Hozzáadás dokumentumba addToDoc=Hozzáadás dokumentumba
reset=Reset
legal.privacy=Adatvédelmi nyilatkozat legal.privacy=Adatvédelmi nyilatkozat
legal.terms=Feltételek és feltételek legal.terms=Feltételek és feltételek
@@ -818,12 +817,7 @@ sign.save=Aláíráshoz mentés
sign.personalSigs=Személyi aláíráshoz sign.personalSigs=Személyi aláíráshoz
sign.sharedSigs=Megosztott aláíráshoz sign.sharedSigs=Megosztott aláíráshoz
sign.noSavedSigs=Nincsenek mentett aláírások találat sign.noSavedSigs=Nincsenek mentett aláírások találat
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Javítás repair.title=Javítás
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Halaman
pages=Halaman-halaman pages=Halaman-halaman
loading=Mengambil data... loading=Mengambil data...
addToDoc=Tambahkan ke Dokumen addToDoc=Tambahkan ke Dokumen
reset=Reset
legal.privacy=Kebijakan Privasi legal.privacy=Kebijakan Privasi
legal.terms=Syarat dan Ketentuan legal.terms=Syarat dan Ketentuan
@@ -818,12 +817,7 @@ sign.save=Simpan Tanda Tangan
sign.personalSigs=Tanda Tangan Pribadi sign.personalSigs=Tanda Tangan Pribadi
sign.sharedSigs=Tanda Tangan Berbagi sign.sharedSigs=Tanda Tangan Berbagi
sign.noSavedSigs=Tidak ditemukan tanda tangan yang disimpan sign.noSavedSigs=Tidak ditemukan tanda tangan yang disimpan
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Perbaiki repair.title=Perbaiki
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Pagina
pages=Pagine pages=Pagine
loading=Caricamento... loading=Caricamento...
addToDoc=Aggiungi al documento addToDoc=Aggiungi al documento
reset=Reset
legal.privacy=Informativa sulla privacy legal.privacy=Informativa sulla privacy
legal.terms=Termini e Condizioni legal.terms=Termini e Condizioni
@@ -142,7 +141,7 @@ navbar.language=Lingue
navbar.settings=Impostazioni navbar.settings=Impostazioni
navbar.allTools=Strumenti navbar.allTools=Strumenti
navbar.multiTool=Strumenti multipli navbar.multiTool=Strumenti multipli
navbar.search=Cerca navbar.search=Search
navbar.sections.organize=Organizza navbar.sections.organize=Organizza
navbar.sections.convertTo=Converti in PDF navbar.sections.convertTo=Converti in PDF
navbar.sections.convertFrom=Converti da PDF navbar.sections.convertFrom=Converti da PDF
@@ -818,12 +817,7 @@ sign.save=Firma salvata
sign.personalSigs=Firme personali sign.personalSigs=Firme personali
sign.sharedSigs=Firme condivise sign.sharedSigs=Firme condivise
sign.noSavedSigs=Nessuna firma salvata trovata sign.noSavedSigs=Nessuna firma salvata trovata
sign.addToAll=Aggiungi a tutte le pagine
sign.delete=Elimina
sign.first=Prima pagina
sign.last=Ultima pagina
sign.next=Prossima pagina
sign.previous=Pagina precedente
#repair #repair
repair.title=Ripara repair.title=Ripara
@@ -949,18 +943,8 @@ multiTool.deleteSelected=Elimina selezionata
multiTool.downloadAll=Esporta multiTool.downloadAll=Esporta
multiTool.downloadSelected=Esporta selezionata multiTool.downloadSelected=Esporta selezionata
multiTool.insertPageBreak=Inserisci interruzione di pagina
multiTool.addFile=Aggiungi file
multiTool.rotateLeft=Ruota a sinistra
multiTool.rotateRight=Ruota a destra
multiTool.split=Dividi
multiTool.moveLeft=Sposta a sinistra
multiTool.moveRight=Sposta a destra
multiTool.delete=Elimina
multiTool.dragDropMessage=Pagina(e) selezionata(e)
#multiTool-advert #multiTool-advert
multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
#view pdf #view pdf
viewPdf.title=Visualizza PDF viewPdf.title=Visualizza PDF

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=プライバシーポリシー legal.privacy=プライバシーポリシー
legal.terms=利用規約 legal.terms=利用規約
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=修復 repair.title=修復
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=페이지
pages=페이지 pages=페이지
loading=로딩 중... loading=로딩 중...
addToDoc=문서에 추가 addToDoc=문서에 추가
reset=Reset
legal.privacy=개인 정보 정책 legal.privacy=개인 정보 정책
legal.terms=이용 약관 legal.terms=이용 약관
@@ -818,12 +817,7 @@ sign.save=서명 저장
sign.personalSigs=개인용 서명 sign.personalSigs=개인용 서명
sign.sharedSigs=공유용 서명 sign.sharedSigs=공유용 서명
sign.noSavedSigs=저장된 서명이 없습니다 sign.noSavedSigs=저장된 서명이 없습니다
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=복구 repair.title=복구
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Pagina
pages=Pagen pages=Pagen
loading=Laden... loading=Laden...
addToDoc=Toevoegen aan document addToDoc=Toevoegen aan document
reset=Reset
legal.privacy=Privacybeleid legal.privacy=Privacybeleid
legal.terms=Voorwaarden van gebruik legal.terms=Voorwaarden van gebruik
@@ -818,12 +817,7 @@ sign.save=Opslaan Signatuur
sign.personalSigs=Persoonlijke Signatuuren sign.personalSigs=Persoonlijke Signatuuren
sign.sharedSigs=Gedeelde Signatuuren sign.sharedSigs=Gedeelde Signatuuren
sign.noSavedSigs=Geen opgeslagen signatuuren gevonden sign.noSavedSigs=Geen opgeslagen signatuuren gevonden
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Repareren repair.title=Repareren
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Terms and Conditions legal.terms=Terms and Conditions
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Reparer repair.title=Reparer
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Strona
pages=Strony pages=Strony
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Polityka Prywatności legal.privacy=Polityka Prywatności
legal.terms=Zasady i Postanowienia legal.terms=Zasady i Postanowienia
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Napraw repair.title=Napraw
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Página
pages=Páginas pages=Páginas
loading=Carregando... loading=Carregando...
addToDoc=Adicionar ao Documento addToDoc=Adicionar ao Documento
reset=Reset
legal.privacy=Política de Privacidade legal.privacy=Política de Privacidade
legal.terms=Termos e Condições legal.terms=Termos e Condições
@@ -818,12 +817,7 @@ sign.save=Salvar Assinatura
sign.personalSigs=Assinaturas Pessoais sign.personalSigs=Assinaturas Pessoais
sign.sharedSigs=Assinaturas Compartilhadas sign.sharedSigs=Assinaturas Compartilhadas
sign.noSavedSigs=Nenhuma assinatura salva encontrada sign.noSavedSigs=Nenhuma assinatura salva encontrada
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Reparar repair.title=Reparar
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Página
pages=Páginas pages=Páginas
loading=A carregar... loading=A carregar...
addToDoc=Adicionar ao Documento addToDoc=Adicionar ao Documento
reset=Reset
legal.privacy=Política de Privacidade legal.privacy=Política de Privacidade
legal.terms=Termos e Condições legal.terms=Termos e Condições
@@ -818,12 +817,7 @@ sign.save=Guardar Assinatura
sign.personalSigs=Assinaturas Pessoais sign.personalSigs=Assinaturas Pessoais
sign.sharedSigs=Assinaturas Compartilhadas sign.sharedSigs=Assinaturas Compartilhadas
sign.noSavedSigs=Nenhuma assinatura guardada encontrada sign.noSavedSigs=Nenhuma assinatura guardada encontrada
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Reparar repair.title=Reparar
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Terms and Conditions legal.terms=Terms and Conditions
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Repară repair.title=Repară
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Страница
pages=Страницы pages=Страницы
loading=Загрузка... loading=Загрузка...
addToDoc=Добавить в документ addToDoc=Добавить в документ
reset=Reset
legal.privacy=Политика конфиденциальности legal.privacy=Политика конфиденциальности
legal.terms=Условия использования legal.terms=Условия использования
@@ -818,12 +817,7 @@ sign.save=Сохранить подпись
sign.personalSigs=Личные подписи sign.personalSigs=Личные подписи
sign.sharedSigs=Общие подписи sign.sharedSigs=Общие подписи
sign.noSavedSigs=Найдено ни одной сохраненной подписи sign.noSavedSigs=Найдено ни одной сохраненной подписи
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Ремонт repair.title=Ремонт
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Terms and Conditions legal.terms=Terms and Conditions
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Opraviť repair.title=Opraviť
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Terms and Conditions legal.terms=Terms and Conditions
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Popravi repair.title=Popravi
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Sidan
pages=Sidor pages=Sidor
loading=Laddar... loading=Laddar...
addToDoc=Lägg till i dokument addToDoc=Lägg till i dokument
reset=Reset
legal.privacy=Dataprotektionspolicy legal.privacy=Dataprotektionspolicy
legal.terms=Villkor och betingelser legal.terms=Villkor och betingelser
@@ -818,12 +817,7 @@ sign.save=Spara signatur
sign.personalSigs=Personliga signaturer sign.personalSigs=Personliga signaturer
sign.sharedSigs=Delade signaturer sign.sharedSigs=Delade signaturer
sign.noSavedSigs=Inga sparade signaturer hittades sign.noSavedSigs=Inga sparade signaturer hittades
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Reparera repair.title=Reparera
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=หน้า
pages=หน้า pages=หน้า
loading=กำลังโหลด... loading=กำลังโหลด...
addToDoc=เพิ่มเข้าสู่เอกสาร addToDoc=เพิ่มเข้าสู่เอกสาร
reset=Reset
legal.privacy=นโยบายความเป็นส่วนตัว legal.privacy=นโยบายความเป็นส่วนตัว
legal.terms=ข้อกำหนดการใช้งาน legal.terms=ข้อกำหนดการใช้งาน
@@ -818,12 +817,7 @@ sign.save=บันทึกลายเซ็น
sign.personalSigs=ลายเซ็นส่วนตัว sign.personalSigs=ลายเซ็นส่วนตัว
sign.sharedSigs=ลายเซ็นร่วม sign.sharedSigs=ลายเซ็นร่วม
sign.noSavedSigs=ไม่พบลายเซ็นที่บันทึกไว้ sign.noSavedSigs=ไม่พบลายเซ็นที่บันทึกไว้
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=ซ่อมแซม repair.title=ซ่อมแซม
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Gizlilik Politikası legal.privacy=Gizlilik Politikası
legal.terms=Şartlar ve koşullar legal.terms=Şartlar ve koşullar
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Onar repair.title=Onar
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Terms and Conditions legal.terms=Terms and Conditions
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Ремонт repair.title=Ремонт
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Terms and Conditions legal.terms=Terms and Conditions
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=Sửa chữa repair.title=Sửa chữa
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=Page
pages=Pages pages=Pages
loading=Loading... loading=Loading...
addToDoc=Add to Document addToDoc=Add to Document
reset=Reset
legal.privacy=Privacy Policy legal.privacy=Privacy Policy
legal.terms=Terms and Conditions legal.terms=Terms and Conditions
@@ -818,12 +817,7 @@ sign.save=Save Signature
sign.personalSigs=Personal Signatures sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=No saved signatures found
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=修复 repair.title=修复
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -81,7 +81,6 @@ page=頁面
pages=頁面 pages=頁面
loading=載入中... loading=載入中...
addToDoc=新增至文件 addToDoc=新增至文件
reset=Reset
legal.privacy=隱私權政策 legal.privacy=隱私權政策
legal.terms=使用條款 legal.terms=使用條款
@@ -818,12 +817,7 @@ sign.save=儲存簽章
sign.personalSigs=個人簽章 sign.personalSigs=個人簽章
sign.sharedSigs=共用簽章 sign.sharedSigs=共用簽章
sign.noSavedSigs=尚未儲存任何簽章 sign.noSavedSigs=尚未儲存任何簽章
sign.addToAll=Add to all pages
sign.delete=Delete
sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
#repair #repair
repair.title=修復 repair.title=修復
@@ -949,16 +943,6 @@ multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=Export multiTool.downloadAll=Export
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File
multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=Rotate Right
multiTool.split=Split
multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@@ -3,14 +3,14 @@
{ {
"moduleName": "ch.qos.logback:logback-classic", "moduleName": "ch.qos.logback:logback-classic",
"moduleUrl": "http://www.qos.ch", "moduleUrl": "http://www.qos.ch",
"moduleVersion": "1.5.12", "moduleVersion": "1.5.11",
"moduleLicense": "GNU Lesser General Public License", "moduleLicense": "GNU Lesser General Public License",
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" "moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
}, },
{ {
"moduleName": "ch.qos.logback:logback-core", "moduleName": "ch.qos.logback:logback-core",
"moduleUrl": "http://www.qos.ch", "moduleUrl": "http://www.qos.ch",
"moduleVersion": "1.5.12", "moduleVersion": "1.5.11",
"moduleLicense": "GNU Lesser General Public License", "moduleLicense": "GNU Lesser General Public License",
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" "moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
}, },
@@ -45,77 +45,77 @@
{ {
"moduleName": "com.fasterxml.jackson.core:jackson-annotations", "moduleName": "com.fasterxml.jackson.core:jackson-annotations",
"moduleUrl": "https://github.com/FasterXML/jackson", "moduleUrl": "https://github.com/FasterXML/jackson",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.core:jackson-core", "moduleName": "com.fasterxml.jackson.core:jackson-core",
"moduleUrl": "https://github.com/FasterXML/jackson-core", "moduleUrl": "https://github.com/FasterXML/jackson-core",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.core:jackson-databind", "moduleName": "com.fasterxml.jackson.core:jackson-databind",
"moduleUrl": "https://github.com/FasterXML/jackson", "moduleUrl": "https://github.com/FasterXML/jackson",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", "moduleName": "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml",
"moduleUrl": "https://github.com/FasterXML/jackson-dataformats-text", "moduleUrl": "https://github.com/FasterXML/jackson-dataformats-text",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.datatype:jackson-datatype-jdk8", "moduleName": "com.fasterxml.jackson.datatype:jackson-datatype-jdk8",
"moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8", "moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", "moduleName": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310",
"moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310", "moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.jaxrs:jackson-jaxrs-base", "moduleName": "com.fasterxml.jackson.jaxrs:jackson-jaxrs-base",
"moduleUrl": "https://github.com/FasterXML/jackson-jaxrs-providers/jackson-jaxrs-base", "moduleUrl": "https://github.com/FasterXML/jackson-jaxrs-providers/jackson-jaxrs-base",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider", "moduleName": "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider",
"moduleUrl": "https://github.com/FasterXML/jackson-jaxrs-providers/jackson-jaxrs-json-provider", "moduleUrl": "https://github.com/FasterXML/jackson-jaxrs-providers/jackson-jaxrs-json-provider",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.module:jackson-module-jaxb-annotations", "moduleName": "com.fasterxml.jackson.module:jackson-module-jaxb-annotations",
"moduleUrl": "https://github.com/FasterXML/jackson-modules-base", "moduleUrl": "https://github.com/FasterXML/jackson-modules-base",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.module:jackson-module-parameter-names", "moduleName": "com.fasterxml.jackson.module:jackson-module-parameter-names",
"moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names", "moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson:jackson-bom", "moduleName": "com.fasterxml.jackson:jackson-bom",
"moduleUrl": "https://github.com/FasterXML/jackson-bom", "moduleUrl": "https://github.com/FasterXML/jackson-bom",
"moduleVersion": "2.18.1", "moduleVersion": "2.17.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@@ -146,18 +146,6 @@
"moduleLicense": "GNU General Public License v3.0", "moduleLicense": "GNU General Public License v3.0",
"moduleLicenseUrl": "https://api.github.com/licenses/gpl-3.0" "moduleLicenseUrl": "https://api.github.com/licenses/gpl-3.0"
}, },
{
"moduleName": "com.github.jai-imageio:jai-imageio-core",
"moduleUrl": "https://github.com/jai-imageio/jai-imageio-core",
"moduleVersion": "1.4.0",
"moduleLicense": "LICENSE.txt"
},
{
"moduleName": "com.github.jai-imageio:jai-imageio-jpeg2000",
"moduleUrl": "https://github.com/jai-imageio/jai-imageio-jpeg2000",
"moduleVersion": "1.4.0",
"moduleLicense": "LICENSE-JJ2000.txt, LICENSE-Sun.txt"
},
{ {
"moduleName": "com.github.stephenc.jcip:jcip-annotations", "moduleName": "com.github.stephenc.jcip:jcip-annotations",
"moduleUrl": "http://stephenc.github.com/jcip-annotations", "moduleUrl": "http://stephenc.github.com/jcip-annotations",
@@ -174,22 +162,21 @@
}, },
{ {
"moduleName": "com.google.errorprone:error_prone_annotations", "moduleName": "com.google.errorprone:error_prone_annotations",
"moduleUrl": "https://errorprone.info/error_prone_annotations", "moduleVersion": "2.11.0",
"moduleVersion": "2.28.0",
"moduleLicense": "Apache 2.0", "moduleLicense": "Apache 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.google.guava:failureaccess", "moduleName": "com.google.guava:failureaccess",
"moduleUrl": "https://github.com/google/guava/", "moduleUrl": "https://github.com/google/guava/",
"moduleVersion": "1.0.2", "moduleVersion": "1.0.1",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.google.guava:guava", "moduleName": "com.google.guava:guava",
"moduleUrl": "https://github.com/google/guava/", "moduleUrl": "https://github.com/google/guava/",
"moduleVersion": "33.3.1-jre", "moduleVersion": "31.1-jre",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@@ -202,8 +189,8 @@
{ {
"moduleName": "com.google.j2objc:j2objc-annotations", "moduleName": "com.google.j2objc:j2objc-annotations",
"moduleUrl": "https://github.com/google/j2objc/", "moduleUrl": "https://github.com/google/j2objc/",
"moduleVersion": "3.0.0", "moduleVersion": "1.3",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
@@ -235,7 +222,7 @@
{ {
"moduleName": "com.h2database:h2", "moduleName": "com.h2database:h2",
"moduleUrl": "https://h2database.com", "moduleUrl": "https://h2database.com",
"moduleVersion": "2.3.232", "moduleVersion": "2.1.214",
"moduleLicense": "MPL 2.0", "moduleLicense": "MPL 2.0",
"moduleLicenseUrl": "https://www.mozilla.org/en-US/MPL/2.0/" "moduleLicenseUrl": "https://www.mozilla.org/en-US/MPL/2.0/"
}, },
@@ -383,17 +370,10 @@
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{
"moduleName": "commons-cli:commons-cli",
"moduleUrl": "http://commons.apache.org/proper/commons-cli/",
"moduleVersion": "1.4",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{ {
"moduleName": "commons-codec:commons-codec", "moduleName": "commons-codec:commons-codec",
"moduleUrl": "https://commons.apache.org/proper/commons-codec/", "moduleUrl": "https://commons.apache.org/proper/commons-codec/",
"moduleVersion": "1.17.1", "moduleVersion": "1.16.1",
"moduleLicense": "Apache-2.0", "moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@@ -413,10 +393,10 @@
}, },
{ {
"moduleName": "commons-logging:commons-logging", "moduleName": "commons-logging:commons-logging",
"moduleUrl": "https://commons.apache.org/proper/commons-logging/", "moduleUrl": "http://jakarta.apache.org/commons/logging/",
"moduleVersion": "1.3.3", "moduleVersion": "1.0.4",
"moduleLicense": "Apache-2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "/LICENSE.txt"
}, },
{ {
"moduleName": "io.dropwizard.metrics:metrics-core", "moduleName": "io.dropwizard.metrics:metrics-core",
@@ -434,34 +414,34 @@
{ {
"moduleName": "io.micrometer:micrometer-commons", "moduleName": "io.micrometer:micrometer-commons",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer", "moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.14.1", "moduleVersion": "1.13.6",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.micrometer:micrometer-core", "moduleName": "io.micrometer:micrometer-core",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer", "moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.14.1", "moduleVersion": "1.13.6",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.micrometer:micrometer-jakarta9", "moduleName": "io.micrometer:micrometer-jakarta9",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer", "moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.14.1", "moduleVersion": "1.13.6",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.micrometer:micrometer-observation", "moduleName": "io.micrometer:micrometer-observation",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer", "moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.14.1", "moduleVersion": "1.13.6",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.smallrye:jandex", "moduleName": "io.smallrye:jandex",
"moduleVersion": "3.2.0", "moduleVersion": "3.1.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@@ -593,7 +573,7 @@
}, },
{ {
"moduleName": "net.bytebuddy:byte-buddy", "moduleName": "net.bytebuddy:byte-buddy",
"moduleVersion": "1.15.10", "moduleVersion": "1.14.19",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@@ -631,17 +611,10 @@
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{
"moduleName": "org.apache.commons:commons-csv",
"moduleUrl": "https://commons.apache.org/proper/commons-csv/",
"moduleVersion": "1.9.0",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{ {
"moduleName": "org.apache.commons:commons-lang3", "moduleName": "org.apache.commons:commons-lang3",
"moduleUrl": "https://commons.apache.org/proper/commons-lang/", "moduleUrl": "https://commons.apache.org/proper/commons-lang/",
"moduleVersion": "3.17.0", "moduleVersion": "3.14.0",
"moduleLicense": "Apache-2.0", "moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@@ -668,13 +641,13 @@
}, },
{ {
"moduleName": "org.apache.logging.log4j:log4j-api", "moduleName": "org.apache.logging.log4j:log4j-api",
"moduleVersion": "2.24.1", "moduleVersion": "2.23.1",
"moduleLicense": "Apache-2.0", "moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "org.apache.logging.log4j:log4j-to-slf4j", "moduleName": "org.apache.logging.log4j:log4j-to-slf4j",
"moduleVersion": "2.24.1", "moduleVersion": "2.23.1",
"moduleLicense": "Apache-2.0", "moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@@ -722,7 +695,7 @@
{ {
"moduleName": "org.apache.tomcat.embed:tomcat-embed-el", "moduleName": "org.apache.tomcat.embed:tomcat-embed-el",
"moduleUrl": "https://tomcat.apache.org/", "moduleUrl": "https://tomcat.apache.org/",
"moduleVersion": "10.1.33", "moduleVersion": "10.1.31",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@@ -760,52 +733,31 @@
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{
"moduleName": "org.bouncycastle:bcmail-jdk15on",
"moduleUrl": "https://www.bouncycastle.org/java.html",
"moduleVersion": "1.69",
"moduleLicense": "Bouncy Castle Licence",
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
},
{
"moduleName": "org.bouncycastle:bcpkix-jdk15on",
"moduleUrl": "https://www.bouncycastle.org/java.html",
"moduleVersion": "1.69",
"moduleLicense": "Bouncy Castle Licence",
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
},
{ {
"moduleName": "org.bouncycastle:bcpkix-jdk18on", "moduleName": "org.bouncycastle:bcpkix-jdk18on",
"moduleUrl": "https://www.bouncycastle.org/java.html", "moduleUrl": "https://www.bouncycastle.org/java.html",
"moduleVersion": "1.79", "moduleVersion": "1.78.1",
"moduleLicense": "Bouncy Castle Licence", "moduleLicense": "Bouncy Castle Licence",
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html" "moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
}, },
{ {
"moduleName": "org.bouncycastle:bcprov-jdk18on", "moduleName": "org.bouncycastle:bcprov-jdk18on",
"moduleUrl": "https://www.bouncycastle.org/java.html", "moduleUrl": "https://www.bouncycastle.org/java.html",
"moduleVersion": "1.79", "moduleVersion": "1.78.1",
"moduleLicense": "Bouncy Castle Licence",
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
},
{
"moduleName": "org.bouncycastle:bcutil-jdk15on",
"moduleUrl": "https://www.bouncycastle.org/java.html",
"moduleVersion": "1.69",
"moduleLicense": "Bouncy Castle Licence", "moduleLicense": "Bouncy Castle Licence",
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html" "moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
}, },
{ {
"moduleName": "org.bouncycastle:bcutil-jdk18on", "moduleName": "org.bouncycastle:bcutil-jdk18on",
"moduleUrl": "https://www.bouncycastle.org/java.html", "moduleUrl": "https://www.bouncycastle.org/java.html",
"moduleVersion": "1.79", "moduleVersion": "1.78.1",
"moduleLicense": "Bouncy Castle Licence", "moduleLicense": "Bouncy Castle Licence",
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html" "moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
}, },
{ {
"moduleName": "org.checkerframework:checker-qual", "moduleName": "org.checkerframework:checker-qual",
"moduleUrl": "https://checkerframework.org/", "moduleUrl": "https://checkerframework.org",
"moduleVersion": "3.43.0", "moduleVersion": "3.12.0",
"moduleLicense": "The MIT License", "moduleLicense": "The MIT License",
"moduleLicenseUrl": "http://opensource.org/licenses/MIT" "moduleLicenseUrl": "http://opensource.org/licenses/MIT"
}, },
@@ -838,182 +790,182 @@
{ {
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client", "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common", "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server", "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server", "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet", "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-annotations", "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-annotations",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-plus", "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-plus",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlet", "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlet",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlets", "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlets",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-webapp", "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-webapp",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-client", "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-client",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-common", "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-common",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-server", "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-server",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-api", "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-api",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-common", "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-common",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-alpn-client", "moduleName": "org.eclipse.jetty:jetty-alpn-client",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-client", "moduleName": "org.eclipse.jetty:jetty-client",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-ee", "moduleName": "org.eclipse.jetty:jetty-ee",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-http", "moduleName": "org.eclipse.jetty:jetty-http",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-io", "moduleName": "org.eclipse.jetty:jetty-io",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-plus", "moduleName": "org.eclipse.jetty:jetty-plus",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-security", "moduleName": "org.eclipse.jetty:jetty-security",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-server", "moduleName": "org.eclipse.jetty:jetty-server",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-session", "moduleName": "org.eclipse.jetty:jetty-session",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-util", "moduleName": "org.eclipse.jetty:jetty-util",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-xml", "moduleName": "org.eclipse.jetty:jetty-xml",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.15", "moduleVersion": "12.0.14",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
@@ -1048,23 +1000,23 @@
{ {
"moduleName": "org.hibernate.common:hibernate-commons-annotations", "moduleName": "org.hibernate.common:hibernate-commons-annotations",
"moduleUrl": "http://hibernate.org", "moduleUrl": "http://hibernate.org",
"moduleVersion": "7.0.3.Final", "moduleVersion": "6.0.6.Final",
"moduleLicense": "Apache License Version 2.0", "moduleLicense": "GNU Library General Public License v2.1 or later",
"moduleLicenseUrl": "https://opensource.org/licenses/Apache-2.0" "moduleLicenseUrl": "http://www.opensource.org/licenses/LGPL-2.1"
}, },
{ {
"moduleName": "org.hibernate.orm:hibernate-core", "moduleName": "org.hibernate.orm:hibernate-core",
"moduleUrl": "https://www.hibernate.org/orm/6.6", "moduleUrl": "https://www.hibernate.org/orm/6.5",
"moduleVersion": "6.6.2.Final", "moduleVersion": "6.5.3.Final",
"moduleLicense": "GNU Library General Public License v2.1 or later", "moduleLicense": "GNU Library General Public License v2.1 or later",
"moduleLicenseUrl": "https://www.opensource.org/licenses/LGPL-2.1" "moduleLicenseUrl": "https://www.opensource.org/licenses/LGPL-2.1"
}, },
{ {
"moduleName": "org.jboss.logging:jboss-logging", "moduleName": "org.jboss.logging:jboss-logging",
"moduleUrl": "http://www.jboss.org", "moduleUrl": "http://www.jboss.org",
"moduleVersion": "3.6.1.Final", "moduleVersion": "3.5.3.Final",
"moduleLicense": "Apache License 2.0", "moduleLicense": "Public Domain",
"moduleLicenseUrl": "https://repository.jboss.org/licenses/apache-2.0.txt" "moduleLicenseUrl": "http://repository.jboss.org/licenses/cc0-1.0.txt"
}, },
{ {
"moduleName": "org.latencyutils:LatencyUtils", "moduleName": "org.latencyutils:LatencyUtils",
@@ -1073,12 +1025,6 @@
"moduleLicense": "Public Domain, per Creative Commons CC0", "moduleLicense": "Public Domain, per Creative Commons CC0",
"moduleLicenseUrl": "http://creativecommons.org/publicdomain/zero/1.0/" "moduleLicenseUrl": "http://creativecommons.org/publicdomain/zero/1.0/"
}, },
{
"moduleName": "org.locationtech.jts:jts-core",
"moduleVersion": "1.18.1",
"moduleLicense": "Eclipse Public License, Version 2.0",
"moduleLicenseUrl": "https://github.com/locationtech/jts/blob/master/LICENSE_EPLv2.txt"
},
{ {
"moduleName": "org.opensaml:opensaml-core", "moduleName": "org.opensaml:opensaml-core",
"moduleVersion": "4.3.2", "moduleVersion": "4.3.2",
@@ -1154,21 +1100,21 @@
{ {
"moduleName": "org.ow2.asm:asm", "moduleName": "org.ow2.asm:asm",
"moduleUrl": "http://asm.ow2.org", "moduleUrl": "http://asm.ow2.org",
"moduleVersion": "9.7.1", "moduleVersion": "9.7",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "org.ow2.asm:asm-commons", "moduleName": "org.ow2.asm:asm-commons",
"moduleUrl": "http://asm.ow2.org", "moduleUrl": "http://asm.ow2.org",
"moduleVersion": "9.7.1", "moduleVersion": "9.7",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "org.ow2.asm:asm-tree", "moduleName": "org.ow2.asm:asm-tree",
"moduleUrl": "http://asm.ow2.org", "moduleUrl": "http://asm.ow2.org",
"moduleVersion": "9.7.1", "moduleVersion": "9.7",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@@ -1207,266 +1153,273 @@
{ {
"moduleName": "org.springframework.boot:spring-boot", "moduleName": "org.springframework.boot:spring-boot",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-actuator", "moduleName": "org.springframework.boot:spring-boot-actuator",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-actuator-autoconfigure", "moduleName": "org.springframework.boot:spring-boot-actuator-autoconfigure",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-autoconfigure", "moduleName": "org.springframework.boot:spring-boot-autoconfigure",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-devtools", "moduleName": "org.springframework.boot:spring-boot-devtools",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter", "moduleName": "org.springframework.boot:spring-boot-starter",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-actuator", "moduleName": "org.springframework.boot:spring-boot-starter-actuator",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-aop",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-data-jpa", "moduleName": "org.springframework.boot:spring-boot-starter-data-jpa",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-jdbc", "moduleName": "org.springframework.boot:spring-boot-starter-jdbc",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-jetty", "moduleName": "org.springframework.boot:spring-boot-starter-jetty",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-json", "moduleName": "org.springframework.boot:spring-boot-starter-json",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-logging", "moduleName": "org.springframework.boot:spring-boot-starter-logging",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-oauth2-client", "moduleName": "org.springframework.boot:spring-boot-starter-oauth2-client",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-security", "moduleName": "org.springframework.boot:spring-boot-starter-security",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-thymeleaf", "moduleName": "org.springframework.boot:spring-boot-starter-thymeleaf",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-web", "moduleName": "org.springframework.boot:spring-boot-starter-web",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.data:spring-data-commons", "moduleName": "org.springframework.data:spring-data-commons",
"moduleUrl": "https://spring.io/projects/spring-data", "moduleUrl": "https://spring.io/projects/spring-data",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.data:spring-data-jpa", "moduleName": "org.springframework.data:spring-data-jpa",
"moduleUrl": "https://projects.spring.io/spring-data-jpa", "moduleUrl": "https://projects.spring.io/spring-data-jpa",
"moduleVersion": "3.4.0", "moduleVersion": "3.3.5",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-config", "moduleName": "org.springframework.security:spring-security-config",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.4.1", "moduleVersion": "6.3.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-core", "moduleName": "org.springframework.security:spring-security-core",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.4.1", "moduleVersion": "6.3.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-crypto", "moduleName": "org.springframework.security:spring-security-crypto",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.4.1", "moduleVersion": "6.3.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-oauth2-client", "moduleName": "org.springframework.security:spring-security-oauth2-client",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.4.1", "moduleVersion": "6.3.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-oauth2-core", "moduleName": "org.springframework.security:spring-security-oauth2-core",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.4.1", "moduleVersion": "6.3.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-oauth2-jose", "moduleName": "org.springframework.security:spring-security-oauth2-jose",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.4.1", "moduleVersion": "6.3.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-saml2-service-provider", "moduleName": "org.springframework.security:spring-security-saml2-service-provider",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.4.1", "moduleVersion": "6.3.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-web", "moduleName": "org.springframework.security:spring-security-web",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.4.1", "moduleVersion": "6.3.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-aop", "moduleName": "org.springframework:spring-aop",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-aspects", "moduleName": "org.springframework:spring-aspects",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-beans", "moduleName": "org.springframework:spring-beans",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-context", "moduleName": "org.springframework:spring-context",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-core", "moduleName": "org.springframework:spring-core",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-expression", "moduleName": "org.springframework:spring-expression",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-jcl", "moduleName": "org.springframework:spring-jcl",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-jdbc", "moduleName": "org.springframework:spring-jdbc",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-orm", "moduleName": "org.springframework:spring-orm",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-tx", "moduleName": "org.springframework:spring-tx",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-web", "moduleName": "org.springframework:spring-web",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-webmvc", "moduleName": "org.springframework:spring-webmvc",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.0", "moduleVersion": "6.1.14",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
@@ -1511,17 +1464,10 @@
{ {
"moduleName": "org.yaml:snakeyaml", "moduleName": "org.yaml:snakeyaml",
"moduleUrl": "https://bitbucket.org/snakeyaml/snakeyaml", "moduleUrl": "https://bitbucket.org/snakeyaml/snakeyaml",
"moduleVersion": "2.3", "moduleVersion": "2.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{
"moduleName": "technology.tabula:tabula",
"moduleUrl": "http://github.com/tabulapdf/tabula-java",
"moduleVersion": "1.0.5",
"moduleLicense": "MIT License",
"moduleLicenseUrl": "http://www.opensource.org/licenses/mit-license.php"
},
{ {
"moduleName": "xml-apis:xml-apis", "moduleName": "xml-apis:xml-apis",
"moduleUrl": "http://xml.apache.org/commons/components/external/", "moduleUrl": "http://xml.apache.org/commons/components/external/",

View File

@@ -19,15 +19,6 @@
transform-origin: top left; transform-origin: top left;
} }
#drag-container .multidrag {
position: fixed;
max-width: 200px;
max-height: 200px;
transform-origin: top left;
margin-left: 1rem;
background-color: rgba(0, 29, 41, 0.9);
}
.drag-manager_dragging { .drag-manager_dragging {
width: 0px; width: 0px;
visibility: hidden; visibility: hidden;

View File

@@ -100,30 +100,3 @@ input:-webkit-autofill:focus {
input[data-autocompleted] { input[data-autocompleted] {
background-color: transparent !important; background-color: transparent !important;
} }
.btn-tooltip {
position: absolute;
display: none;
bottom: 3.2rem;
white-space: nowrap;
flex-wrap: nowrap;
width: fit-content;
padding: 7px;
background-color: rgba(0, 29, 41, 0.9);
border-radius: 3px;
font-size: 12px;
color: whitesmoke;
animation: fadeup 0.15s linear;
}
@keyframes fadeup {
0% {
transform: translateY(10px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
.btn:hover .btn-tooltip {
display: block;
}

View File

@@ -61,6 +61,10 @@ label {
padding: 0; padding: 0;
} }
#export-button {
margin-left: auto;
}
.bg-card { .bg-card {
background-color: var(--md-sys-color-surface-5); background-color: var(--md-sys-color-surface-5);
border-radius: 3rem; border-radius: 3rem;
@@ -286,6 +290,3 @@ label {
.checkbox-label { .checkbox-label {
font-size: medium; font-size: medium;
} }
.btn {
position: relative;
}

View File

@@ -140,7 +140,7 @@ span.icon-text::after {
position: relative; position: relative;
} }
/* .tooltip-text { .tooltip-text {
visibility: hidden; visibility: hidden;
background-color: rgb(71 67 67 / 80%); background-color: rgb(71 67 67 / 80%);
color: #fff; color: #fff;
@@ -162,7 +162,7 @@ span.icon-text::after {
.nav-item:hover .tooltip-text { .nav-item:hover .tooltip-text {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} */ }
.dropdown-menu.scrollable-y { .dropdown-menu.scrollable-y {
overflow-y: scroll; overflow-y: scroll;
@@ -324,18 +324,6 @@ span.icon-text::after {
margin-top: 0; margin-top: 0;
} }
/* .icon-hide {
display: none;
} */
}
@media (max-width:1199px) {
.icon-hide {
display: inline-flex;
}
}
@media (min-width:1200px) {
.icon-hide { .icon-hide {
display: none; display: none;
} }

View File

@@ -1,5 +1,5 @@
.pdf-actions_button-container { .pdf-actions_button-container {
z-index: 4; z-index: 2;
display: flex; display: flex;
opacity: 0; opacity: 0;
transition: opacity 0.1s linear; transition: opacity 0.1s linear;
@@ -46,7 +46,7 @@
width: 80px; width: 80px;
height: 100%; height: 100%;
z-index: 3; z-index: 1;
opacity: 0; opacity: 0;
transition: opacity 0.2s; transition: opacity 0.2s;
} }
@@ -116,7 +116,6 @@ html[dir="rtl"] .pdf-actions_container:last-child>.pdf-actions_insert-file-butto
translate: 50% -50%; translate: 50% -50%;
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 100px; border-radius: 100px;
z-index: 4;
} }
.pdf-actions_split-file-button { .pdf-actions_split-file-button {
@@ -126,9 +125,9 @@ html[dir="rtl"] .pdf-actions_container:last-child>.pdf-actions_insert-file-butto
translate: 0 -50%; translate: 0 -50%;
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 100px; border-radius: 100px;
z-index: 3;
} }
.pdf-actions_checkbox { .pdf-actions_checkbox {
position: absolute; position: absolute;
top: 5px; top: 5px;
@@ -138,7 +137,7 @@ html[dir="rtl"] .pdf-actions_container:last-child>.pdf-actions_insert-file-butto
padding: 6px 8px; padding: 6px 8px;
border-radius: 8px; border-radius: 8px;
font-size: 16px; font-size: 16px;
z-index: 10; z-index: 2;
} }
.hidden { .hidden {
@@ -151,5 +150,4 @@ html[dir="rtl"] .pdf-actions_container:last-child>.pdf-actions_insert-file-butto
translate: 0% -50%; translate: 0% -50%;
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 100px; border-radius: 100px;
z-index: 5;
} }

View File

@@ -62,54 +62,53 @@ select#font-select option {
background-color: rgba(52, 152, 219, 0.2); background-color: rgba(52, 152, 219, 0.2);
/* Darken background on hover */ /* Darken background on hover */
} }
.signature-grid { .signature-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem; gap: 1rem;
padding: 1rem; padding: 1rem;
max-height: 400px; max-height: 400px;
overflow-y: auto; overflow-y: auto;
} }
.signature-list { .signature-list {
max-height: 400px; max-height: 400px;
overflow-y: auto; overflow-y: auto;
} }
.signature-list-item { .signature-list-item {
padding: 0.75rem; padding: 0.75rem;
border: 1px solid #dee2e6; border: 1px solid #dee2e6;
border-radius: 4px; border-radius: 4px;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
.signature-list-item:hover { .signature-list-item:hover {
background-color: #f8f9fa; background-color: #f8f9fa;
} }
.signature-list-info { .signature-list-info {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.signature-list-name { .signature-list-name {
font-weight: 500; font-weight: 500;
} }
.signature-list-details { .signature-list-details {
color: #6c757d; color: #6c757d;
font-size: 0.875rem; font-size: 0.875rem;
} }
.signature-list-details small:not(:last-child) { .signature-list-details small:not(:last-child) {
margin-right: 1rem; margin-right: 1rem;
} }
.view-toggle { .view-toggle {
text-align: right; text-align: right;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
} }

View File

@@ -1,8 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-az" viewBox="0 0 640 480">
<path fill="#3f9c35" d="M.1 0h640v480H.1z"/>
<path fill="#ed2939" d="M.1 0h640v320H.1z"/>
<path fill="#00b9e4" d="M.1 0h640v160H.1z"/>
<circle cx="304" cy="240" r="72" fill="#fff"/>
<circle cx="320" cy="240" r="60" fill="#ed2939"/>
<path fill="#fff" d="m384 200 7.7 21.5 20.6-9.8-9.8 20.7L424 240l-21.5 7.7 9.8 20.6-20.6-9.8L384 280l-7.7-21.5-20.6 9.8 9.8-20.6L344 240l21.5-7.7-9.8-20.6 20.6 9.8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 501 B

View File

@@ -34,14 +34,6 @@
const url = this.action; const url = this.action;
const files = $("#fileInput-input")[0].files; const files = $("#fileInput-input")[0].files;
const formData = new FormData(this); const formData = new FormData(this);
const submitButton = document.getElementById("submitBtn");
const showGameBtn = document.getElementById("show-game-btn");
const originalButtonText = submitButton.textContent;
var boredWaiting = localStorage.getItem("boredWaiting") || "disabled";
if (showGameBtn) {
showGameBtn.style.display = "none";
}
// Remove empty file entries // Remove empty file entries
for (let [key, value] of formData.entries()) { for (let [key, value] of formData.entries()) {
@@ -50,10 +42,14 @@
} }
} }
const override = $("#override").val() || ""; const override = $("#override").val() || "";
const originalButtonText = $("#submitBtn").text();
$("#submitBtn").text("Processing...");
console.log(override); console.log(override);
// Set a timeout to show the game button if operation takes more than 5 seconds // Set a timeout to show the game button if operation takes more than 5 seconds
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
var boredWaiting = localStorage.getItem("boredWaiting") || "disabled";
const showGameBtn = document.getElementById("show-game-btn");
if (boredWaiting === "enabled" && showGameBtn) { if (boredWaiting === "enabled" && showGameBtn) {
showGameBtn.style.display = "block"; showGameBtn.style.display = "block";
showGameBtn.parentNode.insertBefore(document.createElement('br'), showGameBtn.nextSibling); showGameBtn.parentNode.insertBefore(document.createElement('br'), showGameBtn.nextSibling);
@@ -61,9 +57,6 @@
}, 5000); }, 5000);
try { try {
submitButton.textContent = "Processing...";
submitButton.disabled = true;
if (remoteCall === true) { if (remoteCall === true) {
if (override === "multi" || (!multipleInputsForSingleRequest && files.length > 1 && override !== "single")) { if (override === "multi" || (!multipleInputsForSingleRequest && files.length > 1 && override !== "single")) {
await submitMultiPdfForm(url, files); await submitMultiPdfForm(url, files);
@@ -72,17 +65,12 @@
} }
} }
clearFileInput(); clearFileInput();
clearTimeout(timeoutId); clearTimeout(timeoutId);
if (showGameBtn) { $("#submitBtn").text(originalButtonText);
showGameBtn.style.display = "none";
showGameBtn.style.marginTop = "";
}
submitButton.textContent = originalButtonText;
submitButton.disabled = false;
// After process finishes, check for boredWaiting and gameDialog open status // After process finishes, check for boredWaiting and gameDialog open status
const boredWaiting = localStorage.getItem("boredWaiting") || "disabled";
const gameDialog = document.getElementById('game-container-wrapper'); const gameDialog = document.getElementById('game-container-wrapper');
if (boredWaiting === "enabled" && gameDialog && gameDialog.open) { if (boredWaiting === "enabled" && gameDialog && gameDialog.open) {
// Display a green banner at the bottom of the screen saying "Download complete" // Display a green banner at the bottom of the screen saying "Download complete"
@@ -99,47 +87,80 @@
} }
} catch (error) { } catch (error) {
clearFileInput();
clearTimeout(timeoutId); clearTimeout(timeoutId);
showGameBtn.style.display = "none";
submitButton.textContent = originalButtonText;
submitButton.disabled = false;
handleDownloadError(error); handleDownloadError(error);
$("#submitBtn").text(originalButtonText);
console.error(error); console.error(error);
} }
}); });
}); });
async function getPDFPageCount(file) { function getPDFPageCount(file) {
try { try {
const arrayBuffer = await file.arrayBuffer(); if (file.type !== 'application/pdf') {
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdfjs-legacy/pdf.worker.mjs' return null;
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; }
return pdf.numPages;
// Create a URL for the file
const url = URL.createObjectURL(file);
try {
// Ensure the worker is properly set
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
}
// Load the PDF document
const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
// Get the page count
const pageCount = pdf.numPages;
return pageCount;
} finally {
// Clean up the URL
URL.revokeObjectURL(url);
}
} catch (error) { } catch (error) {
console.error('Error getting PDF page count:', error); console.error('Error counting PDF pages:', error);
return null; return null;
} }
} }
function trackFileProcessing(file, startTime, success, errorMessage = null) {
const endTime = performance.now();
const processingTime = endTime - startTime;
posthog.capture('file_processing', {
success: success,
file_type: file.type || 'unknown',
file_size: file.size,
processing_time: processingTime,
error_message: errorMessage,
pdf_pages: file.type === 'application/pdf' ? getPDFPageCount(file) : null
});
}
async function handleSingleDownload(url, formData, isMulti = false, isZip = false) { async function handleSingleDownload(url, formData, isMulti = false, isZip = false) {
const startTime = performance.now(); const startTime = performance.now();
const file = formData.get('fileInput'); const file = formData.get('fileInput');
let success = false;
let errorMessage = null;
try { try {
const response = await fetch(url, { method: "POST", body: formData }); const response = await fetch(url, { method: "POST", body: formData });
const contentType = response.headers.get("content-type"); const contentType = response.headers.get("content-type");
if (!response.ok) { if (!response.ok) {
errorMessage = response.status;
if (response.status === 401) { if (response.status === 401) {
showSessionExpiredPrompt(); showSessionExpiredPrompt();
trackFileProcessing(file, startTime, false, 'unauthorized');
return; return;
} }
if (contentType && contentType.includes("application/json")) { if (contentType && contentType.includes("application/json")) {
console.error("Throwing error banner, response was not okay"); console.error("Throwing error banner, response was not okay");
return handleJsonResponse(response); const jsonResponse = await handleJsonResponse(response);
trackFileProcessing(file, startTime, false, jsonResponse?.error || 'unknown_error');
return jsonResponse;
} }
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }
@@ -148,8 +169,8 @@
let filename = getFilenameFromContentDisposition(contentDisposition); let filename = getFilenameFromContentDisposition(contentDisposition);
const blob = await response.blob(); const blob = await response.blob();
success = true; trackFileProcessing(file, startTime, true, null);
if (contentType.includes("application/pdf") || contentType.includes("image/")) { if (contentType.includes("application/pdf") || contentType.includes("image/")) {
clearFileInput(); clearFileInput();
return handleResponse(blob, filename, !isMulti, isZip); return handleResponse(blob, filename, !isMulti, isZip);
@@ -158,30 +179,16 @@
return handleResponse(blob, filename, false, isZip); return handleResponse(blob, filename, false, isZip);
} }
} catch (error) {
success = false;
errorMessage = error.message;
console.error("Error in handleSingleDownload:", error);
throw error;
} finally {
const processingTime = performance.now() - startTime;
// Capture analytics } catch (error) {
const pageCount = file && file.type === 'application/pdf' ? await getPDFPageCount(file) : null; clearFileInput();
if(analyticsEnabled) { console.error("Error in handleSingleDownload:", error);
posthog.capture('file_processing', { trackFileProcessing(file, startTime, false, error.message);
success: success, throw error;
file_type: file ? file.type || 'unknown' : 'unknown',
file_size: file ? file.size : 0,
processing_time: processingTime,
error_message: errorMessage,
pdf_pages: pageCount
});
}
} }
} }
function getFilenameFromContentDisposition(contentDisposition) { function getFilenameFromContentDisposition(contentDisposition) {
let filename; let filename;
if (contentDisposition && contentDisposition.indexOf("attachment") !== -1) { if (contentDisposition && contentDisposition.indexOf("attachment") !== -1) {
@@ -217,8 +224,7 @@
if (considerViewOptions) { if (considerViewOptions) {
if (downloadOption === "sameWindow") { if (downloadOption === "sameWindow") {
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
window.location.href = url; return { filename, blob, url };
return;
} else if (downloadOption === "newWindow") { } else if (downloadOption === "newWindow") {
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
window.open(url, "_blank"); window.open(url, "_blank");
@@ -356,14 +362,14 @@
if(editSectionElement){ if(editSectionElement){
editSectionElement.style.display = "none"; editSectionElement.style.display = "none";
} }
let cropPdfCanvas = document.querySelector("#cropPdfCanvas"); let cropPdfCanvas = document.querySelector("#crop-pdf-canvas");
let overlayCanvas = document.querySelector("#overlayCanvas"); let overlayCanvas = document.querySelector("#overlayCanvas");
if(cropPdfCanvas && overlayCanvas){ if(cropPdfCanvas && overlayCanvas){
cropPdfCanvas.width = 0; cropPdfCanvas.width = 0;
cropPdfCanvas.height = 0; cropPdfCanvas.heigth = 0;
overlayCanvas.width = 0; overlayCanvas.width = 0;
overlayCanvas.height = 0; overlayCanvas.heigth = 0;
} }
} else{ } else{
console.log("Disabled for 'Merge'"); console.log("Disabled for 'Merge'");

View File

@@ -4,87 +4,86 @@ const DraggableUtils = {
nextId: 0, nextId: 0,
pdfDoc: null, pdfDoc: null,
pageIndex: 0, pageIndex: 0,
elementAllPages: [],
documentsMap: new Map(), documentsMap: new Map(),
lastInteracted: null, lastInteracted: null,
init() { init() {
interact(".draggable-canvas") interact(".draggable-canvas")
.draggable({ .draggable({
listeners: { listeners: {
move: (event) => { move: (event) => {
const target = event.target; const target = event.target;
const x = (parseFloat(target.getAttribute("data-bs-x")) || 0) const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
+ event.dx; + event.dx;
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0) const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
+ event.dy; + event.dy;
target.style.transform = `translate(${x}px, ${y}px)`; target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute("data-bs-x", x); target.setAttribute("data-bs-x", x);
target.setAttribute("data-bs-y", y); target.setAttribute("data-bs-y", y);
this.onInteraction(target); this.onInteraction(target);
//update the last interacted element //update the last interacted element
this.lastInteracted = event.target; this.lastInteracted = event.target;
},
}, },
}) },
.resizable({ })
edges: { left: true, right: true, bottom: true, top: true }, .resizable({
listeners: { edges: { left: true, right: true, bottom: true, top: true },
move: (event) => { listeners: {
var target = event.target; move: (event) => {
var x = parseFloat(target.getAttribute("data-bs-x")) || 0; var target = event.target;
var y = parseFloat(target.getAttribute("data-bs-y")) || 0; var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
// check if control key is pressed // check if control key is pressed
if (event.ctrlKey) { if (event.ctrlKey) {
const aspectRatio = target.offsetWidth / target.offsetHeight; const aspectRatio = target.offsetWidth / target.offsetHeight;
// preserve aspect ratio // preserve aspect ratio
let width = event.rect.width; let width = event.rect.width;
let height = event.rect.height; let height = event.rect.height;
if (Math.abs(event.deltaRect.width) >= Math.abs( if (Math.abs(event.deltaRect.width) >= Math.abs(
event.deltaRect.height)) { event.deltaRect.height)) {
height = width / aspectRatio; height = width / aspectRatio;
} else { } else {
width = height * aspectRatio; width = height * aspectRatio;
}
event.rect.width = width;
event.rect.height = height;
} }
target.style.width = event.rect.width + "px"; event.rect.width = width;
target.style.height = event.rect.height + "px"; event.rect.height = height;
}
// translate when resizing from top or left edges target.style.width = event.rect.width + "px";
x += event.deltaRect.left; target.style.height = event.rect.height + "px";
y += event.deltaRect.top;
target.style.transform = "translate(" + x + "px," + y + "px)"; // translate when resizing from top or left edges
x += event.deltaRect.left;
y += event.deltaRect.top;
target.setAttribute("data-bs-x", x); target.style.transform = "translate(" + x + "px," + y + "px)";
target.setAttribute("data-bs-y", y);
target.textContent = Math.round(event.rect.width) + "\u00D7" 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); + Math.round(event.rect.height);
this.onInteraction(target); this.onInteraction(target);
},
}, },
},
modifiers: [ modifiers: [
interact.modifiers.restrictSize({ interact.modifiers.restrictSize({
min: { width: 5, height: 5 }, min: {width: 5, height: 5},
}), }),
], ],
inertia: true, inertia: true,
}); });
//Arrow key Support for Add-Image and Sign pages //Arrow key Support for Add-Image and Sign pages
if (window.location.pathname.endsWith('sign') || window.location.pathname.endsWith('add-image')) { if(window.location.pathname.endsWith('sign') || window.location.pathname.endsWith('add-image')) {
window.addEventListener('keydown', (event) => { window.addEventListener('keydown', (event) => {
//Check for last interacted element //Check for last interacted element
if (!this.lastInteracted) { if (!this.lastInteracted){
return; return;
} }
// Get the currently selected element // Get the currently selected element
@@ -198,68 +197,6 @@ const DraggableUtils = {
deleteAllDraggableCanvases() { deleteAllDraggableCanvases() {
this.boxDragContainer.querySelectorAll(".draggable-canvas").forEach((el) => el.remove()); this.boxDragContainer.querySelectorAll(".draggable-canvas").forEach((el) => el.remove());
}, },
async addAllPagesDraggableCanvas(element) {
if (element) {
let currentPage = this.pageIndex
if (!this.elementAllPages.includes(element)) {
this.elementAllPages.push(element)
element.style.filter = 'sepia(1) hue-rotate(90deg) brightness(1.2)';
let newElement = {
"element": element,
"offsetWidth": element.width,
"offsetHeight": element.height
}
let pagesMap = this.documentsMap.get(this.pdfDoc);
if (!pagesMap) {
pagesMap = {};
this.documentsMap.set(this.pdfDoc, pagesMap);
}
let page = this.pageIndex
for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
if (pagesMap[`${pageIndex}-offsetWidth`]) {
if (!pagesMap[pageIndex].includes(newElement)) {
pagesMap[pageIndex].push(newElement);
}
} else {
pagesMap[pageIndex] = []
pagesMap[pageIndex].push(newElement)
pagesMap[`${pageIndex}-offsetWidth`] = pagesMap[`${page}-offsetWidth`];
pagesMap[`${pageIndex}-offsetHeight`] = pagesMap[`${page}-offsetHeight`];
}
await this.goToPage(pageIndex)
}
} else {
const index = this.elementAllPages.indexOf(element);
if (index !== -1) {
this.elementAllPages.splice(index, 1);
}
element.style.filter = '';
let pagesMap = this.documentsMap.get(this.pdfDoc);
if (!pagesMap) {
pagesMap = {};
this.documentsMap.set(this.pdfDoc, pagesMap);
}
for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
if (pagesMap[`${pageIndex}-offsetWidth`] && pageIndex != currentPage) {
const pageElements = pagesMap[pageIndex];
pageElements.forEach(elementPage => {
const elementIndex = pageElements.findIndex(elementPage => elementPage['element'].id === element.id);
if (elementIndex !== -1) {
pageElements.splice(elementIndex, 1);
}
});
}
await this.goToPage(pageIndex)
}
}
await this.goToPage(currentPage)
}
},
deleteDraggableCanvas(element) { deleteDraggableCanvas(element) {
if (element) { if (element) {
//Check if deleted element is the last interacted //Check if deleted element is the last interacted
@@ -304,7 +241,7 @@ const DraggableUtils = {
} }
const draggablesData = pagesMap[this.pageIndex]; const draggablesData = pagesMap[this.pageIndex];
if (draggablesData && Array.isArray(draggablesData)) { if (draggablesData) {
draggablesData.forEach((draggableData) => this.boxDragContainer.appendChild(draggableData.element)); draggablesData.forEach((draggableData) => this.boxDragContainer.appendChild(draggableData.element));
} }
@@ -336,13 +273,6 @@ const DraggableUtils = {
//return pdfCanvas.toDataURL(); //return pdfCanvas.toDataURL();
}, },
async goToPage(pageIndex) {
this.storePageContents();
await this.renderPage(this.pdfDoc, pageIndex);
this.loadPageContents();
},
async incrementPage() { async incrementPage() {
if (this.pageIndex < this.pdfDoc.numPages - 1) { if (this.pageIndex < this.pdfDoc.numPages - 1) {
this.storePageContents(); this.storePageContents();
@@ -358,7 +288,7 @@ const DraggableUtils = {
} }
}, },
parseTransform(element) { }, parseTransform(element) {},
async getOverlayedPdfDocument() { async getOverlayedPdfDocument() {
const pdfBytes = await this.pdfDoc.getData(); const pdfBytes = await this.pdfDoc.getData();
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, { const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
@@ -367,7 +297,6 @@ const DraggableUtils = {
this.storePageContents(); this.storePageContents();
const pagesMap = this.documentsMap.get(this.pdfDoc); const pagesMap = this.documentsMap.get(this.pdfDoc);
for (let pageIdx in pagesMap) { for (let pageIdx in pagesMap) {
if (pageIdx.includes("offset")) { if (pageIdx.includes("offset")) {
continue; continue;
@@ -375,12 +304,10 @@ const DraggableUtils = {
console.log(typeof pageIdx); console.log(typeof pageIdx);
const page = pdfDocModified.getPage(parseInt(pageIdx)); const page = pdfDocModified.getPage(parseInt(pageIdx));
let draggablesData = pagesMap[pageIdx]; const draggablesData = pagesMap[pageIdx];
const offsetWidth = pagesMap[pageIdx + "-offsetWidth"]; const offsetWidth = pagesMap[pageIdx + "-offsetWidth"];
const offsetHeight = pagesMap[pageIdx + "-offsetHeight"]; const offsetHeight = pagesMap[pageIdx + "-offsetHeight"];
for (const draggableData of draggablesData) { for (const draggableData of draggablesData) {
// embed the draggable canvas // embed the draggable canvas
const draggableElement = draggableData.element; const draggableElement = draggableData.element;
@@ -397,24 +324,6 @@ const DraggableUtils = {
width: draggableData.offsetWidth, width: draggableData.offsetWidth,
height: draggableData.offsetHeight, height: draggableData.offsetHeight,
}; };
//Auxiliary variables
let widthAdjusted = page.getWidth();
let heightAdjusted = page.getHeight();
const rotation = page.getRotation();
//Normalizing angle
let normalizedAngle = rotation.angle % 360;
if (normalizedAngle < 0) {
normalizedAngle += 360;
}
//Changing the page dimension if the angle is 90 or 270
if (normalizedAngle === 90 || normalizedAngle === 270) {
let widthTemp = widthAdjusted;
widthAdjusted = heightAdjusted;
heightAdjusted = widthTemp;
}
const draggablePositionRelative = { const draggablePositionRelative = {
x: draggablePositionPixels.x / offsetWidth, x: draggablePositionPixels.x / offsetWidth,
y: draggablePositionPixels.y / offsetHeight, y: draggablePositionPixels.y / offsetHeight,
@@ -422,39 +331,22 @@ const DraggableUtils = {
height: draggablePositionPixels.height / offsetHeight, height: draggablePositionPixels.height / offsetHeight,
}; };
const draggablePositionPdf = { const draggablePositionPdf = {
x: draggablePositionRelative.x * widthAdjusted, x: draggablePositionRelative.x * page.getWidth(),
y: draggablePositionRelative.y * heightAdjusted, y: draggablePositionRelative.y * page.getHeight(),
width: draggablePositionRelative.width * widthAdjusted, width: draggablePositionRelative.width * page.getWidth(),
height: draggablePositionRelative.height * heightAdjusted, height: draggablePositionRelative.height * page.getHeight(),
}; };
//Defining the image if the page has a 0-degree angle
let x = draggablePositionPdf.x
let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height
//Defining the image position if it is at other angles
if (normalizedAngle === 90) {
x = draggablePositionPdf.y + draggablePositionPdf.height;
y = draggablePositionPdf.x;
} else if (normalizedAngle === 180) {
x = widthAdjusted - draggablePositionPdf.x;
y = draggablePositionPdf.y + draggablePositionPdf.height;
} else if (normalizedAngle === 270) {
x = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height;
y = widthAdjusted - draggablePositionPdf.x;
}
// draw the image // draw the image
page.drawImage(pdfImageObject, { page.drawImage(pdfImageObject, {
x: x, x: draggablePositionPdf.x,
y: y, y: page.getHeight() - draggablePositionPdf.y - draggablePositionPdf.height,
width: draggablePositionPdf.width, width: draggablePositionPdf.width,
height: draggablePositionPdf.height, height: draggablePositionPdf.height,
rotate: rotation
}); });
} }
} }
this.loadPageContents(); this.loadPageContents();
return pdfDocModified; return pdfDocModified;
}, },

View File

@@ -76,7 +76,7 @@ async function checkForUpdate() {
document.getElementById("update-btn").style.display = "block"; document.getElementById("update-btn").style.display = "block";
} }
if (updateLink !== null) { if (updateLink !== null) {
document.getElementById("app-update").innerHTML = updateAvailable.replace("{0}", '<b>' + currentVersion + '</b>').replace("{1}", '<b>' + latestVersion + '</b>'); document.getElementById("app-update").innerText = updateAvailable.replace("{0}", '<b>' + currentVersion + '</b>').replace("{1}", '<b>' + latestVersion + '</b>');
if (updateLink.classList.contains("visually-hidden")) { if (updateLink.classList.contains("visually-hidden")) {
updateLink.classList.remove("visually-hidden"); updateLink.classList.remove("visually-hidden");
} }

View File

@@ -1,59 +1,27 @@
function filterCards() { function filterCards() {
var input = document.getElementById("searchBar"); var input = document.getElementById("searchBar");
var filter = input.value.toUpperCase(); var filter = input.value.toUpperCase();
var cards = document.querySelectorAll(".feature-card");
let featureGroups = document.querySelectorAll(".feature-group"); for (var i = 0; i < cards.length; i++) {
const collapsedGroups = getCollapsedGroups(); var card = cards[i];
var title = card.querySelector("h5.card-title").innerText;
var text = card.querySelector("p.card-text").innerText;
for (const featureGroup of featureGroups) { // Get the navbar tags associated with the card
var cards = featureGroup.querySelectorAll(".feature-card"); var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`);
var navbarTags = navbarItem ? navbarItem.getAttribute("data-bs-tags") : "";
let groupMatchesFilter = false; var content = title + " " + text + " " + navbarTags;
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
var title = card.querySelector("h5.card-title").innerText;
var text = card.querySelector("p.card-text").innerText;
// Get the navbar tags associated with the card if (content.toUpperCase().indexOf(filter) > -1) {
var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`); card.style.display = "";
var navbarTags = navbarItem ? navbarItem.getAttribute("data-bs-tags") : "";
var content = title + " " + text + " " + navbarTags;
if (content.toUpperCase().indexOf(filter) > -1) {
card.style.display = "";
groupMatchesFilter = true;
} else {
card.style.display = "none";
}
}
if (!groupMatchesFilter) {
featureGroup.style.display = "none";
} else { } else {
featureGroup.style.display = ""; card.style.display = "none";
resetOrTemporarilyExpandGroup(featureGroup, filter, collapsedGroups);
} }
} }
} }
function getCollapsedGroups() {
return localStorage.getItem("collapsedGroups") ? JSON.parse(localStorage.getItem("collapsedGroups")) : [];
}
function resetOrTemporarilyExpandGroup(featureGroup, filterKeywords = "", collapsedGroups = []) {
const shouldResetCollapse = filterKeywords.trim() === "";
if (shouldResetCollapse) {
// Resetting the group's expand/collapse to its original state (as in collapsed groups)
const isCollapsed = collapsedGroups.indexOf(featureGroup.id) != -1;
expandCollapseToggle(featureGroup, !isCollapsed);
} else {
// Temporarily expands feature group without affecting the actual/stored collapsed groups
featureGroup.classList.remove("collapsed");
featureGroup.querySelector(".header-expand-button").classList.remove("collapsed");
}
}
function updateFavoritesSection() { function updateFavoritesSection() {
const favoritesContainer = document.getElementById("groupFavorites").querySelector(".feature-group-container"); const favoritesContainer = document.getElementById("groupFavorites").querySelector(".feature-group-container");
favoritesContainer.style.maxHeight = "none"; favoritesContainer.style.maxHeight = "none";

View File

@@ -1,21 +1,29 @@
class DragDropManager { class DragDropManager {
dragContainer;
wrapper;
pageDirection;
movePageTo;
pageDragging;
draggelEl;
draggedImageEl;
hoveredEl;
endInsertionElement;
constructor(id, wrapperId) { constructor(id, wrapperId) {
this.dragContainer = document.getElementById(id); this.dragContainer = document.getElementById(id);
this.pageDirection = document.documentElement.getAttribute("dir"); this.pageDirection = document.documentElement.getAttribute("dir");
this.wrapper = document.getElementById(wrapperId); this.wrapper = document.getElementById(wrapperId);
this.pageDragging = false; this.pageDragging = false;
this.hoveredEl = undefined; this.hoveredEl = undefined;
this.draggelEl = undefined;
this.draggedImageEl = undefined; this.draggedImageEl = undefined;
this.draggedEl = undefined;
this.selectedPageElements = []; // Store selected pages for multi-page mode
// Add CSS dynamically var styleElement = document.createElement("link");
const styleElement = document.createElement("link");
styleElement.rel = "stylesheet"; styleElement.rel = "stylesheet";
styleElement.href = "css/dragdrop.css"; styleElement.href = "css/dragdrop.css";
document.head.appendChild(styleElement); document.head.appendChild(styleElement);
// Create the endpoint element
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("drag-manager_endpoint"); div.classList.add("drag-manager_endpoint");
div.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-arrow-down" viewBox="0 0 16 16"> div.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-arrow-down" viewBox="0 0 16 16">
@@ -24,7 +32,6 @@ class DragDropManager {
</svg>`; </svg>`;
this.endInsertionElement = div; this.endInsertionElement = div;
// Bind methods
this.startDraggingPage = this.startDraggingPage.bind(this); this.startDraggingPage = this.startDraggingPage.bind(this);
this.onDragEl = this.onDragEl.bind(this); this.onDragEl = this.onDragEl.bind(this);
this.stopDraggingPage = this.stopDraggingPage.bind(this); this.stopDraggingPage = this.stopDraggingPage.bind(this);
@@ -33,41 +40,20 @@ class DragDropManager {
} }
startDraggingPage(div) { startDraggingPage(div) {
if (window.selectPage) { this.pageDragging = true;
// Multi-page drag logic this.draggedEl = div;
this.selectedPageElements = window.selectedPages.map((index) => { const img = div.querySelector("img");
const pageEl = document.getElementById(`page-container-${index}`); div.classList.add("drag-manager_dragging");
if (pageEl) { const imageSrc = img.src;
pageEl.initialTransform = pageEl.style.transform || "translate(0px, 0px)";
}
return pageEl;
}).filter(Boolean);
if (this.selectedPageElements.length === 0) return; const imgEl = document.createElement("img");
imgEl.classList.add("dragged-img");
imgEl.src = imageSrc;
this.draggedImageEl = imgEl;
imgEl.style.visibility = "hidden";
imgEl.style.transform = `rotate(${img.style.rotate === "" ? "0deg" : img.style.rotate}) translate(-50%, -50%)`;
this.dragContainer.appendChild(imgEl);
this.pageDragging = true;
this.draggedImageEl = document.createElement("div");
this.draggedImageEl.classList.add("multidrag");
this.draggedImageEl.textContent = `${this.selectedPageElements.length} ${window.translations.dragDropMessage}`;
this.draggedImageEl.style.visibility = "hidden";
this.dragContainer.appendChild(this.draggedImageEl);
} else {
// Single-page drag logic
this.pageDragging = true;
this.draggedEl = div;
const img = div.querySelector("img");
div.classList.add("drag-manager_dragging");
const imgEl = document.createElement("img");
imgEl.classList.add("dragged-img");
imgEl.src = img.src;
imgEl.style.visibility = "hidden";
imgEl.style.transform = `rotate(${img.style.rotate === "" ? "0deg" : img.style.rotate}) translate(-50%, -50%)`;
this.draggedImageEl = imgEl;
this.dragContainer.appendChild(imgEl);
}
// Common setup for both modes
window.addEventListener("mouseup", this.stopDraggingPage); window.addEventListener("mouseup", this.stopDraggingPage);
window.addEventListener("mousemove", this.onDragEl); window.addEventListener("mousemove", this.onDragEl);
this.wrapper.classList.add("drag-manager_dragging-container"); this.wrapper.classList.add("drag-manager_dragging-container");
@@ -88,43 +74,21 @@ class DragDropManager {
this.wrapper.classList.remove("drag-manager_dragging-container"); this.wrapper.classList.remove("drag-manager_dragging-container");
this.wrapper.removeChild(this.endInsertionElement); this.wrapper.removeChild(this.endInsertionElement);
window.removeEventListener("mouseup", this.stopDraggingPage); window.removeEventListener("mouseup", this.stopDraggingPage);
this.draggedImageEl = undefined;
if (this.draggedImageEl) {
this.dragContainer.removeChild(this.draggedImageEl);
this.draggedImageEl = undefined;
}
if (window.selectPage) {
// Multi-page drop logic
if (!this.hoveredEl) {
this.selectedPageElements.forEach((pageEl) => {
pageEl.style.transform = pageEl.initialTransform || "translate(0px, 0px)";
pageEl.classList.remove("drag-manager_dragging");
});
} else {
this.selectedPageElements.forEach((pageEl) => {
pageEl.classList.remove("drag-manager_dragging");
if (this.hoveredEl === this.endInsertionElement) {
this.movePageTo(pageEl);
} else {
this.movePageTo(pageEl, this.hoveredEl);
}
});
}
this.selectedPageElements = [];
window.resetPages()
} else {
// Single-page drop logic
if (!this.hoveredEl) return;
this.draggedEl.classList.remove("drag-manager_dragging");
if (this.hoveredEl === this.endInsertionElement) {
this.movePageTo(this.draggedEl);
} else {
this.movePageTo(this.draggedEl, this.hoveredEl);
}
}
this.pageDragging = false; this.pageDragging = false;
this.draggedEl.classList.remove("drag-manager_dragging");
this.hoveredEl?.classList.remove("drag-manager_draghover");
this.dragContainer.childNodes.forEach((dragChild) => {
this.dragContainer.removeChild(dragChild);
});
if (!this.hoveredEl) {
return;
}
if (this.hoveredEl === this.endInsertionElement) {
this.movePageTo(this.draggedEl);
return;
}
this.movePageTo(this.draggedEl, this.hoveredEl);
} }
setActions({ movePageTo }) { setActions({ movePageTo }) {

View File

@@ -110,32 +110,31 @@ class PdfActionsManager {
const moveUp = document.createElement("button"); const moveUp = document.createElement("button");
moveUp.classList.add("pdf-actions_move-left-button", "btn", "btn-secondary"); moveUp.classList.add("pdf-actions_move-left-button", "btn", "btn-secondary");
moveUp.innerHTML = `<span class="material-symbols-rounded">arrow_${leftDirection}_alt</span><span class="btn-tooltip">${window.translations.moveLeft}</span>`; moveUp.innerHTML = `<span class="material-symbols-rounded">arrow_${leftDirection}_alt</span>`;
moveUp.onclick = this.moveUpButtonCallback; moveUp.onclick = this.moveUpButtonCallback;
buttonContainer.appendChild(moveUp); buttonContainer.appendChild(moveUp);
const moveDown = document.createElement("button"); const moveDown = document.createElement("button");
moveDown.classList.add("pdf-actions_move-right-button", "btn", "btn-secondary"); moveDown.classList.add("pdf-actions_move-right-button", "btn", "btn-secondary");
moveDown.innerHTML = `<span class="material-symbols-rounded">arrow_${rightDirection}_alt</span><span class="btn-tooltip">${window.translations.moveRight}</span>`; moveDown.innerHTML = `<span class="material-symbols-rounded">arrow_${rightDirection}_alt</span>`;
moveDown.onclick = this.moveDownButtonCallback; moveDown.onclick = this.moveDownButtonCallback;
buttonContainer.appendChild(moveDown); buttonContainer.appendChild(moveDown);
const rotateCCW = document.createElement("button"); const rotateCCW = document.createElement("button");
rotateCCW.classList.add("btn", "btn-secondary"); rotateCCW.classList.add("btn", "btn-secondary");
rotateCCW.innerHTML = `<span class="material-symbols-rounded">rotate_left</span><span class="btn-tooltip">${window.translations.rotateLeft}</span>`; rotateCCW.innerHTML = `<span class="material-symbols-rounded">rotate_left</span>`;
rotateCCW.onclick = this.rotateCCWButtonCallback; rotateCCW.onclick = this.rotateCCWButtonCallback;
buttonContainer.appendChild(rotateCCW); buttonContainer.appendChild(rotateCCW);
const rotateCW = document.createElement("button"); const rotateCW = document.createElement("button");
rotateCW.classList.add("btn", "btn-secondary"); rotateCW.classList.add("btn", "btn-secondary");
rotateCW.innerHTML = `<span class="material-symbols-rounded">rotate_right</span><span class="btn-tooltip">${window.translations.rotateRight}</span>`; rotateCW.innerHTML = `<span class="material-symbols-rounded">rotate_right</span>`;
rotateCW.onclick = this.rotateCWButtonCallback; rotateCW.onclick = this.rotateCWButtonCallback;
buttonContainer.appendChild(rotateCW); buttonContainer.appendChild(rotateCW);
const deletePage = document.createElement("button"); const deletePage = document.createElement("button");
deletePage.classList.add("btn", "btn-danger"); deletePage.classList.add("btn", "btn-danger");
deletePage.innerHTML = `<span class="material-symbols-rounded">delete</span><span class="btn-tooltip"></span><span class="btn-tooltip">${window.translations.delete}</span>`; deletePage.innerHTML = `<span class="material-symbols-rounded">delete</span>`;
deletePage.onclick = this.deletePageButtonCallback; deletePage.onclick = this.deletePageButtonCallback;
buttonContainer.appendChild(deletePage); buttonContainer.appendChild(deletePage);
@@ -190,19 +189,19 @@ class PdfActionsManager {
const insertFileButton = document.createElement("button"); const insertFileButton = document.createElement("button");
insertFileButton.classList.add("btn", "btn-primary", "pdf-actions_insert-file-button"); insertFileButton.classList.add("btn", "btn-primary", "pdf-actions_insert-file-button");
insertFileButton.innerHTML = `<span class="material-symbols-rounded">add</span></span><span class="btn-tooltip">${window.translations.addFile}</span>`; insertFileButton.innerHTML = `<span class="material-symbols-rounded">add</span>`;
insertFileButton.onclick = this.insertFileButtonCallback; insertFileButton.onclick = this.insertFileButtonCallback;
insertFileButtonContainer.appendChild(insertFileButton); insertFileButtonContainer.appendChild(insertFileButton);
const splitFileButton = document.createElement("button"); const splitFileButton = document.createElement("button");
splitFileButton.classList.add("btn", "btn-primary", "pdf-actions_split-file-button"); splitFileButton.classList.add("btn", "btn-primary", "pdf-actions_split-file-button");
splitFileButton.innerHTML = `<span class="material-symbols-rounded">cut</span></span><span class="btn-tooltip">${window.translations.split}</span>`; splitFileButton.innerHTML = `<span class="material-symbols-rounded">cut</span>`;
splitFileButton.onclick = this.splitFileButtonCallback; splitFileButton.onclick = this.splitFileButtonCallback;
insertFileButtonContainer.appendChild(splitFileButton); insertFileButtonContainer.appendChild(splitFileButton);
const insertFileBlankButton = document.createElement("button"); const insertFileBlankButton = document.createElement("button");
insertFileBlankButton.classList.add("btn", "btn-primary", "pdf-actions_insert-file-blank-button"); insertFileBlankButton.classList.add("btn", "btn-primary", "pdf-actions_insert-file-blank-button");
insertFileBlankButton.innerHTML = `<span class="material-symbols-rounded">insert_page_break</span></span><span class="btn-tooltip">${window.translations.insertPageBreak}</span>`; insertFileBlankButton.innerHTML = `<span class="material-symbols-rounded">insert_page_break</span>`;
insertFileBlankButton.onclick = this.insertFileBlankButtonCallback; insertFileBlankButton.onclick = this.insertFileBlankButtonCallback;
insertFileButtonContainer.appendChild(insertFileBlankButton); insertFileButtonContainer.appendChild(insertFileBlankButton);

View File

@@ -29,7 +29,6 @@ class PdfContainer {
this.updatePagesFromCSV = this.updatePagesFromCSV.bind(this); this.updatePagesFromCSV = this.updatePagesFromCSV.bind(this);
this.addFilesBlankAll = this.addFilesBlankAll.bind(this) this.addFilesBlankAll = this.addFilesBlankAll.bind(this)
this.removeAllElements = this.removeAllElements.bind(this); this.removeAllElements = this.removeAllElements.bind(this);
this.resetPages = this.resetPages.bind(this);
this.pdfAdapters = pdfAdapters; this.pdfAdapters = pdfAdapters;
@@ -56,7 +55,6 @@ class PdfContainer {
window.updatePageNumbersAndCheckboxes = this.updatePageNumbersAndCheckboxes; window.updatePageNumbersAndCheckboxes = this.updatePageNumbersAndCheckboxes;
window.addFilesBlankAll = this.addFilesBlankAll window.addFilesBlankAll = this.addFilesBlankAll
window.removeAllElements = this.removeAllElements; window.removeAllElements = this.removeAllElements;
window.resetPages = this.resetPages;
const filenameInput = document.getElementById("filename-input"); const filenameInput = document.getElementById("filename-input");
const downloadBtn = document.getElementById("export-button"); const downloadBtn = document.getElementById("export-button");
@@ -122,24 +120,12 @@ class PdfContainer {
async addFilesFromFiles(files, nextSiblingElement) { async addFilesFromFiles(files, nextSiblingElement) {
this.fileName = files[0].name; this.fileName = files[0].name;
for (var i = 0; i < files.length; i++) { for (var i = 0; i < files.length; i++) {
const startTime = Date.now();
let processingTime, errorMessage = null, pageCount = 0;
try {
const file = files[i]; const file = files[i];
if (file.type === "application/pdf") { if (file.type === "application/pdf") {
const { renderer, pdfDocument } = await this.loadFile(file); await this.addPdfFile(file, nextSiblingElement);
pageCount = renderer.pageCount || 0;
await this.addPdfFile(renderer, pdfDocument, nextSiblingElement);
} else if (file.type.startsWith("image/")) { } else if (file.type.startsWith("image/")) {
await this.addImageFile(file, nextSiblingElement); await this.addImageFile(file, nextSiblingElement);
} }
processingTime = Date.now() - startTime;
this.captureFileProcessingEvent(true, file, processingTime, null, pageCount);
} catch (error) {
processingTime = Date.now() - startTime;
errorMessage = error.message || "Unknown error";
this.captureFileProcessingEvent(false, files[i], processingTime, errorMessage, pageCount);
}
} }
document.querySelectorAll(".enable-on-file").forEach((element) => { document.querySelectorAll(".enable-on-file").forEach((element) => {
@@ -147,23 +133,6 @@ class PdfContainer {
}); });
} }
captureFileProcessingEvent(success, file, processingTime, errorMessage, pageCount) {
try{
if(analyticsEnabled){
posthog.capture('file_processing', {
success,
file_type: file?.type || 'unknown',
file_size: file?.size || 0,
processing_time: processingTime,
error_message: errorMessage,
pdf_pages: pageCount,
});
}
}catch{
}
}
async addFilesBlank(nextSiblingElement) { async addFilesBlank(nextSiblingElement) {
const pdfContent = ` const pdfContent = `
%PDF-1.4 %PDF-1.4
@@ -217,12 +186,14 @@ class PdfContainer {
} }
async addPdfFile(renderer, pdfDocument, nextSiblingElement) { async addPdfFile(file, nextSiblingElement) {
const { renderer, pdfDocument } = await this.loadFile(file);
for (var i = 0; i < renderer.pageCount; i++) { for (var i = 0; i < renderer.pageCount; i++) {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("page-container"); div.classList.add("page-container");
div.id = "page-container-" + (i + 1);
var img = document.createElement("img"); var img = document.createElement("img");
img.classList.add("page-image"); img.classList.add("page-image");
const imageSrc = await renderer.renderPage(i); const imageSrc = await renderer.renderPage(i);
@@ -231,6 +202,7 @@ class PdfContainer {
img.rend = renderer; img.rend = renderer;
img.doc = pdfDocument; img.doc = pdfDocument;
div.appendChild(img); div.appendChild(img);
this.pdfAdapters.forEach((adapter) => { this.pdfAdapters.forEach((adapter) => {
adapter.adapt?.(div); adapter.adapt?.(div);
}); });
@@ -369,8 +341,8 @@ class PdfContainer {
toggleSelectAll() { toggleSelectAll() {
const checkboxes = document.querySelectorAll(".pdf-actions_checkbox"); const checkboxes = document.querySelectorAll(".pdf-actions_checkbox");
window.selectAll = !window.selectAll; window.selectAll = !window.selectAll;
const selectIcon = document.getElementById("select-All-Container"); const selectIcon = document.getElementById("select-icon");
const deselectIcon = document.getElementById("deselect-All-Container"); const deselectIcon = document.getElementById("deselect-icon");
if (selectIcon.style.display === "none") { if (selectIcon.style.display === "none") {
selectIcon.style.display = "inline"; selectIcon.style.display = "inline";
@@ -469,7 +441,7 @@ class PdfContainer {
const selectedPagesList = document.getElementById("selected-pages-list"); const selectedPagesList = document.getElementById("selected-pages-list");
const selectedPagesInput = document.getElementById("csv-input"); const selectedPagesInput = document.getElementById("csv-input");
selectedPagesList.innerHTML = ""; // Clear the list selectedPagesList.innerHTML = ""; // Clear the list
window.selectedPages.sort((a, b) => a - b);
window.selectedPages.forEach((page) => { window.selectedPages.forEach((page) => {
const pageItem = document.createElement("div"); const pageItem = document.createElement("div");
pageItem.className = "page-item"; pageItem.className = "page-item";
@@ -729,31 +701,6 @@ class PdfContainer {
} }
} }
resetPages() {
const pageContainers = this.pagesContainer.querySelectorAll(".page-container");
pageContainers.forEach((container, index) => {
container.id = "page-container-" + (index + 1);
});
const checkboxes = document.querySelectorAll(".pdf-actions_checkbox");
window.selectAll = false;
const selectIcon = document.getElementById("select-All-Container");
const deselectIcon = document.getElementById("deselect-All-Container");
selectIcon.style.display = "inline";
deselectIcon.style.display = "none";
checkboxes.forEach((checkbox) => {
const pageNumber = Array.from(checkbox.parentNode.parentNode.children).indexOf(checkbox.parentNode) + 1;
const index = window.selectedPages.indexOf(pageNumber);
if (index !== -1) {
window.selectedPages.splice(index, 1);
}
});
window.toggleSelectPageVisibility();
}
setDownloadAttribute() { setDownloadAttribute() {
this.downloadLink.setAttribute("download", this.fileName ? this.fileName : "managed.pdf"); this.downloadLink.setAttribute("download", this.fileName ? this.fileName : "managed.pdf");
@@ -798,7 +745,7 @@ class PdfContainer {
const selectedPages = document.getElementById("selected-pages-display"); const selectedPages = document.getElementById("selected-pages-display");
selectedPages.classList.toggle("hidden", !window.selectPage); selectedPages.classList.toggle("hidden", !window.selectPage);
const selectAll = document.getElementById("select-All-Container"); const selectAll = document.getElementById("select-All-Container");
selectAll.classList.toggle("hidden", !window.selectPage); selectedPages.classList.toggle("hidden", !window.selectPage);
const exportSelected = document.getElementById("export-selected-button"); const exportSelected = document.getElementById("export-selected-button");
exportSelected.classList.toggle("hidden", !window.selectPage); exportSelected.classList.toggle("hidden", !window.selectPage);
const selectPagesButton = document.getElementById("select-pages-button"); const selectPagesButton = document.getElementById("select-pages-button");

View File

@@ -28,7 +28,7 @@ window.onload = function () {
// Show search results as user types in search box // Show search results as user types in search box
document.querySelector("#navbarSearchInput").addEventListener("input", function (e) { document.querySelector("#navbarSearchInput").addEventListener("input", function (e) {
var searchText = e.target.value.trim().toLowerCase(); // Trim whitespace and convert to lowercase var searchText = e.target.value.trim().toLowerCase(); // Trim whitespace and convert to lowercase
var items = document.querySelectorAll('a.dropdown-item[data-bs-tags]'); var items = document.querySelectorAll(".dropdown-item, .nav-link");
var resultsBox = document.querySelector("#searchResults"); var resultsBox = document.querySelector("#searchResults");
// Clear any previous results // Clear any previous results
@@ -83,7 +83,7 @@ searchDropdown.addEventListener('shown.bs.dropdown', function () {
searchDropdown.addEventListener('mouseenter', function () { searchDropdown.addEventListener('mouseenter', function () {
const dropdownInstance = new bootstrap.Dropdown(searchDropdown); const dropdownInstance = new bootstrap.Dropdown(searchDropdown);
dropdownInstance.show(); dropdownInstance.show();
setTimeout(() => { setTimeout(() => {
searchInput.focus(); searchInput.focus();
}, 100); }, 100);

View File

@@ -19,30 +19,27 @@
<form id="PDFToCSVForm" th:action="@{'/api/v1/convert/pdf/csv'}" method="post" enctype="multipart/form-data"> <form id="PDFToCSVForm" th:action="@{'/api/v1/convert/pdf/csv'}" method="post" enctype="multipart/form-data">
<input id="pageId" type="hidden" name="pageId"> <input id="pageId" type="hidden" name="pageId">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<button type="submit" class="btn btn-primary" th:text="#{PDFToCSV.submit}" id="submitBtn"></button> <button type="submit" class="btn btn-primary" th:text="#{PDFToCSV.submit}"></button>
</form> </form>
<p id="instruction-text" style="margin: 0; display: none" th:text="#{PDFToCSV.prompt}"></p> <p id="instruction-text" style="margin: 0; display: none" th:text="#{PDFToCSV.prompt}"></p>
<div style="position: relative; width: auto;" id="canvasesContainer"> <div style="position: relative; display: inline-block;">
<div> <div>
<div style="display:none ;margin: 3px;position: absolute;top: 0;width: 120px;justify-content:space-between;z-index: 10" id="pagination-button-container"> <div style="display:none ;margin: 3px;position: absolute;top: 0;width: 120px;justify-content:space-between;z-index: 10" id="pagination-button-container">
<button id='previous-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> &lt; </button> <button id='previous-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> &lt; </button>
<button id='next-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> &gt; </button> <button id='next-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> &gt; </button>
</div> </div>
<canvas id="cropPdfCanvas" style="width: 100%"></canvas> <canvas id="crop-pdf-canvas" style="position: absolute; top: 0; left: 0; z-index: 1;"></canvas>
</div> </div>
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2; width: 100%"></canvas> <canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2;"></canvas>
</div> </div>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script> <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script> <script>
let pdfCanvas = document.getElementById('cropPdfCanvas'); let pdfCanvas = document.getElementById('crop-pdf-canvas');
let overlayCanvas = document.getElementById('overlayCanvas'); let overlayCanvas = document.getElementById('overlayCanvas');
let canvasesContainer = document.getElementById('canvasesContainer');
canvasesContainer.style.display = "none";
// let paginationBtnContainer = ; // let paginationBtnContainer = ;
let context = pdfCanvas.getContext('2d'); let context = pdfCanvas.getContext('2d');
let overlayContext = overlayCanvas.getContext('2d');
let btn1Object = document.getElementById('previous-page-btn'); let btn1Object = document.getElementById('previous-page-btn');
let btn2Object = document.getElementById('next-page-btn'); let btn2Object = document.getElementById('next-page-btn');
@@ -63,8 +60,6 @@
let rectWidth = 0; let rectWidth = 0;
let rectHeight = 0; let rectHeight = 0;
let timeId = null; // timeout id for resizing canvases event
btn1Object.addEventListener('click',function (e){ btn1Object.addEventListener('click',function (e){
if (currentPage !== 1) { if (currentPage !== 1) {
currentPage = currentPage - 1; currentPage = currentPage - 1;
@@ -107,13 +102,14 @@
} }
}); });
function renderPageFromFile(file) { fileInput.addEventListener('change', function(e) {
file = e.target.files[0];
if (file.type === 'application/pdf') { if (file.type === 'application/pdf') {
let reader = new FileReader(); let reader = new FileReader();
reader.onload = function (ev) { reader.onload = function(ev) {
let typedArray = new Uint8Array(reader.result); let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) { pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
pdfDoc = pdf; pdfDoc = pdf;
totalPages = pdf.numPages; totalPages = pdf.numPages;
renderPage(currentPage); renderPage(currentPage);
@@ -121,37 +117,9 @@
pageId.value = currentPage; pageId.value = currentPage;
}; };
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
document.getElementById("pagination-button-container").style.display = "flex"; document.getElementById("pagination-button-container").style.display="flex";
document.getElementById("instruction-text").style.display = "block"; document.getElementById("instruction-text").style.display="block";
} }
}
window.addEventListener("resize", function() {
clearTimeout(timeId);
timeId = setTimeout(function () {
if (fileInput.files.length == 0) return;
let canvasesContainer = document.getElementById('canvasesContainer');
let containerRect = canvasesContainer.getBoundingClientRect();
context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
pdfCanvas.width = containerRect.width;
pdfCanvas.height = containerRect.height;
overlayCanvas.width = containerRect.width;
overlayCanvas.height = containerRect.height;
let file = fileInput.files[0];
renderPageFromFile(file);
}, 1000);
});
fileInput.addEventListener('change', function(e) {
canvasesContainer.style.display = "block"; // set for visual purposes
file = e.target.files[0];
renderPageFromFile(file);
}); });
function renderPage(pageNumber) { function renderPage(pageNumber) {

View File

@@ -14,7 +14,7 @@
<div class="col-md-9 bg-card"> <div class="col-md-9 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon organize">database</span> <span class="material-symbols-rounded tool-header-icon organize">database</span>
<span class="tool-header-text" th:text="#{database.title}">Database Im-/Export</span> <span class="tool-header-text" text="#{database.title}">Database Im-/Export</span>
</div> </div>
<p th:if="${error}" th:text="#{'database.' + ${error}}" class="alert alert-danger text-center"></p> <p th:if="${error}" th:text="#{'database.' + ${error}}" class="alert alert-danger text-center"></p>
<p th:if="${infoMessage}" th:text="#{'database.' + ${infoMessage}}" class="alert alert-success text-center"></p> <p th:if="${infoMessage}" th:text="#{'database.' + ${infoMessage}}" class="alert alert-success text-center"></p>
@@ -31,7 +31,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr th:each="backup : ${backupFiles}"> <tr th:each="backup : ${systemUpdate}">
<td th:text="${backup.fileName}"></td> <td th:text="${backup.fileName}"></td>
<td th:text="${backup.formattedCreationDate}"></td> <td th:text="${backup.formattedCreationDate}"></td>
<td th:text="${backup.formattedFileSize}"></td> <td th:text="${backup.formattedFileSize}"></td>
@@ -41,11 +41,10 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<h6 class="text-end"><span class="badge bg-dark" th:text="${databaseVersion}">DB-Version</span></h6>
</div> </div>
<hr> <hr>
<form th:action="@{'/api/v1/database/import-database'}" method="post" enctype="multipart/form-data" class="bg-card mt-3 mb-3"> <form th:action="@{'/api/v1/database/import-database'}" method="post" enctype="multipart/form-data" class="bg-card mt-3 mb-3">
<div style="background: var(--md-sys-color-error-container);border-radius: 2rem;" class="mb-3 p-3"> <div style="background: var(--md-sys-color-error-container);padding: .8rem;border-radius: 2rem;" class="mb-3">
<p th:text="#{database.info_1}"></p> <p th:text="#{database.info_1}"></p>
<p th:text="#{database.info_2}"></p> <p th:text="#{database.info_2}"></p>
</div> </div>

View File

@@ -219,4 +219,4 @@
</div> </div>
</div> </div>
<script th:src="@{'/js/fileInput.js'}"></script> <script th:src="@{'/js/fileInput.js'}"></script>
</th:block> </th:block>

View File

@@ -4,7 +4,6 @@
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ca_CA"> <img th:src="@{'/images/flags/es-ct.svg'}" alt="icon" width="20" height="15"> Català</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ca_CA"> <img th:src="@{'/images/flags/es-ct.svg'}" alt="icon" width="20" height="15"> Català</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_CN"> <img th:src="@{'/images/flags/cn.svg'}" alt="icon" width="20" height="15"> 简体中文</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_CN"> <img th:src="@{'/images/flags/cn.svg'}" alt="icon" width="20" height="15"> 简体中文</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_TW"> <img th:src="@{'/images/flags/tw.svg'}" alt="icon" width="20" height="15"> 繁體中文</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_TW"> <img th:src="@{'/images/flags/tw.svg'}" alt="icon" width="20" height="15"> 繁體中文</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="az_AZ"> <img th:src="@{'/images/flags/az.svg'}" alt="icon" width="20" height="15"> Azərbaycan Dili</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="da_DK"> <img th:src="@{'/images/flags/dk.svg'}" alt="icon" width="20" height="15"> Dansk</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="da_DK"> <img th:src="@{'/images/flags/dk.svg'}" alt="icon" width="20" height="15"> Dansk</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="de_DE"> <img th:src="@{'/images/flags/de.svg'}" alt="icon" width="20" height="15"> Deutsch</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="de_DE"> <img th:src="@{'/images/flags/de.svg'}" alt="icon" width="20" height="15"> Deutsch</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_GB"> <img th:src="@{'/images/flags/gb.svg'}" alt="icon" width="20" height="15"> English (GB)</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_GB"> <img th:src="@{'/images/flags/gb.svg'}" alt="icon" width="20" height="15"> English (GB)</a>

View File

@@ -3,8 +3,8 @@
<div> <div>
<span th:utext="#{multiTool-advert.message(|/multi-tool|)}"></span> <span th:utext="#{multiTool-advert.message(|/multi-tool|)}"></span>
<button id="closeMultiToolAdvert" style="position: absolute; <button id="closeMultiToolAdvert" style="position: absolute;
inset-inline-end: 12px; top: 10px;
inset-block-start: 10px; right: 12px;
border: none; border: none;
background: transparent; background: transparent;
color: white; color: white;
@@ -16,13 +16,13 @@
<style> <style>
.multi-toolAdvert { .multi-toolAdvert {
margin-bottom: 10px; margin-bottom: 10px;
margin-inline-start: 50%; margin-left: 50%;
transform: translateX(-50%);
max-width: 52rem; max-width: 52rem;
z-index: 0; z-index: 0;
background-color: var(--md-sys-color-surface-5); background-color: var(--md-sys-color-surface-5);
border-radius: 2rem; border-radius: 2rem;
padding-block: 10px; padding: 10px 27px 10px 20px;
padding-inline: 20px 27px;
font-size: 0.9rem; font-size: 0.9rem;
display: none; display: none;
justify-content: center; justify-content: center;
@@ -43,7 +43,6 @@
const closeBtn = document.getElementById('closeMultiToolAdvert'); const closeBtn = document.getElementById('closeMultiToolAdvert');
const cacheKey = `closeMultiToolAdvert_${window.location.pathname}`; const cacheKey = `closeMultiToolAdvert_${window.location.pathname}`;
const isRTL = document.documentElement.dir === 'rtl';
if (localStorage.getItem(cacheKey) !== 'true') { if (localStorage.getItem(cacheKey) !== 'true') {
advert.style.display = 'flex'; advert.style.display = 'flex';
@@ -53,13 +52,6 @@
advert.style.display = 'none'; advert.style.display = 'none';
localStorage.setItem(cacheKey, 'true'); localStorage.setItem(cacheKey, 'true');
}); });
if (isRTL) {
advert.style.transform = 'translateX(50%)'; // Flip direction for RTL
} else {
advert.style.transform = 'translateX(-50%)';
}
}); });
</script> </script>
</div> </div>

View File

@@ -47,7 +47,7 @@
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2"> <div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.organize}"></h6> <h6 class="menu-title" th:text="#{navbar.sections.organize}"></h6>
<div <div
th:replace="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'organize')}"> th:replace="~{fragments/navbarEntry :: navbarEntry ('compress-pdf', 'zoom_in_map', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPdfs.tags', 'advance')}">
</div> </div>
<div <div
th:replace="~{fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'add_to_photos', 'home.merge.title', 'home.merge.desc', 'merge.tags', 'organize')}"> th:replace="~{fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'add_to_photos', 'home.merge.title', 'home.merge.desc', 'merge.tags', 'organize')}">
@@ -214,7 +214,7 @@
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2"> <div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.advance}"></h6> <h6 class="menu-title" th:text="#{navbar.sections.advance}"></h6>
<div <div
th:replace="~{fragments/navbarEntry :: navbarEntry ('compress-pdf', 'zoom_in_map', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPdfs.tags', 'advance')}"> th:replace="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'advance')}">
</div> </div>
<div <div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pipeline', 'family_history', 'home.pipeline.title', 'home.pipeline.desc', 'pipeline.tags', 'advance')}"> th:replace="~{fragments/navbarEntry :: navbarEntry ('pipeline', 'family_history', 'home.pipeline.title', 'home.pipeline.desc', 'pipeline.tags', 'advance')}">
@@ -330,7 +330,7 @@
<span class="material-symbols-rounded" id="dark-mode-icon"> <span class="material-symbols-rounded" id="dark-mode-icon">
dark_mode dark_mode
</span> </span>
<span class="icon-text icon-hide" id="dark-mode-text" th:data-text="#{navbar.darkmode}" th:text="#{navbar.darkmode}"></span> <span class="icon-text icon-hide tooltip-text" id="dark-mode-text" th:data-text="#{navbar.darkmode}" th:text="#{navbar.darkmode}"></span>
</a> </a>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
@@ -380,7 +380,7 @@
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
settings settings
</span> </span>
<span class="icon-text icon-hide" th:data-text="#{navbar.settings}" th:text="#{navbar.settings}"></span> <span class="icon-text icon-hide tooltip-text" th:data-text="#{navbar.settings}" th:text="#{navbar.settings}"></span>
</a> </a>
</li> </li>
</ul> </ul>
@@ -411,7 +411,7 @@
th:title="#{visitGithub}"> th:title="#{visitGithub}">
<img th:src="@{'/images/github.svg'}" alt="github"> <img th:src="@{'/images/github.svg'}" alt="github">
</a> </a>
<a href="https://hub.docker.com/r/stirlingtools/stirling-pdf" class="mx-1" role="button" target="_blank"th:title="#{seeDockerHub}"> <a href="https://hub.docker.com/r/frooodle/s-pdf" class="mx-1" role="button" target="_blank"th:title="#{seeDockerHub}">
<img th:src="@{'/images/docker.svg'}" alt="docker"> <img th:src="@{'/images/docker.svg'}" alt="docker">
</a> </a>
<a href="https://discord.gg/Cn8pWhQRxZ" class="mx-1" role="button" target="_blank" th:title="#{joinDiscord}"> <a href="https://discord.gg/Cn8pWhQRxZ" class="mx-1" role="button" target="_blank" th:title="#{joinDiscord}">

View File

@@ -31,79 +31,67 @@
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
add add
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.addFile}"></span>
</button> </button>
<button class="btn btn-secondary enable-on-file" onclick="rotateAll(-90)" disabled> <button class="btn btn-secondary enable-on-file" onclick="rotateAll(-90)" disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
rotate_left rotate_left
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.rotateLeft}"></span>
</button> </button>
<button class="btn btn-secondary enable-on-file" onclick="rotateAll(90)" disabled> <button class="btn btn-secondary enable-on-file" onclick="rotateAll(90)" disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
rotate_right rotate_right
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.rotateRight}"></span>
</button> </button>
<button class="btn btn-secondary enable-on-file" onclick="splitAll()" disabled> <button class="btn btn-secondary enable-on-file" onclick="splitAll()" disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
cut cut
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.split}"></span> </button>
<button id="select-pages-container" class="btn btn-secondary enable-on-file"
th:title="#{multiTool.selectPages}" onclick="toggleSelectPageVisibility()" disabled>
<span id="select-pages-button" class="material-symbols-rounded">
event_list
</span>
</button>
<button id="select-All-Container" class="btn btn-secondary enable-on-file hidden"
onclick="toggleSelectAll()" disabled>
<span th:title="#{multiTool.selectAll}" class="material-symbols-rounded"
id="select-icon">select_all</span>
<span th:title="#{multiTool.deselectAll}" class="material-symbols-rounded" style="display: none;"
id="deselect-icon">deselect</span>
</button>
<div class=" button-container">
<button th:title="#{multiTool.deleteSelected}" id="delete-button" class="btn btn-danger hidden"
onclick="deleteSelected()">
<span class="material-symbols-rounded">delete</span>
</button>
</div>
<button id="export-selected-button" class="btn btn-primary enable-on-file hidden"
onclick="exportPdf(true)" disabled>
<span th:title="#{multiTool.downloadSelected}" class="material-symbols-rounded">
file_save
</span>
</button> </button>
<button class="btn btn-secondary enable-on-file" onclick="addFilesBlankAll()" disabled> <button class="btn btn-secondary enable-on-file" onclick="addFilesBlankAll()" disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
insert_page_break insert_page_break
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.insertPageBreak}"></span>
</button> </button>
<button id="select-pages-container" class="btn btn-secondary enable-on-file" <button id="export-button" class="btn btn-primary enable-on-file" onclick="exportPdf(false)" disabled>
onclick="toggleSelectPageVisibility()" disabled> <span th:title="#{multiTool.downloadAll}" class="material-symbols-rounded">
<span id="select-pages-button" class="material-symbols-rounded"> download
event_list
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.selectPages}"></span>
</button> </button>
<button id="deselect-All-Container" class="btn btn-secondary enable-on-file hidden" </div>
onclick="toggleSelectAll()" disabled> <div class="mb-3">
<span class="material-symbols-rounded" id="deselect-icon">deselect</span> <button type="button" id="resetFileInputBtn" class="btn btn-danger" onclick="removeAllElements()" th:text="#{reset}">Reset</button>
<span class="btn-tooltip" th:text="#{multiTool.deselectAll}"></span>
</button>
<button id="select-All-Container" class="btn btn-secondary enable-on-file hidden"
onclick="toggleSelectAll()" disabled>
<span class="material-symbols-rounded"
id="select-icon">select_all</span>
<span class="btn-tooltip" th:text="#{multiTool.selectAll}"></span>
</button>
<div class="button-container">
<button id="delete-button" class="btn btn-danger delete hidden" onclick="deleteSelected()">
<span class="material-symbols-rounded">delete</span>
<span class="btn-tooltip" th:text="#{multiTool.deleteSelected}"></span>
</button>
</div>
<div style="margin-left:auto">
<button id="export-selected-button" class="btn btn-primary enable-on-file hidden"
onclick="exportPdf(true)" disabled>
<span class="btn-tooltip" th:text="#{multiTool.downloadSelected}"></span>
<span class="material-symbols-rounded">
file_save
</span>
</button>
<button id="export-button" class="btn btn-primary enable-on-file" onclick="exportPdf(false)"
disabled>
<span class="material-symbols-rounded">
download
</span>
<span class="btn-tooltip" th:text="#{multiTool.downloadAll}"></span>
</button>
</div>
</div> </div>
<div id="selected-pages-display" class="selected-pages-container hidden"> <div id="selected-pages-display" class="selected-pages-container hidden">
<div style="display:flex; height:3rem; margin-right:1rem"> <div style="display:flex; height:3rem; margin-right:1rem">
<h5 th:text="#{multiTool.selectedPages}" style="white-space: nowrap; margin-right: 1rem;">Selected <h5 th:text="#{multiTool.selectedPages}" style="white-space: nowrap; margin-right: 1rem;">Selected
Pages</h5> Pages</h5>
<input type="text" id="csv-input" class="form-control" style="height:2.5rem" placeholder="1,3,5-10" value=""> <input type="text" id="csv-input" class="form-control" style="height:2.5rem" placeholder="1,3,5-10"
value="">
</div> </div>
<ul id="selected-pages-list" class="pages-list"></ul> <ul id="selected-pages-list" class="pages-list"></ul>
</div> </div>
@@ -112,6 +100,13 @@
<div class="multi-tool-container"> <div class="multi-tool-container">
<div class="d-flex flex-wrap" id="pages-container-wrapper"> <div class="d-flex flex-wrap" id="pages-container-wrapper">
<div id="pages-container"> <div id="pages-container">
<div class="page-container" th:each="file, status: ${fileList}"
th:id="'page-container-' + ${status.index}">
<div class="page-number-container">
<span th:text="${status.index + 1}"></span>
</div>
<img th:src="${file.imageUrl}" alt="File Page">
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -129,21 +124,6 @@
window.selectedPages = []; window.selectedPages = [];
window.selectPage = false; window.selectPage = false;
window.selectAll = false; window.selectAll = false;
window.translations = {
rotateLeft: '[[#{multiTool.rotateLeft}]]',
rotateRight: '[[#{multiTool.rotateRight}]]',
moveLeft: '[[#{multiTool.moveLeft}]]',
moveRight: '[[#{multiTool.moveRight}]]',
delete: '[[#{multiTool.delete}]]',
split: '[[#{multiTool.split}]]',
addFile: '[[#{multiTool.addFile}]]',
insertPageBreak:'[[#{multiTool.insertPageBreak}]]',
dragDropMessage:'[[#{multiTool.dragDropMessage}]]'
};
const csvInput = document.getElementById("csv-input"); const csvInput = document.getElementById("csv-input");
csvInput.addEventListener("keydown", function (event) { csvInput.addEventListener("keydown", function (event) {
if (event.key === "Enter") { if (event.key === "Enter") {

View File

@@ -1,12 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
xmlns:th="https://www.thymeleaf.org"> <head>
<head>
<th:block th:insert="~{fragments/common :: head(title=#{sign.title}, header=#{sign.header})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{sign.title}, header=#{sign.header})}"></th:block>
<link rel="stylesheet" th:href="@{'/css/sign.css'}"> <link rel="stylesheet" th:href="@{'/css/sign.css'}">
<th:block th:each="font : ${fonts}"> <th:block th:each="font : ${fonts}">
<style th:inline="text"> <style th:inline="text">
@font-face { @font-face {
font-family: "[[${font.name}]]"; font-family: "[[${font.name}]]";
@@ -18,435 +16,372 @@
cursive; cursive;
} }
</style> </style>
</th:block> </th:block>
<script th:src="@{'/js/thirdParty/signature_pad.umd.min.js'}"></script> <script th:src="@{'/js/thirdParty/signature_pad.umd.min.js'}"></script>
<script th:src="@{'/js/thirdParty/interact.min.js'}"></script> <script th:src="@{'/js/thirdParty/interact.min.js'}"></script>
</head> </head>
<body> <body>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block> <th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br> <br><br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon sign">signature</span> <span class="material-symbols-rounded tool-header-icon sign">signature</span>
<span class="tool-header-text" th:text="#{sign.header}"></span> <span class="tool-header-text" th:text="#{sign.header}"></span>
</div>
<!-- pdf selector -->
<div
th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}">
</div>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script>
let currentPreviewSrc = null;
function toggleSignatureView() {
const gridView = document.getElementById('gridView');
const listView = document.getElementById('listView');
const gridText = document.querySelector('.grid-view-text');
const listText = document.querySelector('.list-view-text');
if (gridView.style.display !== 'none') {
gridView.style.display = 'none';
listView.style.display = 'block';
gridText.style.display = 'none';
listText.style.display = 'inline';
} else {
gridView.style.display = 'block';
listView.style.display = 'none';
gridText.style.display = 'inline';
listText.style.display = 'none';
}
}
function previewSignature(element) {
const src = element.dataset.src;
currentPreviewSrc = src;
// Extract filename from the data source path
const filename = element.querySelector('.signature-list-name').textContent;
// Update preview modal
const previewImage = document.getElementById('previewImage');
const previewFileName = document.getElementById('previewFileName');
previewImage.src = src;
previewFileName.textContent = filename;
const modal = new bootstrap.Modal(document.getElementById('signaturePreview'));
modal.show();
}
function addSignatureFromPreview() {
if (currentPreviewSrc) {
DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc);
bootstrap.Modal.getInstance(document.getElementById('signaturePreview')).hide();
}
}
let originalFileName = '';
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (file) {
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;
await DraggableUtils.renderPage(pdfDoc, 0);
document.querySelectorAll(".show-on-file-selected").forEach(el => {
el.style.cssText = '';
});
}
});
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".show-on-file-selected").forEach(el => {
el.style.cssText = "display:none !important";
});
});
</script>
<div class="tab-group show-on-file-selected">
<div class="tab-container" th:title="#{sign.upload}">
<div
th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}">
</div>
</div> </div>
<div class="tab-container drawing-pad-container" th:title="#{sign.draw}"> <!-- pdf selector -->
<canvas id="drawing-pad-canvas"></canvas> <div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}"></div>
<br> <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()" <script>
th:text="#{sign.clear}"></button> let currentPreviewSrc = null;
<button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()"
th:text="#{sign.add}"></button>
</div>
<div class="tab-container" th:title="#{sign.saved}"> function toggleSignatureView() {
<div class="saved-signatures-section" th:if="${not #lists.isEmpty(signatures)}"> const gridView = document.getElementById('gridView');
<!-- View Toggle Button --> const listView = document.getElementById('listView');
<div class="view-toggle mb-3"> const gridText = document.querySelector('.grid-view-text');
<button class="btn btn-outline-secondary btn-sm" onclick="toggleSignatureView()"> const listText = document.querySelector('.list-view-text');
<span class="material-symbols-rounded grid-view-text">view_list</span>
<span class="material-symbols-rounded list-view-text" style="display: none;">grid_view</span>
</button>
</div>
<!-- Preview Modal --> if (gridView.style.display !== 'none') {
<div class="modal fade" id="signaturePreview" tabindex="-1"> gridView.style.display = 'none';
<div class="modal-dialog modal-dialog-centered"> listView.style.display = 'block';
<div class="modal-content"> gridText.style.display = 'none';
<div class="modal-header"> listText.style.display = 'inline';
<h5 class="modal-title"><span id="previewFileName"></span></h5> } else {
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> gridView.style.display = 'block';
</div> listView.style.display = 'none';
<div class="modal-body text-center"> gridText.style.display = 'inline';
<img id="previewImage" src="" alt="Signature Preview" style="max-width: 100%;"> listText.style.display = 'none';
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"
th:text="#{close}"></button>
<button type="button" class="btn btn-primary" onclick="addSignatureFromPreview()"
th:text="#{addToDoc}">Add to Document</button>
</div>
</div>
</div>
</div>
<!-- Grid View -->
<div id="gridView">
<!-- Personal Signatures -->
<div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Personal'])}">
<h5 th:text="#{sign.personalSigs}"></h5>
<div class="signature-grid">
<div th:each="sig : ${signatures}" th:if="${sig.category == 'Personal'}" class="signature-item">
<img th:src="@{'/api/v1/general/sign/' + ${sig.fileName}}" th:alt="${sig.fileName}"
th:data-filename="${sig.fileName}" style="max-width: 200px; cursor: pointer;"
onclick="DraggableUtils.createDraggableCanvasFromUrl(this.src)" />
<div class="signature-name" th:text="${sig.fileName}"></div>
</div>
</div>
</div>
<!-- Shared Signatures -->
<div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Shared'])}">
<h5 th:text="#{sign.sharedSigs}"></h5>
<div class="signature-grid">
<div th:each="sig : ${signatures}" th:if="${sig.category == 'Shared'}" class="signature-item">
<img th:src="@{'/api/v1/general/sign/' + ${sig.fileName}}" th:alt="${sig.fileName}"
th:data-filename="${sig.fileName}" style="max-width: 200px; cursor: pointer;"
onclick="DraggableUtils.createDraggableCanvasFromUrl(this.src)" />
<div class="signature-name" th:text="${sig.fileName}"></div>
</div>
</div>
</div>
</div>
<!-- List View (Initially Hidden) -->
<div id="listView" style="display: none;">
<!-- Personal Signatures -->
<div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Personal'])}">
<h5 th:text="#{sign.personalSigs}"></h5>
<div class="signature-list">
<div th:each="sig : ${signatures}" th:if="${sig.category == 'Personal'}"
class="signature-list-item" th:data-src="@{'/api/v1/general/sign/' + ${sig.fileName}}"
onclick="previewSignature(this)">
<div class="signature-list-info">
<span th:text="${sig.fileName}" class="signature-list-name"></span>
</div>
</div>
</div>
</div>
<!-- Shared Signatures -->
<div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Shared'])}">
<h5 th:text="#{sign.sharedSigs}"></h5>
<div class="signature-list">
<div th:each="sig : ${signatures}" th:if="${sig.category == 'Shared'}"
class="signature-list-item" th:data-src="@{'/api/v1/general/sign/' + ${sig.fileName}}"
onclick="previewSignature(this)">
<div class="signature-list-info">
<span th:text="${sig.fileName}" class="signature-list-name"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<div th:if="${#lists.isEmpty(signatures)}" class="text-center p-3">
<p th:text="#{sign.noSavedSigs}">No saved signatures found</p>
</div>
</div>
<div class="tab-container" th:title="#{sign.text}">
<label class="form-check-label" for="sigText" th:text="#{text}"></label>
<textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
<label th:text="#{font}"></label>
<select class="form-control" name="font" id="font-select">
<option th:each="font : ${fonts}" th:value="${font.name}" th:text="${font.name}"
th:class="${font.name.toLowerCase()+'-font'}"></option>
</select>
<div class="margin-auto-parent">
<button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center"
onclick="addDraggableFromText()" th:text="#{sign.add}"></button>
</div>
</div>
</div>
<script>
const imageUpload = document.querySelector('input[name=image-upload]');
imageUpload.addEventListener('change', e => {
if (!e.target.files) return;
for (const imageFile of e.target.files) {
var reader = new FileReader();
reader.readAsDataURL(imageFile);
reader.onloadend = function (e) {
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
};
}
});
</script>
<script>
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; function previewSignature(element) {
let croppedHeight = maxY - minY; const src = element.dataset.src;
if (croppedWidth < 0 || croppedHeight < 0) return null; currentPreviewSrc = src;
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
let croppedCanvas = document.createElement('canvas'), // Extract filename from the data source path
croppedCtx = croppedCanvas.getContext('2d'); const filename = element.querySelector('.signature-list-name').textContent;
croppedCanvas.width = croppedWidth; // Update preview modal
croppedCanvas.height = croppedHeight; const previewImage = document.getElementById('previewImage');
croppedCtx.putImageData(cuttedImageData, 0, 0); const previewFileName = document.getElementById('previewFileName');
return croppedCanvas.toDataURL(); previewImage.src = src;
} previewFileName.textContent = filename;
function resizeCanvas() { const modal = new bootstrap.Modal(document.getElementById('signaturePreview'));
var ratio = Math.max(window.devicePixelRatio || 1, 1); modal.show();
var additionalFactor = 10;
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); function addSignatureFromPreview() {
</script> if (currentPreviewSrc) {
<script> DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc);
function addDraggableFromText() { bootstrap.Modal.getInstance(document.getElementById('signaturePreview')).hide();
const sigText = document.getElementById('sigText').value; }
const font = document.querySelector('select[name=font]').value; }
const fontSize = 100;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.font = `${fontSize}px ${font}`;
const textWidth = ctx.measureText(sigText).width;
const textHeight = fontSize;
let paragraphs = sigText.split(/\r?\n/); let originalFileName = '';
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (file) {
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;
await DraggableUtils.renderPage(pdfDoc, 0);
canvas.width = textWidth; document.querySelectorAll(".show-on-file-selected").forEach(el => {
canvas.height = paragraphs.length * textHeight * 1.35; // for tails el.style.cssText = '';
ctx.font = `${fontSize}px ${font}`; });
}
ctx.textBaseline = 'top';
let y = 0;
paragraphs.forEach(paragraph => {
ctx.fillText(paragraph, 0, y);
y += fontSize;
}); });
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".show-on-file-selected").forEach(el => {
el.style.cssText = "display:none !important";
});
});
</script>
const dataURL = canvas.toDataURL(); <div class="tab-group show-on-file-selected">
DraggableUtils.createDraggableCanvasFromUrl(dataURL); <div class="tab-container" th:title="#{sign.upload}">
} <div th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"></div>
</script> <script>
const imageUpload = document.querySelector('input[name=image-upload]');
imageUpload.addEventListener('change', e => {
if (!e.target.files) return;
for (const imageFile of e.target.files) {
var reader = new FileReader();
reader.readAsDataURL(imageFile);
reader.onloadend = function (e) {
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
};
}
});
</script>
</div>
<!-- draggables box --> <div class="tab-container drawing-pad-container" th:title="#{sign.draw}">
<div id="box-drag-container" class="show-on-file-selected"> <canvas id="drawing-pad-canvas"></canvas>
<canvas id="pdf-canvas"></canvas> <br>
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script> <button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()" th:text="#{sign.clear}"></button>
<script th:src="@{'/js/draggable-utils.js'}"></script> <button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()" th:text="#{sign.add}"></button>
<div class="draggable-buttons-box ignore-rtl"> <script>
<button class="btn btn-outline-secondary" const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())"> const signaturePad = new SignaturePad(signaturePadCanvas, {
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" minWidth: 1,
viewBox="0 0 16 16"> maxWidth: 2,
<path penColor: 'black',
d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z" /> });
<path
d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z" /> function addDraggableFromPad() {
</svg> if (signaturePad.isEmpty()) return;
<span class="btn-tooltip" th:text="#{sign.delete}"></span> const startTime = Date.now();
</button> const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas);
<button class="btn btn-outline-secondary" console.log(Date.now() - startTime);
onclick="DraggableUtils.addAllPagesDraggableCanvas(DraggableUtils.getLastInteracted())"> DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
<span class="material-symbols-rounded"> }
content_copy
</span> function getCroppedCanvasDataUrl(canvas) {
<span class="btn-tooltip" th:text="#{sign.addToAll}"></span> let originalCtx = canvas.getContext('2d');
</button> let originalWidth = canvas.width;
<button class="btn btn-outline-secondary" onclick="goToFirstOrLastPage(false)" style="margin-left:auto"> let originalHeight = canvas.height;
<span class="material-symbols-rounded"> let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
keyboard_double_arrow_left
</span> let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1, x = 0, y = 0, currentPixelColorValueIndex;
<span class="btn-tooltip" th:text="#{sign.first}"></span>
</button> for (y = 0; y < originalHeight; y++) {
<button class="btn btn-outline-secondary" id="incrementPage" for (x = 0; x < originalWidth; x++) {
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()"> currentPixelColorValueIndex = (y * originalWidth + x) * 4;
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
class="bi bi-chevron-left" viewBox="0 0 16 16"> if (currentPixelAlphaValue > 0) {
<path fill-rule="evenodd" if (minX > x) minX = x;
d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z" /> if (maxX < x) maxX = x;
</svg> if (minY > y) minY = y;
<span class="btn-tooltip" th:text="#{sign.previous}"></span> if (maxY < y) maxY = y;
</button> }
<button class="btn btn-outline-secondary" id="decrementPage" }
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()"> }
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-chevron-right" viewBox="0 0 16 16"> let croppedWidth = maxX - minX;
<path fill-rule="evenodd" let croppedHeight = maxY - minY;
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" /> if (croppedWidth < 0 || croppedHeight < 0) return null;
</svg> let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
<span class="btn-tooltip" th:text="#{sign.next}"></span>
</button> let croppedCanvas = document.createElement('canvas'),
<button class="btn btn-outline-secondary" onclick="goToFirstOrLastPage(true)"> croppedCtx = croppedCanvas.getContext('2d');
<span class="material-symbols-rounded">
keyboard_double_arrow_right croppedCanvas.width = croppedWidth;
</span> croppedCanvas.height = croppedHeight;
<span class="btn-tooltip" th:text="#{sign.last}"></span> croppedCtx.putImageData(cuttedImageData, 0, 0);
</button>
return croppedCanvas.toDataURL();
}
function resizeCanvas() {
var ratio = Math.max(window.devicePixelRatio || 1, 1);
var additionalFactor = 10;
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);
</script>
</div>
<div class="tab-container" th:title="#{sign.saved}">
<div class="saved-signatures-section" th:if="${not #lists.isEmpty(signatures)}">
<!-- View Toggle Button -->
<div class="view-toggle mb-3">
<button class="btn btn-outline-secondary btn-sm" onclick="toggleSignatureView()">
<span class="material-symbols-rounded grid-view-text">view_list</span>
<span class="material-symbols-rounded list-view-text" style="display: none;">grid_view</span>
</button>
</div>
<!-- Preview Modal -->
<div class="modal fade" id="signaturePreview" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><span id="previewFileName"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<img id="previewImage" src="" alt="Signature Preview" style="max-width: 100%;">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
<button type="button" class="btn btn-primary" onclick="addSignatureFromPreview()" th:text="#{addToDoc}">Add to Document</button>
</div>
</div>
</div>
</div>
<!-- Grid View -->
<div id="gridView">
<!-- Personal Signatures -->
<div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Personal'])}">
<h5 th:text="#{sign.personalSigs}"></h5>
<div class="signature-grid">
<div th:each="sig : ${signatures}" th:if="${sig.category == 'Personal'}" class="signature-item">
<img th:src="@{'/api/v1/general/sign/' + ${sig.fileName}}" th:alt="${sig.fileName}" th:data-filename="${sig.fileName}" style="max-width: 200px; cursor: pointer;" onclick="DraggableUtils.createDraggableCanvasFromUrl(this.src)"/>
<div class="signature-name" th:text="${sig.fileName}"></div>
</div>
</div>
</div>
<!-- Shared Signatures -->
<div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Shared'])}">
<h5 th:text="#{sign.sharedSigs}"></h5>
<div class="signature-grid">
<div th:each="sig : ${signatures}" th:if="${sig.category == 'Shared'}" class="signature-item">
<img th:src="@{'/api/v1/general/sign/' + ${sig.fileName}}" th:alt="${sig.fileName}" th:data-filename="${sig.fileName}" style="max-width: 200px; cursor: pointer;" onclick="DraggableUtils.createDraggableCanvasFromUrl(this.src)"/>
<div class="signature-name" th:text="${sig.fileName}"></div>
</div>
</div>
</div>
</div>
<!-- List View (Initially Hidden) -->
<div id="listView" style="display: none;">
<!-- Personal Signatures -->
<div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Personal'])}">
<h5 th:text="#{sign.personalSigs}"></h5>
<div class="signature-list">
<div th:each="sig : ${signatures}" th:if="${sig.category == 'Personal'}" class="signature-list-item" th:data-src="@{'/api/v1/general/sign/' + ${sig.fileName}}" onclick="previewSignature(this)">
<div class="signature-list-info">
<span th:text="${sig.fileName}" class="signature-list-name"></span>
</div>
</div>
</div>
</div>
<!-- Shared Signatures -->
<div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Shared'])}">
<h5 th:text="#{sign.sharedSigs}"></h5>
<div class="signature-list">
<div th:each="sig : ${signatures}" th:if="${sig.category == 'Shared'}" class="signature-list-item" th:data-src="@{'/api/v1/general/sign/' + ${sig.fileName}}" onclick="previewSignature(this)">
<div class="signature-list-info">
<span th:text="${sig.fileName}" class="signature-list-name"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<div th:if="${#lists.isEmpty(signatures)}" class="text-center p-3">
<p th:text="#{sign.noSavedSigs}">No saved signatures found</p>
</div>
</div>
<div class="tab-container" th:title="#{sign.text}">
<label class="form-check-label" for="sigText" th:text="#{text}"></label>
<textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
<label th:text="#{font}"></label>
<select class="form-control" name="font" id="font-select">
<option th:each="font : ${fonts}" th:value="${font.name}" th:text="${font.name}" th:class="${font.name.toLowerCase()+'-font'}"></option>
</select>
<div class="margin-auto-parent">
<button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center" onclick="addDraggableFromText()" th:text="#{sign.add}"></button>
</div>
<script>
function addDraggableFromText() {
const sigText = document.getElementById('sigText').value;
const font = document.querySelector('select[name=font]').value;
const fontSize = 100;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.font = `${fontSize}px ${font}`;
const textWidth = ctx.measureText(sigText).width;
const textHeight = fontSize;
let paragraphs = sigText.split(/\r?\n/);
canvas.width = textWidth;
canvas.height = paragraphs.length * textHeight * 1.35; // for tails
ctx.font = `${fontSize}px ${font}`;
ctx.textBaseline = 'top';
let y = 0;
paragraphs.forEach(paragraph => {
ctx.fillText(paragraph, 0, y);
y += fontSize;
});
const dataURL = canvas.toDataURL();
DraggableUtils.createDraggableCanvasFromUrl(dataURL);
}
</script>
</div>
</div> </div>
<!-- draggables box -->
<div id="box-drag-container" class="show-on-file-selected">
<canvas id="pdf-canvas"></canvas>
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
<script th:src="@{'/js/draggable-utils.js'}"></script>
<div class="draggable-buttons-box ignore-rtl">
<button class="btn btn-outline-secondary" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z" />
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z" />
</svg>
</button>
<button class="btn btn-outline-secondary" onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()" style="margin-left:auto">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z" />
</svg>
</button>
<button class="btn btn-outline-secondary" onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" />
</svg>
</button>
</div>
</div>
<!-- download button -->
<div class="margin-auto-parent">
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center" th:text="#{downloadPdf}"></button>
</div>
<script>
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();
});
</script>
</div> </div>
<!-- download button -->
<div class="margin-auto-parent">
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center"
th:text="#{downloadPdf}"></button>
</div>
<script>
async function goToFirstOrLastPage(page) {
if (page) {
const lastPage = DraggableUtils.pdfDoc.numPages
await DraggableUtils.goToPage(lastPage - 1)
} else {
await DraggableUtils.goToPage(0)
}
}
</script>
<script>
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();
});
</script>
</div> </div>
</div> </div>
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <!-- Link the draggable.js file -->
</div> <script th:src="@{'/js/draggable.js'}"></script>
<!-- Link the draggable.js file --> </body>
<script th:src="@{'/js/draggable.js'}"></script>
</body>
</html> </html>

View File

@@ -49,7 +49,7 @@ check_health() {
build_and_test() { build_and_test() {
local version_tag="alpha" local version_tag="alpha"
local dockerfile_name="./Dockerfile" local dockerfile_name="./Dockerfile"
local image_base="stirlingtools/stirling-pdf" local image_base="frooodle/s-pdf"
local security_suffix="" local security_suffix=""
local docker_compose_base="./exampleYmlFiles/docker-compose-latest" local docker_compose_base="./exampleYmlFiles/docker-compose-latest"
local compose_suffix=".yml" local compose_suffix=".yml"