Compare commits

..

70 Commits

Author SHA1 Message Date
github-actions[bot]
ee4b7e02ab 📝 Update README: Translation Progress Table (#2327)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-26 08:17:56 +00:00
albanobattistella
e6c5634165 Update messages_it_IT.properties (#2334) 2024-11-26 08:17:09 +00:00
Anthony Stirling
5188eb3b04 Update build.gradle 2024-11-26 08:16:45 +00:00
Anthony Stirling
3fa6bcb2ee navbar fix multi tool and compress location (#2331) 2024-11-25 21:42:49 +00:00
github-actions[bot]
0b359ad4a8 Update translation files (#2329)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-25 20:55:56 +00:00
reecebrowne
23ee77f6ab Additional sign tooltips (#2328)
* Add tooltip to sign add to all pages feature

* Additional Tooltips
2024-11-25 20:43:05 +00:00
github-actions[bot]
bfc1ed2b39 Update translation files (#2326)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-25 20:13:03 +00:00
reecebrowne
da46d942ba Add tooltip to sign add to all pages feature (#2325) 2024-11-25 20:11:27 +00:00
Anthony Stirling
5936e856f0 metrics 2024-11-25 14:02:17 +00:00
github-actions[bot]
fd906d36dd Update 3rd Party Licenses (#2321)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-24 17:21:54 +00:00
Ludy
8f4709d82e Bump com.h2database:h2 from 2.1.214 to 2.3.232 (#2314) 2024-11-24 14:36:53 +00:00
dependabot[bot]
8445f2719b Bump org.springframework:spring-webmvc from 6.1.14 to 6.2.0 (#2268)
Bumps [org.springframework:spring-webmvc](https://github.com/spring-projects/spring-framework) from 6.1.14 to 6.2.0.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.14...v6.2.0)

---
updated-dependencies:
- dependency-name: org.springframework:spring-webmvc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-24 14:36:30 +00:00
github-actions[bot]
eaa64e1471 Update 3rd Party Licenses (#2318)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-24 12:16:01 +00:00
Ludy
4abb0cb85e Fix: id for submit button added (#2320) 2024-11-24 10:31:50 +00:00
Omar Ahmed Hassan
afad06bed4 Extract tables from PDF to CSV using Tabula (#2312)
* Add Tabula dependency and exclude slf4j-simple

- Add tabula-java dependency to extract tables into CSV.
- Exclude slf4j-simple due to Logback

* Add a flexible CSVWriter

- Add FlexibleCSVWriter which extends CSVWriter to pass a custom CSVFormat, as CSVWriter's parameterized constructor (that allows changing CSVFormat) is protected.

* Use Tabula in extracting tables from PDF

- Use Tabula in extracting tables from PDF instead of the existing implementation

* Delete PDFTableStripper as It is unneeded

- Delete PDFTableStripper as It is unneeded as Tabula-Java is used instead.

* Use correct class in ExtractCSVController logger

* Exclude gson and bcprov-jdk15on dependencies from tabula

- Exclude gson and bcprov-jdk15on from tabula-java due to detected security vulnerabilities.
2024-11-23 23:28:44 +00:00
github-actions[bot]
faa8a9752c 📝 Update README: Translation Progress Table (#2317)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-23 23:05:40 +00:00
Omar Ahmed Hassan
989538e340 Update Arabic Language for Multi tool section (#2316)
* Update Arabic Language for Multi tool section

* Add a back line at 955-956
2024-11-23 22:36:21 +00:00
Ludy
5b8bdc3352 improves readability (#2313) 2024-11-23 22:09:46 +00:00
Thomas BERNARD
f559eaa4e8 French translation (again) (#2315)
* French translations for multiTool

* fix french translation invalidPasswordMessage/confirmPasswordErrorMessage

* french translation : reset/navbar.search/sign.saved

* fix my invalidPasswordMessage french translation :)
2024-11-23 22:09:27 +00:00
github-actions[bot]
f306e00fba Update 3rd Party Licenses (#2310)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-23 12:16:20 +00:00
github-actions[bot]
e09d6f9998 📝 Update README: Translation Progress Table (#2311)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-23 12:16:16 +00:00
Ludy
3a27aa16d5 Improves security when processing properties files (#2303)
* Improves security when processing properties files

* Check for spaces in the key

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-23 11:49:49 +00:00
Ludy
9a96109ea2 Fix: Prevents duplicate listing of search results (#2306) 2024-11-23 11:37:13 +00:00
albanobattistella
ad1cce378f Update messages_it_IT.properties (#2307)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-23 10:53:26 +00:00
Ludy
9abb105835 Fix: Fixes dependency bug and replaces obsolete method (#2309) 2024-11-23 10:51:17 +00:00
Renan
204bae3bc1 Sign multiple PDF pages at the same time in the same location (#2008) (#2278)
* Sign multiple PDF pages at the same time in the same location (#2008)

* Modifying the functionality of how the signature is added to all pages (#2008)

* Adding the functionality to reverse the addition on all pages and implementing buttons to navigate to the first and last pages (#2008)
2024-11-22 17:40:09 +00:00
reecebrowne
547f23fe78 Posthog multitool (#2301)
* Posthog functionality

* Posthog in multitool

* check if anylitics enabled
2024-11-22 17:38:44 +00:00
Rafael Encinas
543ad083a2 Fix file clear for errors (#2302)
* Prevent file input from being removed when an error occurs

* Fix a bug preventing fetch when 'Bored waiting' btn isn't present
2024-11-22 17:11:23 +00:00
github-actions[bot]
61bccd1d8b 📝 Update README: Translation Progress Table (#2300)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-22 15:27:11 +00:00
github-actions[bot]
83be709299 Update translation files (#2298)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-22 15:17:04 +00:00
reecebrowne
0e602153f3 Feature/2198/multitool multi select move pages (#2294)
* Multitool - Select multiple pages for rotation tool

* Multitool multi select delete feature

* Multitool multi select UI improvements and big fixes

* Multitool multi select select all and UI improvements

* Multi tool multi select, download selected, clean up and bug fixes

* Groundwork for multiselect drag and drop

* Multi select drag and drop finalised

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* Turn off select mode after multidrag

---------

Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-22 11:39:22 +00:00
Anthony Stirling
597619740a Update build.gradle 2024-11-22 09:29:41 +00:00
github-actions[bot]
41a39a0a94 Update 3rd Party Licenses (#2295)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-22 09:22:03 +00:00
dependabot[bot]
b14fba064d Bump org.springframework.security:spring-security-saml2-service-provider from 6.3.4 to 6.4.1 (#2296)
Bump org.springframework.security:spring-security-saml2-service-provider

Bumps [org.springframework.security:spring-security-saml2-service-provider](https://github.com/spring-projects/spring-security) from 6.3.4 to 6.4.1.
- [Release notes](https://github.com/spring-projects/spring-security/releases)
- [Changelog](https://github.com/spring-projects/spring-security/blob/main/RELEASE.adoc)
- [Commits](https://github.com/spring-projects/spring-security/compare/6.3.4...6.4.1)

---
updated-dependencies:
- dependency-name: org.springframework.security:spring-security-saml2-service-provider
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 09:19:58 +00:00
alonsofabila-dev
bd29dd1ac3 Bored waiting button doesnt remove itself after processing (#2079) (#2235)
Fix: Bored waiting button doesnt remove itself after processing (#2079)

hide bored waiting? button after request handling both success and error cases to properly hide the button.

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-22 09:14:26 +00:00
dependabot[bot]
6d3f14375e Bump bouncycastleVersion from 1.78.1 to 1.79 (#2177)
Bumps `bouncycastleVersion` from 1.78.1 to 1.79.

Updates `org.bouncycastle:bcprov-jdk18on` from 1.78.1 to 1.79
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

Updates `org.bouncycastle:bcpkix-jdk18on` from 1.78.1 to 1.79
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcprov-jdk18on
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bouncycastle:bcpkix-jdk18on
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 09:06:30 +00:00
dependabot[bot]
888aec5701 Bump io.micrometer:micrometer-core from 1.13.6 to 1.14.1 (#2253)
Bumps [io.micrometer:micrometer-core](https://github.com/micrometer-metrics/micrometer) from 1.13.6 to 1.14.1.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.13.6...v1.14.1)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 09:06:16 +00:00
dependabot[bot]
92e7e85e77 Bump gradle from 8.7-jdk17 to 8.11-jdk17 (#2269)
* Bump gradle from 8.7-jdk17 to 8.11-jdk17

Bumps gradle from 8.7-jdk17 to 8.11-jdk17.

---
updated-dependencies:
- dependency-name: gradle
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update gradle-wrapper.properties

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-22 09:02:22 +00:00
github-actions[bot]
3bf467e4ff 📝 Update README: Translation Progress Table (#2283)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-22 08:45:22 +00:00
yusif043-bit
dc1887db4d Translation az (#2287)
* Azerbaijani flag and dropdown item added

* Azerbaijani Language file Added

* AZ - ignore_translation.toml init

* AZ Translation Enterprise Edition Section

* Translation for Generic

* translation-az pipeline

* Translation for Analytics

* Translation for NAVBAR

* Translation for SETTINGS

* translation-az homepage

* Translation for #login

* Translation for (showJS)

* Translation for #showJS

* Translation for #PDFToWord

* Translation for #PDFToPresentation

* Translation for #PDFToText

* Translation for #PDFToHTML

* Translation for #PDFToXML

* Translation for #PDFToCSV

* Translation for #repair

* Translation for #pageLayout

* Translation for #pdfToSinglePage

* Translation for #pageExtracter

* Translation for #getPdfInfo

* Translation for #markdown-to-pdf

* Translation for #PDFToXML

* Translation for #html-to-pdf

* Translation for #PDFToHTML

* Translation for #PDFToText

* Translation for #PDFToPresentation

* Translation for #PDFToWord

* Translation for #PDFToCSV

* Translation for #url-to-pdf

* Translation for #pdfToImage

* Translation for #BookToPDF

* Translation for #PDFToBook

* Translation for #autoRedact

* Translation for #Add image

* Translation for #File to PDF

* Translation for (remove-image)

* Translation for (remove-image)

* Translation for (survey)

* Translation for (licenses)

* Translation for (printFile)

* Translation for (split-bysections)

* Translation for (overlay-pdfs)

* Translation for (split-by-size-or-count)

* Translation for (addPageNumbers)

* Translation for (adjustContrast)

* Translation for (autoSplitPDF)

* Translation for (scalePages)

* Translation for (removeCertSign)

* Translation for (removeAnnotations)

* Translation for (sign)

* Translation for (flatten)

* Translation for (extractImages)

* Translation for (merge)

* Translation for (view pdf)

* Translation for (pageRemover)

* Translation for (rotate)

* az Translation for replace-invert-color

* az Translation for addstamprequest

* Translation for (remove-image)

* Translation for #url-to-pdf

* Update messages_az_AZ.properties

* Update README.md

* Update README.md

* Update messages_az_AZ.properties

---------

Co-authored-by: NureddinFarzaliyev <nureddin.fa@gmail.com>
Co-authored-by: Lucifer25x <lucifer25x@protonmail.com>
Co-authored-by: islamd7 <vusal04999@gmail.com>
Co-authored-by: Valida Rahmanova <validerehmanova04@gmail.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-22 08:44:48 +00:00
albanobattistella
bab2052a60 Update messages_it_IT.properties (#2289)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-22 08:37:31 +00:00
Ludy
7773df7443 Fix: Convert a single string in the array to a list array (#2293) 2024-11-21 21:18:41 +00:00
Ludy
32d575b4e9 try to reduce the permission; update only translation files (#2291) 2024-11-21 20:00:12 +00:00
reecebrowne
4ebeedc028 Hover tools tooltips (#2290)
* Multi-tool advert on pages that share functionality

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* Rtl CSS

* Upgraded tooltips on multitool. Order selected pages list. Repositionicons. Minor additional tweaks

* restore gb translations

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* remove blankspace

* Restore hover tooltips

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

---------

Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-21 19:33:19 +00:00
reecebrowne
b4b005bc2e Feature/ux improvements (#2288)
* Multi-tool advert on pages that share functionality

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* Rtl CSS

* Upgraded tooltips on multitool. Order selected pages list. Repositionicons. Minor additional tweaks

* restore gb translations

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* remove blankspace

---------

Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-21 17:34:50 +00:00
Omar Ahmed Hassan
b92bcfe915 Update Arabic language (#2282) 2024-11-21 14:54:05 +00:00
Omar Ahmed Hassan
aeca2b23d9 Fix: Expand and de-clutter menus for matching search results in homepage #2264 (#2277)
* Hide empty menus that don't match search criteria

- Hide empty menus (accordions/feature groups) that don't match search criteria.

* Expand menus automatically for matching search results

- Fix a bug where menus (accordions/feature groups) did not automatically expand on the homepage when search results matched.

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-21 14:24:45 +00:00
Anthony Stirling
68349c4426 Update downloader.js 2024-11-21 11:47:47 +00:00
Ludy
0f6d5e5a41 Note for PR creators added (#2279) 2024-11-21 11:31:32 +00:00
Ludy
df1c5476d9 Update German language (#2276) 2024-11-20 19:45:44 +00:00
Anthony Stirling
d0d6a70250 Metrics changes (#2273)
* Update downloader.js

* Update downloader.js

* Update common.html

* Update downloader.js
2024-11-20 10:32:44 +00:00
Anthony Stirling
a6ae3734ca Frooodle patch 8 (#2275)
* Update check_properties.yml

* Update messages_en_GB.properties

* Update messages_en_GB.properties
2024-11-20 09:44:51 +00:00
Anthony Stirling
c239d95131 Update PR-Demo-cleanup.yml 2024-11-20 08:41:29 +00:00
Anthony Stirling
d591874da6 Update PR-Demo-cleanup.yml 2024-11-20 08:28:51 +00:00
Anthony Stirling
6c623d8d84 Update MetricsAggregatorService.java (#2272) 2024-11-20 08:20:01 +00:00
Rafael Encinas
e059caa14e Fix id typo for "cropPdfCanvas" querySelector (#2271)
Fix id typo
2024-11-20 07:53:14 +00:00
dependabot[bot]
8eab35761d Bump org.projectlombok:lombok from 1.18.34 to 1.18.36 (#2266)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.34 to 1.18.36.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.34...v1.18.36)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 23:16:31 +00:00
Anthony Stirling
c43af24ffe Update PR-Demo-Comment.yml 2024-11-17 16:17:44 +00:00
Anthony Stirling
e1b3cc736c Update PR-Demo-Comment.yml 2024-11-17 15:54:50 +00:00
Anthony Stirling
0fb9e18636 Update PR-Demo-Comment.yml 2024-11-17 15:53:29 +00:00
Ludy
5e1aac0b84 Read login data from application.properties (#2263)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-17 14:08:41 +00:00
Anthony Stirling
60bf649260 Update and rename PR-Demos.yml to PR-Demo-cleanup.yml 2024-11-17 13:43:48 +00:00
Anthony Stirling
a58696a38e Create PR-Demo-Comment.yml (#2261)
* Create PR-Demo-Comment.yml

* Update PR-Demo-Comment.yml
2024-11-17 13:32:14 +00:00
Ludy
44abc67678 shows the titles of the buttons (#2262)
* shows the titles of the buttons

* Update navbar.css
2024-11-17 12:33:41 +00:00
Anthony Stirling
d1e690ff8d Update PR-Demos.yml 2024-11-17 10:13:59 +00:00
Anthony Stirling
5dc8fa08ee Create PR-Demos.yml (#2260)
* Create PR-Demos.yml

* Update build.gradle

* Update build.gradle

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml
2024-11-16 22:24:00 +00:00
Anthony Stirling
db028dfe27 docker move 2024-11-16 11:31:26 +00:00
Anthony Stirling
c24c504350 Delete Jenkinsfile 2024-11-16 11:22:17 +00:00
Anthony Stirling
5dcfe64d1c Update README.md 2024-11-16 11:11:12 +00:00
Anthony Stirling
d843696703 Update README.md 2024-11-16 11:07:54 +00:00
88 changed files with 3578 additions and 1393 deletions

View File

@@ -9,8 +9,9 @@ The script also provides functionality to update the translation files to match
adjusting the format. adjusting the format.
Usage: Usage:
python script_name.py --reference-file <path_to_reference_file> --branch <branch_name> [--files <list_of_changed_files>] python check_language_properties.py --reference-file <path_to_reference_file> --branch <branch_name> [--actor <actor_name>] [--files <list_of_changed_files>]
""" """
import copy import copy
import glob import glob
import os import os
@@ -18,6 +19,10 @@ 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 = []
@@ -95,7 +100,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(branch + file_path) basename_current_file = os.path.basename(os.path.join(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")
@@ -103,7 +108,7 @@ def update_missing_keys(reference_file, file_list, branch=""):
): ):
continue continue
current_properties = parse_properties_file(branch + file_path) current_properties = parse_properties_file(os.path.join(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)
@@ -114,60 +119,79 @@ 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(branch + file_path, updated_properties) write_json_file(os.path.join(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):
with open(file_path, "r", encoding="utf-8") as file: if os.path.isfile(file_path) and os.path.exists(file_path):
return file.read().splitlines() with open(file_path, "r", encoding="utf-8") as file:
return file.read().splitlines()
return [""]
def check_for_differences(reference_file, file_list, branch): def check_for_differences(reference_file, file_list, branch, actor):
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( report.append(f"#### 🔄 Reference Branch: `{reference_branch}`")
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
for file_path in file_list: file_arr = 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"#### 🗂️ **Checking File:** `{basename_current_file}`...") report.append(f"#### 📃 **File Check:** `{basename_current_file}`")
current_lines = read_properties(branch + "/" + file_path) current_lines = read_properties(os.path.join(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("- **Test 1 Status:** ❌ Failed") report.append("1. **Test 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" - **Issue:** Missing lines! Comments, empty lines, or translation strings are missing. Details: {reference_line_count} (reference) vs {current_line_count} (current)." f" - **_Mismatched line count_**: {reference_line_count} (reference) vs {current_line_count} (current). Comments, empty lines, or translation strings are missing."
) )
elif reference_line_count < current_line_count: elif reference_line_count < current_line_count:
report.append( report.append(
f" - **Issue:** Too many lines! Check your translation files! Details: {reference_line_count} (reference) vs {current_line_count} (current)." 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."
) )
# update_missing_keys(reference_file, [file_path], branch + "/")
else: else:
report.append("- **Test 1 Status:** ✅ Passed") report.append("1. **Test Status:** ✅ **_Passed_**")
# Check for missing or extra keys # Check for missing or extra keys
current_keys = [] current_keys = []
@@ -192,32 +216,42 @@ def check_for_differences(reference_file, file_list, branch):
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("- **Test 2 Status:** ❌ Failed") report.append("2. **Test 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" - **Issue:** There are keys in ***{basename_current_file}*** `{missing_keys_str}` that are not present in ***{basename_reference_file}***!" f" - **_Extra 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" - **Issue:** There are keys in ***{basename_reference_file}*** `{extra_keys_str}` that are not present in ***{basename_current_file}***!" f" - **_Missing 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("- **Test 2 Status:** ✅ Passed") report.append("2. **Test 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))
@@ -225,6 +259,11 @@ def check_for_differences(reference_file, file_list, branch):
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,
@@ -244,11 +283,21 @@ 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.getcwd() + "/src/**/messages_*.properties", recursive=True os.path.join(
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) check_for_differences(args.reference_file, file_list, args.branch, args.actor)

179
.github/workflows/PR-Demo-Comment.yml vendored Normal file
View File

@@ -0,0 +1,179 @@
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
});

78
.github/workflows/PR-Demo-cleanup.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
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,18 +6,22 @@ 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:
@@ -26,13 +30,6 @@ 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:
@@ -49,56 +46,73 @@ 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 }}
gh pr view ${{ github.event.pull_request.number }} --json files -q ".files[].path" > ../changed_files.txt # Store files in a safe way, only allowing valid properties files
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)
BRANCH_PATH="pr-branch" echo "Processing changed files..."
echo "BRANCH_PATH=${BRANCH_PATH}" >> $GITHUB_ENV mapfile -t CHANGED_FILES < changed_files.txt
CHANGED_FILES=$(cat changed_files.txt | tr '\n' ' ')
echo "CHANGED_FILES=${CHANGED_FILES}" >> $GITHUB_ENV CHANGED_FILES_STR="${CHANGED_FILES[*]}"
echo "Changed files: ${CHANGED_FILES}" echo "CHANGED_FILES=${CHANGED_FILES_STR}" >> $GITHUB_ENV
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 echo "${{ env.CHANGED_FILES }}" | grep -q 'src/main/resources/messages_en_GB.properties'; then if grep -Fxq "src/main/resources/messages_en_GB.properties" changed_files.txt; 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 ${{ env.REFERENCE_FILE }}" run: echo "Reference file is set to ${REFERENCE_FILE}"
- name: Run Python script to check files - name: Run Python script to check files
id: run-check id: run-check
run: | run: |
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 echo "Running Python script to check files..."
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 failure.txt ] && [ -s failure.txt ]; then if [ -f result.txt ] && [ -s result.txt ]; then
echo "Test failed, capturing output..." echo "Test, capturing output..."
ERROR_OUTPUT=$(cat failure.txt) SCRIPT_OUTPUT=$(cat result.txt)
echo "ERROR_OUTPUT<<EOF" >> $GITHUB_ENV echo "SCRIPT_OUTPUT<<EOF" >> $GITHUB_ENV
echo "$ERROR_OUTPUT" >> $GITHUB_ENV echo "$SCRIPT_OUTPUT" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV
echo $ERROR_OUTPUT echo "${SCRIPT_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 errors found." echo "No update found."
echo "ERROR_OUTPUT=" >> $GITHUB_ENV echo "SCRIPT_OUTPUT=" >> $GITHUB_ENV
echo "FAIL_JOB=false" >> $GITHUB_ENV
fi fi
- name: Post comment on PR - name: Post comment on PR
if: env.ERROR_OUTPUT != '' if: env.SCRIPT_OUTPUT != ''
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
script: | script: |
const { GITHUB_REPOSITORY, ERROR_OUTPUT } = process.env; const { GITHUB_REPOSITORY, SCRIPT_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;
@@ -120,7 +134,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${ERROR_OUTPUT}\n` body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n`
}); });
console.log("Updated existing comment."); console.log("Updated existing comment.");
} else if (!comment) { } else if (!comment) {
@@ -129,33 +143,24 @@ jobs:
owner: repoOwner, owner: repoOwner,
repo: repoName, repo: repoName,
issue_number: prNumber, issue_number: prNumber,
body: `## 🚀 Translation Verification Summary\n\n\n${ERROR_OUTPUT}\n` body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_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: Set up git config - name: Fail job if errors found
# run: | if: env.FAIL_JOB == 'true'
# git config --global user.name "github-actions[bot]" run: |
# git config --global user.email "github-actions[bot]@users.noreply.github.com" echo "Failing the job because errors were detected."
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
@@ -169,7 +174,10 @@ jobs:
- name: Run Python script to check files - name: Run Python script to check files
id: run-check id: run-check
run: | run: |
python .github/scripts/check_language_properties.py --reference-file src/main/resources/messages_en_GB.properties --branch main 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 - name: Set up git config
run: | run: |
@@ -184,7 +192,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@v6 uses: peter-evans/create-pull-request@v7
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update translation files" commit-message: "Update translation files"
@@ -193,6 +201,8 @@ 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]
@@ -200,3 +210,4 @@ jobs:
labels: Translation labels: Translation
draft: false draft: false
delete-branch: true delete-branch: true
sign-commits: true

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: frooodle/s-pdf:latest image: stirlingtools/stirling-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 frooodle/s-pdf:latest -f ./Dockerfile . docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-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 frooodle/s-pdf:latest-ultra-lite -f ./Dockerfile-ultra-lite . docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-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 frooodle/s-pdf:latest-fat -f ./Dockerfile-fat . docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-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.7-jdk17 AS build FROM gradle:8.11-jdk17 AS build
# Set the working directory # Set the working directory
WORKDIR /app WORKDIR /app

45
Jenkinsfile vendored
View File

@@ -1,45 +0,0 @@
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/frooodle/s-pdf> > <https://hub.docker.com/r/stirlingtools/stirling-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/frooodle/s-pdf/latest?label=Stirling-PDF%20Full) ![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-ultra-lite?label=Stirling-PDF%20Ultra-Lite) ![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-fat?label=Stirling-PDF%20Fat) ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/stirlingtools/stirling-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 \
frooodle/s-pdf:latest stirlingtools/stirling-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: frooodle/s-pdf:latest image: stirlingtools/stirling-pdf:latest
ports: ports:
- '8080:8080' - '8080:8080'
volumes: volumes:
@@ -186,46 +186,47 @@ Certain functionality like `Sign` supports pre-saved files stored at `/customFil
## Supported Languages ## Supported Languages
Stirling-PDF currently supports 36 languages! Stirling-PDF currently supports 37 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![97%](https://geps.dev/progress/97) | | Arabic (العربية) (ar_AR) | ![99%](https://geps.dev/progress/99) |
| Basque (Euskara) (eu_ES) | ![55%](https://geps.dev/progress/55) | | Azerbaijani (Azərbaycan Dili) (az_AZ) | ![76%](https://geps.dev/progress/76) |
| Bulgarian (Български) (bg_BG) | ![96%](https://geps.dev/progress/96) | | Basque (Euskara) (eu_ES) | ![54%](https://geps.dev/progress/54) |
| Catalan (Català) (ca_CA) | ![90%](https://geps.dev/progress/90) | | Bulgarian (Български) (bg_BG) | ![95%](https://geps.dev/progress/95) |
| Croatian (Hrvatski) (hr_HR) | ![98%](https://geps.dev/progress/98) | | Catalan (Català) (ca_CA) | ![89%](https://geps.dev/progress/89) |
| Czech (Česky) (cs_CZ) | ![97%](https://geps.dev/progress/97) | | Croatian (Hrvatski) (hr_HR) | ![96%](https://geps.dev/progress/96) |
| Danish (Dansk) (da_DK) | ![96%](https://geps.dev/progress/96) | | Czech (Česky) (cs_CZ) | ![96%](https://geps.dev/progress/96) |
| Dutch (Nederlands) (nl_NL) | ![96%](https://geps.dev/progress/96) | | Danish (Dansk) (da_DK) | ![95%](https://geps.dev/progress/95) |
| 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) | ![97%](https://geps.dev/progress/97) | | French (Français) (fr_FR) | ![98%](https://geps.dev/progress/98) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) | | German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) |
| Greek (Ελληνικά) (el_GR) | ![97%](https://geps.dev/progress/97) | | Greek (Ελληνικά) (el_GR) | ![96%](https://geps.dev/progress/96) |
| Hindi (हिंदी) (hi_IN) | ![95%](https://geps.dev/progress/95) | | Hindi (हिंदी) (hi_IN) | ![93%](https://geps.dev/progress/93) |
| Hungarian (Magyar) (hu_HU) | ![98%](https://geps.dev/progress/98) | | Hungarian (Magyar) (hu_HU) | ![96%](https://geps.dev/progress/96) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![97%](https://geps.dev/progress/97) | | Indonesian (Bahasa Indonesia) (id_ID) | ![96%](https://geps.dev/progress/96) |
| Irish (Gaeilge) (ga_IE) | ![88%](https://geps.dev/progress/88) | | Irish (Gaeilge) (ga_IE) | ![86%](https://geps.dev/progress/86) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![85%](https://geps.dev/progress/85) | | Japanese (日本語) (ja_JP) | ![84%](https://geps.dev/progress/84) |
| Korean (한국어) (ko_KR) | ![95%](https://geps.dev/progress/95) | | Korean (한국어) (ko_KR) | ![94%](https://geps.dev/progress/94) |
| Norwegian (Norsk) (no_NB) | ![87%](https://geps.dev/progress/87) | | Norwegian (Norsk) (no_NB) | ![86%](https://geps.dev/progress/86) |
| Polish (Polski) (pl_PL) | ![97%](https://geps.dev/progress/97) | | Polish (Polski) (pl_PL) | ![95%](https://geps.dev/progress/95) |
| Portuguese (Português) (pt_PT) | ![97%](https://geps.dev/progress/97) | | Portuguese (Português) (pt_PT) | ![96%](https://geps.dev/progress/96) |
| Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) | | Portuguese Brazilian (Português) (pt_BR) | ![96%](https://geps.dev/progress/96) |
| Romanian (Română) (ro_RO) | ![90%](https://geps.dev/progress/90) | | Romanian (Română) (ro_RO) | ![89%](https://geps.dev/progress/89) |
| Russian (Русский) (ru_RU) | ![97%](https://geps.dev/progress/97) | | Russian (Русский) (ru_RU) | ![95%](https://geps.dev/progress/95) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![70%](https://geps.dev/progress/70) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![69%](https://geps.dev/progress/69) |
| Simplified Chinese (简体中文) (zh_CN) | ![91%](https://geps.dev/progress/91) | | Simplified Chinese (简体中文) (zh_CN) | ![90%](https://geps.dev/progress/90) |
| Slovakian (Slovensky) (sk_SK) | ![82%](https://geps.dev/progress/82) | | Slovakian (Slovensky) (sk_SK) | ![81%](https://geps.dev/progress/81) |
| Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) | | Spanish (Español) (es_ES) | ![96%](https://geps.dev/progress/96) |
| Swedish (Svenska) (sv_SE) | ![97%](https://geps.dev/progress/97) | | Swedish (Svenska) (sv_SE) | ![95%](https://geps.dev/progress/95) |
| Thai (ไทย) (th_TH) | ![96%](https://geps.dev/progress/96) | | Thai (ไทย) (th_TH) | ![94%](https://geps.dev/progress/94) |
| Traditional Chinese (繁體中文) (zh_TW) | ![98%](https://geps.dev/progress/98) | | Traditional Chinese (繁體中文) (zh_TW) | ![97%](https://geps.dev/progress/97) |
| Turkish (Türkçe) (tr_TR) | ![92%](https://geps.dev/progress/92) | | Turkish (Türkçe) (tr_TR) | ![90%](https://geps.dev/progress/90) |
| Ukrainian (Українська) (uk_UA) | ![80%](https://geps.dev/progress/80) | | Ukrainian (Українська) (uk_UA) | ![79%](https://geps.dev/progress/79) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![88%](https://geps.dev/progress/88) | | Vietnamese (Tiếng Việt) (vi_VN) | ![87%](https://geps.dev/progress/87) |
## 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.3.5" id "org.springframework.boot" version "3.4.0"
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,19 +10,21 @@ 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.3.5" springBootVersion = "3.4.0"
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.34" lombokVersion = "1.18.36"
bouncycastleVersion = "1.78.1" bouncycastleVersion = "1.79"
} }
group = "stirling.software" group = "stirling.software"
version = "0.33.1" version = "0.34.0"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21
@@ -119,7 +121,7 @@ configurations.all {
} }
dependencies { dependencies {
//security updates //security updates
implementation "org.springframework:spring-webmvc:6.1.14" implementation "org.springframework:spring-webmvc:6.2.0"
implementation("io.github.pixee:java-security-toolkit:1.2.0") implementation("io.github.pixee:java-security-toolkit:1.2.0")
@@ -141,11 +143,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.security:spring-security-saml2-service-provider:6.3.4' implementation 'org.springframework.security:spring-security-saml2-service-provider:6.4.1'
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5' implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
//2.2.x requires rebuild of DB file.. need migration path // Don't upgrade h2database
runtimeOnly "com.h2database:h2:2.1.214" runtimeOnly "com.h2database:h2:2.3.232"
// 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"
@@ -201,12 +202,19 @@ 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.13.6" implementation "io.micrometer:micrometer-core:1.14.1"
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: frooodle/s-pdf:latest-fat image: stirlingtools/stirling-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: frooodle/s-pdf:latest image: stirlingtools/stirling-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: frooodle/s-pdf:latest image: stirlingtools/stirling-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: frooodle/s-pdf:latest-ultra-lite image: stirlingtools/stirling-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: frooodle/s-pdf:latest-ultra-lite image: stirlingtools/stirling-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: frooodle/s-pdf:latest image: stirlingtools/stirling-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.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -3,6 +3,11 @@ ignore = [
'language.direction', 'language.direction',
] ]
[az_AZ]
ignore = [
'language.direction',
]
[bg_BG] [bg_BG]
ignore = [ ignore = [
'language.direction', 'language.direction',

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))
.assertingPartyDetails( .assertingPartyMetadata(
(details) -> (details) ->
details.entityId(samlConf.getIdpIssuer()) details.entityId(samlConf.getIdpIssuer())
.singleSignOnServiceLocation( .singleSignOnServiceLocation(

View File

@@ -34,6 +34,12 @@ 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
@@ -134,7 +140,8 @@ 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 = DriverManager.getConnection(url, "sa", ""); try (Connection conn =
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();
@@ -147,7 +154,8 @@ 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 = DriverManager.getConnection(url, "sa", "")) { try (Connection conn =
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()) {
@@ -189,7 +197,8 @@ 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 = DriverManager.getConnection(url, "sa", ""); try (Connection conn =
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,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,79 +18,36 @@ 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(CropController.class); private static final Logger logger = LoggerFactory.getLogger(ExtractCSVController.class);
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data") @PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
@Operation( @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")
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 {
ArrayList<String> tableData = new ArrayList<>();
int columnsCount = 0;
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
final double res = 72; // PDF units are at 72 DPI
PDFTableStripper stripper = new PDFTableStripper();
PDPage pdPage = document.getPage(form.getPageId() - 1);
stripper.extractTable(pdPage);
columnsCount = stripper.getColumns();
for (int c = 0; c < columnsCount; ++c) {
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(); StringWriter writer = new StringWriter();
try (CSVWriter csvWriter = new CSVWriter(writer)) { try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
csvWriter.writeNext(headersList.toArray(new String[0])); CSVFormat format = CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build();
for (String record : recordList) { Writer csvWriter = new FlexibleCSVWriter(format);
csvWriter.writeNext(record.split("\\|")); SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
try (ObjectExtractor extractor = new ObjectExtractor(document)) {
Page page = extractor.extract(form.getPageId());
List<Table> tables = sea.extract(page);
csvWriter.write(writer, tables);
} }
} }
@@ -99,41 +56,12 @@ 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,8 +160,7 @@ 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 img = PDImageXObject.createFromFileByExtension(logoFile, doc);
PDImageXObject.createFromFileByExtension(logoFile, doc);
cs.drawImage(img, 100, 0); cs.drawImage(img, 100, 0);
cs.restoreGraphicsState(); cs.restoreGraphicsState();
} }
@@ -209,10 +208,7 @@ public class CertSignController {
} }
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign") @PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
@Operation( @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")
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();
@@ -242,7 +238,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");
@@ -314,22 +310,19 @@ 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 = try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
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 = InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder()
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray()); .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 = PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); pkInfo = ((PEMEncryptedKeyPair) pemObject)
pkInfo = .decryptKeyPair(decProv)
((PEMEncryptedKeyPair) pemObject) .getPrivateKeyInfo();
.decryptKeyPair(decProv)
.getPrivateKeyInfo();
} else { } else {
pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo(); pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();
} }

View File

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

View File

@@ -0,0 +1,16 @@
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 = 900000) // Run every 15 minutes @Scheduled(fixedRate = 1800000) // Run every 30 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,11 +32,19 @@ public class MetricsAggregatorService {
.counters() .counters()
.forEach( .forEach(
counter -> { counter -> {
String key = String method = counter.getId().getTag("method");
String.format( String uri = counter.getId().getTag("uri");
"http_requests_%s_%s",
counter.getId().getTag("method"), // Skip if either method or uri is null
counter.getId().getTag("uri").replace("/", "_")); if (method == null || uri == null) {
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,6 +15,7 @@ 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;
@@ -26,19 +27,25 @@ 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();
} }
@@ -64,6 +71,16 @@ 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"));
@@ -132,7 +149,6 @@ 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

@@ -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;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=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.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,11 @@ page=صفحة
pages=صفحات pages=صفحات
loading=جارٍ التحميل... loading=جارٍ التحميل...
addToDoc=إضافة إلى المستند addToDoc=إضافة إلى المستند
reset=Reset reset=إعداة ضبط
legal.privacy=سياسة الخصوصية legal.privacy=سياسة الخصوصية
legal.terms=شروط الاستخدام legal.terms=شروط الاستخدام
legal.accessibility=Accessibility legal.accessibility=إمكانية الوصول
legal.cookie=سياسة ملفات تعريف الارتباط legal.cookie=سياسة ملفات تعريف الارتباط
legal.impressum=بيان الهوية legal.impressum=بيان الهوية
@@ -119,8 +119,8 @@ pipelineOptions.validateButton=تحقق
######################## ########################
enterpriseEdition.button=ترقية إلى محترف enterpriseEdition.button=ترقية إلى محترف
enterpriseEdition.warning=هذه الخاصية متوفرة فقط للمستخدمين المحترفين. enterpriseEdition.warning=هذه الخاصية متوفرة فقط للمستخدمين المحترفين.
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. enterpriseEdition.yamlAdvert=يدعم Stirling PDF Pro ملفات الإعدادات YAML وميزات SSO أخرى
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro enterpriseEdition.ssoAdvert=هل تبحث عن المزيد من ميزات إدارة المستخدمين؟ اطلع على Stirling PDF Pro
################# #################
@@ -142,7 +142,7 @@ navbar.language=اللغات
navbar.settings=إعدادات navbar.settings=إعدادات
navbar.allTools=أدوات navbar.allTools=أدوات
navbar.multiTool=أدوات متعددة navbar.multiTool=أدوات متعددة
navbar.search=Search navbar.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 +247,8 @@ database.fileNotFound=لم يتم العثور على الملف
database.fileNullOrEmpty=يجب ألا يكون الملف فارغًا أو خاليًا database.fileNullOrEmpty=يجب ألا يكون الملف فارغًا أو خاليًا
database.failedImportFile=فشل استيراد الملف database.failedImportFile=فشل استيراد الملف
session.expired=Your session has expired. Please refresh the page and try again. session.expired=لقد انتهت جلستك. يرجى تحديث الصفحة والمحاولة مرة أخرى
session.refreshPage=Refresh Page session.refreshPage=تحديث الصفحة
############# #############
# HOME-PAGE # # HOME-PAGE #
@@ -513,15 +513,15 @@ home.splitPdfByChapters.desc=قسم مستند PDF إلى ملفات متعدد
splitPdfByChapters.tags=تجزئة، فصول، علامات تبويب، تنظيم splitPdfByChapters.tags=تجزئة، فصول، علامات تبويب، تنظيم
#replace-invert-color #replace-invert-color
replace-color.title=Replace-Invert-Color replace-color.title=إستبدال-عكس اللون
replace-color.header=استبدال-إلغاء مirro لون PDF replace-color.header=استبدال-عكس لون PDF
home.replaceColorPdf.title=Replace and Invert Color home.replaceColorPdf.title=إستبدال و عكس الألوان
home.replaceColorPdf.desc=استبدال الألوان للنصوص والخلفيات في المستندات PDF وإلغاء تعكير اللون الكامل للمستند لتقليل حجم الملف home.replaceColorPdf.desc=استبدال الألوان للنصوص والخلفيات في المستندات PDF وإلغاء تعكير اللون الكامل للمستند لتقليل حجم الملف
replaceColorPdf.tags=استبدال اللون، عمليات الصفحة، الخلفية، جانب الخادم replaceColorPdf.tags=استبدال اللون، عمليات الصفحة، الخلفية، جانب الخادم
replace-color.selectText.1=خيارات استبدال-إلغاء مirro لون replace-color.selectText.1=خيارات استبدال أو عكس الألوان
replace-color.selectText.2=Default(Default high contrast colors) replace-color.selectText.2=افتراضي(ألوان التباين العالي الافتراضية)
replace-color.selectText.3=خصيصة (ألوان شخصية) replace-color.selectText.3=خصيصة (ألوان شخصية)
replace-color.selectText.4=Full-Invert(Invert all colors) replace-color.selectText.4=عكس كامل(عكس جميع الألوان)
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,7 +818,12 @@ 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=إصلاح
@@ -935,17 +940,27 @@ 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=Select All multiTool.selectAll=تحديد الكل
multiTool.deselectAll=Deselect All multiTool.deselectAll=إلغاء تحديد الكل
multiTool.selectPages=Page Select multiTool.selectPages=تحديد الصفحة
multiTool.selectedPages=Selected Pages multiTool.selectedPages=الصفحات المحددة
multiTool.page=Page multiTool.page=صفحة
multiTool.deleteSelected=Delete Selected multiTool.deleteSelected=حذف المحدد
multiTool.downloadAll=Export multiTool.downloadAll=تصدير
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=تصدير المحدد
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=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=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
#view pdf #view pdf
viewPdf.title=عرض PDF viewPdf.title=عرض PDF
@@ -1239,9 +1254,9 @@ splitByChapters.title=تجزئة المستند حسب الفصول
splitByChapters.header=تجزئة المستند حسب الفصول splitByChapters.header=تجزئة المستند حسب الفصول
splitByChapters.bookmarkLevel=مستوى العلامات التذكارية splitByChapters.bookmarkLevel=مستوى العلامات التذكارية
splitByChapters.includeMetadata=شامل البيانات المرفقة splitByChapters.includeMetadata=شامل البيانات المرفقة
splitByChapters.allowDuplicates=Allow Duplicates splitByChapters.allowDuplicates=السماح بالتكرار
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. splitByChapters.desc.1=هذه الأداة تقوم بتقسيم ملف PDF إلى عدة ملفات PDF استناداً إلى بنية فصوله
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.2=مستوى الإشارة المرجعية: اختر مستوى الإشارات المرجعية التي تريد استخدامها للتقسيم (0 للمستوى الأعلى، 1 للمستوى الثاني، وما إلى ذلك)
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

@@ -818,7 +818,12 @@ 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=Поправи
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 @@ page=Seite
pages=Seiten pages=Seiten
loading=Laden... loading=Laden...
addToDoc=In Dokument hinzufügen addToDoc=In Dokument hinzufügen
reset=Reset reset=Zurücksetzen
legal.privacy=Datenschutz legal.privacy=Datenschutz
legal.terms=AGB legal.terms=AGB
@@ -818,7 +818,12 @@ 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
@@ -944,8 +949,18 @@ 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=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=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!
#view pdf #view pdf
viewPdf.title=PDF anzeigen viewPdf.title=PDF anzeigen

View File

@@ -818,7 +818,12 @@ 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=Επιδιόρθωση
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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 en fin. invalidPasswordMessage=Le mot de passe ne peut pas être vide et ne doit pas contenir d'espaces au début ou à la fin.
confirmPasswordErrorMessage=Nouveau Mot de passe et Confirmer le Nouveau Mot de passe doivent correspondre. confirmPasswordErrorMessage=Le nouveau mot de passe et sa confirmation doivent être identiques.
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,7 @@ page=Page
pages=Pages pages=Pages
loading=Chargement... loading=Chargement...
addToDoc=Ajouter au Document addToDoc=Ajouter au Document
reset=Reset 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 +142,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=Search navbar.search=Rechercher
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,12 +813,17 @@ 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=Saved Signatures sign.saved=Sceaux enregistrées
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
@@ -935,17 +940,27 @@ 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=Select All multiTool.selectAll=Tout sélectionner
multiTool.deselectAll=Deselect All multiTool.deselectAll=Tout déselectionner
multiTool.selectPages=Page Select multiTool.selectPages=Sélection des pages
multiTool.selectedPages=Selected Pages multiTool.selectedPages=Pages sélectionnées
multiTool.page=Page multiTool.page=Page
multiTool.deleteSelected=Delete Selected multiTool.deleteSelected=Supprimer la sélection
multiTool.downloadAll=Export multiTool.downloadAll=Exporter
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Exporter la sélection
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=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=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 !
#view pdf #view pdf
viewPdf.title=Visualiser un PDF viewPdf.title=Visualiser un PDF

View File

@@ -818,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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=मरम्मत
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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=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!

View File

@@ -818,7 +818,12 @@ 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=修復
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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=복구
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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ă
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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=Ремонт
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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ť
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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=ซ่อมแซม
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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=Ремонт
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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=修复
@@ -944,6 +949,16 @@ 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,7 +818,12 @@ 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=修復
@@ -944,6 +949,16 @@ 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.11", "moduleVersion": "1.5.12",
"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.11", "moduleVersion": "1.5.12",
"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.17.2", "moduleVersion": "2.18.1",
"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.17.2", "moduleVersion": "2.18.1",
"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.17.2", "moduleVersion": "2.18.1",
"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.17.2", "moduleVersion": "2.18.1",
"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.17.2", "moduleVersion": "2.18.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.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.17.2", "moduleVersion": "2.18.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.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.17.2", "moduleVersion": "2.18.1",
"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.17.2", "moduleVersion": "2.18.1",
"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.17.2", "moduleVersion": "2.18.1",
"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.17.2", "moduleVersion": "2.18.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.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.17.2", "moduleVersion": "2.18.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"
}, },
@@ -146,6 +146,18 @@
"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",
@@ -162,21 +174,22 @@
}, },
{ {
"moduleName": "com.google.errorprone:error_prone_annotations", "moduleName": "com.google.errorprone:error_prone_annotations",
"moduleVersion": "2.11.0", "moduleUrl": "https://errorprone.info/error_prone_annotations",
"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.1", "moduleVersion": "1.0.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.google.guava:guava", "moduleName": "com.google.guava:guava",
"moduleUrl": "https://github.com/google/guava/", "moduleUrl": "https://github.com/google/guava/",
"moduleVersion": "31.1-jre", "moduleVersion": "33.3.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"
}, },
@@ -189,8 +202,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": "1.3", "moduleVersion": "3.0.0",
"moduleLicense": "The Apache Software 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"
}, },
{ {
@@ -222,7 +235,7 @@
{ {
"moduleName": "com.h2database:h2", "moduleName": "com.h2database:h2",
"moduleUrl": "https://h2database.com", "moduleUrl": "https://h2database.com",
"moduleVersion": "2.1.214", "moduleVersion": "2.3.232",
"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/"
}, },
@@ -370,10 +383,17 @@
"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.16.1", "moduleVersion": "1.17.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"
}, },
@@ -393,10 +413,10 @@
}, },
{ {
"moduleName": "commons-logging:commons-logging", "moduleName": "commons-logging:commons-logging",
"moduleUrl": "http://jakarta.apache.org/commons/logging/", "moduleUrl": "https://commons.apache.org/proper/commons-logging/",
"moduleVersion": "1.0.4", "moduleVersion": "1.3.3",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "/LICENSE.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.dropwizard.metrics:metrics-core", "moduleName": "io.dropwizard.metrics:metrics-core",
@@ -414,34 +434,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.13.6", "moduleVersion": "1.14.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": "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.13.6", "moduleVersion": "1.14.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": "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.13.6", "moduleVersion": "1.14.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": "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.13.6", "moduleVersion": "1.14.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": "io.smallrye:jandex", "moduleName": "io.smallrye:jandex",
"moduleVersion": "3.1.2", "moduleVersion": "3.2.0",
"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"
}, },
@@ -573,7 +593,7 @@
}, },
{ {
"moduleName": "net.bytebuddy:byte-buddy", "moduleName": "net.bytebuddy:byte-buddy",
"moduleVersion": "1.14.19", "moduleVersion": "1.15.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"
}, },
@@ -611,10 +631,17 @@
"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.14.0", "moduleVersion": "3.17.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"
}, },
@@ -641,13 +668,13 @@
}, },
{ {
"moduleName": "org.apache.logging.log4j:log4j-api", "moduleName": "org.apache.logging.log4j:log4j-api",
"moduleVersion": "2.23.1", "moduleVersion": "2.24.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.23.1", "moduleVersion": "2.24.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"
}, },
@@ -695,7 +722,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.31", "moduleVersion": "10.1.33",
"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"
}, },
@@ -733,31 +760,52 @@
"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.78.1", "moduleVersion": "1.79",
"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.78.1", "moduleVersion": "1.79",
"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.78.1", "moduleVersion": "1.79",
"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.12.0", "moduleVersion": "3.43.0",
"moduleLicense": "The MIT License", "moduleLicense": "The MIT License",
"moduleLicenseUrl": "http://opensource.org/licenses/MIT" "moduleLicenseUrl": "http://opensource.org/licenses/MIT"
}, },
@@ -790,182 +838,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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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.14", "moduleVersion": "12.0.15",
"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/"
}, },
@@ -1000,23 +1048,23 @@
{ {
"moduleName": "org.hibernate.common:hibernate-commons-annotations", "moduleName": "org.hibernate.common:hibernate-commons-annotations",
"moduleUrl": "http://hibernate.org", "moduleUrl": "http://hibernate.org",
"moduleVersion": "6.0.6.Final", "moduleVersion": "7.0.3.Final",
"moduleLicense": "GNU Library General Public License v2.1 or later", "moduleLicense": "Apache License Version 2.0",
"moduleLicenseUrl": "http://www.opensource.org/licenses/LGPL-2.1" "moduleLicenseUrl": "https://opensource.org/licenses/Apache-2.0"
}, },
{ {
"moduleName": "org.hibernate.orm:hibernate-core", "moduleName": "org.hibernate.orm:hibernate-core",
"moduleUrl": "https://www.hibernate.org/orm/6.5", "moduleUrl": "https://www.hibernate.org/orm/6.6",
"moduleVersion": "6.5.3.Final", "moduleVersion": "6.6.2.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.5.3.Final", "moduleVersion": "3.6.1.Final",
"moduleLicense": "Public Domain", "moduleLicense": "Apache License 2.0",
"moduleLicenseUrl": "http://repository.jboss.org/licenses/cc0-1.0.txt" "moduleLicenseUrl": "https://repository.jboss.org/licenses/apache-2.0.txt"
}, },
{ {
"moduleName": "org.latencyutils:LatencyUtils", "moduleName": "org.latencyutils:LatencyUtils",
@@ -1025,6 +1073,12 @@
"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",
@@ -1100,21 +1154,21 @@
{ {
"moduleName": "org.ow2.asm:asm", "moduleName": "org.ow2.asm:asm",
"moduleUrl": "http://asm.ow2.org", "moduleUrl": "http://asm.ow2.org",
"moduleVersion": "9.7", "moduleVersion": "9.7.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": "org.ow2.asm:asm-commons", "moduleName": "org.ow2.asm:asm-commons",
"moduleUrl": "http://asm.ow2.org", "moduleUrl": "http://asm.ow2.org",
"moduleVersion": "9.7", "moduleVersion": "9.7.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": "org.ow2.asm:asm-tree", "moduleName": "org.ow2.asm:asm-tree",
"moduleUrl": "http://asm.ow2.org", "moduleUrl": "http://asm.ow2.org",
"moduleVersion": "9.7", "moduleVersion": "9.7.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"
}, },
@@ -1153,273 +1207,266 @@
{ {
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.5", "moduleVersion": "3.4.0",
"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.3.4", "moduleVersion": "6.4.1",
"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.3.4", "moduleVersion": "6.4.1",
"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.3.4", "moduleVersion": "6.4.1",
"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.3.4", "moduleVersion": "6.4.1",
"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.3.4", "moduleVersion": "6.4.1",
"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.3.4", "moduleVersion": "6.4.1",
"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.3.4", "moduleVersion": "6.4.1",
"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.3.4", "moduleVersion": "6.4.1",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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.1.14", "moduleVersion": "6.2.0",
"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"
}, },
@@ -1464,10 +1511,17 @@
{ {
"moduleName": "org.yaml:snakeyaml", "moduleName": "org.yaml:snakeyaml",
"moduleUrl": "https://bitbucket.org/snakeyaml/snakeyaml", "moduleUrl": "https://bitbucket.org/snakeyaml/snakeyaml",
"moduleVersion": "2.2", "moduleVersion": "2.3",
"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,6 +19,15 @@
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,3 +100,30 @@ 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,10 +61,6 @@ 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;
@@ -290,3 +286,6 @@ 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,6 +324,18 @@ 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: 2; z-index: 4;
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: 1; z-index: 3;
opacity: 0; opacity: 0;
transition: opacity 0.2s; transition: opacity 0.2s;
} }
@@ -116,6 +116,7 @@ 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 {
@@ -125,9 +126,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;
@@ -137,7 +138,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: 2; z-index: 10;
} }
.hidden { .hidden {
@@ -150,4 +151,5 @@ 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,53 +62,54 @@ 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

@@ -0,0 +1,8 @@
<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>

After

Width:  |  Height:  |  Size: 501 B

View File

@@ -34,6 +34,14 @@
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()) {
@@ -42,14 +50,10 @@
} }
} }
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);
@@ -57,6 +61,9 @@
}, 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);
@@ -65,12 +72,17 @@
} }
} }
clearFileInput(); clearFileInput();
clearTimeout(timeoutId); clearTimeout(timeoutId);
$("#submitBtn").text(originalButtonText); if (showGameBtn) {
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"
@@ -87,23 +99,41 @@
} }
} 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) {
try {
const arrayBuffer = await file.arrayBuffer();
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdfjs-legacy/pdf.worker.mjs'
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
return pdf.numPages;
} catch (error) {
console.error('Error getting PDF page count:', error);
return null;
}
}
async function handleSingleDownload(url, formData, isMulti = false, isZip = false) { async function handleSingleDownload(url, formData, isMulti = false, isZip = false) {
const startTime = performance.now();
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) {
// Handle 401 Unauthorized error
showSessionExpiredPrompt(); showSessionExpiredPrompt();
return; return;
} }
@@ -118,6 +148,8 @@
let filename = getFilenameFromContentDisposition(contentDisposition); let filename = getFilenameFromContentDisposition(contentDisposition);
const blob = await response.blob(); const blob = await response.blob();
success = true;
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);
@@ -127,13 +159,29 @@
} }
} catch (error) { } catch (error) {
clearFileInput(); success = false;
errorMessage = error.message;
console.error("Error in handleSingleDownload:", error); console.error("Error in handleSingleDownload:", error);
throw error; throw error;
} finally {
const processingTime = performance.now() - startTime;
// Capture analytics
const pageCount = file && file.type === 'application/pdf' ? await getPDFPageCount(file) : null;
if(analyticsEnabled) {
posthog.capture('file_processing', {
success: success,
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) {
@@ -308,14 +356,14 @@
if(editSectionElement){ if(editSectionElement){
editSectionElement.style.display = "none"; editSectionElement.style.display = "none";
} }
let cropPdfCanvas = document.querySelector("#crop-pdf-canvas"); let cropPdfCanvas = document.querySelector("#cropPdfCanvas");
let overlayCanvas = document.querySelector("#overlayCanvas"); let overlayCanvas = document.querySelector("#overlayCanvas");
if(cropPdfCanvas && overlayCanvas){ if(cropPdfCanvas && overlayCanvas){
cropPdfCanvas.width = 0; cropPdfCanvas.width = 0;
cropPdfCanvas.heigth = 0; cropPdfCanvas.height = 0;
overlayCanvas.width = 0; overlayCanvas.width = 0;
overlayCanvas.heigth = 0; overlayCanvas.height = 0;
} }
} else{ } else{
console.log("Disabled for 'Merge'"); console.log("Disabled for 'Merge'");

View File

@@ -4,6 +4,7 @@ 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,
@@ -197,6 +198,68 @@ 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
@@ -241,7 +304,7 @@ const DraggableUtils = {
} }
const draggablesData = pagesMap[this.pageIndex]; const draggablesData = pagesMap[this.pageIndex];
if (draggablesData) { if (draggablesData && Array.isArray(draggablesData)) {
draggablesData.forEach((draggableData) => this.boxDragContainer.appendChild(draggableData.element)); draggablesData.forEach((draggableData) => this.boxDragContainer.appendChild(draggableData.element));
} }
@@ -273,6 +336,13 @@ 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();
@@ -297,6 +367,7 @@ 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;
@@ -304,7 +375,8 @@ const DraggableUtils = {
console.log(typeof pageIdx); console.log(typeof pageIdx);
const page = pdfDocModified.getPage(parseInt(pageIdx)); const page = pdfDocModified.getPage(parseInt(pageIdx));
const draggablesData = pagesMap[pageIdx]; let draggablesData = pagesMap[pageIdx];
const offsetWidth = pagesMap[pageIdx + "-offsetWidth"]; const offsetWidth = pagesMap[pageIdx + "-offsetWidth"];
const offsetHeight = pagesMap[pageIdx + "-offsetHeight"]; const offsetHeight = pagesMap[pageIdx + "-offsetHeight"];
@@ -383,7 +455,6 @@ const DraggableUtils = {
}); });
} }
} }
this.loadPageContents(); this.loadPageContents();
return pdfDocModified; return pdfDocModified;
}, },

View File

@@ -1,24 +1,56 @@
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");
for (var i = 0; i < cards.length; i++) { let featureGroups = document.querySelectorAll(".feature-group");
var card = cards[i]; const collapsedGroups = getCollapsedGroups();
var title = card.querySelector("h5.card-title").innerText;
var text = card.querySelector("p.card-text").innerText;
// Get the navbar tags associated with the card for (const featureGroup of featureGroups) {
var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`); var cards = featureGroup.querySelectorAll(".feature-card");
var navbarTags = navbarItem ? navbarItem.getAttribute("data-bs-tags") : "";
var content = title + " " + text + " " + navbarTags; let groupMatchesFilter = false;
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;
if (content.toUpperCase().indexOf(filter) > -1) { // Get the navbar tags associated with the card
card.style.display = ""; var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`);
} else { var navbarTags = navbarItem ? navbarItem.getAttribute("data-bs-tags") : "";
card.style.display = "none";
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 {
featureGroup.style.display = "";
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");
} }
} }

View File

@@ -1,29 +1,21 @@
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
var styleElement = document.createElement("link"); // Add CSS dynamically
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">
@@ -32,6 +24,7 @@ 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);
@@ -40,20 +33,41 @@ class DragDropManager {
} }
startDraggingPage(div) { startDraggingPage(div) {
this.pageDragging = true; if (window.selectPage) {
this.draggedEl = div; // Multi-page drag logic
const img = div.querySelector("img"); this.selectedPageElements = window.selectedPages.map((index) => {
div.classList.add("drag-manager_dragging"); const pageEl = document.getElementById(`page-container-${index}`);
const imageSrc = img.src; if (pageEl) {
pageEl.initialTransform = pageEl.style.transform || "translate(0px, 0px)";
}
return pageEl;
}).filter(Boolean);
const imgEl = document.createElement("img"); if (this.selectedPageElements.length === 0) return;
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");
@@ -74,21 +88,43 @@ 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,31 +110,32 @@ 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>`; moveUp.innerHTML = `<span class="material-symbols-rounded">arrow_${leftDirection}_alt</span><span class="btn-tooltip">${window.translations.moveLeft}</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>`; moveDown.innerHTML = `<span class="material-symbols-rounded">arrow_${rightDirection}_alt</span><span class="btn-tooltip">${window.translations.moveRight}</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>`; rotateCCW.innerHTML = `<span class="material-symbols-rounded">rotate_left</span><span class="btn-tooltip">${window.translations.rotateLeft}</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>`; rotateCW.innerHTML = `<span class="material-symbols-rounded">rotate_right</span><span class="btn-tooltip">${window.translations.rotateRight}</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>`; deletePage.innerHTML = `<span class="material-symbols-rounded">delete</span><span class="btn-tooltip"></span><span class="btn-tooltip">${window.translations.delete}</span>`;
deletePage.onclick = this.deletePageButtonCallback; deletePage.onclick = this.deletePageButtonCallback;
buttonContainer.appendChild(deletePage); buttonContainer.appendChild(deletePage);
@@ -189,19 +190,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>`; insertFileButton.innerHTML = `<span class="material-symbols-rounded">add</span></span><span class="btn-tooltip">${window.translations.addFile}</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>`; splitFileButton.innerHTML = `<span class="material-symbols-rounded">cut</span></span><span class="btn-tooltip">${window.translations.split}</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>`; insertFileBlankButton.innerHTML = `<span class="material-symbols-rounded">insert_page_break</span></span><span class="btn-tooltip">${window.translations.insertPageBreak}</span>`;
insertFileBlankButton.onclick = this.insertFileBlankButtonCallback; insertFileBlankButton.onclick = this.insertFileBlankButtonCallback;
insertFileButtonContainer.appendChild(insertFileBlankButton); insertFileButtonContainer.appendChild(insertFileBlankButton);

View File

@@ -29,6 +29,7 @@ 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;
@@ -55,6 +56,7 @@ 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");
@@ -120,12 +122,24 @@ 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") {
await this.addPdfFile(file, nextSiblingElement); const { renderer, pdfDocument } = await this.loadFile(file);
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) => {
@@ -133,6 +147,23 @@ 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
@@ -186,14 +217,12 @@ class PdfContainer {
} }
async addPdfFile(file, nextSiblingElement) { async addPdfFile(renderer, pdfDocument, 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);
@@ -202,7 +231,6 @@ 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);
}); });
@@ -341,8 +369,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-icon"); const selectIcon = document.getElementById("select-All-Container");
const deselectIcon = document.getElementById("deselect-icon"); const deselectIcon = document.getElementById("deselect-All-Container");
if (selectIcon.style.display === "none") { if (selectIcon.style.display === "none") {
selectIcon.style.display = "inline"; selectIcon.style.display = "inline";
@@ -441,7 +469,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";
@@ -701,6 +729,31 @@ 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");
@@ -745,7 +798,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");
selectedPages.classList.toggle("hidden", !window.selectPage); selectAll.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(".dropdown-item, .nav-link"); var items = document.querySelectorAll('a.dropdown-item[data-bs-tags]');
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,7 +19,7 @@
<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}"></button> <button type="submit" class="btn btn-primary" th:text="#{PDFToCSV.submit}" id="submitBtn"></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>

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" text="#{database.title}">Database Im-/Export</span> <span class="tool-header-text" th: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 : ${systemUpdate}"> <tr th:each="backup : ${backupFiles}">
<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,10 +41,11 @@
</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);padding: .8rem;border-radius: 2rem;" class="mb-3"> <div style="background: var(--md-sys-color-error-container);border-radius: 2rem;" class="mb-3 p-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

@@ -201,6 +201,7 @@
window.stirlingPDF.error = /*[[#{error}]]*/ "Error"; window.stirlingPDF.error = /*[[#{error}]]*/ "Error";
})(); })();
</script> </script>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script th:src="@{'/js/downloader.js'}"></script> <script th:src="@{'/js/downloader.js'}"></script>
<div class="custom-file-chooser" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}"> <div class="custom-file-chooser" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
@@ -218,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,6 +4,7 @@
<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;
top: 10px; inset-inline-end: 12px;
right: 12px; inset-block-start: 10px;
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-left: 50%; margin-inline-start: 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: 10px 27px 10px 20px; padding-block: 10px;
padding-inline: 20px 27px;
font-size: 0.9rem; font-size: 0.9rem;
display: none; display: none;
justify-content: center; justify-content: center;
@@ -43,6 +43,7 @@
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';
@@ -52,6 +53,13 @@
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 ('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', 'organize')}">
</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 ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'advance')}"> 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 ('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 tooltip-text" id="dark-mode-text" th:data-text="#{navbar.darkmode}" th:text="#{navbar.darkmode}"></span> <span class="icon-text icon-hide" 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 tooltip-text" th:data-text="#{navbar.settings}" th:text="#{navbar.settings}"></span> <span class="icon-text icon-hide" 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/frooodle/s-pdf" class="mx-1" role="button" target="_blank"th:title="#{seeDockerHub}"> <a href="https://hub.docker.com/r/stirlingtools/stirling-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,64 +31,79 @@
<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>
</button> <span class="btn-tooltip" th:text="#{multiTool.split}"></span>
<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="export-button" class="btn btn-primary enable-on-file" onclick="exportPdf(false)" disabled> <button id="select-pages-container" class="btn btn-secondary enable-on-file"
<span th:title="#{multiTool.downloadAll}" class="material-symbols-rounded"> onclick="toggleSelectPageVisibility()" disabled>
download <span id="select-pages-button" class="material-symbols-rounded">
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"
onclick="toggleSelectAll()" disabled>
<span class="material-symbols-rounded" id="deselect-icon">deselect</span>
<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" <input type="text" id="csv-input" class="form-control" style="height:2.5rem" placeholder="1,3,5-10" value="">
value="">
</div> </div>
<ul id="selected-pages-list" class="pages-list"></ul> <ul id="selected-pages-list" class="pages-list"></ul>
</div> </div>
@@ -97,13 +112,6 @@
<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>
@@ -121,6 +129,21 @@
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") {
@@ -162,4 +185,4 @@
</script> </script>
</body> </body>
</html> </html>

View File

@@ -1,10 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
<head> xmlns:th="https://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{sign.title}, header=#{sign.header})}"></th:block>
<link rel="stylesheet" th:href="@{'/css/sign.css'}">
<th:block th:each="font : ${fonts}"> <head>
<th:block th:insert="~{fragments/common :: head(title=#{sign.title}, header=#{sign.header})}"></th:block>
<link rel="stylesheet" th:href="@{'/css/sign.css'}">
<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}]]";
@@ -16,372 +18,435 @@
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>
<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>
<div class="tab-container drawing-pad-container" th:title="#{sign.draw}">
<canvas id="drawing-pad-canvas"></canvas>
<br>
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()" th:text="#{sign.clear}"></button>
<button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()" th:text="#{sign.add}"></button>
<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;
let croppedHeight = maxY - minY;
if (croppedWidth < 0 || croppedHeight < 0) return null;
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
let croppedCanvas = document.createElement('canvas'),
croppedCtx = croppedCanvas.getContext('2d');
croppedCanvas.width = croppedWidth;
croppedCanvas.height = croppedHeight;
croppedCtx.putImageData(cuttedImageData, 0, 0);
return croppedCanvas.toDataURL();
}
function 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>
<!-- 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>
<!-- 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 class="tab-container drawing-pad-container" th:title="#{sign.draw}">
<canvas id="drawing-pad-canvas"></canvas>
<br>
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()"
th:text="#{sign.clear}"></button>
<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}">
<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>
</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;
let croppedHeight = maxY - minY;
if (croppedWidth < 0 || croppedHeight < 0) return null;
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
let croppedCanvas = document.createElement('canvas'),
croppedCtx = croppedCanvas.getContext('2d');
croppedCanvas.width = croppedWidth;
croppedCanvas.height = croppedHeight;
croppedCtx.putImageData(cuttedImageData, 0, 0);
return croppedCanvas.toDataURL();
}
function 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>
<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>
<!-- 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>
<span class="btn-tooltip" th:text="#{sign.delete}"></span>
</button>
<button class="btn btn-outline-secondary"
onclick="DraggableUtils.addAllPagesDraggableCanvas(DraggableUtils.getLastInteracted())">
<span class="material-symbols-rounded">
content_copy
</span>
<span class="btn-tooltip" th:text="#{sign.addToAll}"></span>
</button>
<button class="btn btn-outline-secondary" onclick="goToFirstOrLastPage(false)" style="margin-left:auto">
<span class="material-symbols-rounded">
keyboard_double_arrow_left
</span>
<span class="btn-tooltip" th:text="#{sign.first}"></span>
</button>
<button class="btn btn-outline-secondary" id="incrementPage"
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()">
<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>
<span class="btn-tooltip" th:text="#{sign.previous}"></span>
</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">
<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>
<span class="btn-tooltip" th:text="#{sign.next}"></span>
</button>
<button class="btn btn-outline-secondary" onclick="goToFirstOrLastPage(true)">
<span class="material-symbols-rounded">
keyboard_double_arrow_right
</span>
<span class="btn-tooltip" th:text="#{sign.last}"></span>
</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>
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>
<!-- Link the draggable.js file --> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
<script th:src="@{'/js/draggable.js'}"></script> </div>
</body> <!-- Link the draggable.js file -->
<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="frooodle/s-pdf" local image_base="stirlingtools/stirling-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"