Compare commits
18 Commits
update_tra
...
v0.38.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
737be6c458 | ||
|
|
2e8abb7bb2 | ||
|
|
e2d75ead27 | ||
|
|
28e89a373f | ||
|
|
b4451da2f4 | ||
|
|
8353c399d2 | ||
|
|
af1b156ba6 | ||
|
|
3654743d95 | ||
|
|
58bd9b36cd | ||
|
|
d31b379b5c | ||
|
|
35c85bfeb8 | ||
|
|
64469061fd | ||
|
|
57c343910f | ||
|
|
319ba24be0 | ||
|
|
ec12470188 | ||
|
|
ec88e893c8 | ||
|
|
4ef5a0688b | ||
|
|
86438d7ad3 |
29
.github/pull_request_template.md
vendored
29
.github/pull_request_template.md
vendored
@@ -1,15 +1,34 @@
|
|||||||
# Description
|
# Description of Changes
|
||||||
|
|
||||||
Please provide a summary of the changes, including relevant motivation and context.
|
Please provide a summary of the changes, including:
|
||||||
|
|
||||||
|
- What was changed
|
||||||
|
- Why the change was made
|
||||||
|
- Any challenges encountered
|
||||||
|
|
||||||
Closes #(issue_number)
|
Closes #(issue_number)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
|
### General
|
||||||
|
|
||||||
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
|
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
|
||||||
|
- [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable)
|
||||||
|
- [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable)
|
||||||
- [ ] I have performed a self-review of my own code
|
- [ ] I have performed a self-review of my own code
|
||||||
- [ ] I have attached images of the change if it is UI based
|
|
||||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
|
||||||
- [ ] If my code has heavily changed functionality I have updated relevant docs on [Stirling-PDFs doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
|
|
||||||
- [ ] My changes generate no new warnings
|
- [ ] My changes generate no new warnings
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed)
|
||||||
- [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)
|
- [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)
|
||||||
|
|
||||||
|
### UI Changes (if applicable)
|
||||||
|
|
||||||
|
- [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR)
|
||||||
|
|
||||||
|
### Testing (if applicable)
|
||||||
|
|
||||||
|
- [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
|
||||||
|
|||||||
78
.github/scripts/check_language_properties.py
vendored
78
.github/scripts/check_language_properties.py
vendored
@@ -21,25 +21,60 @@ import argparse
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def find_duplicate_keys(file_path):
|
||||||
|
"""
|
||||||
|
Identifies duplicate keys in a .properties file.
|
||||||
|
:param file_path: Path to the .properties file.
|
||||||
|
:return: List of tuples (key, first_occurrence_line, duplicate_line).
|
||||||
|
"""
|
||||||
|
keys = {}
|
||||||
|
duplicates = []
|
||||||
|
|
||||||
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
|
for line_number, line in enumerate(file, start=1):
|
||||||
|
stripped_line = line.strip()
|
||||||
|
|
||||||
|
# Skip empty lines and comments
|
||||||
|
if not stripped_line or stripped_line.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Split the line into key and value
|
||||||
|
if "=" in stripped_line:
|
||||||
|
key, _ = stripped_line.split("=", 1)
|
||||||
|
key = key.strip()
|
||||||
|
|
||||||
|
# Check if the key already exists
|
||||||
|
if key in keys:
|
||||||
|
duplicates.append((key, keys[key], line_number))
|
||||||
|
else:
|
||||||
|
keys[key] = line_number
|
||||||
|
|
||||||
|
return duplicates
|
||||||
|
|
||||||
|
|
||||||
# Maximum size for properties files (e.g., 200 KB)
|
# Maximum size for properties files (e.g., 200 KB)
|
||||||
MAX_FILE_SIZE = 200 * 1024
|
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 structured list of its contents.
|
||||||
|
:param file_path: Path to the .properties file.
|
||||||
|
:return: List of dictionaries representing each line in the file.
|
||||||
|
"""
|
||||||
properties_list = []
|
properties_list = []
|
||||||
with open(file_path, "r", encoding="utf-8") as file:
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
for line_number, line in enumerate(file, start=1):
|
for line_number, line in enumerate(file, start=1):
|
||||||
stripped_line = line.strip()
|
stripped_line = line.strip()
|
||||||
|
|
||||||
# Empty lines
|
# Handle empty lines
|
||||||
if not stripped_line:
|
if not stripped_line:
|
||||||
properties_list.append(
|
properties_list.append(
|
||||||
{"line_number": line_number, "type": "empty", "content": ""}
|
{"line_number": line_number, "type": "empty", "content": ""}
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Comments
|
# Handle comments
|
||||||
if stripped_line.startswith("#"):
|
if stripped_line.startswith("#"):
|
||||||
properties_list.append(
|
properties_list.append(
|
||||||
{
|
{
|
||||||
@@ -50,7 +85,7 @@ def parse_properties_file(file_path):
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Key-value pairs
|
# Handle key-value pairs
|
||||||
match = re.match(r"^([^=]+)=(.*)$", line)
|
match = re.match(r"^([^=]+)=(.*)$", line)
|
||||||
if match:
|
if match:
|
||||||
key, value = match.groups()
|
key, value = match.groups()
|
||||||
@@ -67,9 +102,14 @@ def parse_properties_file(file_path):
|
|||||||
|
|
||||||
|
|
||||||
def write_json_file(file_path, updated_properties):
|
def write_json_file(file_path, updated_properties):
|
||||||
|
"""
|
||||||
|
Writes updated properties back to the file in their original format.
|
||||||
|
:param file_path: Path to the .properties file.
|
||||||
|
:param updated_properties: List of updated properties to write.
|
||||||
|
"""
|
||||||
updated_lines = {entry["line_number"]: entry for entry in updated_properties}
|
updated_lines = {entry["line_number"]: entry for entry in updated_properties}
|
||||||
|
|
||||||
# Sort by line numbers and retain comments and empty lines
|
# Sort lines by their numbers and retain comments and empty lines
|
||||||
all_lines = sorted(set(updated_lines.keys()))
|
all_lines = sorted(set(updated_lines.keys()))
|
||||||
|
|
||||||
original_format = []
|
original_format = []
|
||||||
@@ -88,8 +128,8 @@ def write_json_file(file_path, updated_properties):
|
|||||||
# Replace entries with those from the current JSON
|
# Replace entries with those from the current JSON
|
||||||
original_format.append(entry)
|
original_format.append(entry)
|
||||||
|
|
||||||
# Write back in the original format
|
# Write the updated content back to the file
|
||||||
with open(file_path, "w", encoding="utf-8") as file:
|
with open(file_path, "w", encoding="utf-8", newline="\n") as file:
|
||||||
for entry in original_format:
|
for entry in original_format:
|
||||||
if entry["type"] == "comment":
|
if entry["type"] == "comment":
|
||||||
file.write(f"{entry['content']}\n")
|
file.write(f"{entry['content']}\n")
|
||||||
@@ -100,6 +140,12 @@ 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=""):
|
||||||
|
"""
|
||||||
|
Updates missing keys in the translation files based on the reference file.
|
||||||
|
:param reference_file: Path to the reference .properties file.
|
||||||
|
:param file_list: List of translation files to update.
|
||||||
|
:param branch: Branch where the files are located.
|
||||||
|
"""
|
||||||
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(os.path.join(branch, file_path))
|
||||||
@@ -245,6 +291,24 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
report.append("2. **Test Status:** ✅ **_Passed_**")
|
report.append("2. **Test Status:** ✅ **_Passed_**")
|
||||||
|
|
||||||
|
if find_duplicate_keys(os.path.join(branch, file_path)):
|
||||||
|
has_differences = True
|
||||||
|
output = "\n".join(
|
||||||
|
[
|
||||||
|
f" - `{key}`: first at line {first}, duplicate at `line {duplicate}`"
|
||||||
|
for key, first, duplicate in find_duplicate_keys(
|
||||||
|
os.path.join(branch, file_path)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
report.append("3. **Test Status:** ❌ **_Failed_**")
|
||||||
|
report.append(" - **Issue:**")
|
||||||
|
report.append(" - duplicate entries were found:")
|
||||||
|
report.append(output)
|
||||||
|
else:
|
||||||
|
report.append("3. **Test Status:** ✅ **_Passed_**")
|
||||||
|
|
||||||
report.append("")
|
report.append("")
|
||||||
report.append("---")
|
report.append("---")
|
||||||
report.append("")
|
report.append("")
|
||||||
|
|||||||
104
.github/workflows/sync_files.yml
vendored
104
.github/workflows/sync_files.yml
vendored
@@ -13,47 +13,116 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync-readme:
|
read_bot_entries:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
outputs:
|
||||||
contents: write
|
userName: ${{ steps.get-user-id.outputs.user_name }}
|
||||||
pull-requests: write
|
userEmail: ${{ steps.get-user-id.outputs.user_email }}
|
||||||
|
committer: ${{ steps.committer.outputs.committer }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Generate GitHub App Token
|
||||||
|
id: generate-token
|
||||||
|
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Get GitHub App User ID
|
||||||
|
id: get-user-id
|
||||||
|
run: |
|
||||||
|
USER_NAME="${{ steps.generate-token.outputs.app-slug }}[bot]"
|
||||||
|
USER_ID=$(gh api "/users/$USER_NAME" --jq .id)
|
||||||
|
USER_EMAIL="$USER_ID+$USER_NAME@users.noreply.github.com"
|
||||||
|
echo "user_name=$USER_NAME" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "user_email=$USER_EMAIL" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "user-id=$USER_ID" >> "$GITHUB_OUTPUT"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
|
- id: committer
|
||||||
|
run: |
|
||||||
|
COMMITTER="${{ steps.get-user-id.outputs.user_name }} <${{ steps.get-user-id.outputs.user_email }}>"
|
||||||
|
echo "committer=$COMMITTER" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
sync-files:
|
||||||
|
needs: ["read_bot_entries"]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Generate GitHub App Token
|
||||||
|
id: generate-token
|
||||||
|
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||||
|
with:
|
||||||
|
app-id: ${{ vars.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
cache: 'pip' # caching pip dependencies
|
cache: 'pip' # caching pip dependencies
|
||||||
- name: Install dependencies
|
|
||||||
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
|
- name: Sync translation property files
|
||||||
- name: Sync README
|
run: |
|
||||||
run: python scripts/counter_translation.py
|
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: |
|
||||||
git config --global user.name "github-actions[bot]"
|
git config --global user.name ${{ needs.read_bot_entries.outputs.userName }}
|
||||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
git config --global user.email ${{ needs.read_bot_entries.outputs.userEmail }}
|
||||||
|
|
||||||
- name: Run git add
|
- name: Run git add
|
||||||
run: |
|
run: |
|
||||||
git add .
|
git add .
|
||||||
git diff --staged --quiet || git commit -m ":memo: Sync README
|
git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "no changes"
|
||||||
> Made via sync_files.yml" || echo "no changes"
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
|
||||||
|
|
||||||
|
- name: Sync README
|
||||||
|
run: |
|
||||||
|
python scripts/counter_translation.py
|
||||||
|
|
||||||
|
- name: Run git add
|
||||||
|
run: |
|
||||||
|
git add .
|
||||||
|
git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "no changes"
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
commit-message: Update files
|
commit-message: Update files
|
||||||
committer: GitHub Action <action@github.com>
|
committer: ${{ needs.read_bot_entries.outputs.committer }}
|
||||||
author: GitHub Action <action@github.com>
|
author: ${{ needs.read_bot_entries.outputs.committer }}
|
||||||
signoff: true
|
signoff: true
|
||||||
branch: sync_readme
|
branch: sync_readme
|
||||||
title: ":memo: Update README: Translation Progress Table"
|
title: ":memo: Sync translation files + Update README.md (Translation Progress Table)"
|
||||||
body: |
|
body: |
|
||||||
|
#### Description
|
||||||
|
|
||||||
|
This Pull Request was automatically generated to synchronize updates to translation files and documentation. The changes include:
|
||||||
|
|
||||||
|
1. **Synchronization of Translation Files:**
|
||||||
|
- Updated content based on the latest changes in `messages_en_GB.properties`.
|
||||||
|
- Ensured consistency between all language files and the reference file.
|
||||||
|
|
||||||
|
2. **Update README.md:**
|
||||||
|
- Generated the translation progress table.
|
||||||
|
- Displayed the current status of translations for all supported languages.
|
||||||
|
|
||||||
|
---
|
||||||
Auto-generated by [create-pull-request][1]
|
Auto-generated by [create-pull-request][1]
|
||||||
|
|
||||||
[1]: https://github.com/peter-evans/create-pull-request
|
[1]: https://github.com/peter-evans/create-pull-request
|
||||||
@@ -61,3 +130,6 @@ jobs:
|
|||||||
delete-branch: true
|
delete-branch: true
|
||||||
labels: Documentation,Translation,github-actions
|
labels: Documentation,Translation,github-actions
|
||||||
sign-commits: true
|
sign-commits: true
|
||||||
|
add-paths: |
|
||||||
|
README.md
|
||||||
|
src/main/resources/messages_*.properties
|
||||||
|
|||||||
72
.github/workflows/update-translations.yml
vendored
72
.github/workflows/update-translations.yml
vendored
@@ -1,72 +0,0 @@
|
|||||||
name: Update Translations
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches: ["main"]
|
|
||||||
paths:
|
|
||||||
- "src/main/resources/messages_en_GB.properties"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-translations-main:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Harden Runner
|
|
||||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
|
||||||
with:
|
|
||||||
python-version: "3.12"
|
|
||||||
|
|
||||||
- name: Run Python script to check files
|
|
||||||
id: run-check
|
|
||||||
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
|
|
||||||
|
|
||||||
- name: Set up git config
|
|
||||||
run: |
|
|
||||||
git config --global user.name "github-actions[bot]"
|
|
||||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
|
|
||||||
- name: Add translation keys
|
|
||||||
run: |
|
|
||||||
git add src/main/resources/messages_*.properties
|
|
||||||
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create Pull Request
|
|
||||||
id: cpr
|
|
||||||
if: env.CHANGES_DETECTED == 'true'
|
|
||||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
commit-message: "Update translation files"
|
|
||||||
committer: GitHub Action <action@github.com>
|
|
||||||
author: GitHub Action <action@github.com>
|
|
||||||
signoff: true
|
|
||||||
branch: update_translation_files
|
|
||||||
title: "Update translation files"
|
|
||||||
add-paths: |
|
|
||||||
src/main/resources/messages_*.properties
|
|
||||||
body: |
|
|
||||||
Auto-generated by [create-pull-request][1]
|
|
||||||
|
|
||||||
[1]: https://github.com/peter-evans/create-pull-request
|
|
||||||
draft: false
|
|
||||||
delete-branch: true
|
|
||||||
labels: Translation,github-actions
|
|
||||||
sign-commits: true
|
|
||||||
36
README.md
36
README.md
@@ -117,42 +117,42 @@ Stirling-PDF currently supports 39 languages!
|
|||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| -------------------------------------------- | -------------------------------------- |
|
| -------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Persian (فارسی) (fa_IR) |  |
|
| Persian (فارسی) (fa_IR) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Slovenian (Slovenščina) (sl_SI) |  |
|
| Slovenian (Slovenščina) (sl_SI) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
|
|||||||
70
build.gradle
70
build.gradle
@@ -7,8 +7,8 @@ plugins {
|
|||||||
id "edu.sc.seis.launch4j" version "3.0.6"
|
id "edu.sc.seis.launch4j" version "3.0.6"
|
||||||
id "com.diffplug.spotless" version "7.0.2"
|
id "com.diffplug.spotless" version "7.0.2"
|
||||||
id "com.github.jk1.dependency-license-report" version "2.9"
|
id "com.github.jk1.dependency-license-report" version "2.9"
|
||||||
//id "nebula.lint" version "19.0.3"
|
//id "nebula.lint" version "19.0.3"
|
||||||
id("org.panteleyev.jpackageplugin") version "1.6.0"
|
id("org.panteleyev.jpackageplugin") version "1.6.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
import com.github.jk1.license.render.*
|
import com.github.jk1.license.render.*
|
||||||
@@ -25,7 +25,7 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.37.1"
|
version = "0.38.0"
|
||||||
|
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@@ -35,9 +35,9 @@ java {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url = "https://jitpack.io" }
|
||||||
maven { url "https://build.shibboleth.net/maven/releases" }
|
maven { url = "https://build.shibboleth.net/maven/releases" }
|
||||||
maven { url "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
maven { url = "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
|
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
|
||||||
exclude "stirling/software/SPDF/UI/impl/**"
|
exclude "stirling/software/SPDF/UI/impl/**"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -138,10 +138,10 @@ jpackage {
|
|||||||
|
|
||||||
// Windows-specific configuration
|
// Windows-specific configuration
|
||||||
windows {
|
windows {
|
||||||
launcherAsService = false
|
launcherAsService = false
|
||||||
appVersion = project.version
|
appVersion = project.version
|
||||||
|
|
||||||
winConsole = false
|
winConsole = false
|
||||||
winMenu = true // Creates start menu entry
|
winMenu = true // Creates start menu entry
|
||||||
winShortcut = true // Creates desktop shortcut
|
winShortcut = true // Creates desktop shortcut
|
||||||
winShortcutPrompt = true // Lets user choose whether to create shortcuts
|
winShortcutPrompt = true // Lets user choose whether to create shortcuts
|
||||||
@@ -157,7 +157,7 @@ jpackage {
|
|||||||
|
|
||||||
// macOS-specific configuration
|
// macOS-specific configuration
|
||||||
mac {
|
mac {
|
||||||
appVersion = getMacVersion(project.version.toString())
|
appVersion = getMacVersion(project.version.toString())
|
||||||
icon = "src/main/resources/static/favicon.icns"
|
icon = "src/main/resources/static/favicon.icns"
|
||||||
type = "dmg"
|
type = "dmg"
|
||||||
macPackageIdentifier = "com.stirling.software.pdf"
|
macPackageIdentifier = "com.stirling.software.pdf"
|
||||||
@@ -181,7 +181,7 @@ jpackage {
|
|||||||
|
|
||||||
// Linux-specific configuration
|
// Linux-specific configuration
|
||||||
linux {
|
linux {
|
||||||
appVersion = project.version
|
appVersion = project.version
|
||||||
icon = "src/main/resources/static/favicon.png"
|
icon = "src/main/resources/static/favicon.png"
|
||||||
type = "deb" // Can also use "rpm" for Red Hat-based systems
|
type = "deb" // Can also use "rpm" for Red Hat-based systems
|
||||||
|
|
||||||
@@ -229,9 +229,9 @@ launch4j {
|
|||||||
outfile="Stirling-PDF.exe"
|
outfile="Stirling-PDF.exe"
|
||||||
|
|
||||||
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
||||||
headerType = "gui"
|
headerType = "gui"
|
||||||
} else {
|
} else {
|
||||||
headerType = "console"
|
headerType = "console"
|
||||||
}
|
}
|
||||||
jarTask = tasks.bootJar
|
jarTask = tasks.bootJar
|
||||||
|
|
||||||
@@ -239,13 +239,11 @@ launch4j {
|
|||||||
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
||||||
|
|
||||||
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
||||||
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
|
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
|
||||||
} else {
|
} else {
|
||||||
variables=["BROWSER_OPEN=true"]
|
variables=["BROWSER_OPEN=true"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
jreMinVersion="17"
|
jreMinVersion="17"
|
||||||
|
|
||||||
mutexName="Stirling-PDF"
|
mutexName="Stirling-PDF"
|
||||||
@@ -286,10 +284,10 @@ configurations.all {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
|
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
|
||||||
implementation "me.friwi:jcefmaven:127.3.1"
|
implementation "me.friwi:jcefmaven:127.3.1"
|
||||||
implementation "org.openjfx:javafx-controls:21"
|
implementation "org.openjfx:javafx-controls:21"
|
||||||
implementation "org.openjfx:javafx-swing:21"
|
implementation "org.openjfx:javafx-swing:21"
|
||||||
}
|
}
|
||||||
|
|
||||||
//security updates
|
//security updates
|
||||||
@@ -315,10 +313,10 @@ 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.session:spring-session-core:$springBootVersion"
|
implementation "org.springframework.session:spring-session-core:$springBootVersion"
|
||||||
implementation "org.springframework:spring-jdbc:6.2.1"
|
implementation "org.springframework:spring-jdbc:6.2.1"
|
||||||
|
|
||||||
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
|
// Don't upgrade h2database
|
||||||
runtimeOnly "com.h2database:h2:2.3.232"
|
runtimeOnly "com.h2database:h2:2.3.232"
|
||||||
runtimeOnly "org.postgresql:postgresql:42.7.4"
|
runtimeOnly "org.postgresql:postgresql:42.7.4"
|
||||||
@@ -328,7 +326,7 @@ dependencies {
|
|||||||
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
|
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
|
||||||
}
|
}
|
||||||
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
|
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
|
||||||
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
|
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
|
||||||
implementation 'com.coveo:saml-client:5.0.0'
|
implementation 'com.coveo:saml-client:5.0.0'
|
||||||
|
|
||||||
|
|
||||||
@@ -397,9 +395,9 @@ dependencies {
|
|||||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
||||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
||||||
implementation "com.fathzer:javaluator:3.0.5"
|
implementation "com.fathzer:javaluator:3.0.5"
|
||||||
|
|
||||||
implementation 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
|
implementation 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
|
||||||
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
||||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||||
@@ -423,13 +421,13 @@ task writeVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
swaggerhubUpload {
|
swaggerhubUpload {
|
||||||
//dependsOn generateOpenApiDocs // Depends on your task generating Swagger docs
|
// dependsOn = generateOpenApiDocs // Depends on your task generating Swagger docs
|
||||||
api "Stirling-PDF" // The name of your API on SwaggerHub
|
api = "Stirling-PDF" // The name of your API on SwaggerHub
|
||||||
owner "Frooodle" // Your SwaggerHub username (or organization name)
|
owner = "Frooodle" // Your SwaggerHub username (or organization name)
|
||||||
version project.version // The version of your API
|
version = project.version // The version of your API
|
||||||
inputFile "./SwaggerDoc.json" // The path to your Swagger docs
|
inputFile = "./SwaggerDoc.json" // The path to your Swagger docs
|
||||||
token "${System.getenv("SWAGGERHUB_API_KEY")}" // Your SwaggerHub API key, passed as an environment variable
|
token = "${System.getenv("SWAGGERHUB_API_KEY")}" // Your SwaggerHub API key, passed as an environment variable
|
||||||
oas "3.0.0" // The version of the OpenAPI Specification you"re using
|
oas = "3.0.0" // The version of the OpenAPI Specification you"re using
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@@ -454,4 +452,4 @@ task printMacVersion {
|
|||||||
doLast {
|
doLast {
|
||||||
println getMacVersion(project.version.toString())
|
println getMacVersion(project.version.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -452,9 +452,9 @@ home.MarkdownToPDF.title=Markdown zu PDF
|
|||||||
home.MarkdownToPDF.desc=Konvertiert jede Markdown-Datei zu PDF
|
home.MarkdownToPDF.desc=Konvertiert jede Markdown-Datei zu PDF
|
||||||
MarkdownToPDF.tags=markup,web-content,transformation,konvertieren
|
MarkdownToPDF.tags=markup,web-content,transformation,konvertieren
|
||||||
|
|
||||||
home.PDFToMarkdown.title=PDF to Markdown
|
home.PDFToMarkdown.title=PDF zu Markdown
|
||||||
home.PDFToMarkdown.desc=Converts any PDF to Markdown
|
home.PDFToMarkdown.desc=Konvertiert jedes PDF in Markdown
|
||||||
PDFToMarkdown.tags=markup,web-content,transformation,convert,md
|
PDFToMarkdown.tags=markup,web inhalt,transformation,konvertieren,md
|
||||||
|
|
||||||
home.getPdfInfo.title=Alle Informationen anzeigen
|
home.getPdfInfo.title=Alle Informationen anzeigen
|
||||||
home.getPdfInfo.desc=Erfasst alle möglichen Informationen in einer PDF
|
home.getPdfInfo.desc=Erfasst alle möglichen Informationen in einer PDF
|
||||||
@@ -650,9 +650,9 @@ MarkdownToPDF.credit=Verwendet WeasyPrint
|
|||||||
|
|
||||||
|
|
||||||
#pdf-to-markdown
|
#pdf-to-markdown
|
||||||
PDFToMarkdown.title=PDF To Markdown
|
PDFToMarkdown.title=PDF zu Markdown
|
||||||
PDFToMarkdown.header=PDF To Markdown
|
PDFToMarkdown.header=PDF zu Markdown
|
||||||
PDFToMarkdown.submit=Convert
|
PDFToMarkdown.submit=Konvertieren
|
||||||
|
|
||||||
|
|
||||||
#url-to-pdf
|
#url-to-pdf
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -820,7 +820,7 @@ removeBlanks.whitePercentDesc=Odstotek strani, ki mora imeti 'bele' slikovne pik
|
|||||||
removeBlanks.submit=Odstrani praznine
|
removeBlanks.submit=Odstrani praznine
|
||||||
|
|
||||||
|
|
||||||
#removeAnotations
|
#removeAnnotations
|
||||||
removeAnnotations.title=Odstrani opombe
|
removeAnnotations.title=Odstrani opombe
|
||||||
removeAnnotations.header=Odstrani opombe
|
removeAnnotations.header=Odstrani opombe
|
||||||
removeAnnotations.submit=Odstrani
|
removeAnnotations.submit=Odstrani
|
||||||
@@ -1153,7 +1153,7 @@ removePassword.selectText.2=Geslo
|
|||||||
removePassword.submit=Odstrani
|
removePassword.submit=Odstrani
|
||||||
|
|
||||||
|
|
||||||
#sprememba metapodatkov
|
#changeMetadata
|
||||||
changeMetadata.title=Spremeni metapodatke
|
changeMetadata.title=Spremeni metapodatke
|
||||||
changeMetadata.header=Spremeni metapodatke
|
changeMetadata.header=Spremeni metapodatke
|
||||||
changeMetadata.selectText.1=Prosimo, uredite spremenljivke, ki jih želite spremeniti
|
changeMetadata.selectText.1=Prosimo, uredite spremenljivke, ki jih želite spremeniti
|
||||||
@@ -1197,6 +1197,7 @@ PDFToPresentation.selectText.1=Oblika izhodne datoteke
|
|||||||
PDFToPresentation.credit=Ta storitev uporablja LibreOffice za pretvorbo datotek.
|
PDFToPresentation.credit=Ta storitev uporablja LibreOffice za pretvorbo datotek.
|
||||||
PDFToPresentation.submit=Pretvori
|
PDFToPresentation.submit=Pretvori
|
||||||
|
|
||||||
|
|
||||||
#PDFToText
|
#PDFToText
|
||||||
PDFToText.title=PDF v RTF (Besedilo)
|
PDFToText.title=PDF v RTF (Besedilo)
|
||||||
PDFToText.header=PDF v RTF (Besedilo)
|
PDFToText.header=PDF v RTF (Besedilo)
|
||||||
@@ -1224,7 +1225,6 @@ PDFToCSV.header=PDF v CSV
|
|||||||
PDFToCSV.prompt=Izberite stran za ekstrahiranje tabele
|
PDFToCSV.prompt=Izberite stran za ekstrahiranje tabele
|
||||||
PDFToCSV.submit=Izvleček
|
PDFToCSV.submit=Izvleček
|
||||||
|
|
||||||
|
|
||||||
#split-by-size-or-count
|
#split-by-size-or-count
|
||||||
split-by-size-or-count.title=Razdeli PDF po velikosti ali številu
|
split-by-size-or-count.title=Razdeli PDF po velikosti ali številu
|
||||||
split-by-size-or-count.header=Razdeli PDF po velikosti ali številu
|
split-by-size-or-count.header=Razdeli PDF po velikosti ali številu
|
||||||
@@ -1253,7 +1253,7 @@ overlay-pdfs.position.background=Ozadje
|
|||||||
overlay-pdfs.submit=Pošlji
|
overlay-pdfs.submit=Pošlji
|
||||||
|
|
||||||
|
|
||||||
#razdeljeno po delih
|
#split-by-sections
|
||||||
split-by-sections.title=Razdeli PDF po razdelkih
|
split-by-sections.title=Razdeli PDF po razdelkih
|
||||||
split-by-sections.header=Razdeli PDF na razdelke
|
split-by-sections.header=Razdeli PDF na razdelke
|
||||||
split-by-sections.horizontal.label=Vodoravna delitev
|
split-by-sections.horizontal.label=Vodoravna delitev
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ changeMetadata.tags=標題,作者,日期,建立,時間,出版商,製作人,統
|
|||||||
|
|
||||||
home.fileToPDF.title=檔案轉 PDF
|
home.fileToPDF.title=檔案轉 PDF
|
||||||
home.fileToPDF.desc=將幾乎所有格式轉換為 PDF(DOCX、PNG、XLS、PPT、TXT 等等)
|
home.fileToPDF.desc=將幾乎所有格式轉換為 PDF(DOCX、PNG、XLS、PPT、TXT 等等)
|
||||||
fileToPDF.tags=轉換,格式,文件,圖片,幻燈片,文字,轉換,辦公室,文件,Word,Excel,PowerPoint
|
fileToPDF.tags=轉換,格式,文件,圖片,投影片,文字,轉換,office,docs,Word,Excel,PowerPoint
|
||||||
|
|
||||||
home.ocr.title=OCR / 清理掃描
|
home.ocr.title=OCR / 清理掃描
|
||||||
home.ocr.desc=清理掃描並從 PDF 中的影像中偵測文字並重新新增為文字。
|
home.ocr.desc=清理掃描並從 PDF 中的影像中偵測文字並重新新增為文字。
|
||||||
@@ -344,15 +344,15 @@ extractImages.tags=圖片,照片,儲存,存檔,壓縮檔,捕獲,抓取
|
|||||||
|
|
||||||
home.pdfToPDFA.title=PDF 轉 PDF/A
|
home.pdfToPDFA.title=PDF 轉 PDF/A
|
||||||
home.pdfToPDFA.desc=將 PDF 轉換為長期儲存的 PDF/A
|
home.pdfToPDFA.desc=將 PDF 轉換為長期儲存的 PDF/A
|
||||||
pdfToPDFA.tags=存檔,長期,標準,轉換,儲存,儲存
|
pdfToPDFA.tags=存檔,長期,標準,轉換,儲存,保存
|
||||||
|
|
||||||
home.PDFToWord.title=PDF 轉 Word
|
home.PDFToWord.title=PDF 轉 Word
|
||||||
home.PDFToWord.desc=將 PDF 轉換為 Word 格式(DOC、DOCX 和 ODT)
|
home.PDFToWord.desc=將 PDF 轉換為 Word 格式(DOC、DOCX 和 ODT)
|
||||||
PDFToWord.tags=doc,docx,odt,word,轉換,格式,轉換,辦公室,微軟,docfile
|
PDFToWord.tags=doc,docx,odt,word,轉換,格式,轉檔,office,微軟,docfile
|
||||||
|
|
||||||
home.PDFToPresentation.title=PDF 轉簡報
|
home.PDFToPresentation.title=PDF 轉簡報
|
||||||
home.PDFToPresentation.desc=將 PDF 轉換為簡報格式(PPT、PPTX 和 ODP)
|
home.PDFToPresentation.desc=將 PDF 轉換為簡報格式(PPT、PPTX 和 ODP)
|
||||||
PDFToPresentation.tags=幻燈片,展示,辦公室,微軟
|
PDFToPresentation.tags=投影片,展示,office,微軟
|
||||||
|
|
||||||
home.PDFToText.title=PDF 轉 RTF(文字)
|
home.PDFToText.title=PDF 轉 RTF(文字)
|
||||||
home.PDFToText.desc=將 PDF 轉換為文字或 RTF 格式
|
home.PDFToText.desc=將 PDF 轉換為文字或 RTF 格式
|
||||||
@@ -365,7 +365,7 @@ PDFToHTML.tags=網頁內容,瀏覽器友善
|
|||||||
|
|
||||||
home.PDFToXML.title=PDF 轉 XML
|
home.PDFToXML.title=PDF 轉 XML
|
||||||
home.PDFToXML.desc=將 PDF 轉換為 XML 格式
|
home.PDFToXML.desc=將 PDF 轉換為 XML 格式
|
||||||
PDFToXML.tags=資料提取,結構化內容,互操作,轉換,轉換
|
PDFToXML.tags=資料提取,結構化內容,互操作,轉換,轉檔
|
||||||
|
|
||||||
home.ScannerImageSplit.title=偵測/分割掃描照片
|
home.ScannerImageSplit.title=偵測/分割掃描照片
|
||||||
home.ScannerImageSplit.desc=從照片/PDF 中分割多張照片
|
home.ScannerImageSplit.desc=從照片/PDF 中分割多張照片
|
||||||
@@ -381,7 +381,7 @@ flatten.tags=靜態,停用,非互動,簡化
|
|||||||
|
|
||||||
home.repair.title=修復
|
home.repair.title=修復
|
||||||
home.repair.desc=嘗試修復損壞/破損的 PDF
|
home.repair.desc=嘗試修復損壞/破損的 PDF
|
||||||
repair.tags=修復,恢復,修正,恢復
|
repair.tags=修復,恢復,修正,復原
|
||||||
|
|
||||||
home.removeBlanks.title=移除空白頁面
|
home.removeBlanks.title=移除空白頁面
|
||||||
home.removeBlanks.desc=偵測並從文件中移除空白頁面
|
home.removeBlanks.desc=偵測並從文件中移除空白頁面
|
||||||
@@ -437,7 +437,7 @@ autoSplitPDF.tags=基於 QR Code,分離,掃描區段,組織
|
|||||||
|
|
||||||
home.sanitizePdf.title=清理
|
home.sanitizePdf.title=清理
|
||||||
home.sanitizePdf.desc=從 PDF 檔案中移除指令碼和其他元素
|
home.sanitizePdf.desc=從 PDF 檔案中移除指令碼和其他元素
|
||||||
sanitizePdf.tags=清理,安全,安全,移除威脅
|
sanitizePdf.tags=清理,安全,無害,移除威脅
|
||||||
|
|
||||||
home.URLToPDF.title=網址/網站轉 PDF
|
home.URLToPDF.title=網址/網站轉 PDF
|
||||||
home.URLToPDF.desc=將任何 http(s) 網址轉換為 PDF
|
home.URLToPDF.desc=將任何 http(s) 網址轉換為 PDF
|
||||||
@@ -445,16 +445,16 @@ URLToPDF.tags=網頁捕獲,儲存頁面,網頁轉文件,存檔
|
|||||||
|
|
||||||
home.HTMLToPDF.title=HTML 轉 PDF
|
home.HTMLToPDF.title=HTML 轉 PDF
|
||||||
home.HTMLToPDF.desc=將任何 HTML 檔案或壓縮檔轉換為 PDF
|
home.HTMLToPDF.desc=將任何 HTML 檔案或壓縮檔轉換為 PDF
|
||||||
HTMLToPDF.tags=標記,網頁內容,轉換,轉換
|
HTMLToPDF.tags=標記,網頁內容,轉換,轉檔
|
||||||
|
|
||||||
|
|
||||||
home.MarkdownToPDF.title=Markdown 轉 PDF
|
home.MarkdownToPDF.title=Markdown 轉 PDF
|
||||||
home.MarkdownToPDF.desc=將任何 Markdown 檔案轉換為 PDF
|
home.MarkdownToPDF.desc=將任何 Markdown 檔案轉換為 PDF
|
||||||
MarkdownToPDF.tags=標記,網頁內容,轉換,轉換
|
MarkdownToPDF.tags=標記,網頁內容,轉換,轉檔,md
|
||||||
|
|
||||||
home.PDFToMarkdown.title=PDF to Markdown
|
home.PDFToMarkdown.title=PDF 轉 Markdown
|
||||||
home.PDFToMarkdown.desc=Converts any PDF to Markdown
|
home.PDFToMarkdown.desc=將任何 PDF 轉換為 Markdown 檔案
|
||||||
PDFToMarkdown.tags=markup,web-content,transformation,convert,md
|
PDFToMarkdown.tags=標記語言,網頁內容,轉換,轉檔,md
|
||||||
|
|
||||||
home.getPdfInfo.title=取得 PDF 的所有資訊
|
home.getPdfInfo.title=取得 PDF 的所有資訊
|
||||||
home.getPdfInfo.desc=取得 PDF 的所有可能資訊
|
home.getPdfInfo.desc=取得 PDF 的所有可能資訊
|
||||||
@@ -477,11 +477,11 @@ showJS.tags=JS
|
|||||||
|
|
||||||
home.autoRedact.title=自動塗黑
|
home.autoRedact.title=自動塗黑
|
||||||
home.autoRedact.desc=根據輸入的文字自動塗黑 PDF 中的文字
|
home.autoRedact.desc=根據輸入的文字自動塗黑 PDF 中的文字
|
||||||
autoRedact.tags=塗黑,隱藏,塗黑,黑色,標記,隱藏
|
autoRedact.tags=塗改,隱藏,塗黑,黑色,標記,遮蔽
|
||||||
|
|
||||||
home.redact.title=手動塗黑
|
home.redact.title=手動塗黑
|
||||||
home.redact.desc=依據選取的文字、繪製的形狀和選取的頁面塗黑 PDF
|
home.redact.desc=依據選取的文字、繪製的形狀和選取的頁面塗黑 PDF
|
||||||
redact.tags=塗黑,隱藏,黑色標記,黑色,標記,隱藏,手動
|
redact.tags=塗改,隱藏,塗黑,黑色,標記,遮蔽,手動
|
||||||
|
|
||||||
home.tableExtraxt.title=PDF 轉 CSV
|
home.tableExtraxt.title=PDF 轉 CSV
|
||||||
home.tableExtraxt.desc=從 PDF 中提取表格並將其轉換為 CSV
|
home.tableExtraxt.desc=從 PDF 中提取表格並將其轉換為 CSV
|
||||||
@@ -942,7 +942,7 @@ compress.title=壓縮
|
|||||||
compress.header=壓縮 PDF
|
compress.header=壓縮 PDF
|
||||||
compress.credit=此服務使用 qpdf 進行 PDF 壓縮/最佳化。
|
compress.credit=此服務使用 qpdf 進行 PDF 壓縮/最佳化。
|
||||||
compress.selectText.1=手動模式 - 從 1 到 5
|
compress.selectText.1=手動模式 - 從 1 到 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=在最佳化等級 6 到 9 時,除了一般 PDF 壓縮外,圖片解析度也會降低以進一步減少檔案大小。較高的壓縮等級會進行更高強度的圖片壓縮(最多可壓縮到原始大小的 50%),以達到更高的壓縮率,但可能會影響圖片品質。
|
||||||
compress.selectText.2=最佳化等級:
|
compress.selectText.2=最佳化等級:
|
||||||
compress.selectText.3=4(對於含有文字的影像來說結果很糟)
|
compress.selectText.3=4(對於含有文字的影像來說結果很糟)
|
||||||
compress.selectText.4=自動模式 - 自動調整品質使 PDF 達到指定的檔案大小
|
compress.selectText.4=自動模式 - 自動調整品質使 PDF 達到指定的檔案大小
|
||||||
|
|||||||
@@ -376,6 +376,132 @@
|
|||||||
"moduleLicense": "UnboundID SCIM2 SDK Free Use License",
|
"moduleLicense": "UnboundID SCIM2 SDK Free Use License",
|
||||||
"moduleLicenseUrl": "https://github.com/pingidentity/scim2"
|
"moduleLicenseUrl": "https://github.com/pingidentity/scim2"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-ext-emoji",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-ext-gfm-strikethrough",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-ext-ins",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-ext-superscript",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-ext-tables",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-ext-wikilink",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-html2md-converter",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-jira-converter",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-ast",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-builder",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-collection",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-data",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-dependency",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-format",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-html",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-misc",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-options",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-sequence",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.vladsch.flexmark:flexmark-util-visitor",
|
||||||
|
"moduleVersion": "0.64.8",
|
||||||
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
|
"moduleLicenseUrl": "http://opensource.org/licenses/BSD-2-Clause"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.zaxxer:HikariCP",
|
"moduleName": "com.zaxxer:HikariCP",
|
||||||
"moduleUrl": "https://github.com/brettwooldridge/HikariCP",
|
"moduleUrl": "https://github.com/brettwooldridge/HikariCP",
|
||||||
@@ -1108,6 +1234,20 @@
|
|||||||
"moduleLicense": "Apache License 2.0",
|
"moduleLicense": "Apache License 2.0",
|
||||||
"moduleLicenseUrl": "https://repository.jboss.org/licenses/apache-2.0.txt"
|
"moduleLicenseUrl": "https://repository.jboss.org/licenses/apache-2.0.txt"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.jetbrains:annotations",
|
||||||
|
"moduleUrl": "https://github.com/JetBrains/java-annotations",
|
||||||
|
"moduleVersion": "24.0.1",
|
||||||
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.jsoup:jsoup",
|
||||||
|
"moduleUrl": "https://jsoup.org/",
|
||||||
|
"moduleVersion": "1.15.4",
|
||||||
|
"moduleLicense": "The MIT License",
|
||||||
|
"moduleLicenseUrl": "https://jsoup.org/license"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "org.latencyutils:LatencyUtils",
|
"moduleName": "org.latencyutils:LatencyUtils",
|
||||||
"moduleUrl": "http://latencyutils.github.io/LatencyUtils/",
|
"moduleUrl": "http://latencyutils.github.io/LatencyUtils/",
|
||||||
|
|||||||
@@ -126,6 +126,10 @@ html {
|
|||||||
border-color: rgba(6, 114, 197, 0.82) !important;
|
border-color: rgba(6, 114, 197, 0.82) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pdfToImageBtn:hover .btn-tooltip {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
#redactionsPaletteContainer {
|
#redactionsPaletteContainer {
|
||||||
height: var(--toolButton-height);
|
height: var(--toolButton-height);
|
||||||
width: var(--toolButton-width);
|
width: var(--toolButton-width);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ select#font-select option {
|
|||||||
margin-left: -2.2rem;
|
margin-left: -2.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.draggable-buttons-box>button {
|
.draggable-buttons-box > button {
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
background-color: rgba(13, 110, 253, 0.1);
|
background-color: rgba(13, 110, 253, 0.1);
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
@@ -40,7 +40,6 @@ select#font-select option {
|
|||||||
max-width: 4rem;
|
max-width: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.rotation-handle {
|
.rotation-handle {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@@ -163,3 +162,76 @@ select#font-select option {
|
|||||||
.small-file-container-saved:hover .drag-icon {
|
.small-file-container-saved:hover .drag-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The container must be positioned relative: */
|
||||||
|
.custom-select {
|
||||||
|
position: relative;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-select select {
|
||||||
|
display: none; /*hide original SELECT element: */
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-selected {
|
||||||
|
background-color: inherit;
|
||||||
|
line-height: 30px;
|
||||||
|
font-size: 30px;
|
||||||
|
border-radius: 3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the arrow inside the select element: */
|
||||||
|
.select-selected:after {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: 50%;
|
||||||
|
right: 10px;
|
||||||
|
translate: 0 -50%;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border: 6px solid transparent;
|
||||||
|
border-color: #fff transparent transparent transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Point the arrow upwards when the select box is open (active): */
|
||||||
|
.select-selected.select-arrow-active:after {
|
||||||
|
border-color: transparent transparent #fff transparent;
|
||||||
|
translate: 0 -75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* style the items (options), including the selected item: */
|
||||||
|
.select-items div,
|
||||||
|
.select-selected {
|
||||||
|
color: inherit;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-items div {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-color: transparent transparent transparent transparent;
|
||||||
|
|
||||||
|
line-height: 30px;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style items (options): */
|
||||||
|
.select-items {
|
||||||
|
position: absolute;
|
||||||
|
background-color: inherit;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 101;
|
||||||
|
border: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the items when the select box is closed: */
|
||||||
|
.select-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-items div:hover,
|
||||||
|
.same-as-selected {
|
||||||
|
background-color: rgba(54, 54, 54, 0.1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,21 +8,21 @@ window.goToFirstOrLastPage = goToFirstOrLastPage;
|
|||||||
let currentPreviewSrc = null;
|
let currentPreviewSrc = null;
|
||||||
|
|
||||||
function toggleSignatureView() {
|
function toggleSignatureView() {
|
||||||
const gridView = document.getElementById('gridView');
|
const gridView = document.getElementById("gridView");
|
||||||
const listView = document.getElementById('listView');
|
const listView = document.getElementById("listView");
|
||||||
const gridText = document.querySelector('.grid-view-text');
|
const gridText = document.querySelector(".grid-view-text");
|
||||||
const listText = document.querySelector('.list-view-text');
|
const listText = document.querySelector(".list-view-text");
|
||||||
|
|
||||||
if (gridView.style.display !== 'none') {
|
if (gridView.style.display !== "none") {
|
||||||
gridView.style.display = 'none';
|
gridView.style.display = "none";
|
||||||
listView.style.display = 'block';
|
listView.style.display = "block";
|
||||||
gridText.style.display = 'none';
|
gridText.style.display = "none";
|
||||||
listText.style.display = 'inline';
|
listText.style.display = "inline";
|
||||||
} else {
|
} else {
|
||||||
gridView.style.display = 'block';
|
gridView.style.display = "block";
|
||||||
listView.style.display = 'none';
|
listView.style.display = "none";
|
||||||
gridText.style.display = 'inline';
|
gridText.style.display = "inline";
|
||||||
listText.style.display = 'none';
|
listText.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,63 +30,204 @@ function previewSignature(element) {
|
|||||||
const src = element.dataset.src;
|
const src = element.dataset.src;
|
||||||
currentPreviewSrc = src;
|
currentPreviewSrc = src;
|
||||||
|
|
||||||
const filename = element.querySelector('.signature-list-name').textContent;
|
const filename = element.querySelector(".signature-list-name").textContent;
|
||||||
|
|
||||||
const previewImage = document.getElementById('previewImage');
|
const previewImage = document.getElementById("previewImage");
|
||||||
const previewFileName = document.getElementById('previewFileName');
|
const previewFileName = document.getElementById("previewFileName");
|
||||||
|
|
||||||
previewImage.src = src;
|
previewImage.src = src;
|
||||||
previewFileName.textContent = filename;
|
previewFileName.textContent = filename;
|
||||||
|
|
||||||
const modal = new bootstrap.Modal(document.getElementById('signaturePreview'));
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById("signaturePreview")
|
||||||
|
);
|
||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSignatureFromPreview() {
|
function addSignatureFromPreview() {
|
||||||
if (currentPreviewSrc) {
|
if (currentPreviewSrc) {
|
||||||
DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc);
|
DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc);
|
||||||
bootstrap.Modal.getInstance(document.getElementById('signaturePreview')).hide();
|
bootstrap.Modal.getInstance(
|
||||||
|
document.getElementById("signaturePreview")
|
||||||
|
).hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let originalFileName = '';
|
let originalFileName = "";
|
||||||
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
|
document
|
||||||
const fileInput = event.target;
|
.querySelector("input[name=pdf-upload]")
|
||||||
fileInput.addEventListener('file-input-change', async (e) => {
|
.addEventListener("change", async (event) => {
|
||||||
const {allFiles} = e.detail;
|
const fileInput = event.target;
|
||||||
if (allFiles && allFiles.length > 0) {
|
fileInput.addEventListener("file-input-change", async (e) => {
|
||||||
const file = allFiles[0];
|
const { allFiles } = e.detail;
|
||||||
originalFileName = file.name.replace(/\.[^/.]+$/, '');
|
if (allFiles && allFiles.length > 0) {
|
||||||
const pdfData = await file.arrayBuffer();
|
const file = allFiles[0];
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
|
originalFileName = file.name.replace(/\.[^/.]+$/, "");
|
||||||
const pdfDoc = await pdfjsLib.getDocument({data: pdfData}).promise;
|
const pdfData = await file.arrayBuffer();
|
||||||
await DraggableUtils.renderPage(pdfDoc, 0);
|
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) => {
|
document.querySelectorAll(".show-on-file-selected").forEach((el) => {
|
||||||
el.style.cssText = '';
|
el.style.cssText = "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
document.querySelectorAll('.show-on-file-selected').forEach((el) => {
|
|
||||||
el.style.cssText = 'display:none !important';
|
|
||||||
});
|
|
||||||
document.querySelectorAll('.small-file-container-saved img ').forEach((img) => {
|
|
||||||
img.addEventListener('dragstart', (e) => {
|
|
||||||
e.dataTransfer.setData('fileUrl', img.src);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Delete') {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
document.querySelectorAll(".show-on-file-selected").forEach((el) => {
|
||||||
|
el.style.cssText = "display:none !important";
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.querySelectorAll(".small-file-container-saved img ")
|
||||||
|
.forEach((img) => {
|
||||||
|
img.addEventListener("dragstart", (e) => {
|
||||||
|
e.dataTransfer.setData("fileUrl", img.src);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Delete") {
|
||||||
DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted());
|
DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addCustomSelect();
|
||||||
});
|
});
|
||||||
|
|
||||||
const imageUpload = document.querySelector('input[name=image-upload]');
|
function addCustomSelect() {
|
||||||
imageUpload.addEventListener('change', (e) => {
|
let customSelectElementContainer =
|
||||||
|
document.getElementById("signFontSelection");
|
||||||
|
let originalSelectElement =
|
||||||
|
customSelectElementContainer.querySelector("select");
|
||||||
|
|
||||||
|
let optionsCount = originalSelectElement.length;
|
||||||
|
|
||||||
|
let selectedItem = createAndStyleSelectedItem();
|
||||||
|
|
||||||
|
customSelectElementContainer.appendChild(selectedItem);
|
||||||
|
|
||||||
|
let customSelectionsOptionsContainer = createCustomOptionsContainer();
|
||||||
|
createAndAddCustomOptions();
|
||||||
|
customSelectElementContainer.appendChild(customSelectionsOptionsContainer);
|
||||||
|
|
||||||
|
selectedItem.addEventListener("click", function (e) {
|
||||||
|
/* When the select box is clicked, close any other select boxes,
|
||||||
|
and open/close the current select box: */
|
||||||
|
e.stopPropagation();
|
||||||
|
closeAllSelect(this);
|
||||||
|
this.nextSibling.classList.toggle("select-hide");
|
||||||
|
this.classList.toggle("select-arrow-active");
|
||||||
|
});
|
||||||
|
|
||||||
|
function createAndAddCustomOptions() {
|
||||||
|
for (let j = 0; j < optionsCount; j++) {
|
||||||
|
/* For each option in the original select element,
|
||||||
|
create a new DIV that will act as an option item: */
|
||||||
|
let customOptionItem = createAndStyleCustomOption(j);
|
||||||
|
|
||||||
|
customOptionItem.addEventListener("click", onCustomOptionClick);
|
||||||
|
customSelectionsOptionsContainer.appendChild(customOptionItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCustomOptionsContainer() {
|
||||||
|
let customSelectionsOptionsContainer = document.createElement("DIV");
|
||||||
|
customSelectionsOptionsContainer.setAttribute(
|
||||||
|
"class",
|
||||||
|
"select-items select-hide"
|
||||||
|
);
|
||||||
|
return customSelectionsOptionsContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAndStyleSelectedItem() {
|
||||||
|
let selectedItem = document.createElement("DIV");
|
||||||
|
selectedItem.setAttribute("class", "select-selected");
|
||||||
|
selectedItem.innerHTML =
|
||||||
|
originalSelectElement.options[
|
||||||
|
originalSelectElement.selectedIndex
|
||||||
|
].innerHTML;
|
||||||
|
|
||||||
|
selectedItem.style.fontFamily = window.getComputedStyle(
|
||||||
|
originalSelectElement.options[originalSelectElement.selectedIndex]
|
||||||
|
).fontFamily;
|
||||||
|
return selectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCustomOptionClick(e) {
|
||||||
|
/* When an item is clicked, update the original select box,
|
||||||
|
and the selected item: */
|
||||||
|
let selectElement =
|
||||||
|
this.parentNode.parentNode.getElementsByTagName("select")[0];
|
||||||
|
let optionsCount = selectElement.length;
|
||||||
|
let currentlySelectedCustomOption = this.parentNode.previousSibling;
|
||||||
|
for (let i = 0; i < optionsCount; i++) {
|
||||||
|
if (selectElement.options[i].innerHTML == this.innerHTML) {
|
||||||
|
selectElement.selectedIndex = i;
|
||||||
|
currentlySelectedCustomOption.innerHTML = this.innerHTML;
|
||||||
|
currentlySelectedCustomOption.style.fontFamily = this.style.fontFamily;
|
||||||
|
|
||||||
|
let previouslySelectedOption =
|
||||||
|
this.parentNode.getElementsByClassName("same-as-selected");
|
||||||
|
|
||||||
|
if (previouslySelectedOption && previouslySelectedOption.length > 0)
|
||||||
|
previouslySelectedOption[0].classList.remove("same-as-selected");
|
||||||
|
|
||||||
|
this.classList.add("same-as-selected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentlySelectedCustomOption.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAndStyleCustomOption(j) {
|
||||||
|
let customOptionItem = document.createElement("DIV");
|
||||||
|
customOptionItem.innerHTML = originalSelectElement.options[j].innerHTML;
|
||||||
|
customOptionItem.classList.add(originalSelectElement.options[j].className);
|
||||||
|
customOptionItem.style.fontFamily = window.getComputedStyle(
|
||||||
|
originalSelectElement.options[j]
|
||||||
|
).fontFamily;
|
||||||
|
|
||||||
|
if (j == originalSelectElement.selectedIndex)
|
||||||
|
customOptionItem.classList.add("same-as-selected");
|
||||||
|
return customOptionItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAllSelect(element) {
|
||||||
|
/* A function that will close all select boxes in the document,
|
||||||
|
except the current select box: */
|
||||||
|
let allSelectedOptions = document.getElementsByClassName("select-selected");
|
||||||
|
let allSelectedOptionsCount = allSelectedOptions.length;
|
||||||
|
let indicesOfContainersToHide = [];
|
||||||
|
for (let i = 0; i < allSelectedOptionsCount; i++) {
|
||||||
|
if (element == allSelectedOptions[i]) {
|
||||||
|
indicesOfContainersToHide.push(i);
|
||||||
|
} else {
|
||||||
|
allSelectedOptions[i].classList.remove("select-arrow-active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideOptionsContainers(indicesOfContainersToHide);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the user clicks anywhere outside the select box,
|
||||||
|
then close all select boxes: */
|
||||||
|
document.addEventListener("click", closeAllSelect);
|
||||||
|
|
||||||
|
function hideOptionsContainers(containersIndices) {
|
||||||
|
let allOptionsContainers = document.getElementsByClassName("select-items");
|
||||||
|
let allSelectionListsContainerCount = allOptionsContainers.length;
|
||||||
|
for (let i = 0; i < allSelectionListsContainerCount; i++) {
|
||||||
|
if (containersIndices.indexOf(i)) {
|
||||||
|
allOptionsContainers[i].classList.add("select-hide");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageUpload = document.querySelector("input[name=image-upload]");
|
||||||
|
imageUpload.addEventListener("change", (e) => {
|
||||||
if (!e.target.files) return;
|
if (!e.target.files) return;
|
||||||
for (const imageFile of e.target.files) {
|
for (const imageFile of e.target.files) {
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
@@ -97,11 +238,11 @@ imageUpload.addEventListener('change', (e) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
|
const signaturePadCanvas = document.getElementById("drawing-pad-canvas");
|
||||||
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
maxWidth: 2,
|
maxWidth: 2,
|
||||||
penColor: 'black',
|
penColor: "black",
|
||||||
});
|
});
|
||||||
|
|
||||||
function addDraggableFromPad() {
|
function addDraggableFromPad() {
|
||||||
@@ -113,7 +254,7 @@ function addDraggableFromPad() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCroppedCanvasDataUrl(canvas) {
|
function getCroppedCanvasDataUrl(canvas) {
|
||||||
let originalCtx = canvas.getContext('2d');
|
let originalCtx = canvas.getContext("2d");
|
||||||
let originalWidth = canvas.width;
|
let originalWidth = canvas.width;
|
||||||
let originalHeight = canvas.height;
|
let originalHeight = canvas.height;
|
||||||
let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
|
let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
|
||||||
@@ -129,7 +270,8 @@ function getCroppedCanvasDataUrl(canvas) {
|
|||||||
for (y = 0; y < originalHeight; y++) {
|
for (y = 0; y < originalHeight; y++) {
|
||||||
for (x = 0; x < originalWidth; x++) {
|
for (x = 0; x < originalWidth; x++) {
|
||||||
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
|
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
|
||||||
let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
|
let currentPixelAlphaValue =
|
||||||
|
imageData.data[currentPixelColorValueIndex + 3];
|
||||||
if (currentPixelAlphaValue > 0) {
|
if (currentPixelAlphaValue > 0) {
|
||||||
if (minX > x) minX = x;
|
if (minX > x) minX = x;
|
||||||
if (maxX < x) maxX = x;
|
if (maxX < x) maxX = x;
|
||||||
@@ -142,10 +284,15 @@ function getCroppedCanvasDataUrl(canvas) {
|
|||||||
let croppedWidth = maxX - minX;
|
let croppedWidth = maxX - minX;
|
||||||
let croppedHeight = maxY - minY;
|
let croppedHeight = maxY - minY;
|
||||||
if (croppedWidth < 0 || croppedHeight < 0) return null;
|
if (croppedWidth < 0 || croppedHeight < 0) return null;
|
||||||
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
|
let cuttedImageData = originalCtx.getImageData(
|
||||||
|
minX,
|
||||||
|
minY,
|
||||||
|
croppedWidth,
|
||||||
|
croppedHeight
|
||||||
|
);
|
||||||
|
|
||||||
let croppedCanvas = document.createElement('canvas'),
|
let croppedCanvas = document.createElement("canvas"),
|
||||||
croppedCtx = croppedCanvas.getContext('2d');
|
croppedCtx = croppedCanvas.getContext("2d");
|
||||||
|
|
||||||
croppedCanvas.width = croppedWidth;
|
croppedCanvas.width = croppedWidth;
|
||||||
croppedCanvas.height = croppedHeight;
|
croppedCanvas.height = croppedHeight;
|
||||||
@@ -158,9 +305,13 @@ function resizeCanvas() {
|
|||||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||||
var additionalFactor = 10;
|
var additionalFactor = 10;
|
||||||
|
|
||||||
signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
|
signaturePadCanvas.width =
|
||||||
signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
|
signaturePadCanvas.offsetWidth * ratio * additionalFactor;
|
||||||
signaturePadCanvas.getContext('2d').scale(ratio * additionalFactor, ratio * additionalFactor);
|
signaturePadCanvas.height =
|
||||||
|
signaturePadCanvas.offsetHeight * ratio * additionalFactor;
|
||||||
|
signaturePadCanvas
|
||||||
|
.getContext("2d")
|
||||||
|
.scale(ratio * additionalFactor, ratio * additionalFactor);
|
||||||
|
|
||||||
signaturePad.clear();
|
signaturePad.clear();
|
||||||
}
|
}
|
||||||
@@ -174,12 +325,12 @@ new IntersectionObserver((entries, observer) => {
|
|||||||
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
|
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
|
||||||
|
|
||||||
function addDraggableFromText() {
|
function addDraggableFromText() {
|
||||||
const sigText = document.getElementById('sigText').value;
|
const sigText = document.getElementById("sigText").value;
|
||||||
const font = document.querySelector('select[name=font]').value;
|
const font = document.querySelector("select[name=font]").value;
|
||||||
const fontSize = 100;
|
const fontSize = 100;
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement("canvas");
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext("2d");
|
||||||
ctx.font = `${fontSize}px ${font}`;
|
ctx.font = `${fontSize}px ${font}`;
|
||||||
const textWidth = ctx.measureText(sigText).width;
|
const textWidth = ctx.measureText(sigText).width;
|
||||||
const textHeight = fontSize;
|
const textHeight = fontSize;
|
||||||
@@ -190,7 +341,7 @@ function addDraggableFromText() {
|
|||||||
canvas.height = paragraphs.length * textHeight * 1.35; // for tails
|
canvas.height = paragraphs.length * textHeight * 1.35; // for tails
|
||||||
ctx.font = `${fontSize}px ${font}`;
|
ctx.font = `${fontSize}px ${font}`;
|
||||||
|
|
||||||
ctx.textBaseline = 'top';
|
ctx.textBaseline = "top";
|
||||||
|
|
||||||
let y = 0;
|
let y = 0;
|
||||||
|
|
||||||
@@ -212,8 +363,8 @@ async function goToFirstOrLastPage(page) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('download-pdf').addEventListener('click', async () => {
|
document.getElementById("download-pdf").addEventListener("click", async () => {
|
||||||
const downloadButton = document.getElementById('download-pdf');
|
const downloadButton = document.getElementById("download-pdf");
|
||||||
const originalContent = downloadButton.innerHTML;
|
const originalContent = downloadButton.innerHTML;
|
||||||
|
|
||||||
downloadButton.disabled = true;
|
downloadButton.disabled = true;
|
||||||
@@ -224,13 +375,13 @@ document.getElementById('download-pdf').addEventListener('click', async () => {
|
|||||||
try {
|
try {
|
||||||
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
|
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
|
||||||
const modifiedPdfBytes = await modifiedPdf.save();
|
const modifiedPdfBytes = await modifiedPdf.save();
|
||||||
const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
|
const blob = new Blob([modifiedPdfBytes], { type: "application/pdf" });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement("a");
|
||||||
link.href = URL.createObjectURL(blob);
|
link.href = URL.createObjectURL(blob);
|
||||||
link.download = originalFileName + '_signed.pdf';
|
link.download = originalFileName + "_signed.pdf";
|
||||||
link.click();
|
link.click();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error downloading PDF:', error);
|
console.error("Error downloading PDF:", error);
|
||||||
} finally {
|
} finally {
|
||||||
downloadButton.disabled = false;
|
downloadButton.disabled = false;
|
||||||
downloadButton.innerHTML = originalContent;
|
downloadButton.innerHTML = originalContent;
|
||||||
|
|||||||
@@ -4919,6 +4919,26 @@ dialog :link {
|
|||||||
left: 3px;
|
left: 3px;
|
||||||
top: var(--toolbar-height);
|
top: var(--toolbar-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toolbarViewerRight > div.splitToolbarButton .btn-tooltip {
|
||||||
|
bottom: unset !important;
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#man-shape-redact {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#man-shape-redact .btn-tooltip {
|
||||||
|
bottom: 100%;
|
||||||
|
left: -5px;
|
||||||
|
white-space: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#redactionsPaletteContainer .btn-tooltip {
|
||||||
|
white-space: normal !important;
|
||||||
|
hyphens: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 885px) {
|
@media (max-width: 885px) {
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
#font-select option[value="[[${font.name}]]"] {
|
#font-select option[value="[[${font.name}]]"] {
|
||||||
font-family: "[[${font.name}]]",
|
font-family: "[[${font.name}]]",
|
||||||
cursive;
|
cursive
|
||||||
|
!important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</th:block>
|
</th:block>
|
||||||
@@ -133,10 +134,12 @@
|
|||||||
<label class="form-check-label" for="sigText" th:text="#{text}"></label>
|
<label class="form-check-label" for="sigText" th:text="#{text}"></label>
|
||||||
<textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
|
<textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
|
||||||
<label th:text="#{font}"></label>
|
<label th:text="#{font}"></label>
|
||||||
<select class="form-control" name="font" id="font-select">
|
<div id="signFontSelection" class="custom-select form-control">
|
||||||
<option th:each="font : ${fonts}" th:value="${font.name}" th:text="${font.name}"
|
<select class="form-control" name="font" id="font-select">
|
||||||
th:class="${font.name.toLowerCase()+'-font'}"></option>
|
<option th:each="font : ${fonts}" th:value="${font.name}" th:text="${font.name}"
|
||||||
</select>
|
th:class="${font.name.toLowerCase()+'-font'}"></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="margin-auto-parent">
|
<div class="margin-auto-parent">
|
||||||
<button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center"
|
<button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center"
|
||||||
onclick="addDraggableFromText()" th:text="#{sign.add}"></button>
|
onclick="addDraggableFromText()" th:text="#{sign.add}"></button>
|
||||||
|
|||||||
Reference in New Issue
Block a user