diff --git a/.dockerignore b/.dockerignore index 54b49fd807..50f7376686 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,7 @@ frontend/dist frontend/build frontend/.vite frontend/.tauri +frontend/src-tauri/target # Gradle build artifacts .gradle diff --git a/.github/workflows/PR-Demo-Comment-with-react.yml b/.github/workflows/PR-Demo-Comment-with-react.yml index 82f1e0140e..bfde13275f 100644 --- a/.github/workflows/PR-Demo-Comment-with-react.yml +++ b/.github/workflows/PR-Demo-Comment-with-react.yml @@ -180,7 +180,7 @@ jobs: uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . - file: ./Dockerfile + file: ./docker/embedded/Dockerfile push: true tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }} build-args: VERSION_TAG=alpha diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03f684c4f0..9a049bb90c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -262,7 +262,7 @@ jobs: strategy: fail-fast: false matrix: - docker-rev: ["Dockerfile", "Dockerfile.ultra-lite", "Dockerfile.fat"] + docker-rev: ["docker/embedded/Dockerfile", "docker/embedded/Dockerfile.ultra-lite", "docker/embedded/Dockerfile.fat"] steps: - name: Harden Runner uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 @@ -301,7 +301,7 @@ jobs: with: builder: ${{ steps.buildx.outputs.name }} context: . - file: ./docker/backend/${{ matrix.docker-rev }} + file: ./${{ matrix.docker-rev }} push: false cache-from: type=gha cache-to: type=gha,mode=max diff --git a/.github/workflows/push-docker-v2.yml b/.github/workflows/push-docker-v2.yml index 23d89ef880..5f2b70f50d 100644 --- a/.github/workflows/push-docker-v2.yml +++ b/.github/workflows/push-docker-v2.yml @@ -5,6 +5,7 @@ on: push: branches: - V2-master + - alljavadocker # cancel in-progress jobs if a new job is triggered # This is useful to avoid running multiple builds for the same branch if a new commit is pushed @@ -93,10 +94,10 @@ jobs: type=raw,value=${{ steps.versionNumber.outputs.versionNumber }} type=raw,value=latest - - name: Generate tags for latest (V2-demo branch - test) + - name: Generate tags for latest (alljavadocker branch - test) id: meta-test uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 - if: github.ref == 'refs/heads/V2-demo' + if: github.ref == 'refs/heads/alljavadocker' with: images: | ghcr.io/stirling-tools/stirling-pdf-test @@ -110,7 +111,7 @@ jobs: with: builder: ${{ steps.buildx.outputs.name }} context: . - file: ./docker/Dockerfile.unified + file: ./docker/embedded/Dockerfile push: true cache-from: type=gha cache-to: type=gha,mode=max @@ -149,10 +150,10 @@ jobs: type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat type=raw,value=latest-fat - - name: Generate tags for latest-fat (V2-demo branch - test) + - name: Generate tags for latest-fat (alljavadocker branch - test) id: meta-fat-test uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 - if: github.ref == 'refs/heads/V2-demo' + if: github.ref == 'refs/heads/alljavadocker' with: images: | ghcr.io/stirling-tools/stirling-pdf-test @@ -166,7 +167,7 @@ jobs: with: builder: ${{ steps.buildx.outputs.name }} context: . - file: ./docker/Dockerfile.unified + file: ./docker/embedded/Dockerfile.fat push: true cache-from: type=gha cache-to: type=gha,mode=max @@ -203,10 +204,10 @@ jobs: type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite type=raw,value=latest-ultra-lite - - name: Generate tags for ultra-lite (V2-demo branch - test) + - name: Generate tags for ultra-lite (alljavadocker branch - test) id: meta-lite-test uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 - if: github.ref == 'refs/heads/V2-demo' + if: github.ref == 'refs/heads/alljavadocker' with: images: | ghcr.io/stirling-tools/stirling-pdf-test @@ -220,7 +221,7 @@ jobs: with: builder: ${{ steps.buildx.outputs.name }} context: . - file: ./docker/Dockerfile.unified-lite + file: ./docker/embedded/Dockerfile.ultra-lite push: true cache-from: type=gha cache-to: type=gha,mode=max diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml index 48e3514333..3f2a0c8d07 100644 --- a/.github/workflows/push-docker.yml +++ b/.github/workflows/push-docker.yml @@ -107,7 +107,7 @@ jobs: with: builder: ${{ steps.buildx.outputs.name }} context: . - file: ./Dockerfile + file: ./docker/embedded/Dockerfile push: true cache-from: type=gha cache-to: type=gha,mode=max @@ -152,7 +152,7 @@ jobs: if: github.ref != 'refs/heads/main' with: context: . - file: ./Dockerfile.ultra-lite + file: ./docker/embedded/Dockerfile.ultra-lite push: true cache-from: type=gha cache-to: type=gha,mode=max @@ -183,7 +183,7 @@ jobs: with: builder: ${{ steps.buildx.outputs.name }} context: . - file: ./Dockerfile.fat + file: ./docker/embedded/Dockerfile.fat push: true cache-from: type=gha cache-to: type=gha,mode=max diff --git a/.github/workflows/testdriver.yml b/.github/workflows/testdriver.yml index 2bd47bee34..828c84d621 100644 --- a/.github/workflows/testdriver.yml +++ b/.github/workflows/testdriver.yml @@ -66,7 +66,7 @@ jobs: uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . - file: ./Dockerfile + file: ./docker/embedded/Dockerfile push: true tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:test-${{ github.sha }} build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} diff --git a/README.md b/README.md index b5a58362d2..d9e0d5a418 100644 --- a/README.md +++ b/README.md @@ -1,173 +1,69 @@ -

-

Stirling-PDF

+

+ Stirling PDF logo +

-[![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf) -[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/HYmhKj45pU) -[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Stirling-Tools/Stirling-PDF/badge)](https://scorecard.dev/viewer/?uri=github.com/Stirling-Tools/Stirling-PDF) -[![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf) +

Stirling PDF - The Open-Source PDF Platform

-Stirling PDF - Open source locally hosted web PDF editor | Product Hunt -[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af) +Stirling PDF is a powerful, open-source PDF editing platform. Run it as a personal desktop app, in the browser, or deploy it on your own servers with a private API. Edit, sign, redact, convert, and automate PDFs without sending documents to external services. -[Stirling-PDF](https://www.stirlingpdf.com) is a robust, locally hosted web-based PDF manipulation tool using Docker. It enables you to carry out various operations on PDF files, including splitting, merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application has evolved to encompass a comprehensive set of features, addressing all your PDF requirements. +

+ + Docker Pulls + + + Discord + + + OpenSSF Scorecard + + + GitHub Repo stars + +

-All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point. +![Stirling PDF - Dashboard](images/home-light.png) -Homepage: [https://stirlingpdf.com](https://stirlingpdf.com) +## Key Capabilities -All documentation available at [https://docs.stirlingpdf.com/](https://docs.stirlingpdf.com/) +- **Everywhere you work** - Desktop client, browser UI, and self-hosted server with a private API. +- **50+ PDF tools** - Edit, merge, split, sign, redact, convert, OCR, compress, and more. +- **Automation & workflows** - No-code pipelines direct in UI with APIs to process millions of PDFs. +- **Enterprise‑grade** - SSO, auditing, and flexible on‑prem deployments. +- **Developer platform** - REST APIs available for nearly all tools to integrate into your existing systems. +- **Global UI** - Interface available in 40+ languages. -![stirling-home](images/stirling-home.jpg) +For a full feature list, see the docs: **https://docs.stirlingpdf.com** -## Features +## Quick Start -- 50+ PDF Operations -- Parallel file processing and downloads -- Dark mode support -- Custom download options -- Custom 'Pipelines' to run multiple features in a automated queue -- API for integration with external scripts -- Optional Login and Authentication support (see [here](https://docs.stirlingpdf.com/Advanced%20Configuration/System%20and%20Security) for documentation) -- Database Backup and Import (see [here](https://docs.stirlingpdf.com/Advanced%20Configuration/DATABASE) for documentation) -- Enterprise features like SSO (see [here](https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration) for documentation) +```bash +docker run -p 8080:8080 docker.stirlingpdf.com/stirlingtools/stirling-pdf +``` -## PDF Features +Then open: http://localhost:8080 -### Page Operations +For full installation options (including desktop and Kubernetes), see our [Documentation Guide](https://docs.stirlingpdf.com/#documentation-guide). -- View and modify PDFs - View multi-page PDFs with custom viewing, sorting, and searching. Plus, on-page edit features like annotating, drawing, and adding text and images. (Using PDF.js with Joxit and Liberation fonts) -- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages -- Merge multiple PDFs into a single resultant file -- Split PDFs into multiple files at specified page numbers or extract all pages as individual files -- Reorganize PDF pages into different orders -- Rotate PDFs in 90-degree increments -- Remove pages -- Multi-page layout (format PDFs into a multi-paged page) -- Scale page contents size by set percentage -- Adjust contrast -- Crop PDF -- Auto-split PDF (with physically scanned page dividers) -- Extract page(s) -- Convert PDF to a single page -- Overlay PDFs on top of each other -- PDF to a single page -- Split PDF by sections +## Resources -### Conversion Operations +- [**Documentation**](https://docs.stirlingpdf.com) +- [**Homepage**](https://stirling.com) +- [**API Docs**](https://registry.scalar.com/@stirlingpdf/apis/stirling-pdf-processing-api/) +- [**Server Plan & Enterprise**](https://docs.stirlingpdf.com/Paid-Offerings) -- Convert PDFs to and from images -- Convert any common file to PDF (using LibreOffice) -- Convert PDF to Word/PowerPoint/others (using LibreOffice) -- Convert HTML to PDF -- Convert PDF to XML -- Convert PDF to CSV -- URL to PDF -- Markdown to PDF +## Support -### Security & Permissions +- **Community** [Discord](https://discord.gg/HYmhKj45pU) +- **Bug Reports**: [Github issues](https://github.com/Stirling-Tools/Stirling-PDF/issues) -- Add and remove passwords -- Change/set PDF permissions -- Add watermark(s) -- Certify/sign PDFs -- Sanitize PDFs -- Auto-redact text +## Contributing -### Other Operations +We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. -- Add/generate/write signatures -- Split by Size or PDF -- Repair PDFs -- Detect and remove blank pages -- Compare two PDFs and show differences in text -- Add images to PDFs -- Compress PDFs to decrease their filesize (using qpdf) -- Extract images from PDF -- Remove images from PDF -- Extract images from scans -- Remove annotations -- Add page numbers -- Auto-rename files by detecting PDF header text -- OCR on PDF (using Tesseract OCR) -- PDF/A conversion (using LibreOffice) -- Edit metadata -- Flatten PDFs -- Get all information on a PDF to view or export as JSON -- Show/detect embedded JavaScript +For development setup, see the [Developer Guide](DeveloperGuide.md). +For adding translations, see the [Translation Guide](devGuide/HowToAddNewLanguage.md). +## License -# 📖 Get Started - -Visit our comprehensive documentation at [docs.stirlingpdf.com](https://docs.stirlingpdf.com) for: - -- Installation guides for all platforms -- Configuration options -- Feature documentation -- API reference -- Security setup -- Enterprise features - - -## Supported Languages - -Stirling-PDF currently supports 40 languages! - -| Language | Progress | -| -------------------------------------------- | -------------------------------------- | -| Arabic (العربية) (ar_AR) | ![87%](https://geps.dev/progress/87) | -| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![86%](https://geps.dev/progress/86) | -| Basque (Euskara) (eu_ES) | ![86%](https://geps.dev/progress/86) | -| Bulgarian (Български) (bg_BG) | ![86%](https://geps.dev/progress/86) | -| Catalan (Català) (ca_CA) | ![85%](https://geps.dev/progress/85) | -| Croatian (Hrvatski) (hr_HR) | ![86%](https://geps.dev/progress/86) | -| Czech (Česky) (cs_CZ) | ![84%](https://geps.dev/progress/84) | -| Danish (Dansk) (da_DK) | ![85%](https://geps.dev/progress/85) | -| Dutch (Nederlands) (nl_NL) | ![85%](https://geps.dev/progress/85) | -| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | -| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | -| French (Français) (fr_FR) | ![85%](https://geps.dev/progress/85) | -| German (Deutsch) (de_DE) | ![86%](https://geps.dev/progress/86) | -| Greek (Ελληνικά) (el_GR) | ![86%](https://geps.dev/progress/86) | -| Hindi (हिंदी) (hi_IN) | ![86%](https://geps.dev/progress/86) | -| Hungarian (Magyar) (hu_HU) | ![86%](https://geps.dev/progress/86) | -| Indonesian (Bahasa Indonesia) (id_ID) | ![85%](https://geps.dev/progress/85) | -| Irish (Gaeilge) (ga_IE) | ![86%](https://geps.dev/progress/86) | -| Italian (Italiano) (it_IT) | ![85%](https://geps.dev/progress/85) | -| Japanese (日本語) (ja_JP) | ![86%](https://geps.dev/progress/86) | -| Korean (한국어) (ko_KR) | ![86%](https://geps.dev/progress/86) | -| Norwegian (Norsk) (no_NB) | ![86%](https://geps.dev/progress/86) | -| Persian (فارسی) (fa_IR) | ![86%](https://geps.dev/progress/86) | -| Polish (Polski) (pl_PL) | ![86%](https://geps.dev/progress/86) | -| Portuguese (Português) (pt_PT) | ![86%](https://geps.dev/progress/86) | -| Portuguese Brazilian (Português) (pt_BR) | ![86%](https://geps.dev/progress/86) | -| Romanian (Română) (ro_RO) | ![85%](https://geps.dev/progress/85) | -| Russian (Русский) (ru_RU) | ![86%](https://geps.dev/progress/86) | -| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![86%](https://geps.dev/progress/86) | -| Simplified Chinese (简体中文) (zh_CN) | ![87%](https://geps.dev/progress/87) | -| Slovakian (Slovensky) (sk_SK) | ![86%](https://geps.dev/progress/86) | -| Slovenian (Slovenščina) (sl_SI) | ![86%](https://geps.dev/progress/86) | -| Spanish (Español) (es_ES) | ![86%](https://geps.dev/progress/86) | -| Swedish (Svenska) (sv_SE) | ![86%](https://geps.dev/progress/86) | -| Thai (ไทย) (th_TH) | ![86%](https://geps.dev/progress/86) | -| Tibetan (བོད་ཡིག་) (bo_CN) | ![65%](https://geps.dev/progress/65) | -| Traditional Chinese (繁體中文) (zh_TW) | ![87%](https://geps.dev/progress/87) | -| Turkish (Türkçe) (tr_TR) | ![86%](https://geps.dev/progress/86) | -| Ukrainian (Українська) (uk_UA) | ![86%](https://geps.dev/progress/86) | -| Vietnamese (Tiếng Việt) (vi_VN) | ![86%](https://geps.dev/progress/86) | -| Malayalam (മലയാളം) (ml_IN) | ![73%](https://geps.dev/progress/73) | - -## Stirling PDF Enterprise - -Stirling PDF offers an Enterprise edition of its software. This is the same great software but with added features, support and comforts. -Check out our [Enterprise docs](https://docs.stirlingpdf.com/Pro) - - -## 🤝 Looking to contribute? - -Join our community: -- [Contribution Guidelines](CONTRIBUTING.md) -- [Translation Guide (How to add custom languages)](devGuide/HowToAddNewLanguage.md) -- [Developer Guide](devGuide/DeveloperGuide.md) -- [Issue Tracker](https://github.com/Stirling-Tools/Stirling-PDF/issues) -- [Discord Community](https://discord.gg/HYmhKj45pU) +Stirling PDF is open-core. See [LICENSE](LICENSE) for details. diff --git a/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java b/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java index 2dd3d420a5..a0adec7de2 100644 --- a/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java @@ -39,6 +39,7 @@ public class RequestUriUtils { // Specific static files bundled with the frontend if (normalizedUri.equals("/robots.txt") || normalizedUri.equals("/favicon.ico") + || normalizedUri.equals("/manifest.json") || normalizedUri.equals("/site.webmanifest") || normalizedUri.equals("/manifest-classic.json") || normalizedUri.equals("/index.html")) { diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java index daa6233ce8..424621b8ad 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java @@ -1,20 +1,89 @@ package stirling.software.SPDF.controller.web; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import jakarta.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import jakarta.servlet.http.HttpServletRequest; + @Controller public class ReactRoutingController { - @GetMapping( - "/{path:^(?!api|static|robots\\.txt|favicon\\.ico|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js)[^\\.]*$}") - public String forwardRootPaths() { - return "forward:/index.html"; + @Value("${server.servlet.context-path:/}") + private String contextPath; + + private String cachedIndexHtml; + private boolean indexHtmlExists = false; + + @PostConstruct + public void init() { + // Only cache if index.html exists (production builds) + ClassPathResource resource = new ClassPathResource("static/index.html"); + if (resource.exists()) { + try { + this.cachedIndexHtml = processIndexHtml(); + this.indexHtmlExists = true; + } catch (IOException e) { + // Failed to cache, will process on each request + this.indexHtmlExists = false; + } + } + } + + private String processIndexHtml() throws IOException { + ClassPathResource resource = new ClassPathResource("static/index.html"); + + try (InputStream inputStream = resource.getInputStream()) { + String html = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + + // Replace %BASE_URL% with the actual context path for base href + String baseUrl = contextPath.endsWith("/") ? contextPath : contextPath + "/"; + html = html.replace("%BASE_URL%", baseUrl); + // Also rewrite any existing tag (Vite may have baked one in) + html = + html.replaceFirst( + "", + ""); + + // Inject context path as a global variable for API calls + String contextPathScript = + ""; + html = html.replace("", contextPathScript + ""); + + return html; + } + } + + @GetMapping(value = {"/", "/index.html"}, produces = MediaType.TEXT_HTML_VALUE) + public ResponseEntity serveIndexHtml(HttpServletRequest request) + throws IOException { + if (indexHtmlExists && cachedIndexHtml != null) { + return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(cachedIndexHtml); + } + // Fallback: process on each request (dev mode or cache failed) + return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(processIndexHtml()); } @GetMapping( - "/{path:^(?!api|static|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js)[^\\.]*}/{subpath:^(?!.*\\.).*$}") - public String forwardNestedPaths() { - return "forward:/index.html"; + "/{path:^(?!api|static|robots\\.txt|favicon\\.ico|manifest.*\\.json|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*$}") + public ResponseEntity forwardRootPaths(HttpServletRequest request) + throws IOException { + return serveIndexHtml(request); + } + + @GetMapping( + "/{path:^(?!api|static|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*}/{subpath:^(?!.*\\.).*$}") + public ResponseEntity forwardNestedPaths(HttpServletRequest request) + throws IOException { + return serveIndexHtml(request); } } diff --git a/app/core/src/main/java/stirling/software/SPDF/service/SharedSignatureService.java b/app/core/src/main/java/stirling/software/SPDF/service/SharedSignatureService.java index 6c349581db..7043118a46 100644 --- a/app/core/src/main/java/stirling/software/SPDF/service/SharedSignatureService.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/SharedSignatureService.java @@ -179,7 +179,7 @@ public class SharedSignatureService { StandardOpenOption.TRUNCATE_EXISTING); // Store reference to image file - response.setDataUrl("/api/v1/general/sign/" + imageFileName); + response.setDataUrl("/api/v1/general/signatures/" + imageFileName); } log.info("Saved signature {} for user {}", request.getId(), username); @@ -207,7 +207,7 @@ public class SharedSignatureService { sig.setLabel(id); // Use ID as label sig.setType("image"); // Default type sig.setScope("personal"); - sig.setDataUrl("/api/v1/general/sign/" + fileName); + sig.setDataUrl("/api/v1/general/signatures/" + fileName); sig.setCreatedAt( Files.getLastModifiedTime(path).toMillis()); sig.setUpdatedAt( @@ -238,7 +238,7 @@ public class SharedSignatureService { sig.setLabel(id); // Use ID as label sig.setType("image"); // Default type sig.setScope("shared"); - sig.setDataUrl("/api/v1/general/sign/" + fileName); + sig.setDataUrl("/api/v1/general/signatures/" + fileName); sig.setCreatedAt( Files.getLastModifiedTime(path).toMillis()); sig.setUpdatedAt( diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/SignatureController.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/SignatureController.java index f42b23a972..a073c21371 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/SignatureController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/SignatureController.java @@ -1,7 +1,12 @@ package stirling.software.proprietary.controller.api; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -18,6 +23,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import stirling.software.common.annotations.api.UserApi; +import stirling.software.common.configuration.InstallationPathConfig; import stirling.software.proprietary.model.api.signature.SavedSignatureRequest; import stirling.software.proprietary.model.api.signature.SavedSignatureResponse; import stirling.software.proprietary.security.service.UserService; @@ -38,6 +44,7 @@ public class SignatureController { private final SignatureService signatureService; private final UserService userService; + private static final String ALL_USERS_FOLDER = "ALL_USERS"; /** * Save a new signature for the authenticated user. Enforces storage limits and authentication @@ -84,19 +91,105 @@ public class SignatureController { } /** - * Delete a signature owned by the authenticated user. Users can only delete their own personal - * signatures, not shared ones. + * Update a signature label. Users can update labels for their own personal signatures and for + * shared signatures. + */ + @PostMapping("/{signatureId}/label") + @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") + public ResponseEntity updateSignatureLabel( + @PathVariable String signatureId, @RequestBody Map body) { + try { + String username = userService.getCurrentUsername(); + String newLabel = body.get("label"); + + if (newLabel == null || newLabel.trim().isEmpty()) { + log.warn("Invalid label update request"); + return ResponseEntity.badRequest().build(); + } + + signatureService.updateSignatureLabel(username, signatureId, newLabel); + log.info("User {} updated label for signature {}", username, signatureId); + return ResponseEntity.noContent().build(); + } catch (IOException e) { + log.warn("Failed to update signature label: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + } + + /** + * Delete a signature owned by the authenticated user. Users can delete their own personal + * signatures. Admins can also delete shared signatures. */ @DeleteMapping("/{signatureId}") + @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") public ResponseEntity deleteSignature(@PathVariable String signatureId) { try { String username = userService.getCurrentUsername(); - signatureService.deleteSignature(username, signatureId); - log.info("User {} deleted signature {}", username, signatureId); - return ResponseEntity.noContent().build(); + boolean isAdmin = userService.isCurrentUserAdmin(); + + // Validate filename to prevent path traversal + if (signatureId.contains("..") + || signatureId.contains("/") + || signatureId.contains("\\")) { + log.warn("Invalid signature ID: {}", signatureId); + return ResponseEntity.badRequest().build(); + } + + // Try to delete from personal folder first + try { + signatureService.deleteSignature(username, signatureId); + log.info("User {} deleted personal signature {}", username, signatureId); + return ResponseEntity.noContent().build(); + } catch (IOException e) { + // If not found in personal folder, check if it's in shared folder + if (isAdmin) { + // Admin can delete from shared folder + if (deleteFromSharedFolder(signatureId)) { + log.info("Admin {} deleted shared signature {}", username, signatureId); + return ResponseEntity.noContent().build(); + } + } + // If not admin or not found in shared folder either, return 404 + throw e; + } } catch (IOException e) { log.warn("Failed to delete signature {} for user: {}", signatureId, e.getMessage()); return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); } } + + /** + * Delete a signature from the shared (ALL_USERS) folder. Only admins should call this method. + */ + private boolean deleteFromSharedFolder(String signatureId) throws IOException { + String signatureBasePath = InstallationPathConfig.getSignaturesPath(); + Path sharedFolder = Paths.get(signatureBasePath, ALL_USERS_FOLDER); + boolean deleted = false; + + if (Files.exists(sharedFolder)) { + try (Stream stream = Files.list(sharedFolder)) { + List matchingFiles = + stream.filter( + path -> + path.getFileName() + .toString() + .startsWith(signatureId + ".")) + .toList(); + for (Path file : matchingFiles) { + Files.delete(file); + deleted = true; + log.info("Deleted shared signature file: {}", file); + } + } + + // Also delete metadata file if it exists + Path metadataPath = sharedFolder.resolve(signatureId + ".json"); + if (Files.exists(metadataPath)) { + Files.delete(metadataPath); + log.info("Deleted shared signature metadata: {}", metadataPath); + } + } + + return deleted; + } } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java index e683fa8dc5..a3a5eee4f0 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java @@ -324,10 +324,14 @@ public class SecurityConfiguration { .authenticated()); // Handle User/Password Logins if (securityProperties.isUserPass()) { + // v2: Authentication is handled via API (/api/v1/auth/login), not form login + // We configure form login to handle Spring Security redirects, + // but use /perform_login as the processing URL so /login remains a React route http.formLogin( formLogin -> formLogin - .loginPage("/login") + .loginPage("/login") // Redirect here when unauthenticated + .loginProcessingUrl("/perform_login") // Process form posts here (not /login) .successHandler( new CustomAuthenticationSuccessHandler( loginAttemptService, diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/SignatureService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/SignatureService.java index 6f849ebfe6..63ee285123 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/SignatureService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/SignatureService.java @@ -2,6 +2,7 @@ package stirling.software.proprietary.service; import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,6 +14,8 @@ import java.util.stream.Stream; import org.springframework.stereotype.Service; +import com.fasterxml.jackson.databind.ObjectMapper; + import lombok.extern.slf4j.Slf4j; import stirling.software.common.configuration.InstallationPathConfig; @@ -31,6 +34,7 @@ public class SignatureService implements PersonalSignatureServiceInterface { private final String SIGNATURE_BASE_PATH; private final String ALL_USERS_FOLDER = "ALL_USERS"; + private final ObjectMapper objectMapper = new ObjectMapper(); // Storage limits per user private static final int MAX_SIGNATURES_PER_USER = 20; @@ -88,6 +92,14 @@ public class SignatureService implements PersonalSignatureServiceInterface { response.setCreatedAt(timestamp); response.setUpdatedAt(timestamp); + // Copy text signature properties if present + if ("text".equals(request.getType())) { + response.setSignerName(request.getSignerName()); + response.setFontFamily(request.getFontFamily()); + response.setFontSize(request.getFontSize()); + response.setTextColor(request.getTextColor()); + } + // Extract and save image data String dataUrl = request.getDataUrl(); if (dataUrl != null && dataUrl.startsWith("data:image/")) { @@ -133,6 +145,19 @@ public class SignatureService implements PersonalSignatureServiceInterface { response.setDataUrl("/api/v1/general/signatures/" + imageFileName); } + // Save metadata JSON file + String metadataFileName = request.getId() + ".json"; + Path metadataPath = targetFolder.resolve(metadataFileName); + verifyPathWithinDirectory(metadataPath, targetFolder); + + String metadataJson = objectMapper.writeValueAsString(response); + Files.writeString( + metadataPath, + metadataJson, + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + log.info("Saved signature {} for user {} (scope: {})", request.getId(), username, scope); return response; } @@ -179,6 +204,13 @@ public class SignatureService implements PersonalSignatureServiceInterface { log.info("Deleted signature file: {}", file); } } + + // Also delete metadata file if it exists + Path metadataPath = personalFolder.resolve(signatureId + ".json"); + if (Files.exists(metadataPath)) { + Files.delete(metadataPath); + log.info("Deleted signature metadata: {}", metadataPath); + } } if (!deleted) { @@ -186,6 +218,50 @@ public class SignatureService implements PersonalSignatureServiceInterface { } } + /** Update a signature label. */ + public void updateSignatureLabel(String username, String signatureId, String newLabel) + throws IOException { + validateFileName(signatureId); + + // Try personal folder first + Path personalFolder = Paths.get(SIGNATURE_BASE_PATH, username); + Path metadataPath = personalFolder.resolve(signatureId + ".json"); + + if (Files.exists(metadataPath)) { + updateMetadataLabel(metadataPath, newLabel); + log.info("Updated label for personal signature {} (user: {})", signatureId, username); + return; + } + + // If not found in personal, try shared folder + Path sharedFolder = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER); + Path sharedMetadataPath = sharedFolder.resolve(signatureId + ".json"); + + if (Files.exists(sharedMetadataPath)) { + updateMetadataLabel(sharedMetadataPath, newLabel); + log.info("Updated label for shared signature {}", signatureId); + return; + } + + throw new FileNotFoundException("Signature metadata not found"); + } + + private void updateMetadataLabel(Path metadataPath, String newLabel) throws IOException { + String metadataJson = Files.readString(metadataPath, StandardCharsets.UTF_8); + SavedSignatureResponse sig = + objectMapper.readValue(metadataJson, SavedSignatureResponse.class); + sig.setLabel(newLabel); + sig.setUpdatedAt(System.currentTimeMillis()); + + String updatedJson = objectMapper.writeValueAsString(sig); + Files.writeString( + metadataPath, + updatedJson, + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } + // Private helper methods private void enforceStorageLimits(String username, String dataUrlToAdd) throws IOException { @@ -245,16 +321,31 @@ public class SignatureService implements PersonalSignatureServiceInterface { String fileName = path.getFileName().toString(); String id = fileName.substring(0, fileName.lastIndexOf('.')); - SavedSignatureResponse sig = new SavedSignatureResponse(); - sig.setId(id); - sig.setLabel(id); - sig.setType("image"); - sig.setScope(scope); - sig.setCreatedAt(Files.getLastModifiedTime(path).toMillis()); - sig.setUpdatedAt(Files.getLastModifiedTime(path).toMillis()); + // Try to load metadata from JSON file + Path metadataPath = folder.resolve(id + ".json"); + SavedSignatureResponse sig; - // Set unified URL path (works for both personal and shared) - sig.setDataUrl("/api/v1/general/signatures/" + fileName); + if (Files.exists(metadataPath)) { + // Load from metadata file + String metadataJson = + Files.readString( + metadataPath, StandardCharsets.UTF_8); + sig = + objectMapper.readValue( + metadataJson, SavedSignatureResponse.class); + } else { + // Fallback for old signatures without metadata + sig = new SavedSignatureResponse(); + sig.setId(id); + sig.setLabel(id); + sig.setType("image"); + sig.setScope(scope); + sig.setCreatedAt( + Files.getLastModifiedTime(path).toMillis()); + sig.setUpdatedAt( + Files.getLastModifiedTime(path).toMillis()); + sig.setDataUrl("/api/v1/general/signatures/" + fileName); + } signatures.add(sig); } catch (IOException e) { diff --git a/docker/embedded/Dockerfile b/docker/embedded/Dockerfile new file mode 100644 index 0000000000..a38aee9b4a --- /dev/null +++ b/docker/embedded/Dockerfile @@ -0,0 +1,138 @@ +# Stirling-PDF Dockerfile - Full version with embedded frontend +# Single JAR contains both frontend and backend + +# Stage 1: Build application with embedded frontend +FROM gradle:8.14-jdk21 AS build + +# Install Node.js and npm for frontend build +RUN apt-get update && apt-get install -y \ + curl \ + && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs \ + && npm --version \ + && node --version \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Copy gradle files for dependency resolution +COPY build.gradle . +COPY settings.gradle . +COPY gradlew . +COPY gradle gradle/ +COPY app/core/build.gradle core/. +COPY app/common/build.gradle common/. +COPY app/proprietary/build.gradle proprietary/. +RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0 + +# Set working directory +WORKDIR /app + +# Copy entire project +COPY . . + +# Build JAR with embedded frontend (includes security features controlled at runtime) +RUN DISABLE_ADDITIONAL_FEATURES=false \ + STIRLING_PDF_DESKTOP_UI=false \ + ./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube + +# Stage 2: Runtime image +FROM alpine:3.22.1 + +ARG VERSION_TAG + +# Labels +LABEL org.opencontainers.image.title="Stirling-PDF" +LABEL org.opencontainers.image.description="Stirling-PDF with embedded frontend - Full version" +LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF" +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.vendor="Stirling-Tools" +LABEL org.opencontainers.image.url="https://www.stirlingpdf.com" +LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com" +LABEL maintainer="Stirling-Tools" +LABEL org.opencontainers.image.authors="Stirling-Tools" +LABEL org.opencontainers.image.version="${VERSION_TAG}" +LABEL org.opencontainers.image.keywords="PDF, manipulation, API, Spring Boot, React" + +# Copy scripts and fonts +COPY scripts /scripts +COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ + +# Copy built JAR from build stage +COPY --from=build /app/app/core/build/libs/*.jar /app.jar +COPY --from=build /app/build/libs/restart-helper.jar /restart-helper.jar + +# Environment Variables +ENV VERSION_TAG=$VERSION_TAG \ + JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \ + JAVA_CUSTOM_OPTS="" \ + HOME=/home/stirlingpdfuser \ + PUID=1000 \ + PGID=1000 \ + UMASK=022 \ + PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \ + UNO_PATH=/usr/lib/libreoffice/program \ + URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \ + PATH=$PATH:/opt/venv/bin \ + STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ + TMPDIR=/tmp/stirling-pdf \ + TEMP=/tmp/stirling-pdf \ + TMP=/tmp/stirling-pdf + +# Install all dependencies +RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ + echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ + echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ + apk upgrade --no-cache -a && \ + apk add --no-cache \ + ca-certificates \ + tzdata \ + tini \ + bash \ + curl \ + shadow \ + su-exec \ + openssl \ + openssl-dev \ + openjdk21-jre \ + # Doc conversion + gcompat \ + libc6-compat \ + libreoffice \ + ghostscript \ + fontforge \ + # pdftohtml + poppler-utils \ + # OCR MY PDF + unpaper \ + tesseract-ocr-data-eng \ + tesseract-ocr-data-chi_sim \ + tesseract-ocr-data-deu \ + tesseract-ocr-data-fra \ + tesseract-ocr-data-por \ + ocrmypdf \ + # CV + py3-opencv \ + python3 \ + py3-pip \ + py3-pillow@testing \ + py3-pdf2image@testing && \ + python3 -m venv /opt/venv && \ + /opt/venv/bin/pip install --upgrade pip setuptools && \ + /opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \ + ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \ + ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ + ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \ + mv /usr/share/tessdata /usr/share/tessdata-original && \ + mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \ + fc-cache -f -v && \ + chmod +x /scripts/* && \ + # User permissions + addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ + chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /pipeline /usr/share/fonts/opentype/noto /configs /customFiles /tmp/stirling-pdf && \ + chown stirlingpdfuser:stirlingpdfgroup /app.jar /restart-helper.jar + +EXPOSE 8080/tcp + +# Set user and run command +ENTRYPOINT ["tini", "--", "/scripts/init.sh"] +CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"] diff --git a/docker/embedded/Dockerfile.fat b/docker/embedded/Dockerfile.fat new file mode 100644 index 0000000000..67e648aee0 --- /dev/null +++ b/docker/embedded/Dockerfile.fat @@ -0,0 +1,142 @@ +# Stirling-PDF Dockerfile - Fat version with embedded frontend +# Single JAR contains both frontend and backend with extra fonts for air-gapped environments + +# Stage 1: Build application with embedded frontend +FROM gradle:8.14-jdk21 AS build + +# Install Node.js and npm for frontend build +RUN apt-get update && apt-get install -y \ + curl \ + && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs \ + && npm --version \ + && node --version \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Copy gradle files for dependency resolution +COPY build.gradle . +COPY settings.gradle . +COPY gradlew . +COPY gradle gradle/ +COPY app/core/build.gradle core/. +COPY app/common/build.gradle common/. +COPY app/proprietary/build.gradle proprietary/. +RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0 + +# Set working directory +WORKDIR /app + +# Copy entire project +COPY . . + +# Build JAR with embedded frontend (includes security features controlled at runtime) +RUN DISABLE_ADDITIONAL_FEATURES=false \ + STIRLING_PDF_DESKTOP_UI=false \ + ./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube + +# Stage 2: Runtime image +FROM alpine:3.22.1 + +ARG VERSION_TAG + +# Labels +LABEL org.opencontainers.image.title="Stirling-PDF Fat" +LABEL org.opencontainers.image.description="Stirling-PDF with embedded frontend - Fat version with extra fonts for air-gapped environments" +LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF" +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.vendor="Stirling-Tools" +LABEL org.opencontainers.image.url="https://www.stirlingpdf.com" +LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com" +LABEL maintainer="Stirling-Tools" +LABEL org.opencontainers.image.authors="Stirling-Tools" +LABEL org.opencontainers.image.version="${VERSION_TAG}" +LABEL org.opencontainers.image.keywords="PDF, manipulation, fat, air-gapped, API, Spring Boot, React" + +# Copy scripts and fonts +COPY scripts /scripts +COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ + +# Copy built JAR from build stage +COPY --from=build /app/app/core/build/libs/*.jar /app.jar +COPY --from=build /app/build/libs/restart-helper.jar /restart-helper.jar + +# Environment Variables +ENV VERSION_TAG=$VERSION_TAG \ + JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \ + JAVA_CUSTOM_OPTS="" \ + HOME=/home/stirlingpdfuser \ + PUID=1000 \ + PGID=1000 \ + UMASK=022 \ + FAT_DOCKER=true \ + INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \ + PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \ + UNO_PATH=/usr/lib/libreoffice/program \ + URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \ + PATH=$PATH:/opt/venv/bin \ + STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ + TMPDIR=/tmp/stirling-pdf \ + TEMP=/tmp/stirling-pdf \ + TMP=/tmp/stirling-pdf + +# Install all dependencies plus extra fonts for air-gapped environments +RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ + echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ + echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ + apk upgrade --no-cache -a && \ + apk add --no-cache \ + ca-certificates \ + tzdata \ + tini \ + bash \ + curl \ + shadow \ + su-exec \ + openssl \ + openssl-dev \ + openjdk21-jre \ + # Doc conversion + gcompat \ + libc6-compat \ + libreoffice \ + ghostscript \ + fontforge \ + # pdftohtml + poppler-utils \ + # OCR MY PDF + unpaper \ + tesseract-ocr-data-eng \ + tesseract-ocr-data-chi_sim \ + tesseract-ocr-data-deu \ + tesseract-ocr-data-fra \ + tesseract-ocr-data-por \ + ocrmypdf \ + # Extra fonts for fat version + font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra font-liberation font-linux-libertine \ + # CV + py3-opencv \ + python3 \ + py3-pip \ + py3-pillow@testing \ + py3-pdf2image@testing && \ + python3 -m venv /opt/venv && \ + /opt/venv/bin/pip install --upgrade pip setuptools && \ + /opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \ + ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \ + ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ + ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \ + mv /usr/share/tessdata /usr/share/tessdata-original && \ + mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \ + fc-cache -f -v && \ + chmod +x /scripts/* && \ + # User permissions + addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ + chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /pipeline /usr/share/fonts/opentype/noto /configs /customFiles /tmp/stirling-pdf && \ + chown stirlingpdfuser:stirlingpdfgroup /app.jar /restart-helper.jar + +EXPOSE 8080/tcp + +# Set user and run command +ENTRYPOINT ["tini", "--", "/scripts/init.sh"] +CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"] diff --git a/docker/embedded/Dockerfile.ultra-lite b/docker/embedded/Dockerfile.ultra-lite new file mode 100644 index 0000000000..a39a522ebc --- /dev/null +++ b/docker/embedded/Dockerfile.ultra-lite @@ -0,0 +1,104 @@ +# Stirling-PDF Dockerfile - Ultra-lite version with embedded frontend +# Single JAR contains both frontend and backend with minimal dependencies + +# Stage 1: Build application with embedded frontend +FROM gradle:8.14-jdk21 AS build + +# Install Node.js and npm for frontend build +RUN apt-get update && apt-get install -y \ + curl \ + && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs \ + && npm --version \ + && node --version \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Copy gradle files for dependency resolution +COPY build.gradle . +COPY settings.gradle . +COPY gradlew . +COPY gradle gradle/ +COPY app/core/build.gradle core/. +COPY app/common/build.gradle common/. +COPY app/proprietary/build.gradle proprietary/. +RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0 + +# Set working directory +WORKDIR /app + +# Copy entire project +COPY . . + +# Build ultra-lite JAR with embedded frontend (minimal features) +RUN DISABLE_ADDITIONAL_FEATURES=true \ + STIRLING_PDF_DESKTOP_UI=false \ + ./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube + +# Stage 2: Runtime image +FROM alpine:3.22.1 + +ARG VERSION_TAG + +# Labels +LABEL org.opencontainers.image.title="Stirling-PDF Ultra-Lite" +LABEL org.opencontainers.image.description="Stirling-PDF with embedded frontend - Ultra-lite version with minimal dependencies" +LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF" +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.vendor="Stirling-Tools" +LABEL org.opencontainers.image.url="https://www.stirlingpdf.com" +LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com" +LABEL maintainer="Stirling-Tools" +LABEL org.opencontainers.image.authors="Stirling-Tools" +LABEL org.opencontainers.image.version="${VERSION_TAG}" +LABEL org.opencontainers.image.keywords="PDF, manipulation, ultra-lite, API, Spring Boot, React" + +# Copy scripts +COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh +COPY scripts/installFonts.sh /scripts/installFonts.sh + +# Copy built JAR from build stage +COPY --from=build /app/app/core/build/libs/*.jar /app.jar +COPY --from=build /app/build/libs/restart-helper.jar /restart-helper.jar + +# Environment Variables +ENV VERSION_TAG=$VERSION_TAG \ + JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \ + JAVA_CUSTOM_OPTS="" \ + HOME=/home/stirlingpdfuser \ + PUID=1000 \ + PGID=1000 \ + UMASK=022 \ + STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ + TMPDIR=/tmp/stirling-pdf \ + TEMP=/tmp/stirling-pdf \ + TMP=/tmp/stirling-pdf \ + ENDPOINTS_GROUPS_TO_REMOVE=CLI + +# Install minimal dependencies +RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ + echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ + echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ + apk upgrade --no-cache -a && \ + apk add --no-cache \ + ca-certificates \ + tzdata \ + tini \ + bash \ + curl \ + shadow \ + su-exec \ + openjdk21-jre && \ + mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \ + mkdir -p /usr/share/fonts/opentype/noto && \ + chmod +x /scripts/*.sh && \ + # User permissions + addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ + chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline /tmp/stirling-pdf && \ + chown stirlingpdfuser:stirlingpdfgroup /app.jar /restart-helper.jar + +EXPOSE 8080/tcp + +# Set user and run command +ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"] +CMD ["java", "-Dfile.encoding=UTF-8", "-Djava.io.tmpdir=/tmp/stirling-pdf", "-jar", "/app.jar"] diff --git a/docker/frontend/nginx.conf b/docker/frontend/nginx.conf index 3be5ec9005..ef74321efa 100644 --- a/docker/frontend/nginx.conf +++ b/docker/frontend/nginx.conf @@ -103,8 +103,8 @@ http { add_header Cache-Control "public, immutable"; } - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + # Cache static assets (but not API endpoints) + location ~* ^(?!/api/).*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } diff --git a/docker/unified/nginx.conf b/docker/unified/nginx.conf index 1e47b8619a..cbd5fee813 100644 --- a/docker/unified/nginx.conf +++ b/docker/unified/nginx.conf @@ -106,8 +106,8 @@ http { add_header Cache-Control "public, immutable"; } - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + # Cache static assets (but not API endpoints) + location ~* ^(?!/api/).*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } diff --git a/frontend/index.html b/frontend/index.html index 5dbaa18938..5042c6a41d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,21 +3,21 @@ - + - - + + Stirling PDF
- + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fb12a233e8..163d3f17b2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,26 +11,26 @@ "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.7.7", "@dnd-kit/core": "^6.3.1", - "@embedpdf/core": "^1.4.1", - "@embedpdf/engines": "^1.4.1", - "@embedpdf/plugin-annotation": "^1.4.1", - "@embedpdf/plugin-bookmark": "^1.4.1", - "@embedpdf/plugin-export": "^1.4.1", - "@embedpdf/plugin-history": "^1.4.1", - "@embedpdf/plugin-interaction-manager": "^1.4.1", - "@embedpdf/plugin-loader": "^1.4.1", - "@embedpdf/plugin-pan": "^1.4.1", - "@embedpdf/plugin-print": "^1.4.1", - "@embedpdf/plugin-render": "^1.4.1", - "@embedpdf/plugin-rotate": "^1.4.1", - "@embedpdf/plugin-scroll": "^1.4.1", - "@embedpdf/plugin-search": "^1.4.1", - "@embedpdf/plugin-selection": "^1.4.1", - "@embedpdf/plugin-spread": "^1.4.1", - "@embedpdf/plugin-thumbnail": "^1.4.1", - "@embedpdf/plugin-tiling": "^1.4.1", - "@embedpdf/plugin-viewport": "^1.4.1", - "@embedpdf/plugin-zoom": "^1.4.1", + "@embedpdf/core": "^1.5.0", + "@embedpdf/engines": "^1.5.0", + "@embedpdf/plugin-annotation": "^1.5.0", + "@embedpdf/plugin-bookmark": "^1.5.0", + "@embedpdf/plugin-export": "^1.5.0", + "@embedpdf/plugin-history": "^1.5.0", + "@embedpdf/plugin-interaction-manager": "^1.5.0", + "@embedpdf/plugin-loader": "^1.5.0", + "@embedpdf/plugin-pan": "^1.5.0", + "@embedpdf/plugin-print": "^1.5.0", + "@embedpdf/plugin-render": "^1.5.0", + "@embedpdf/plugin-rotate": "^1.5.0", + "@embedpdf/plugin-scroll": "^1.5.0", + "@embedpdf/plugin-search": "^1.5.0", + "@embedpdf/plugin-selection": "^1.5.0", + "@embedpdf/plugin-spread": "^1.5.0", + "@embedpdf/plugin-thumbnail": "^1.5.0", + "@embedpdf/plugin-tiling": "^1.5.0", + "@embedpdf/plugin-viewport": "^1.5.0", + "@embedpdf/plugin-zoom": "^1.5.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@iconify/react": "^6.0.2", @@ -559,13 +559,13 @@ "license": "0BSD" }, "node_modules/@embedpdf/core": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.4.1.tgz", - "integrity": "sha512-TGpxn2CvAKRnOJWJ3bsK+dKBiCp75ehxftRUmv7wAmPomhnG5XrDfoWJungvO+zbbqAwso6PocdeXINVt3hlAw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.5.0.tgz", + "integrity": "sha512-Yrh9XoVaT8cUgzgqpJ7hx5wg6BqQrCFirqqlSwVb+Ly9oNn4fZbR9GycIWmzJOU5XBnaOJjXfQSaDyoNP0woNA==", "license": "MIT", "dependencies": { - "@embedpdf/engines": "1.4.1", - "@embedpdf/models": "1.4.1" + "@embedpdf/engines": "1.5.0", + "@embedpdf/models": "1.5.0" }, "peerDependencies": { "preact": "^10.26.4", @@ -576,13 +576,13 @@ } }, "node_modules/@embedpdf/engines": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.4.1.tgz", - "integrity": "sha512-yugIb5OwTI/1VnAaEvSYxAd2DvYBPkV/D7wytagyaOq98o3sqzcY2Q9zHt+LhnawA5KKG1e/FDPjCd4qm8gsvg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.5.0.tgz", + "integrity": "sha512-/GzhjHFHWfOaX7vjgFJX/pyq668wYjoda1bZ9MpwF/EF000Wwy2Q0AOhprjldPFz8ASKjwKwqsXmaqrK99yOAQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1", - "@embedpdf/pdfium": "1.4.1" + "@embedpdf/models": "1.5.0", + "@embedpdf/pdfium": "1.5.0" }, "peerDependencies": { "preact": "^10.26.4", @@ -593,31 +593,31 @@ } }, "node_modules/@embedpdf/models": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.4.1.tgz", - "integrity": "sha512-2nTg8Q1qpplBvspZJXMCZOA+/OILpfdNRPddlplxZXY/Upx0rzKXx/e6pXWW7AuOgtfGneT4h9tMs3A595/PdQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.5.0.tgz", + "integrity": "sha512-x/1li3jdag+IzfZkcfRLKLqASLep4v6dgVi3z0JArwaicFra8k1IY2xaVTrwcZyx7pRb/rxvoO9yLHW0Y34NFw==", "license": "MIT" }, "node_modules/@embedpdf/pdfium": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.4.1.tgz", - "integrity": "sha512-BekKEK4UNCwzj7xOffKn6WpL0FQHxq+mTj2iGI3N7OwAX2J/BO2G+rDOB+lvojQG+Dkpg8uqm427ZKJDRyLgVQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.5.0.tgz", + "integrity": "sha512-PI32t2U4ThZC907n2Iwr8E5WqmC574G83u3V9ysNFl29N9kasrY9RiLSzU4W/yQvXPjIbpQHBsbMKXLjCFBI9w==", "license": "MIT" }, "node_modules/@embedpdf/plugin-annotation": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.4.1.tgz", - "integrity": "sha512-d4HibNy6ecyDqx2Y2R8VjaqppSdjNofAJmU6VenOd88wn080sAUqvnkeVJ6ehJH5BoND4ymQrcAkcbVeYK0myA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.5.0.tgz", + "integrity": "sha512-mxEPI6xYwOGaf9fYfoywuj6nwA10eHFPBuN066MzwphDk6DOHJGZ3Vq8zNQBXh20c/Lb25PL718D7MZWxZLUHg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1", - "@embedpdf/utils": "1.4.1" + "@embedpdf/models": "1.5.0", + "@embedpdf/utils": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", - "@embedpdf/plugin-history": "1.4.1", - "@embedpdf/plugin-interaction-manager": "1.4.1", - "@embedpdf/plugin-selection": "1.4.1", + "@embedpdf/core": "1.5.0", + "@embedpdf/plugin-history": "1.5.0", + "@embedpdf/plugin-interaction-manager": "1.5.0", + "@embedpdf/plugin-selection": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -625,15 +625,15 @@ } }, "node_modules/@embedpdf/plugin-bookmark": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-1.4.1.tgz", - "integrity": "sha512-WnfBJdv+Eq5zsMfwDZ5RlXZMGpvKm/ccL6jlTVwtELBhu3wvhjjbBmZdheEOzHMC3VXMNYDMjCeaXkUG4nWoDA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-1.5.0.tgz", + "integrity": "sha512-s3C9PtVesy5X8Ds/C9TEElFiqfKGRklG/uNPTROpNoolfpi0h7qX2xqqh/9+FzKH2nHjVcPB7Pp432v16h7eRA==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", + "@embedpdf/core": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -641,15 +641,15 @@ } }, "node_modules/@embedpdf/plugin-export": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.4.1.tgz", - "integrity": "sha512-g89fREFM/zkt2Ai2Q5dWwDkhXgC/JmVyUniaMgm1fTG/MZ0Z05E7f34DUzX/CKcJyVjxEgl6tojBTMeUbm15bA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.5.0.tgz", + "integrity": "sha512-luk68mNW9l2X31qk4b02phKaqDl9aDXUAgHVz1EWrgwXQ3Oz9WEdu60utYARYDiepDo3Caadll8RwctYSf/anA==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", + "@embedpdf/core": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -658,15 +658,15 @@ } }, "node_modules/@embedpdf/plugin-history": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.4.1.tgz", - "integrity": "sha512-5WLDiNMH6tACkLGGv/lJtNsDeozOhSbrh0mjD1btHun8u7Yscu/Vf8tdJRUOsd+nULivo2nQ2NFNKu0OTbVo8w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.5.0.tgz", + "integrity": "sha512-p7PTNNaIr4gH3jLwX+eLJe1DeUXgi21kVGN6SRx/pocH8esg4jqoOeD/YiRRZoZnPOiy0jBXVhkPkwSmY7a2hQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", + "@embedpdf/core": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -674,15 +674,15 @@ } }, "node_modules/@embedpdf/plugin-interaction-manager": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.4.1.tgz", - "integrity": "sha512-Ng02S9SFIAi9JZS5rI+NXSnZZ1Yk9YYRw4MlN2pig49qOyivZdz0oScZaYxQPewo8ccJkLeghjdeWswOBW/6cA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.5.0.tgz", + "integrity": "sha512-ckHgTfvkW6c5Ta7Mc+Dl9C2foVnvEpqEJ84wyBnqrU0OWbe/jsiPhyKBVeartMGqNI/kVfaQTXupyrKhekAVmg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", + "@embedpdf/core": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -691,15 +691,15 @@ } }, "node_modules/@embedpdf/plugin-loader": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.4.1.tgz", - "integrity": "sha512-m3ZOk8JygsLxoa4cZ+0BVB5pfRWuBCg2/gPqjhoFZNKTqAFw4J6HGUrhYKg94GRYe+w1cTJl/NbTBYuU5DOrsA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.5.0.tgz", + "integrity": "sha512-P4YpIZfaW69etYIjphyaL4cGl2pB14h3OdTE0tRQ2pZYZHFLTvlt4q9B3PVSdhlSrHK5nob7jfLGon2U7xCslg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", + "@embedpdf/core": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -708,17 +708,17 @@ } }, "node_modules/@embedpdf/plugin-pan": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.4.1.tgz", - "integrity": "sha512-zmOZJ9dUqXiaV0F5GPf/5WTWf3jAEkiv153Tl3x8HT9Rfff+WQhV48NruCIBAy/T4jVt4aH7D1zt/B/ftvcdkA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.5.0.tgz", + "integrity": "sha512-EMQ08dHqLkZmFVuLOO6h3AAinFPQoA1r6OlL9z+p0sswq31JAgd4X7+xjYIpI01z/V3+cTzPHzp7qwob5E4tbA==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", - "@embedpdf/plugin-interaction-manager": "1.4.1", - "@embedpdf/plugin-viewport": "1.4.1", + "@embedpdf/core": "1.5.0", + "@embedpdf/plugin-interaction-manager": "1.5.0", + "@embedpdf/plugin-viewport": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -727,15 +727,15 @@ } }, "node_modules/@embedpdf/plugin-print": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-1.4.1.tgz", - "integrity": "sha512-YEjU6rQVW8wb125JXl1wma95+JISwADZpfqZZOtvPBRABp6ce4byblDTNjWVmTYWSgKUZrlXCL3Ff3Ig+bjbjw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-1.5.0.tgz", + "integrity": "sha512-rjorvNxAZfO9X4cFZVU9fHnldMWqMceJGmr3mH+yj7KdHePvNDDP+omyZyZKtxlUZENaeDI2h6k5z0GbhBz6sQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", + "@embedpdf/core": "1.5.0", "preact": "^10.26.4", "react": ">=18.0.0", "react-dom": ">=18.0.0", @@ -744,15 +744,15 @@ } }, "node_modules/@embedpdf/plugin-render": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.4.1.tgz", - "integrity": "sha512-gKCdNKw6WBHBEpTc2DLBWIWOxzsNnaNbpfeY6C4f2Bum0EO+XW3Hl2oIx1uaRHjIhhnXso1J3QweqelsPwDGwg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.5.0.tgz", + "integrity": "sha512-ywwSj0ByrlkvrJIHKRzqxARkOZriki8VJUC+T4MV8fGyF4CzvCRJyKlPktahFz+VxhoodqTh7lBCib68dH+GvA==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", + "@embedpdf/core": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -761,15 +761,15 @@ } }, "node_modules/@embedpdf/plugin-rotate": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.4.1.tgz", - "integrity": "sha512-hVzHkKwMNH3tUhxqJGsj5qTLpYZXbj6E74AEcG0w/fz5FrK7EnofPqt0gRfYmIzxnQGIh+39BRtcp8gmx8UNnw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.5.0.tgz", + "integrity": "sha512-5EmBCsq0VfrE3xWY6ofuVm8S6aK95EbAycRIk1wczcmTdvpsuXZ6P2ZaECUgYMcpZ6uAg4/kGf8X8VVZuCihSQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", + "@embedpdf/core": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -778,16 +778,16 @@ } }, "node_modules/@embedpdf/plugin-scroll": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.4.1.tgz", - "integrity": "sha512-Y9O+matB4j4fLim5s/jn7qIi+lMC9vmDJRpJhiWe8bvD9oYLP2xfD/DdhFgAjRKcNhPoxC+j8q8QN5BMeGAv2Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.5.0.tgz", + "integrity": "sha512-RNmTZCZ8X1mA8cw9M7TMDuhO9GtkOalGha2bBL3En3D1IlDRS7PzNNMSMV7eqT7OQICSTltlpJ8p8Qi5esvL/Q==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", - "@embedpdf/plugin-viewport": "1.4.1", + "@embedpdf/core": "1.5.0", + "@embedpdf/plugin-viewport": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -796,16 +796,16 @@ } }, "node_modules/@embedpdf/plugin-search": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.4.1.tgz", - "integrity": "sha512-8JG4CbOcUsLuT0vHJJ4cECmu+Yn53EokWFUVXi2Mo/XvHjhrQuWmD7+y6s/qQPEpctFYWmUCXTDAX9ynPud+2Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.5.0.tgz", + "integrity": "sha512-TB5b0H8Iobx/azVUBIlG2ClaKtf0y3/Xi3E/iB8BwvkIE2+g6EGfp8IMXIn8WDXST6bbvJEP31Ab0Ilp6SVkiw==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", - "@embedpdf/plugin-loader": "1.4.1", + "@embedpdf/core": "1.5.0", + "@embedpdf/plugin-loader": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -814,17 +814,17 @@ } }, "node_modules/@embedpdf/plugin-selection": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.4.1.tgz", - "integrity": "sha512-lo5Ytk1PH0PrRKv6zKVupm4t02VGsqIrnSIeP6NO8Ujx0wfqEhj//sqIuO/EwfFVJD8lcQIP9UUo9y8baCrEog==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.5.0.tgz", + "integrity": "sha512-zrxLBAZQoPswDuf9q9DrYaQc6B0Ysc2U1hueTjNH/4+ydfl0BFXZkKR63C2e3YmWtXvKjkoIj0GyPzsiBORLUw==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", - "@embedpdf/plugin-interaction-manager": "1.4.1", - "@embedpdf/plugin-viewport": "1.4.1", + "@embedpdf/core": "1.5.0", + "@embedpdf/plugin-interaction-manager": "1.5.0", + "@embedpdf/plugin-viewport": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -833,16 +833,16 @@ } }, "node_modules/@embedpdf/plugin-spread": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.4.1.tgz", - "integrity": "sha512-l+SrDVGTiiItkt2cEtzv7V/X5HhmLbYHcQ8CFobGeIKdJtzKS1Nu/JSKqg7Ki7eCNgyPL1yMNfNE92bNKYVN4w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.5.0.tgz", + "integrity": "sha512-3EU5Cp+fPQSiMjvMR/P2kXxXry/RlnxHLs4JeskAaH95QcqWW3VD+DrHkWSiLFkdhI18rNNGNlMc5RvDGvbXGQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", - "@embedpdf/plugin-loader": "1.4.1", + "@embedpdf/core": "1.5.0", + "@embedpdf/plugin-loader": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -851,16 +851,16 @@ } }, "node_modules/@embedpdf/plugin-thumbnail": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.4.1.tgz", - "integrity": "sha512-bN3msjI0PovazgbPK3LyugYVTwIDo0RyBUhBaG42FgJxeY3hmFOWTPgfUH1QF7twHlySnksIvHRFYR3nViryVw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.5.0.tgz", + "integrity": "sha512-Z2qpyyr5s2M6460KDGu1Vk6rdbQFIoCpnyFAT6e7UaTIKkqJSNpmjqMsBU5PosYCFu/cClpHPvS7tg9/IKAk6g==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", - "@embedpdf/plugin-render": "1.4.1", + "@embedpdf/core": "1.5.0", + "@embedpdf/plugin-render": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -869,18 +869,18 @@ } }, "node_modules/@embedpdf/plugin-tiling": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.4.1.tgz", - "integrity": "sha512-wgTfj5T8HV6KP61iiR63DVNrbVp8sPxTqa1Sm+2/D0jY+EPSSCmpt1/qYWiAXd1X+t78foOjCnfbo7fEMn5/pg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.5.0.tgz", + "integrity": "sha512-0Vx9elHNpMM+zv8hEoZXBEm8Q0+4kU52LxOlTYRr1A5FskF836sUct6g1ngwK1bmfbAfpz+62PnYI2EeilDZig==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", - "@embedpdf/plugin-render": "1.4.1", - "@embedpdf/plugin-scroll": "1.4.1", - "@embedpdf/plugin-viewport": "1.4.1", + "@embedpdf/core": "1.5.0", + "@embedpdf/plugin-render": "1.5.0", + "@embedpdf/plugin-scroll": "1.5.0", + "@embedpdf/plugin-viewport": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -889,15 +889,15 @@ } }, "node_modules/@embedpdf/plugin-viewport": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.4.1.tgz", - "integrity": "sha512-+TgFHKPCLTBiDYe2DdsmTS37hwQgcZ3dYIc7bE0l5cp+GVwouu1h0MTmjL+90loizeWwCiu10E/zXR6hz+CUaQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.5.0.tgz", + "integrity": "sha512-G8GDyYRhfehw72+r4qKkydnA5+AU8qH67g01Y12b0DzI0VIzymh/05Z4dK8DsY3jyWPXJfw2hlg5+KDHaMBHgQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1" + "@embedpdf/models": "1.5.0" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", + "@embedpdf/core": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -906,19 +906,19 @@ } }, "node_modules/@embedpdf/plugin-zoom": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.4.1.tgz", - "integrity": "sha512-9HocmXnPZxqN06q7kyNAmLjgDHOEW8/8QfgNE3nMpRyNHIgnAjxvsWc9lApgp5ErDPG0cSDt0Cduil6nB3wSBQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.5.0.tgz", + "integrity": "sha512-LiDkCd5/IXg2CRORl1Yikan2op+AYXSxhHzCFatyBdwzVj+n4y9I74OwCI62Mar8WDAIMyXZDCQxGPToSm+zDw==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.4.1", + "@embedpdf/models": "1.5.0", "hammerjs": "^2.0.8" }, "peerDependencies": { - "@embedpdf/core": "1.4.1", - "@embedpdf/plugin-interaction-manager": "1.4.1", - "@embedpdf/plugin-scroll": "1.4.1", - "@embedpdf/plugin-viewport": "1.4.1", + "@embedpdf/core": "1.5.0", + "@embedpdf/plugin-interaction-manager": "1.5.0", + "@embedpdf/plugin-scroll": "1.5.0", + "@embedpdf/plugin-viewport": "1.5.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -927,9 +927,9 @@ } }, "node_modules/@embedpdf/utils": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.4.1.tgz", - "integrity": "sha512-vvJ51Qsz3PyJWR2YvDMMpJXg4+YqdV7Vn2cusmW9sx+4EnAiBiw0HevEE+FepgFV8k+A0WbwXzmsujDIQJ7R4A==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-L6jsAPQPGM8ne+MMFAd5gqXb1RNEgNyh16VvVUVKcVnJlBhwil59nVeEQ0cwPhjF5qVeY6MQDIOjBzJqkgXOYg==", "license": "MIT", "peerDependencies": { "preact": "^10.26.4", diff --git a/frontend/package.json b/frontend/package.json index 06400ff70b..ae40089511 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,26 +7,26 @@ "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.7.7", "@dnd-kit/core": "^6.3.1", - "@embedpdf/core": "^1.4.1", - "@embedpdf/engines": "^1.4.1", - "@embedpdf/plugin-annotation": "^1.4.1", - "@embedpdf/plugin-bookmark": "^1.4.1", - "@embedpdf/plugin-export": "^1.4.1", - "@embedpdf/plugin-history": "^1.4.1", - "@embedpdf/plugin-interaction-manager": "^1.4.1", - "@embedpdf/plugin-loader": "^1.4.1", - "@embedpdf/plugin-pan": "^1.4.1", - "@embedpdf/plugin-print": "^1.4.1", - "@embedpdf/plugin-render": "^1.4.1", - "@embedpdf/plugin-rotate": "^1.4.1", - "@embedpdf/plugin-scroll": "^1.4.1", - "@embedpdf/plugin-search": "^1.4.1", - "@embedpdf/plugin-selection": "^1.4.1", - "@embedpdf/plugin-spread": "^1.4.1", - "@embedpdf/plugin-thumbnail": "^1.4.1", - "@embedpdf/plugin-tiling": "^1.4.1", - "@embedpdf/plugin-viewport": "^1.4.1", - "@embedpdf/plugin-zoom": "^1.4.1", + "@embedpdf/core": "^1.5.0", + "@embedpdf/engines": "^1.5.0", + "@embedpdf/plugin-annotation": "^1.5.0", + "@embedpdf/plugin-bookmark": "^1.5.0", + "@embedpdf/plugin-export": "^1.5.0", + "@embedpdf/plugin-history": "^1.5.0", + "@embedpdf/plugin-interaction-manager": "^1.5.0", + "@embedpdf/plugin-loader": "^1.5.0", + "@embedpdf/plugin-pan": "^1.5.0", + "@embedpdf/plugin-print": "^1.5.0", + "@embedpdf/plugin-render": "^1.5.0", + "@embedpdf/plugin-rotate": "^1.5.0", + "@embedpdf/plugin-scroll": "^1.5.0", + "@embedpdf/plugin-search": "^1.5.0", + "@embedpdf/plugin-selection": "^1.5.0", + "@embedpdf/plugin-spread": "^1.5.0", + "@embedpdf/plugin-thumbnail": "^1.5.0", + "@embedpdf/plugin-tiling": "^1.5.0", + "@embedpdf/plugin-viewport": "^1.5.0", + "@embedpdf/plugin-zoom": "^1.5.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@iconify/react": "^6.0.2", diff --git a/frontend/public/locales/ar-AR/translation.toml b/frontend/public/locales/ar-AR/translation.toml index aedbc113f8..0942b37b5a 100644 --- a/frontend/public/locales/ar-AR/translation.toml +++ b/frontend/public/locales/ar-AR/translation.toml @@ -912,9 +912,6 @@ desc = "ابنِ تدفّقات عمل متعددة الخطوات بسلسلة desc = "تراكب ملف PDF فوق آخر" title = "تراكب ملفات PDF" -[home.pdfTextEditor] -title = "محرر نصوص PDF" -desc = "مراجعة وتحرير صادرات Stirling PDF بصيغة JSON مع تحرير نصوص مجمّعة وإعادة إنشاء PDF" [home.addText] tags = "نص,تعليق,تسمية" diff --git a/frontend/public/locales/az-AZ/translation.toml b/frontend/public/locales/az-AZ/translation.toml index 6a02c2c96a..d4c20aea64 100644 --- a/frontend/public/locales/az-AZ/translation.toml +++ b/frontend/public/locales/az-AZ/translation.toml @@ -912,9 +912,6 @@ desc = "PDF əməliyyatlarını zəncirləyərək çoxaddımlı iş axınları q desc = "Bir PDF-i digərinin üstünə qoyur" title = "Üst-Üstə Qoy" -[home.pdfTextEditor] -title = "PDF Mətn Redaktoru" -desc = "Qruplaşdırılmış mətn redaktəsi və PDF yenidən yaradılması ilə Stirling PDF JSON ixraclarını nəzərdən keçirin və redaktə edin" [home.addText] tags = "mətn,şərh,etiket" diff --git a/frontend/public/locales/bg-BG/translation.toml b/frontend/public/locales/bg-BG/translation.toml index f08ed7a19e..2d33576bfb 100644 --- a/frontend/public/locales/bg-BG/translation.toml +++ b/frontend/public/locales/bg-BG/translation.toml @@ -912,9 +912,6 @@ desc = "Създавайте многостъпкови работни проц desc = "Наслагва PDF файлове върху друг PDF" title = "Наслагване PDF-и" -[home.pdfTextEditor] -title = "PDF текстов редактор" -desc = "Преглеждайте и редактирайте JSON експорти на Stirling PDF с групово редактиране на текст и повторно генериране на PDF" [home.addText] tags = "текст,анотация,етикет" diff --git a/frontend/public/locales/ca-CA/translation.toml b/frontend/public/locales/ca-CA/translation.toml index b97d017e5c..2d8bc23e33 100644 --- a/frontend/public/locales/ca-CA/translation.toml +++ b/frontend/public/locales/ca-CA/translation.toml @@ -912,9 +912,6 @@ desc = "Construeix fluxos de treball multietapa enllaçant accions PDF. Ideal pe desc = "Superposa PDFs sobre un altre PDF" title = "Superposar PDFs" -[home.pdfTextEditor] -title = "Editor de text PDF" -desc = "Revisa i edita exportacions JSON de Stirling PDF amb edició de text agrupada i regeneració del PDF" [home.addText] tags = "text,anotació,etiqueta" diff --git a/frontend/public/locales/cs-CZ/translation.toml b/frontend/public/locales/cs-CZ/translation.toml index f4902d6342..0f80dd14b0 100644 --- a/frontend/public/locales/cs-CZ/translation.toml +++ b/frontend/public/locales/cs-CZ/translation.toml @@ -912,9 +912,6 @@ desc = "Vytvářejte vícekrokové workflow řetězením akcí PDF. Ideální pr desc = "Překryje PDF nad jiným PDF" title = "Překrýt PDF" -[home.pdfTextEditor] -title = "Editor textu PDF" -desc = "Prohlížejte a upravujte exporty JSON ze Stirling PDF se skupinovými úpravami textu a regenerací PDF" [home.addText] tags = "text,anotace,štítek" diff --git a/frontend/public/locales/da-DK/translation.toml b/frontend/public/locales/da-DK/translation.toml index 9773d63e3a..bc44aa479a 100644 --- a/frontend/public/locales/da-DK/translation.toml +++ b/frontend/public/locales/da-DK/translation.toml @@ -912,9 +912,6 @@ desc = "Byg flertrins-workflows ved at kæde PDF-handlinger sammen. Ideelt til t desc = "Overlejrer PDF'er oven på en anden PDF" title = "Overlejr PDF'er" -[home.pdfTextEditor] -title = "PDF-teksteditor" -desc = "Gennemse og rediger Stirling PDF JSON-eksporter med grupperet tekstredigering og regenerering af PDF" [home.addText] tags = "tekst,annotering,etiket" diff --git a/frontend/public/locales/de-DE/translation.toml b/frontend/public/locales/de-DE/translation.toml index db83b7ecbe..c8c8e6f71f 100644 --- a/frontend/public/locales/de-DE/translation.toml +++ b/frontend/public/locales/de-DE/translation.toml @@ -912,9 +912,6 @@ desc = "Mehrstufige Arbeitsabläufe durch Verkettung von PDF-Aktionen erstellen. desc = "Ein PDF über ein anderes legen" title = "PDFs überlagern" -[home.pdfTextEditor] -title = "PDF-Texteditor" -desc = "Stirling PDF JSON-Exporte prüfen und bearbeiten – mit gruppierter Textbearbeitung und PDF-Neuerzeugung" [home.addText] tags = "text,annotation,label" diff --git a/frontend/public/locales/el-GR/translation.toml b/frontend/public/locales/el-GR/translation.toml index 64af6e5d63..2656466274 100644 --- a/frontend/public/locales/el-GR/translation.toml +++ b/frontend/public/locales/el-GR/translation.toml @@ -912,9 +912,6 @@ desc = "Δημιουργήστε ροές πολλών βημάτων συνδέ desc = "Επικάλυψη PDF πάνω σε άλλο PDF" title = "Επικάλυψη PDF" -[home.pdfTextEditor] -title = "Επεξεργαστής κειμένου PDF" -desc = "Επιθεωρήστε και επεξεργαστείτε εξαγωγές JSON του Stirling PDF με ομαδοποιημένη επεξεργασία κειμένου και αναδημιουργία PDF" [home.addText] tags = "κείμενο,σχολιασμός,ετικέτα" diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml index bb0b7a814e..a425ad3d50 100644 --- a/frontend/public/locales/en-GB/translation.toml +++ b/frontend/public/locales/en-GB/translation.toml @@ -919,7 +919,7 @@ title = "Overlay PDFs" [home.pdfTextEditor] title = "PDF Text Editor" -desc = "Review and edit Stirling PDF JSON exports with grouped text editing and PDF regeneration" +desc = "Edit existing text and images inside PDFs" [home.addText] tags = "text,annotation,label" diff --git a/frontend/public/locales/es-ES/translation.toml b/frontend/public/locales/es-ES/translation.toml index 29e0924ae4..7890318ff2 100644 --- a/frontend/public/locales/es-ES/translation.toml +++ b/frontend/public/locales/es-ES/translation.toml @@ -912,9 +912,6 @@ desc = "Crear flujos de trabajo de múltiples pasos encadenando acciones de PDF. desc = "Superponer PDFs encima de otro PDF" title = "Superponer PDFs" -[home.pdfTextEditor] -title = "Editor de texto PDF" -desc = "Revise y edite exportaciones JSON de Stirling PDF con edición de texto agrupada y regeneración de PDF" [home.addText] tags = "texto,anotación,etiqueta" diff --git a/frontend/public/locales/eu-ES/translation.toml b/frontend/public/locales/eu-ES/translation.toml index 7a06a08c5b..89a7dddd55 100644 --- a/frontend/public/locales/eu-ES/translation.toml +++ b/frontend/public/locales/eu-ES/translation.toml @@ -912,9 +912,6 @@ desc = "Eraiki hainbat pausotako workflowak PDF ekintzak kateatuz. Egokia zeregi desc = "Overlays PDFs on-top of another PDF" title = "Gainjarri PDFak" -[home.pdfTextEditor] -title = "PDF testu-editorea" -desc = "Berrikusi eta editatu Stirling PDF JSON esportazioak taldekatutako testu-edizioarekin eta PDF birsorkuntzarekin" [home.addText] tags = "testua,anotazioa,etiketa" diff --git a/frontend/public/locales/fa-IR/translation.toml b/frontend/public/locales/fa-IR/translation.toml index 0088ee0451..f2cb9bd911 100644 --- a/frontend/public/locales/fa-IR/translation.toml +++ b/frontend/public/locales/fa-IR/translation.toml @@ -912,9 +912,6 @@ desc = "ساخت گردش‌کارهای چندمرحله‌ای با زنجیر desc = "PDF‌ها را بر روی PDF دیگری هم‌پوشانی می‌کند" title = "هم‌پوشانی PDF‌ها" -[home.pdfTextEditor] -title = "ویرایشگر متن PDF" -desc = "بازبینی و ویرایش خروجی‌های JSON Stirling PDF با ویرایش گروهی متن و بازتولید PDF" [home.addText] tags = "متن,حاشیه‌نویسی,برچسب" diff --git a/frontend/public/locales/fr-FR/translation.toml b/frontend/public/locales/fr-FR/translation.toml index 8982ce7748..2bd5da5c51 100644 --- a/frontend/public/locales/fr-FR/translation.toml +++ b/frontend/public/locales/fr-FR/translation.toml @@ -912,9 +912,6 @@ desc = "Créez des workflows multi-étapes en enchaînant des actions PDF. Idéa desc = "Superposer un PDF sur un autre" title = "Superposer des PDF" -[home.pdfTextEditor] -title = "Éditeur de texte PDF" -desc = "Afficher et modifier les exports JSON de Stirling PDF avec édition de texte groupée et régénération du PDF" [home.addText] tags = "texte,annotation,étiquette" diff --git a/frontend/public/locales/ga-IE/translation.toml b/frontend/public/locales/ga-IE/translation.toml index f38084088d..d34fbfba57 100644 --- a/frontend/public/locales/ga-IE/translation.toml +++ b/frontend/public/locales/ga-IE/translation.toml @@ -912,9 +912,6 @@ desc = "Tóg sreafaí oibre ilchéime trí ghníomhartha PDF a nascadh le chéil desc = "Forleagain PDF ar bharr PDF eile" title = "Forleagan PDF" -[home.pdfTextEditor] -title = "Eagarthóir Téacs PDF" -desc = "Athbhreithnigh agus cuir in eagar onnmhairí JSON ó Stirling PDF le heagarthóireacht téacs ghrúpáilte agus athghiniúint PDF" [home.addText] tags = "téacs,anótáil,lipéad" diff --git a/frontend/public/locales/hi-IN/translation.toml b/frontend/public/locales/hi-IN/translation.toml index f00655db04..f0fcf78333 100644 --- a/frontend/public/locales/hi-IN/translation.toml +++ b/frontend/public/locales/hi-IN/translation.toml @@ -912,9 +912,6 @@ desc = "PDF क्रियाओं को जोड़कर बहु-चर desc = "PDF को दूसरी PDF के ऊपर ओवरले करें" title = "PDF ओवरले करें" -[home.pdfTextEditor] -title = "PDF टेक्स्ट एडिटर" -desc = "ग्रुप्ड टेक्स्ट एडिटिंग और PDF पुनर्जनन के साथ Stirling PDF JSON एक्सपोर्ट की समीक्षा व संपादन करें" [home.addText] tags = "text,annotation,label" diff --git a/frontend/public/locales/hr-HR/translation.toml b/frontend/public/locales/hr-HR/translation.toml index c165b4044d..c19e67180f 100644 --- a/frontend/public/locales/hr-HR/translation.toml +++ b/frontend/public/locales/hr-HR/translation.toml @@ -912,9 +912,6 @@ desc = "Izgradite višekoračne tijekove rada povezivanjem PDF radnji. Idealno z desc = "Preklapa PDF-ove na drugi PDF" title = "Preklapanje PDF-ova" -[home.pdfTextEditor] -title = "PDF uređivač teksta" -desc = "Pregledajte i uredite Stirling PDF JSON izvoze s grupnim uređivanjem teksta i ponovnim generiranjem PDF-a" [home.addText] tags = "tekst,anotacija,oznaka" diff --git a/frontend/public/locales/hu-HU/translation.toml b/frontend/public/locales/hu-HU/translation.toml index 049c862073..514db6f07c 100644 --- a/frontend/public/locales/hu-HU/translation.toml +++ b/frontend/public/locales/hu-HU/translation.toml @@ -912,9 +912,6 @@ desc = "Többlépéses munkafolyamatok összeállítása PDF műveletek összef desc = "PDF-ek egymásra helyezése egy másik PDF-en" title = "PDF-ek egymásra helyezése" -[home.pdfTextEditor] -title = "PDF szövegszerkesztő" -desc = "Nézze át és szerkessze a Stirling PDF JSON exportokat csoportosított szövegszerkesztéssel és PDF-újragenerálással" [home.addText] tags = "szöveg, megjegyzés, címke" diff --git a/frontend/public/locales/id-ID/translation.toml b/frontend/public/locales/id-ID/translation.toml index cd402800d0..010266296b 100644 --- a/frontend/public/locales/id-ID/translation.toml +++ b/frontend/public/locales/id-ID/translation.toml @@ -912,9 +912,6 @@ desc = "Bangun alur kerja multi-langkah dengan merangkai tindakan PDF. Ideal unt desc = "Menumpuk PDF di atas PDF lain" title = "Tumpuk PDF" -[home.pdfTextEditor] -title = "Editor Teks PDF" -desc = "Tinjau dan edit ekspor Stirling PDF JSON dengan pengeditan teks terkelompok dan pembuatan ulang PDF" [home.addText] tags = "teks,anotasi,label" diff --git a/frontend/public/locales/it-IT/translation.toml b/frontend/public/locales/it-IT/translation.toml index cb0ea0b097..ef9d94d83a 100644 --- a/frontend/public/locales/it-IT/translation.toml +++ b/frontend/public/locales/it-IT/translation.toml @@ -912,9 +912,6 @@ desc = "Crea flussi multi‑step concatenando azioni PDF. Ideale per attività r desc = "Sovrapponi un PDF sopra un altro" title = "Sovrapponi PDF" -[home.pdfTextEditor] -title = "Editor testo PDF" -desc = "Rivedi e modifica le esportazioni JSON di Stirling PDF con modifica testo raggruppata e rigenerazione del PDF" [home.addText] tags = "testo,annotazione,etichetta" diff --git a/frontend/public/locales/ja-JP/translation.toml b/frontend/public/locales/ja-JP/translation.toml index c3981c64aa..90b5eeee48 100644 --- a/frontend/public/locales/ja-JP/translation.toml +++ b/frontend/public/locales/ja-JP/translation.toml @@ -912,9 +912,6 @@ desc = "PDF アクションを連結して複数ステップのワークフロ desc = "1つのPDFを別のPDFの上に重ねます" title = "PDFを重ね合わせ" -[home.pdfTextEditor] -title = "PDFテキストエディター" -desc = "グループ化されたテキスト編集とPDF再生成で、Stirling PDF の JSON エクスポートをレビュー・編集します。" [home.addText] tags = "テキスト,注釈,ラベル" diff --git a/frontend/public/locales/ko-KR/translation.toml b/frontend/public/locales/ko-KR/translation.toml index fbf2bdc215..f63fd82c62 100644 --- a/frontend/public/locales/ko-KR/translation.toml +++ b/frontend/public/locales/ko-KR/translation.toml @@ -912,9 +912,6 @@ desc = "PDF 작업을 연결하여 다단계 워크플로를 구성하세요. desc = "PDF를 다른 PDF 위에 오버레이" title = "PDF 오버레이" -[home.pdfTextEditor] -title = "PDF 텍스트 편집기" -desc = "그룹화된 텍스트 편집과 PDF 재생성으로 Stirling PDF의 JSON 내보내기를 검토하고 편집하세요" [home.addText] tags = "텍스트,주석,레이블" diff --git a/frontend/public/locales/ml-ML/translation.toml b/frontend/public/locales/ml-ML/translation.toml index 496594a016..b7f97c3c22 100644 --- a/frontend/public/locales/ml-ML/translation.toml +++ b/frontend/public/locales/ml-ML/translation.toml @@ -912,9 +912,6 @@ desc = "PDF പ്രവർത്തനങ്ങൾ ബന്ധിപ്പി desc = "മറ്റൊരു PDF-ന് മുകളിൽ PDF-കൾ ഓവർലേ ചെയ്യുന്നു" title = "PDF-കൾ ഓവർലേ ചെയ്യുക" -[home.pdfTextEditor] -title = "PDF ടെക്സ്റ്റ് എഡിറ്റർ" -desc = "ഗ്രൂപ്പുചെയ്ത ടെക്സ്റ്റ് എഡിറ്റിംഗിനോടും PDF വീണ്ടും സൃഷ്ടിക്കുന്നതോടും കൂടി Stirling PDF JSON എക്സ്പോർട്ടുകൾ റിവ്യൂ ചെയ്ത് എഡിറ്റ് ചെയ്യുക" [home.addText] tags = "text,annotation,label" diff --git a/frontend/public/locales/nl-NL/translation.toml b/frontend/public/locales/nl-NL/translation.toml index 33f8c84d3d..321acd4753 100644 --- a/frontend/public/locales/nl-NL/translation.toml +++ b/frontend/public/locales/nl-NL/translation.toml @@ -912,9 +912,6 @@ desc = "Bouw workflows met meerdere stappen door PDF-acties te koppelen. Ideaal desc = "Plaatst PDF's over een andere PDF heen" title = "PDF's overlappen" -[home.pdfTextEditor] -title = "PDF-teksteditor" -desc = "Bekijk en bewerk Stirling PDF JSON-exporten met gegroepeerde tekstbewerking en het opnieuw genereren van PDF's" [home.addText] tags = "tekst,annotatie,label" diff --git a/frontend/public/locales/no-NB/translation.toml b/frontend/public/locales/no-NB/translation.toml index afcddabdd5..21b873f015 100644 --- a/frontend/public/locales/no-NB/translation.toml +++ b/frontend/public/locales/no-NB/translation.toml @@ -912,9 +912,6 @@ desc = "Bygg flertrinns arbeidsflyter ved å lenke sammen PDF-handlinger. Ideelt desc = "Legger PDF-er over hverandre" title = "Overlay PDF-er" -[home.pdfTextEditor] -title = "PDF-tekstredigerer" -desc = "Gå gjennom og rediger Stirling PDF JSON-eksporter med gruppert tekstredigering og regenerering av PDF" [home.addText] tags = "tekst,merknad,etikett" diff --git a/frontend/public/locales/pl-PL/translation.toml b/frontend/public/locales/pl-PL/translation.toml index 7770e9a943..bbde870220 100644 --- a/frontend/public/locales/pl-PL/translation.toml +++ b/frontend/public/locales/pl-PL/translation.toml @@ -912,9 +912,6 @@ desc = "Buduj wieloetapowe przepływy, łącząc akcje PDF. Idealne do powtarzaj desc = "Nakłada dokumenty PDF na siebie" title = "Nałóż PDFa" -[home.pdfTextEditor] -title = "Edytor tekstu PDF" -desc = "Przeglądaj i edytuj eksporty JSON z Stirling PDF z grupową edycją tekstu i ponowną generacją PDF" [home.addText] tags = "tekst,adnotacja,etykieta" diff --git a/frontend/public/locales/pt-BR/translation.toml b/frontend/public/locales/pt-BR/translation.toml index b9f07c86ba..76dff8f2d7 100644 --- a/frontend/public/locales/pt-BR/translation.toml +++ b/frontend/public/locales/pt-BR/translation.toml @@ -912,9 +912,6 @@ desc = "Crie fluxos de trabalho de várias etapas encadeando ações de PDF. Ide desc = "Sobrepor um PDF sobre outro" title = "Sobrepor PDFs" -[home.pdfTextEditor] -title = "Editor de texto de PDF" -desc = "Revise e edite exportações JSON do Stirling PDF com edição de texto agrupada e regeneração do PDF" [home.addText] tags = "texto,anotação,rótulo" diff --git a/frontend/public/locales/pt-PT/translation.toml b/frontend/public/locales/pt-PT/translation.toml index 3991bc04ce..e530345e6b 100644 --- a/frontend/public/locales/pt-PT/translation.toml +++ b/frontend/public/locales/pt-PT/translation.toml @@ -912,9 +912,6 @@ desc = "Crie fluxos de trabalho de vários passos encadeando ações de PDF. Ide desc = "Sobrepõe PDFs em cima de outro PDF" title = "Sobrepor PDFs" -[home.pdfTextEditor] -title = "Editor de texto PDF" -desc = "Revise e edite exportações JSON do Stirling PDF com edição de texto agrupado e regeneração de PDF" [home.addText] tags = "texto,anotação,etiqueta" diff --git a/frontend/public/locales/ro-RO/translation.toml b/frontend/public/locales/ro-RO/translation.toml index 6e23782081..57c2ce4812 100644 --- a/frontend/public/locales/ro-RO/translation.toml +++ b/frontend/public/locales/ro-RO/translation.toml @@ -912,9 +912,6 @@ desc = "Construiți fluxuri cu mai mulți pași legând acțiuni PDF. Ideal pent desc = "Suprapune PDF-uri peste alt PDF" title = "Suprapune PDF-uri" -[home.pdfTextEditor] -title = "Editor text PDF" -desc = "Revizuiește și editează exporturile JSON Stirling PDF cu editare de text grupată și regenerare PDF" [home.addText] tags = "text,anotare,etichetă" diff --git a/frontend/public/locales/ru-RU/translation.toml b/frontend/public/locales/ru-RU/translation.toml index e2a9bddcd0..359bb57832 100644 --- a/frontend/public/locales/ru-RU/translation.toml +++ b/frontend/public/locales/ru-RU/translation.toml @@ -912,9 +912,6 @@ desc = "Создавайте многошаговые процессы, связ desc = "Наложить один PDF поверх другого" title = "Наложение PDF" -[home.pdfTextEditor] -title = "Редактор текста в PDF" -desc = "Просмотр и редактирование экспортов Stirling PDF в JSON с групповым редактированием текста и регенерацией PDF" [home.addText] tags = "текст,аннотация,ярлык" diff --git a/frontend/public/locales/sk-SK/translation.toml b/frontend/public/locales/sk-SK/translation.toml index 248e6c7a5f..0b6633ff23 100644 --- a/frontend/public/locales/sk-SK/translation.toml +++ b/frontend/public/locales/sk-SK/translation.toml @@ -912,9 +912,6 @@ desc = "Stavať viacstupňové pracovné postupy spájaním akcií PDF. Ideálne desc = "Prekrýva PDF súbory na iný PDF" title = "Prekrývanie PDF" -[home.pdfTextEditor] -title = "Editor textu PDF" -desc = "Kontrolujte a upravujte Stirling PDF JSON exporty so skupinovými úpravami textu a opätovným vytvorením PDF" [home.addText] tags = "text,anotácia,štítok" diff --git a/frontend/public/locales/sl-SI/translation.toml b/frontend/public/locales/sl-SI/translation.toml index 308652235b..36497b6a9c 100644 --- a/frontend/public/locales/sl-SI/translation.toml +++ b/frontend/public/locales/sl-SI/translation.toml @@ -912,9 +912,6 @@ desc = "Sestavite večkorakovne poteke z veriženjem dejanj PDF. Idealno za pona desc = "Prekriva PDF-je na vrhu drugega PDF-ja" title = "Prekrivanje PDF-jev" -[home.pdfTextEditor] -title = "Urejevalnik besedila PDF" -desc = "Pregledujte in urejajte Stirling PDF JSON izvoze z urejanjem združenega besedila in ponovnim ustvarjanjem PDF" [home.addText] tags = "besedilo,pripomba,oznaka" diff --git a/frontend/public/locales/sr-LATN-RS/translation.toml b/frontend/public/locales/sr-LATN-RS/translation.toml index 9047c9f6c4..a241131237 100644 --- a/frontend/public/locales/sr-LATN-RS/translation.toml +++ b/frontend/public/locales/sr-LATN-RS/translation.toml @@ -912,9 +912,6 @@ desc = "Gradite višekorake tokove rada povezivanjem PDF akcija. Idealno za pona desc = "Preklapa PDF-ove jedan preko drugog" title = "Preklapanje PDF-ova" -[home.pdfTextEditor] -title = "PDF uređivač teksta" -desc = "Pregledajte i uređujte Stirling PDF JSON izvoze uz grupisano uređivanje teksta i ponovno generisanje PDF-a" [home.addText] tags = "tekst,anotacija,oznaka" diff --git a/frontend/public/locales/sv-SE/translation.toml b/frontend/public/locales/sv-SE/translation.toml index 4e8c971ed3..3cf3913ca6 100644 --- a/frontend/public/locales/sv-SE/translation.toml +++ b/frontend/public/locales/sv-SE/translation.toml @@ -912,9 +912,6 @@ desc = "Skapa flerstegade arbetsflöden genom att kedja ihop PDF‑åtgärder. P desc = "Överlagrar PDF:er ovanpå en annan PDF" title = "Överlagra PDF:er" -[home.pdfTextEditor] -title = "PDF-textredigerare" -desc = "Granska och redigera Stirling PDF JSON-exporter med grupperad textredigering och PDF-återgenerering" [home.addText] tags = "text,kommentar,etikett" diff --git a/frontend/public/locales/th-TH/translation.toml b/frontend/public/locales/th-TH/translation.toml index 1bab3870e0..bfd0077b27 100644 --- a/frontend/public/locales/th-TH/translation.toml +++ b/frontend/public/locales/th-TH/translation.toml @@ -912,9 +912,6 @@ desc = "สร้างเวิร์กโฟลว์หลายขั้น desc = "ซ้อนทับ PDF บน PDF อีกไฟล์หนึ่ง" title = "ซ้อนทับ PDF" -[home.pdfTextEditor] -title = "แก้ไขข้อความ PDF" -desc = "ตรวจทานและแก้ไขไฟล์ JSON ที่ส่งออกจาก Stirling PDF ด้วยการแก้ไขข้อความแบบกลุ่มและการสร้าง PDF ใหม่" [home.addText] tags = "text,annotation,label" diff --git a/frontend/public/locales/tr-TR/translation.toml b/frontend/public/locales/tr-TR/translation.toml index fccd07bd49..a5bee2af75 100644 --- a/frontend/public/locales/tr-TR/translation.toml +++ b/frontend/public/locales/tr-TR/translation.toml @@ -912,9 +912,6 @@ desc = "PDF eylemlerini birbirine bağlayarak çok adımlı iş akışları olu desc = "PDF'leri başka bir PDF'nin üzerine bindirir" title = "PDF'leri Bindirme" -[home.pdfTextEditor] -title = "PDF Metin Düzenleyici" -desc = "Gruplu metin düzenleme ve PDF yeniden oluşturma ile Stirling PDF JSON dışa aktarımlarını gözden geçirin ve düzenleyin" [home.addText] tags = "metin,ek açıklama,etiket" diff --git a/frontend/public/locales/uk-UA/translation.toml b/frontend/public/locales/uk-UA/translation.toml index a20b8d7e57..dbb957a6a7 100644 --- a/frontend/public/locales/uk-UA/translation.toml +++ b/frontend/public/locales/uk-UA/translation.toml @@ -912,9 +912,6 @@ desc = "Створюйте багатокрокові робочі процес desc = "Накладення одного PDF поверх іншого PDF" title = "Накладення PDF" -[home.pdfTextEditor] -title = "Редактор тексту PDF" -desc = "Переглядайте й редагуйте JSON-експорти Stirling PDF з груповим редагуванням тексту та повторною генерацією PDF" [home.addText] tags = "текст,анотація,мітка" diff --git a/frontend/public/locales/vi-VN/translation.toml b/frontend/public/locales/vi-VN/translation.toml index ea930753ca..464407a2ac 100644 --- a/frontend/public/locales/vi-VN/translation.toml +++ b/frontend/public/locales/vi-VN/translation.toml @@ -912,9 +912,6 @@ desc = "Xây dựng quy trình nhiều bước bằng cách xâu chuỗi các th desc = "Chồng lớp PDF lên trên PDF khác" title = "Chồng lớp PDF" -[home.pdfTextEditor] -title = "Trình chỉnh sửa văn bản PDF" -desc = "Xem và chỉnh sửa xuất JSON của Stirling PDF với chỉnh sửa văn bản theo nhóm và tái tạo PDF" [home.addText] tags = "văn bản,chú thích,nhãn" diff --git a/frontend/public/locales/zh-BO/translation.toml b/frontend/public/locales/zh-BO/translation.toml index 32cad040d9..efbc9cb914 100644 --- a/frontend/public/locales/zh-BO/translation.toml +++ b/frontend/public/locales/zh-BO/translation.toml @@ -912,9 +912,6 @@ desc = "通过串联 PDF 动作构建多步工作流。适合重复性任务。" desc = "PDF གཞན་ཞིག་གི་སྟེང་དུ་ PDF བརྩེགས་པ།" title = "PDF སྟེང་བརྩེགས།" -[home.pdfTextEditor] -title = "PDF 文本编辑器" -desc = "审阅并编辑 Stirling PDF 的 JSON 导出,支持分组文本编辑与 PDF 再生成" [home.addText] tags = "文本,注释,标签" diff --git a/frontend/public/locales/zh-CN/translation.toml b/frontend/public/locales/zh-CN/translation.toml index 6ad277eb6e..2d166272c5 100644 --- a/frontend/public/locales/zh-CN/translation.toml +++ b/frontend/public/locales/zh-CN/translation.toml @@ -912,9 +912,6 @@ desc = "通过串联 PDF 操作构建多步工作流。适合重复性任务。" desc = "将一个 PDF 叠加在另一个之上" title = "叠加 PDF" -[home.pdfTextEditor] -title = "PDF 文本编辑器" -desc = "审阅并编辑 Stirling PDF 导出的 JSON,支持分组文本编辑并重新生成 PDF" [home.addText] tags = "文本,注释,标签" diff --git a/frontend/public/locales/zh-TW/translation.toml b/frontend/public/locales/zh-TW/translation.toml index d56060e52b..55b4e6b52b 100644 --- a/frontend/public/locales/zh-TW/translation.toml +++ b/frontend/public/locales/zh-TW/translation.toml @@ -912,9 +912,6 @@ desc = "將多個 PDF 動作串接,建立多步驟工作流程。適合重複 desc = "將 PDF 覆蓋在另一個 PDF 上" title = "覆蓋 PDF" -[home.pdfTextEditor] -title = "PDF 文字編輯器" -desc = "檢視與編輯 Stirling PDF 的 JSON 匯出,支援群組文字編輯與重新產生 PDF" [home.addText] tags = "文字,註解,標籤" diff --git a/frontend/src/core/components/tools/sign/SavedSignaturesSection.tsx b/frontend/src/core/components/tools/sign/SavedSignaturesSection.tsx index c2e8ca8f1d..95a8a7e77b 100644 --- a/frontend/src/core/components/tools/sign/SavedSignaturesSection.tsx +++ b/frontend/src/core/components/tools/sign/SavedSignaturesSection.tsx @@ -2,14 +2,16 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ActionIcon, Alert, Badge, Box, Card, Group, Stack, Text, TextInput, Tooltip } from '@mantine/core'; import { LocalIcon } from '@app/components/shared/LocalIcon'; -import { MAX_SAVED_SIGNATURES, SavedSignature, SavedSignatureType } from '@app/hooks/tools/sign/useSavedSignatures'; +import { SavedSignature, SavedSignatureType } from '@app/hooks/tools/sign/useSavedSignatures'; import type { StorageType } from '@app/services/signatureStorageService'; interface SavedSignaturesSectionProps { signatures: SavedSignature[]; disabled?: boolean; isAtCapacity: boolean; + maxLimit: number; storageType?: StorageType | null; + isAdmin?: boolean; onUseSignature: (signature: SavedSignature) => void; onDeleteSignature: (signature: SavedSignature) => void; onRenameSignature: (id: string, label: string) => void; @@ -26,7 +28,9 @@ export const SavedSignaturesSection = ({ signatures, disabled = false, isAtCapacity, + maxLimit, storageType: _storageType, + isAdmin = false, onUseSignature, onDeleteSignature, onRenameSignature, @@ -155,7 +159,7 @@ export const SavedSignaturesSection = ({ {translate( 'saved.emptyDescription', 'Draw, upload, or type a signature above, then use "Save to library" to keep up to {{max}} favourites ready to use.', - { max: MAX_SAVED_SIGNATURES } + { max: maxLimit } )} @@ -216,7 +220,7 @@ export const SavedSignaturesSection = ({ {translate('saved.limitDescription', 'Remove a saved signature before adding new ones (max {{max}}).', { - max: MAX_SAVED_SIGNATURES, + max: maxLimit, })} @@ -365,17 +369,19 @@ export const SavedSignaturesSection = ({ > - - onDeleteSignature(activeSharedSignature)} - disabled={disabled} - > - - - + {isAdmin && ( + + onDeleteSignature(activeSharedSignature)} + disabled={disabled} + > + + + + )} {renderPreview(activeSharedSignature)} diff --git a/frontend/src/core/components/tools/sign/SignSettings.tsx b/frontend/src/core/components/tools/sign/SignSettings.tsx index 6fb3238f8c..233b87373c 100644 --- a/frontend/src/core/components/tools/sign/SignSettings.tsx +++ b/frontend/src/core/components/tools/sign/SignSettings.tsx @@ -14,7 +14,7 @@ import { ImageUploader } from "@app/components/annotation/shared/ImageUploader"; import { TextInputWithFont } from "@app/components/annotation/shared/TextInputWithFont"; import { ColorPicker } from "@app/components/annotation/shared/ColorPicker"; import { LocalIcon } from "@app/components/shared/LocalIcon"; -import { useSavedSignatures, SavedSignature, SavedSignaturePayload, SavedSignatureType, MAX_SAVED_SIGNATURES, AddSignatureResult } from '@app/hooks/tools/sign/useSavedSignatures'; +import { useSavedSignatures, SavedSignature, SavedSignaturePayload, SavedSignatureType, AddSignatureResult } from '@app/hooks/tools/sign/useSavedSignatures'; import { SavedSignaturesSection } from '@app/components/tools/sign/SavedSignaturesSection'; import { buildSignaturePreview } from '@app/utils/signaturePreview'; @@ -96,11 +96,13 @@ const SignSettings = ({ const { savedSignatures, isAtCapacity: isSavedSignatureLimitReached, + maxLimit, addSignature, removeSignature, updateSignatureLabel, byTypeCounts, storageType, + isAdmin, } = useSavedSignatures(); const [signatureSource, setSignatureSource] = useState(() => { const paramSource = parameters.signatureType as SignatureSource; @@ -246,16 +248,19 @@ const SignSettings = ({ (signature: SavedSignature) => { setPlacementManuallyPaused(false); + // Use the data URL directly (already converted to base64 when loaded) + const dataUrlToUse = signature.dataUrl; + if (signature.type === 'canvas') { if (parameters.signatureType !== 'canvas') { onParameterChange('signatureType', 'canvas'); } - setCanvasSignatureData(signature.dataUrl); + setCanvasSignatureData(dataUrlToUse); } else if (signature.type === 'image') { if (parameters.signatureType !== 'image') { onParameterChange('signatureType', 'image'); } - setImageSignatureData(signature.dataUrl); + setImageSignatureData(dataUrlToUse); } else if (signature.type === 'text') { if (parameters.signatureType !== 'text') { onParameterChange('signatureType', 'text'); @@ -269,7 +274,7 @@ const SignSettings = ({ const savedKey = signature.type === 'text' ? buildTextSignatureKey(signature.signerName, signature.fontSize, signature.fontFamily, signature.textColor) - : signature.dataUrl; + : dataUrlToUse; setLastSavedKeyForType(signature.type, savedKey); const activate = () => onActivateSignaturePlacement?.(); @@ -326,8 +331,8 @@ const SignSettings = ({ } else if (isSaved) { tooltipMessage = translate('saved.noChanges', 'Current signature is already saved.'); } else if (isSavedSignatureLimitReached) { - tooltipMessage = translate('saved.limitDescription', 'Remove a saved signature before adding new ones (max {{max}}).', { - max: MAX_SAVED_SIGNATURES, + tooltipMessage = translate('saved.limitDescription', 'You have reached the maximum limit of {{max}} saved signatures. Remove a saved signature before adding new ones.', { + max: maxLimit, }); } @@ -791,7 +796,9 @@ const SignSettings = ({ signatures={savedSignatures} disabled={disabled} isAtCapacity={isSavedSignatureLimitReached} + maxLimit={maxLimit} storageType={storageType} + isAdmin={isAdmin} onUseSignature={handleUseSavedSignature} onDeleteSignature={handleDeleteSavedSignature} onRenameSignature={handleRenameSavedSignature} diff --git a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx index b953b2205c..3e5fa5f1c6 100644 --- a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx +++ b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx @@ -97,7 +97,10 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur strategy: ScrollStrategy.Vertical, initialPage: 0, }), - createPluginRegistration(RenderPluginPackage), + createPluginRegistration(RenderPluginPackage, { + withForms: true, + withAnnotations: true, + }), // Register interaction manager (required for zoom and selection features) createPluginRegistration(InteractionManagerPluginPackage), diff --git a/frontend/src/core/components/viewer/LocalEmbedPDFWithAnnotations.tsx b/frontend/src/core/components/viewer/LocalEmbedPDFWithAnnotations.tsx index 87a5d75143..cfd48185dc 100644 --- a/frontend/src/core/components/viewer/LocalEmbedPDFWithAnnotations.tsx +++ b/frontend/src/core/components/viewer/LocalEmbedPDFWithAnnotations.tsx @@ -85,7 +85,10 @@ export function LocalEmbedPDFWithAnnotations({ strategy: ScrollStrategy.Vertical, initialPage: 0, }), - createPluginRegistration(RenderPluginPackage), + createPluginRegistration(RenderPluginPackage, { + withForms: true, + withAnnotations: true, + }), // Register interaction manager (required for annotations) createPluginRegistration(InteractionManagerPluginPackage), diff --git a/frontend/src/core/constants/app.ts b/frontend/src/core/constants/app.ts index 04057fa376..aedb81a681 100644 --- a/frontend/src/core/constants/app.ts +++ b/frontend/src/core/constants/app.ts @@ -1,7 +1,24 @@ -// Base path from Vite config - build-time constant, normalized (no trailing slash) -// When no subpath, use empty string instead of '.' to avoid relative path issues -export const BASE_PATH = (import.meta.env.BASE_URL || '/').replace(/\/$/, '').replace(/^\.$/, ''); +// Base path - read from tag at runtime to support dynamic subpaths +// Falls back to Vite's BASE_URL for build-time subpaths +// Normalized: no trailing slash, empty string instead of '.' or '/' +function getBasePath(): string { + // Try to read from tag first (runtime subpath support) + if (typeof document !== 'undefined') { + const baseElement = document.querySelector('base'); + if (baseElement) { + const href = baseElement.getAttribute('href'); + if (href && href !== '%BASE_URL%') { + return href.replace(/\/$/, '').replace(/^\.$/, '').replace(/^\/$/, ''); + } + } + } + + // Fall back to Vite's BASE_URL (build-time subpath) + return (import.meta.env.BASE_URL || '/').replace(/\/$/, '').replace(/^\.$/, '').replace(/^\/$/, ''); +} + +export const BASE_PATH = getBasePath(); // EmbedPDF needs time to remove annotations internally before a recreation runs. // Without the buffer we occasionally end up with duplicate annotations or stale image data. diff --git a/frontend/src/core/contexts/TourOrchestrationContext.tsx b/frontend/src/core/contexts/TourOrchestrationContext.tsx index d8b7afdc06..f8364ba5c3 100644 --- a/frontend/src/core/contexts/TourOrchestrationContext.tsx +++ b/frontend/src/core/contexts/TourOrchestrationContext.tsx @@ -110,7 +110,7 @@ export const TourOrchestrationProvider: React.FC<{ children: React.ReactNode }> const loadSampleFile = useCallback(async () => { try { - const response = await fetch('/samples/Sample.pdf'); + const response = await fetch('samples/Sample.pdf'); const blob = await response.blob(); const file = new File([blob], 'Sample.pdf', { type: 'application/pdf' }); diff --git a/frontend/src/core/hooks/tools/sign/useSavedSignatures.ts b/frontend/src/core/hooks/tools/sign/useSavedSignatures.ts index 655038a3f2..b878dcd0de 100644 --- a/frontend/src/core/hooks/tools/sign/useSavedSignatures.ts +++ b/frontend/src/core/hooks/tools/sign/useSavedSignatures.ts @@ -1,7 +1,9 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { signatureStorageService, type StorageType } from '@app/services/signatureStorageService'; +import { useAppConfig } from '@app/contexts/AppConfigContext'; -export const MAX_SAVED_SIGNATURES = 10; +export const MAX_SAVED_SIGNATURES_BACKEND = 20; // Backend limit per user +export const MAX_SAVED_SIGNATURES_LOCALSTORAGE = 10; // LocalStorage limit export type SavedSignatureType = 'canvas' | 'image' | 'text'; export type SignatureScope = 'personal' | 'shared' | 'localStorage'; @@ -49,6 +51,8 @@ export const useSavedSignatures = () => { const [savedSignatures, setSavedSignatures] = useState([]); const [storageType, setStorageType] = useState(null); const [isLoading, setIsLoading] = useState(true); + const { config } = useAppConfig(); + const isAdmin = config?.isAdmin ?? false; // Load signatures and detect storage type on mount useEffect(() => { @@ -97,7 +101,9 @@ export const useSavedSignatures = () => { return () => window.removeEventListener('storage', syncFromStorage); }, [storageType]); - const isAtCapacity = savedSignatures.length >= MAX_SAVED_SIGNATURES; + // Different limits for backend vs localStorage + const maxLimit = storageType === 'backend' ? MAX_SAVED_SIGNATURES_BACKEND : MAX_SAVED_SIGNATURES_LOCALSTORAGE; + const isAtCapacity = savedSignatures.length >= maxLimit; const addSignature = useCallback( async (payload: SavedSignaturePayload, label?: string, scope?: SignatureScope): Promise => { @@ -108,7 +114,7 @@ export const useSavedSignatures = () => { return { success: false, reason: 'invalid' }; } - if (savedSignatures.length >= MAX_SAVED_SIGNATURES) { + if (isAtCapacity) { return { success: false, reason: 'limit' }; } @@ -146,17 +152,24 @@ export const useSavedSignatures = () => { const updateSignatureLabel = useCallback(async (id: string, nextLabel: string) => { try { await signatureStorageService.updateSignatureLabel(id, nextLabel); - setSavedSignatures(prev => - prev.map(entry => - entry.id === id - ? { ...entry, label: nextLabel.trim() || entry.label || 'Signature', updatedAt: Date.now() } - : entry - ) - ); + // Reload signatures to get updated data from backend + if (storageType === 'backend') { + const signatures = await signatureStorageService.loadSignatures(); + setSavedSignatures(signatures); + } else { + // For localStorage, update in place + setSavedSignatures(prev => + prev.map(entry => + entry.id === id + ? { ...entry, label: nextLabel.trim() || entry.label || 'Signature', updatedAt: Date.now() } + : entry + ) + ); + } } catch (error) { console.error('[useSavedSignatures] Failed to update signature label:', error); } - }, []); + }, [storageType]); const replaceSignature = useCallback( async (id: string, payload: SavedSignaturePayload) => { @@ -201,6 +214,7 @@ export const useSavedSignatures = () => { return { savedSignatures, isAtCapacity, + maxLimit, addSignature, removeSignature, updateSignatureLabel, @@ -209,6 +223,7 @@ export const useSavedSignatures = () => { byTypeCounts, storageType, isLoading, + isAdmin, }; }; diff --git a/frontend/src/core/services/signatureStorageService.ts b/frontend/src/core/services/signatureStorageService.ts index 365a0cbc60..63dabe9094 100644 --- a/frontend/src/core/services/signatureStorageService.ts +++ b/frontend/src/core/services/signatureStorageService.ts @@ -14,7 +14,6 @@ interface SignatureStorageCapabilities { class SignatureStorageService { private capabilities: SignatureStorageCapabilities | null = null; private detectionPromise: Promise | null = null; - private blobUrls: Set = new Set(); /** * Detect if backend supports signature storage API @@ -83,9 +82,6 @@ class SignatureStorageService { * Load all signatures */ async loadSignatures(): Promise { - // Clean up old blob URLs before loading new ones - this.cleanup(); - const capabilities = await this.detectCapabilities(); if (capabilities.supportsBackend) { @@ -130,9 +126,7 @@ class SignatureStorageService { const capabilities = await this.detectCapabilities(); if (capabilities.supportsBackend) { - // Backend only stores images - labels not supported for backend signatures - console.log('[SignatureStorage] Label updates not supported for backend signatures'); - return; + await this._updateLabelInBackend(id, label); } else { this._updateLabelInLocalStorage(id, label); } @@ -144,7 +138,7 @@ class SignatureStorageService { const response = await apiClient.get('/api/v1/proprietary/signatures'); const signatures = response.data; - // Fetch image data for each signature and convert to blob URLs + // Fetch image data for each signature and convert to data URLs const signaturePromises = signatures.map(async (sig) => { if (sig.dataUrl && sig.dataUrl.startsWith('/api/v1/general/signatures/')) { try { @@ -153,14 +147,20 @@ class SignatureStorageService { responseType: 'arraybuffer', }); - // Convert to blob URL + // Convert to data URL (base64) for both display and use const blob = new Blob([imageResponse.data], { type: imageResponse.headers['content-type'] || 'image/png', }); - const blobUrl = URL.createObjectURL(blob); - this.blobUrls.add(blobUrl); - return { ...sig, dataUrl: blobUrl }; + const dataUrl = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result as string); + reader.onerror = reject; + reader.readAsDataURL(blob); + }); + + // Use data URL for everything - more reliable than blob URLs + return { ...sig, dataUrl }; } catch (error) { console.error(`[SignatureStorage] Failed to load image for ${sig.id}:`, error); return sig; // Return original if image fetch fails @@ -184,6 +184,10 @@ class SignatureStorageService { await apiClient.delete(`/api/v1/proprietary/signatures/${id}`); } + private async _updateLabelInBackend(id: string, label: string): Promise { + await apiClient.post(`/api/v1/proprietary/signatures/${id}/label`, { label }); + } + // LocalStorage methods private readonly STORAGE_KEY = 'stirling:saved-signatures:v1'; @@ -267,16 +271,6 @@ class SignatureStorageService { return { migrated, failed }; } - - /** - * Clean up blob URLs to prevent memory leaks - */ - cleanup(): void { - this.blobUrls.forEach(url => { - URL.revokeObjectURL(url); - }); - this.blobUrls.clear(); - } } export const signatureStorageService = new SignatureStorageService(); diff --git a/frontend/src/core/utils/automationExecutor.ts b/frontend/src/core/utils/automationExecutor.ts index 760a86d7bb..6f1234f810 100644 --- a/frontend/src/core/utils/automationExecutor.ts +++ b/frontend/src/core/utils/automationExecutor.ts @@ -1,4 +1,4 @@ -import axios from 'axios'; +import apiClient from '@app/services/apiClient'; import { ToolRegistry } from '@app/data/toolsTaxonomy'; import { ToolId } from '@app/types/toolId'; import { AUTOMATION_CONSTANTS } from '@app/constants/automation'; @@ -58,7 +58,7 @@ const executeApiRequest = async ( filePrefix: string, preserveBackendFilename?: boolean ): Promise => { - const response = await axios.post(endpoint, formData, { + const response = await apiClient.post(endpoint, formData, { responseType: 'blob', timeout: AUTOMATION_CONSTANTS.OPERATION_TIMEOUT }); diff --git a/frontend/src/proprietary/components/shared/config/configSections/AdminGeneralSection.tsx b/frontend/src/proprietary/components/shared/config/configSections/AdminGeneralSection.tsx index 0d71d4cac3..faebf4d489 100644 --- a/frontend/src/proprietary/components/shared/config/configSections/AdminGeneralSection.tsx +++ b/frontend/src/proprietary/components/shared/config/configSections/AdminGeneralSection.tsx @@ -335,7 +335,7 @@ export default function AdminGeneralSection() { label: (
{t('admin.settings.general.logoStyle.classicAlt', @@ -348,7 +348,7 @@ export default function AdminGeneralSection() { label: (
{t('admin.settings.general.logoStyle.modernAlt', diff --git a/images/custom-home-dark.png b/images/custom-home-dark.png new file mode 100644 index 0000000000..446d05668e Binary files /dev/null and b/images/custom-home-dark.png differ diff --git a/images/custom-home.png b/images/custom-home.png new file mode 100644 index 0000000000..3ae6e17e45 Binary files /dev/null and b/images/custom-home.png differ diff --git a/images/home-light.png b/images/home-light.png new file mode 100644 index 0000000000..af30a3d371 Binary files /dev/null and b/images/home-light.png differ diff --git a/images/home.png b/images/home.png new file mode 100644 index 0000000000..02dbdd590c Binary files /dev/null and b/images/home.png differ diff --git a/images/login-dark.png b/images/login-dark.png deleted file mode 100644 index e5cfc775e7..0000000000 Binary files a/images/login-dark.png and /dev/null differ diff --git a/images/login-light.png b/images/login-light.png deleted file mode 100644 index 72211bd435..0000000000 Binary files a/images/login-light.png and /dev/null differ diff --git a/images/settings-light.png b/images/settings-light.png deleted file mode 100644 index 627f52468d..0000000000 Binary files a/images/settings-light.png and /dev/null differ diff --git a/images/stirling-home-dark.png b/images/stirling-home-dark.png deleted file mode 100644 index ae049cbcda..0000000000 Binary files a/images/stirling-home-dark.png and /dev/null differ diff --git a/images/stirling-home.jpg b/images/stirling-home.jpg deleted file mode 100644 index 80013403b9..0000000000 Binary files a/images/stirling-home.jpg and /dev/null differ