diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3b1655d --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# BentoPDF Environment Variables +# Copy this file to .env.production and configure as needed. + +# CORS Proxy for digital signature certificate chain fetching +VITE_CORS_PROXY_URL= +VITE_CORS_PROXY_SECRET= + +# WASM Module URLs +# Pre-configured defaults enable advanced PDF features out of the box. +# For air-gapped / offline deployments, point these to your internal server (e.g., /wasm/pymupdf/). +VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/ +VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/ +VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf/dist/ + +# Default UI language (build-time) +# Supported: en, ar, be, fr, de, es, zh, zh-TW, vi, tr, id, it, pt, nl, da +VITE_DEFAULT_LANGUAGE= + +# Custom branding (build-time) +# Replace the default BentoPDF branding with your own. +# Place your logo file in the public/ folder and set the path relative to it. +VITE_BRAND_NAME= +VITE_BRAND_LOGO= +VITE_FOOTER_TEXT= diff --git a/.github/ISSUE_TEMPLATE/bug_feature_question.md b/.github/ISSUE_TEMPLATE/bug_feature_question.md deleted file mode 100644 index 23d24e3..0000000 --- a/.github/ISSUE_TEMPLATE/bug_feature_question.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -name: '🐛 Bug / 💡 Feature / ❓ Question' -about: 'Report a bug, request a feature, or ask a question about BentoPDF' -title: '(Bug) , (Feature) , or (Question) ' -labels: ['needs triage'] -assignees: [] ---- - -## Type of Issue - -Please check one: - -- [ ] 🐛 Bug Report -- [ ] 💡 Feature Request -- [ ] ❓ Question / Help - ---- - -## Description - -Provide a clear and concise description of the issue, feature request, or question. - ---- - -## Steps to Reproduce (for Bugs) - -1. Go to '...' -2. Run '...' -3. Observe error: '...' - -**Expected Behavior:** -Describe what you expected BentoPDF to do. - -**Actual Behavior:** -Describe what actually happened, including error messages. - ---- - -## Feature Request Details (if applicable) - -- What functionality are you requesting? -- Why is this useful? -- Any example or context to illustrate it? - ---- - -## Question Details (if applicable) - -- What is your question? -- What have you tried so far? -- Any relevant code snippet or scenario? - ---- - -## Screenshots / Logs (if applicable) - -Attach any screenshots, logs, or stack traces that help explain the problem or question. - ---- - -## Environment - -- **OS:** (e.g., macOS 14.0 / Ubuntu 22.04 / Windows 11) -- **Dependencies / setup details (if any):** - ---- - -## 💭 Additional Context - -Any other information, suggestions, or references that might help maintainers. - ---- - -✅ **Title Format Reminder:** - -- `(Bug) Text alignment incorrect on multi-line paragraphs` -- `(Feature) Add support for custom PDF metadata` -- `(Question) How to embed custom fonts?` - ---- diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..b638cfd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,122 @@ +name: 🐛 Bug Report +description: Report a bug in BentoPDF +title: "(Bug) " +labels: ["bug", "needs triage"] +body: + - type: markdown + attributes: + value: | + ## ⚠️ Important Notice + **Bug reports without logs or a sample file demonstrating the issue will not be investigated.** + Please help us help you by providing the information needed to reproduce and fix the problem. + + - type: textarea + id: description + attributes: + label: Description + description: Provide a clear and concise description of the bug. + placeholder: What happened? What did you expect to happen? + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: How can we reproduce this issue? + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Upload file '...' + 4. See error + validations: + required: true + + - type: textarea + id: console-logs + attributes: + label: Console Logs + description: Open browser DevTools (F12 → Console tab) and paste any errors here. + placeholder: Paste console logs here... + render: shell + validations: + required: true + + - type: textarea + id: sample-file + attributes: + label: Sample PDF or File + description: | + Attach a sample PDF that reproduces the issue, or describe how to create one. + If you cannot share the original, create a minimal example that shows the problem. + placeholder: Drag and drop your file here, or describe how to reproduce with any PDF... + validations: + required: true + + - type: dropdown + id: browser + attributes: + label: Browser + description: Which browser are you using? + options: + - Chrome + - Firefox + - Safari + - Edge + - Brave + - Other + validations: + required: true + + - type: input + id: browser-version + attributes: + label: Browser Version + description: e.g., Chrome 120, Firefox 121 + placeholder: "120" + validations: + required: true + + - type: dropdown + id: os + attributes: + label: Operating System + options: + - macOS + - Windows + - Linux + - iOS + - Android + - Other + validations: + required: true + + - type: input + id: bentopdf-version + attributes: + label: BentoPDF Version + description: Check the footer or package.json + placeholder: "1.15.4" + validations: + required: false + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any other information that might help us debug this issue. + placeholder: Screenshots, network errors, stack traces, etc. + validations: + required: false + + - type: checkboxes + id: checklist + attributes: + label: Pre-submission Checklist + options: + - label: I have included console logs from the browser DevTools + required: true + - label: I have attached a sample file or described how to reproduce the issue + required: true + - label: I have searched existing issues to ensure this is not a duplicate + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3c041ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 💬 Discord Community + url: https://discord.gg/Bgq3Ay3f2w + about: Join our Discord for quick questions and community support + - name: 📖 Documentation + url: https://github.com/nicholaschen09/BentoPDF#readme + about: Check the README for setup and usage instructions diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..9797c6a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,39 @@ +name: 💡 Feature Request +description: Suggest a new feature for BentoPDF +title: "(Feature) " +labels: ["enhancement", "needs triage"] +body: + - type: textarea + id: description + attributes: + label: Feature Description + description: What functionality are you requesting? + placeholder: Describe the feature you'd like to see... + validations: + required: true + + - type: textarea + id: use-case + attributes: + label: Use Case + description: Why is this feature useful? What problem does it solve? + placeholder: Explain why you need this feature... + validations: + required: true + + - type: textarea + id: examples + attributes: + label: Examples + description: Any examples, mockups, or references to illustrate the feature? + placeholder: Links to similar features, screenshots, etc. + validations: + required: false + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any other information about the feature request. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..ac5e905 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,30 @@ +name: ❓ Question +description: Ask a question about BentoPDF +title: "(Question) " +labels: ["question"] +body: + - type: textarea + id: question + attributes: + label: Question + description: What would you like to know? + placeholder: Your question here... + validations: + required: true + + - type: textarea + id: tried + attributes: + label: What have you tried? + description: What solutions have you already attempted? + placeholder: Describe what you've tried so far... + validations: + required: false + + - type: textarea + id: context + attributes: + label: Additional Context + description: Any relevant code snippets, screenshots, or scenarios. + validations: + required: false diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 0401832..b11f990 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -12,9 +12,11 @@ jobs: # New job to build dist and create release build-and-release: runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') + if: github.repository == 'alam00000/bentopdf' && startsWith(github.ref, 'refs/tags/') permissions: contents: write + env: + HUSKY: 0 steps: - name: Checkout code uses: actions/checkout@v4 @@ -49,10 +51,12 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - docker-build-and-push: + # Build each platform natively in parallel, then merge manifests + build-amd64: runs-on: ubuntu-latest + if: github.repository == 'alam00000/bentopdf' permissions: - contents: write + contents: read packages: write strategy: matrix: @@ -67,18 +71,9 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_TOKEN }} - - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: @@ -86,7 +81,7 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract version and determine release type + - name: Extract version id: version run: | if [[ $GITHUB_REF == refs/tags/v* ]]; then @@ -101,8 +96,7 @@ jobs: echo "is_release=false" >> $GITHUB_OUTPUT fi - # Build and push for releases (with 'latest' tag) - - name: Build and push ${{ matrix.mode.name }} image (release) + - name: Build and push amd64 ${{ matrix.mode.name }} (release) if: steps.version.outputs.is_release == 'true' uses: docker/build-push-action@v6 with: @@ -110,18 +104,12 @@ jobs: build-args: | SIMPLE_MODE=${{ matrix.mode.simple_mode }} tags: | - bentopdf/bentopdf${{ matrix.mode.suffix }}:latest - bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }} - bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }} - ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:latest - ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }} - ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }} - platforms: linux/amd64,linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 + platforms: linux/amd64 + cache-from: type=gha,scope=amd64-${{ matrix.mode.name }} + cache-to: type=gha,mode=max,scope=amd64-${{ matrix.mode.name }} - # Build and push for main branch (with 'edge' tag) - - name: Build and push ${{ matrix.mode.name }} image (edge) + - name: Build and push amd64 ${{ matrix.mode.name }} (edge) if: steps.version.outputs.is_release == 'false' uses: docker/build-push-action@v6 with: @@ -129,10 +117,208 @@ jobs: build-args: | SIMPLE_MODE=${{ matrix.mode.simple_mode }} tags: | - bentopdf/bentopdf${{ matrix.mode.suffix }}:edge - bentopdf/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} - ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge - ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} - platforms: linux/amd64,linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 + platforms: linux/amd64 + cache-from: type=gha,scope=amd64-${{ matrix.mode.name }} + cache-to: type=gha,mode=max,scope=amd64-${{ matrix.mode.name }} + + build-arm64: + runs-on: ubuntu-24.04-arm # Native ARM64 runner + permissions: + contents: read + packages: write + strategy: + matrix: + mode: + - name: default + simple_mode: false + suffix: "" + - name: simple + simple_mode: true + suffix: "-simple" + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract version + id: version + run: | + if [[ $GITHUB_REF == refs/tags/v* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT + echo "is_release=true" >> $GITHUB_OUTPUT + else + SHORT_SHA=${GITHUB_SHA::7} + echo "version=edge" >> $GITHUB_OUTPUT + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "is_release=false" >> $GITHUB_OUTPUT + fi + + - name: Build and push arm64 ${{ matrix.mode.name }} (release) + if: steps.version.outputs.is_release == 'true' + uses: docker/build-push-action@v6 + with: + push: true + build-args: | + SIMPLE_MODE=${{ matrix.mode.simple_mode }} + tags: | + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64 + platforms: linux/arm64 + cache-from: type=gha,scope=arm64-${{ matrix.mode.name }} + cache-to: type=gha,mode=max,scope=arm64-${{ matrix.mode.name }} + + - name: Build and push arm64 ${{ matrix.mode.name }} (edge) + if: steps.version.outputs.is_release == 'false' + uses: docker/build-push-action@v6 + with: + push: true + build-args: | + SIMPLE_MODE=${{ matrix.mode.simple_mode }} + tags: | + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64 + platforms: linux/arm64 + cache-from: type=gha,scope=arm64-${{ matrix.mode.name }} + cache-to: type=gha,mode=max,scope=arm64-${{ matrix.mode.name }} + + # Merge GHCR manifests after both platforms are built + merge-manifests-ghcr: + runs-on: ubuntu-latest + needs: [build-amd64, build-arm64] + permissions: + packages: write + strategy: + matrix: + mode: + - name: default + suffix: "" + - name: simple + suffix: "-simple" + steps: + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract version + id: version + run: | + if [[ $GITHUB_REF == refs/tags/v* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT + echo "is_release=true" >> $GITHUB_OUTPUT + else + SHORT_SHA=${GITHUB_SHA::7} + echo "version=edge" >> $GITHUB_OUTPUT + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "is_release=false" >> $GITHUB_OUTPUT + fi + + - name: Create and push GHCR manifest (release) + if: steps.version.outputs.is_release == 'true' + run: | + docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:latest \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64 + + docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }} \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64 + + docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }} \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64 + + - name: Create and push GHCR manifest (edge) + if: steps.version.outputs.is_release == 'false' + run: | + docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64 + + docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64 + + # Copy images from GHCR to DockerHub + push-to-dockerhub: + runs-on: ubuntu-latest + needs: [merge-manifests-ghcr] + continue-on-error: true + permissions: + contents: read + packages: read + strategy: + matrix: + mode: + - name: default + suffix: "" + - name: simple + suffix: "-simple" + steps: + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Extract version + id: version + run: | + if [[ $GITHUB_REF == refs/tags/v* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT + echo "is_release=true" >> $GITHUB_OUTPUT + else + SHORT_SHA=${GITHUB_SHA::7} + echo "version=edge" >> $GITHUB_OUTPUT + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "is_release=false" >> $GITHUB_OUTPUT + fi + + - name: Copy images to DockerHub (release) + if: steps.version.outputs.is_release == 'true' + run: | + docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:latest \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64 + + docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }} \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64 + + docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }} \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64 + + - name: Copy images to DockerHub (edge) + if: steps.version.outputs.is_release == 'false' + run: | + docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:edge \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64 + + docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \ + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64 diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml new file mode 100644 index 0000000..8d261dc --- /dev/null +++ b/.github/workflows/sponsors.yml @@ -0,0 +1,12 @@ +name: Update Sponsors +on: + workflow_dispatch: +jobs: + update: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: JamesIves/github-sponsors-readme-action@v1 + with: + token: ${{ secrets.SPONSORS_TOKEN }} + file: 'README.md' \ No newline at end of file diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000..3013709 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,67 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + + +# Configure these variables under your repo settings. +# You will likely need to set BASE_URL to be the name of your repo eg 'bentopdf' +env: + SIMPLE_MODE: ${{ vars.SIMPLE_MODE }} + BASE_URL: ${{ vars.BASE_URL }}/ + HUSKY: 0 + +jobs: + # Single deploy job since we're just deploying + deploy: + if: github.repository == 'alam00000/bentopdf' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Install dependencies + run: npm ci + - name: Build distribution + run: | + # export SIMPLE_MODE=${{ vars.SIMPLE_MODE }} + # export BASE_URL=${{ vars.BASE_URL }}/ + npm run build + + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: dist + name: github-pages-deployment + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + with: + artifact_name: github-pages-deployment diff --git a/.github/workflows/update-embedpdf-snippet.yml b/.github/workflows/update-embedpdf-snippet.yml index 58ab27a..3c14152 100644 --- a/.github/workflows/update-embedpdf-snippet.yml +++ b/.github/workflows/update-embedpdf-snippet.yml @@ -39,13 +39,6 @@ jobs: echo "Updating from '${{ steps.current-version.outputs.version }}' to '${{ steps.upstream-version.outputs.version }}'" fi - - name: Setup Node - if: steps.gate.outputs.run == 'true' - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm - - name: Enable corepack (pnpm) if: steps.gate.outputs.run == 'true' run: corepack enable @@ -58,24 +51,31 @@ jobs: - name: Clone upstream embed-pdf-viewer if: steps.gate.outputs.run == 'true' - run: git clone https://github.com/embedpdf/embed-pdf-viewer ../embed-pdf-viewer + run: git clone --depth 1 --branch main https://github.com/embedpdf/embed-pdf-viewer ./_upstream/embed-pdf-viewer + + - name: Setup Node + if: steps.gate.outputs.run == 'true' + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm - name: Install upstream deps if: steps.gate.outputs.run == 'true' - working-directory: ../embed-pdf-viewer - run: pnpm install --frozen-lockfile + working-directory: ./_upstream/embed-pdf-viewer + run: pnpm install --no-frozen-lockfile - name: Build snippet if: steps.gate.outputs.run == 'true' - working-directory: ../embed-pdf-viewer + working-directory: ./_upstream/embed-pdf-viewer run: pnpm run build:snippet - name: Pack snippet tarball if: steps.gate.outputs.run == 'true' - working-directory: ../embed-pdf-viewer + working-directory: ./_upstream/embed-pdf-viewer run: | - npm pack ./snippet --pack-destination ../bentopdf/vendor/embedpdf - ls -l ../bentopdf/vendor/embedpdf + npm pack ./viewers/snippet --pack-destination ../../vendor/embedpdf + ls -l ../../vendor/embedpdf - name: Sanitize tarball (rename pkg and pin deps) if: steps.gate.outputs.run == 'true' @@ -114,19 +114,23 @@ jobs: - name: Refresh lockfile if: steps.gate.outputs.run == 'true' - run: npm install --package-lock-only + run: npm install --package-lock-only --ignore-scripts - name: Write upstream version marker if: steps.gate.outputs.run == 'true' run: | echo "${{ steps.upstream-version.outputs.version }}" > vendor/embedpdf/.upstream-version + - name: Cleanup upstream clone + if: steps.gate.outputs.run == 'true' + run: rm -rf ./_upstream + - name: Create Pull Request if: steps.gate.outputs.run == 'true' uses: peter-evans/create-pull-request@v6 with: - commit-message: 'chore: update embedpdf snippet' - title: 'Update vendored EmbedPDF snippet' + commit-message: "build(deps): bump embedpdf-snippet from ${{ steps.current-version.outputs.version }} to ${{ steps.upstream-version.outputs.version }}" + title: "build(deps): bump embedpdf-snippet from ${{ steps.current-version.outputs.version }} to ${{ steps.upstream-version.outputs.version }}" body: | - Build snippet from upstream embed-pdf-viewer via `npm run build:snippet` - Pack tarball into `vendor/embedpdf/` and point dependency to it diff --git a/.gitignore b/.gitignore index 341cb20..a89bf35 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ dist dist-ssr *.local .npm-cache +.env +.env.production +.env.local # Editor directories and files .vscode/* @@ -27,3 +30,15 @@ dist-ssr coverage/ *.lcov +# Generated sitemap +public/sitemap.xml + +#backup +.seo-backup +libreoffice-wasm-package + +# helm chart +bentopdf-*.tgz + +# test +dist-test \ No newline at end of file diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..1a21bcc --- /dev/null +++ b/.htaccess @@ -0,0 +1,139 @@ +RewriteEngine On +RewriteBase / + +# ============================================ +# 1. SECURITY HEADERS (UPDATED FOR CDN COMPATIBILITY) +# ============================================ + +Header always set X-Frame-Options "SAMEORIGIN" +Header always set X-Content-Type-Options "nosniff" +Header always set X-XSS-Protection "1; mode=block" +Header always set Referrer-Policy "strict-origin-when-cross-origin" +Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()" +Header always set Cross-Origin-Opener-Policy "same-origin" +Header always set Cross-Origin-Embedder-Policy "require-corp" + + +# ============================================ +# 2. BROWSER CACHING +# ============================================ + +ExpiresActive On +ExpiresByType image/jpeg "access plus 1 year" +ExpiresByType image/png "access plus 1 year" +ExpiresByType image/gif "access plus 1 year" +ExpiresByType image/webp "access plus 1 year" +ExpiresByType image/svg+xml "access plus 1 year" +ExpiresByType image/x-icon "access plus 1 year" +ExpiresByType font/woff2 "access plus 1 year" +ExpiresByType font/woff "access plus 1 year" +ExpiresByType font/ttf "access plus 1 year" +ExpiresByType text/css "access plus 1 month" +ExpiresByType application/javascript "access plus 1 month" +ExpiresByType application/wasm "access plus 1 year" +ExpiresByType application/gzip "access plus 1 year" +ExpiresByType text/html "access plus 0 seconds" + + +# ============================================ +# 3. COMPRESSION (STANDARD) +# ============================================ +SetEnvIfNoCase Request_URI "\.gz$" no-gzip +SetEnvIfNoCase Request_URI "\.br$" no-gzip +SetEnvIfNoCase Request_URI "\.wasm$" no-gzip + + +AddOutputFilterByType DEFLATE text/plain +AddOutputFilterByType DEFLATE text/html +AddOutputFilterByType DEFLATE text/xml +AddOutputFilterByType DEFLATE text/css +AddOutputFilterByType DEFLATE application/xml +AddOutputFilterByType DEFLATE application/xhtml+xml +AddOutputFilterByType DEFLATE application/rss+xml +AddOutputFilterByType DEFLATE application/javascript +AddOutputFilterByType DEFLATE application/x-javascript +AddOutputFilterByType DEFLATE application/json +AddOutputFilterByType DEFLATE image/svg+xml +AddOutputFilterByType DEFLATE font/woff +AddOutputFilterByType DEFLATE font/ttf + + +# ============================================ +# 4. MIME TYPES & SPECIAL FILE HANDLING +# ============================================ +AddType application/javascript .js .mjs +AddType application/wasm .wasm +AddType font/woff2 .woff2 +AddType font/woff .woff +AddType image/webp .webp + + +ForceType application/wasm +Header set Content-Encoding "gzip" +Header set Cross-Origin-Resource-Policy "cross-origin" +Header append Vary Accept-Encoding + + + +ForceType application/octet-stream +Header set Content-Encoding "gzip" +Header append Vary Accept-Encoding + + +# ============================================ +# 5. REDIRECTS & ROUTING +# ============================================ +# Canonical WWW +RewriteCond %{HTTP_HOST} ^bentopdf\.com [NC] +RewriteRule ^(.*)$ https://www.bentopdf.com/$1 [L,R=301] + +# Force HTTPS +RewriteCond %{HTTPS} off +RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + +# Remove trailing slash (except for language root directories) +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_URI} !^/(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/$ +RewriteCond %{REQUEST_URI} (.+)/$ +RewriteRule ^ %1 [R=301,L] + +# Existing files/dirs - serve directly +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^ - [L] + +# ============================================ +# 5.1. LANGUAGE ROUTES (MUST BE BEFORE .html extension rule) +# ============================================ +# English prefix redirects to root +RewriteRule ^en/?$ / [R=301,L] +RewriteRule ^en/(.+)$ /$1 [R=301,L] + +# Language prefix root (e.g., /de/ -> /de/index.html) +RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f +RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L] + +# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html) +RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f +RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L] + +# ============================================ +# 5.5. DOCS ROUTING (VitePress) +# ============================================ +RewriteCond %{REQUEST_URI} ^/docs +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME}\.html -f +RewriteRule ^(.*)$ $1.html [L] + +# ============================================ +# 6. ADD .HTML EXTENSION IF FILE EXISTS (ROOT LEVEL ONLY) +# ============================================ +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME}.html -f +RewriteRule ^([^/]+)$ $1.html [L] + +# ============================================ +# 7. ERROR PAGES +# ============================================ +ErrorDocument 404 /404.html diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e70c3cd --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Handlebars partials with template syntax inside HTML attributes +src/partials/footer.html diff --git a/.well-known/funding-manifest-urls b/.well-known/funding-manifest-urls new file mode 100644 index 0000000..79005d3 --- /dev/null +++ b/.well-known/funding-manifest-urls @@ -0,0 +1 @@ +https://www.bentopdf.com/funding.json diff --git a/404.html b/404.html new file mode 100644 index 0000000..b1a8fbc --- /dev/null +++ b/404.html @@ -0,0 +1,177 @@ + + + + + + + + 404 - Page Not Found | BentoPDF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{> navbar }} + +
+
+ +
+

+ 404 +

+
+ + +

+ Page Not Found +

+ + +

+ Oops! The page you're looking for seems to have wandered off into the + digital void. Don't worry though, our PDF tools are still right where + you left them. +

+ + + + + + +
+
+ + {{> footer }} + + + + + + + + + + diff --git a/Dockerfile b/Dockerfile index 373b41e..12520e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,38 +3,73 @@ ARG BASE_URL= # Build stage -FROM node:20-alpine AS builder +FROM public.ecr.aws/docker/library/node:20-alpine AS builder WORKDIR /app COPY package*.json ./ COPY vendor ./vendor -RUN npm ci +ENV HUSKY=0 +RUN npm config set fetch-retries 5 && \ + npm config set fetch-retry-mintimeout 60000 && \ + npm config set fetch-retry-maxtimeout 300000 && \ + npm config set fetch-timeout 600000 && \ + npm ci COPY . . # Build without type checking (vite build only) # Pass SIMPLE_MODE environment variable if provided ARG SIMPLE_MODE=false ENV SIMPLE_MODE=$SIMPLE_MODE +ARG COMPRESSION_MODE=all +ENV COMPRESSION_MODE=$COMPRESSION_MODE -# global arg to local arg +# global arg to local arg - BASE_URL is read from env by vite.config.ts ARG BASE_URL ENV BASE_URL=$BASE_URL -RUN if [ -z "$BASE_URL" ]; then \ - npm run build -- --mode production; \ - else \ - npm run build -- --base=${BASE_URL} --mode production; \ - fi +# WASM module URLs (pre-configured defaults) +# Override these for air-gapped or self-hosted WASM deployments +ARG VITE_WASM_PYMUPDF_URL +ARG VITE_WASM_GS_URL +ARG VITE_WASM_CPDF_URL +ENV VITE_WASM_PYMUPDF_URL=$VITE_WASM_PYMUPDF_URL +ENV VITE_WASM_GS_URL=$VITE_WASM_GS_URL +ENV VITE_WASM_CPDF_URL=$VITE_WASM_CPDF_URL + +# Default UI language (e.g. en, fr, de, es, zh, ar) +ARG VITE_DEFAULT_LANGUAGE +ENV VITE_DEFAULT_LANGUAGE=$VITE_DEFAULT_LANGUAGE + +# Custom branding (e.g. VITE_BRAND_NAME=MyCompany VITE_BRAND_LOGO=my-logo.svg) +ARG VITE_BRAND_NAME +ARG VITE_BRAND_LOGO +ARG VITE_FOOTER_TEXT +ENV VITE_BRAND_NAME=$VITE_BRAND_NAME +ENV VITE_BRAND_LOGO=$VITE_BRAND_LOGO +ENV VITE_FOOTER_TEXT=$VITE_FOOTER_TEXT + +ENV NODE_OPTIONS="--max-old-space-size=3072" + +RUN --mount=type=secret,id=VITE_CORS_PROXY_URL \ + --mount=type=secret,id=VITE_CORS_PROXY_SECRET \ + VITE_CORS_PROXY_URL=$(cat /run/secrets/VITE_CORS_PROXY_URL 2>/dev/null || echo "") \ + VITE_CORS_PROXY_SECRET=$(cat /run/secrets/VITE_CORS_PROXY_SECRET 2>/dev/null || echo "") \ + npm run build:with-docs # Production stage -FROM nginxinc/nginx-unprivileged:stable-alpine-slim +FROM quay.io/nginx/nginx-unprivileged:stable-alpine-slim LABEL org.opencontainers.image.source="https://github.com/alam00000/bentopdf" +LABEL org.opencontainers.image.url="https://github.com/alam00000/bentopdf" # global arg to local arg ARG BASE_URL +# Set this to "true" to disable Nginx listening on IPv6 +ENV DISABLE_IPV6=false + COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html${BASE_URL%/} COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf +COPY --chown=nginx:nginx --chmod=755 nginx-ipv6.sh /docker-entrypoint.d/99-disable-ipv6.sh RUN mkdir -p /etc/nginx/tmp && chown -R nginx:nginx /etc/nginx/tmp EXPOSE 8080 diff --git a/Dockerfile.nonroot b/Dockerfile.nonroot new file mode 100644 index 0000000..dc1e1a8 --- /dev/null +++ b/Dockerfile.nonroot @@ -0,0 +1,82 @@ +# Non-root Dockerfile — supports PUID/PGID environment variables (LSIO-style) +# Usage: docker build -f Dockerfile.nonroot -t bentopdf . +# docker run -d -p 3000:8080 -e PUID=1000 -e PGID=1000 bentopdf + +ARG BASE_URL= + +# Build stage (identical to main Dockerfile) +FROM public.ecr.aws/docker/library/node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +COPY vendor ./vendor +ENV HUSKY=0 +RUN npm config set fetch-retries 5 && \ + npm config set fetch-retry-mintimeout 60000 && \ + npm config set fetch-retry-maxtimeout 300000 && \ + npm config set fetch-timeout 600000 && \ + npm ci +COPY . . + +ARG SIMPLE_MODE=false +ENV SIMPLE_MODE=$SIMPLE_MODE +ARG COMPRESSION_MODE=all +ENV COMPRESSION_MODE=$COMPRESSION_MODE + +ARG BASE_URL +ENV BASE_URL=$BASE_URL + +ARG VITE_WASM_PYMUPDF_URL +ARG VITE_WASM_GS_URL +ARG VITE_WASM_CPDF_URL +ENV VITE_WASM_PYMUPDF_URL=$VITE_WASM_PYMUPDF_URL +ENV VITE_WASM_GS_URL=$VITE_WASM_GS_URL +ENV VITE_WASM_CPDF_URL=$VITE_WASM_CPDF_URL + +# Default UI language (e.g. en, fr, de, es, zh, ar) +ARG VITE_DEFAULT_LANGUAGE +ENV VITE_DEFAULT_LANGUAGE=$VITE_DEFAULT_LANGUAGE + +# Custom branding (e.g. VITE_BRAND_NAME=MyCompany VITE_BRAND_LOGO=my-logo.svg) +ARG VITE_BRAND_NAME +ARG VITE_BRAND_LOGO +ARG VITE_FOOTER_TEXT +ENV VITE_BRAND_NAME=$VITE_BRAND_NAME +ENV VITE_BRAND_LOGO=$VITE_BRAND_LOGO +ENV VITE_FOOTER_TEXT=$VITE_FOOTER_TEXT + +ENV NODE_OPTIONS="--max-old-space-size=3072" + +RUN --mount=type=secret,id=VITE_CORS_PROXY_URL \ + --mount=type=secret,id=VITE_CORS_PROXY_SECRET \ + VITE_CORS_PROXY_URL=$(cat /run/secrets/VITE_CORS_PROXY_URL 2>/dev/null || echo "") \ + VITE_CORS_PROXY_SECRET=$(cat /run/secrets/VITE_CORS_PROXY_SECRET 2>/dev/null || echo "") \ + npm run build:with-docs + +# Production stage — uses standard nginx (starts as root, drops to PUID/PGID) +FROM nginx:stable-alpine-slim + +LABEL org.opencontainers.image.source="https://github.com/alam00000/bentopdf" +LABEL org.opencontainers.image.url="https://github.com/alam00000/bentopdf" + +ARG BASE_URL + +ENV PUID=1000 +ENV PGID=1000 +ENV DISABLE_IPV6=false + +RUN apk add --no-cache su-exec + +COPY --from=builder /app/dist /usr/share/nginx/html${BASE_URL%/} +COPY nginx.conf /etc/nginx/nginx.conf +COPY --chmod=755 entrypoint.sh /entrypoint.sh + +RUN mkdir -p /etc/nginx/tmp \ + /var/cache/nginx/client_temp \ + /var/cache/nginx/proxy_temp \ + /var/cache/nginx/fastcgi_temp \ + /var/cache/nginx/uwsgi_temp \ + /var/cache/nginx/scgi_temp + +EXPOSE 8080 +ENTRYPOINT ["/entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] diff --git a/ICLA.md b/ICLA.md index 48086fb..c80debb 100644 --- a/ICLA.md +++ b/ICLA.md @@ -85,13 +85,13 @@ This Agreement shall be governed by and construed in accordance with the laws of By submitting a pull request or other Contribution to the Project, and by typing your name and date below (or by signing electronically via CLA Assistant), you agree to the terms of this Individual Contributor License Agreement. -**Full Legal Name:** ___________________________ +**Full Legal Name:** Stephan Paternotte -**GitHub Username:** ___________________________ +**GitHub Username:** Stephan-P -**Email Address:** ___________________________ +**Email Address:** stephan@paternottes.net -**Date:** ___________________________ +**Date:** 20-12-2025 **Signature:** ___________________________ diff --git a/README.md b/README.md index 4ea93d0..2948dda 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,57 @@ -

+

BentoPDF

+

+ + DigitalOcean Referral Badge + +

**BentoPDF** is a powerful, privacy-first, client-side PDF toolkit that is self hostable and allows you to manipulate, edit, merge, and process PDF files directly in your browser. No server-side processing is required, ensuring your files remain secure and private. -![Docker Pulls](https://img.shields.io/docker/pulls/bentopdf/bentopdf) [![Ko-fi](https://img.shields.io/badge/Buy%20me%20a%20Coffee-yellow?logo=kofi&style=flat-square)](https://ko-fi.com/alio0) ![GitHub Stars](https://img.shields.io/github/stars/alam00000/bentopdf?style=social) +[![Docker Downloads](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fghcr-badge.elias.eu.org%2Fapi%2Falam00000%2Fbentopdf%2Fbentopdf&query=%24.downloadCount&logo=docker&label=Docker%20Downloads&color=blue)](https://github.com/alam00000/bentopdf/pkgs/container/bentopdf) [![Ko-fi](https://img.shields.io/badge/Buy%20me%20a%20Coffee-yellow?logo=kofi&style=flat-square)](https://ko-fi.com/alio01) ![GitHub Stars](https://img.shields.io/github/stars/alam00000/bentopdf?style=social) [![Sponsor me on GitHub](https://img.shields.io/badge/Sponsor-%E2%9D%A4-ff69b4)](https://github.com/sponsors/alam00000) ![BentoPDF Tools](public/images/bentopdf-tools.png) --- +## Table of Contents + +- [Join Us on Discord](#-join-us-on-discord) +- [Documentation](#-documentation) +- [Licensing](#-licensing) +- [Stargazers over time](#-stargazers-over-time) +- [Thank You to Our Sponsors](#-thank-you-to-our-sponsors) +- [Why BentoPDF?](#-why-bentopdf) +- [Features / Tools Supported](#️-features--tools-supported) + - [Organize & Manage PDFs](#organize--manage-pdfs) + - [Edit & Modify PDFs](#edit--modify-pdfs) + - [Convert to PDF](#convert-to-pdf) + - [Convert from PDF](#convert-from-pdf) + - [Secure & Optimize PDFs](#secure--optimize-pdfs) +- [Translations](#-translations) +- [Getting Started](#-getting-started) + - [Prerequisites](#prerequisites) + - [Quick Start](#-quick-start) + - [Static Hosting](#static-hosting-using-netlify-vercel-and-github-pages) + - [Self-Hosting Locally](#-self-hosting-locally) + - [Docker Compose / Podman Compose](#-run-with-docker-compose--podman-compose-recommended) + - [Podman Quadlet](#-podman-quadlet-systemd-integration) + - [Simple Mode](#-simple-mode-for-internal-use) + - [Custom Branding](#-custom-branding) + - [WASM Configuration](#wasm-configuration) + - [Air-Gapped / Offline Deployment](#air-gapped--offline-deployment) + - [Security Features](#-security-features) + - [Digital Signature CORS Proxy](#digital-signature-cors-proxy-required) + - [Version Management](#-version-management) + - [Development Setup](#-development-setup) +- [Tech Stack & Background](#️-tech-stack--background) +- [Roadmap](#️-roadmap) +- [Contributing](#-contributing) +- [Special Thanks](#special-thanks) + +--- + ## 📢 Join Us on Discord [![Discord](https://img.shields.io/badge/Discord-Join%20Server-7289da?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/Bgq3Ay3f2w) @@ -18,14 +60,51 @@ Have questions, feature requests, or want to chat with the community? Join our D --- +## 📚 Documentation + +[![Documentation](https://img.shields.io/badge/Docs-VitePress-646cff?style=for-the-badge&logo=vite&logoColor=white)](https://bentopdf.com/docs/) + +Visit our [Documentation](https://bentopdf.com/docs/) for: + +- **Getting Started** guide +- **Tools Reference** (50+ tools) +- **Self-Hosting** guides (Docker, Vercel, Netlify, Cloudflare, AWS, Hostinger, Nginx, Apache) +- **Contributing** guide +- **Commercial License** details + +--- + ## 📜 Licensing -BentoPDF is dual-licensed: +BentoPDF is **dual-licensed** to fit your needs: -- **AGPL-3.0** for open-source projects where you share your full source code publicly -- **Commercial License** for proprietary/closed-source applications - **[Get Lifetime License for $49](https://ko-fi.com/s/f32ca4cb75)** (a one-time lifetime purchase, includes all feature updates forever) +| License | Best For | Price | +| -------------- | -------------------------------------------- | ------------------ | +| **AGPL-3.0** | Open-source projects with public source code | **Free** | +| **Commercial** | Proprietary / closed-source applications | **$49** (lifetime) | -For more details, see our [Licensing Page](https://bentopdf.com/licensing.html) +

+ + Get Commercial License + +

+ +> **One-time purchase** · **Unlimited devices & users** · **Lifetime updates** · **No AGPL obligations** + +📖 For more details, see our [Licensing Page](https://bentopdf.com/licensing.html) + +### AGPL Components (Pre-configured via CDN) + +BentoPDF does **not** bundle AGPL-licensed processing libraries in its source code, but **pre-configures CDN URLs** so all features work out of the box with zero setup: + +| Component | License | Features Enabled | +| ---------------------- | -------- | --------------------------------------------------------------------------------------------------- | +| **PyMuPDF** | AGPL-3.0 | PDF to Text/Markdown/SVG/DOCX, Extract Images/Tables, EPUB/MOBI/XPS conversion, Compression, Deskew | +| **Ghostscript** | AGPL-3.0 | PDF/A Conversion, Font to Outline | +| **CoherentPDF (CPDF)** | AGPL-3.0 | Merge, Split by Bookmarks, Table of Contents, PDF to/from JSON, Attachments | + +> [!TIP] +> **Zero-config by default.** WASM modules are loaded at runtime from jsDelivr CDN. No manual configuration is needed. For custom deployments (air-gapped, self-hosted), see [WASM Configuration](#wasm-configuration) below.
@@ -35,6 +114,18 @@ For more details, see our [Licensing Page](https://bentopdf.com/licensing.html) --- +## 💖 Thank You to Our Sponsors + +We're incredibly grateful to all our sponsors and supporters who help keep BentoPDF free and open source! + +[![Sponsor me on GitHub](https://img.shields.io/badge/Become%20a%20Sponsor-%E2%9D%A4-ff69b4?style=for-the-badge)](https://github.com/sponsors/alam00000) +[![Buy me a Coffee](https://img.shields.io/badge/Buy%20me%20a%20Coffee-yellow?style=for-the-badge&logo=kofi)](https://ko-fi.com/alio01) + + + + +--- + ## ✨ Why BentoPDF? - **Privacy First**: All processing happens in your browser. Your files are never uploaded to a server, guaranteeing 100% privacy. @@ -50,67 +141,102 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs. ### Organize & Manage PDFs -| Tool Name | Description | -| :------------------------ | :------------------------------------------------------------------------- | -| **Merge PDFs** | Combine multiple PDF files into one. | -| **Split PDFs** | Extract specific pages or divide a document into smaller files. | -| **Organize Pages** | Reorder, duplicate, or delete pages with a simple drag-and-drop interface. | -| **Extract Pages** | Save a specific range of pages as a new PDF. | -| **Delete Pages** | Remove unwanted pages from your document. | -| **Rotate PDF** | Rotate individual or all pages in a document. | -| **N-Up PDF** | Combine multiple pages onto a single page. | -| **View PDF** | A powerful, integrated PDF viewer. | -| **Alternate & Mix pages** | Merge pages by alternating pages from each PDF. | -| **Posterize PDF** | Split a PDF into multiple smaller pages for print. | -| **PDF Multi Tool** | Merge, Split, Organize, Delete, Rotate, Add Blank Pages, Extract and Duplicate in an unified interface. | -| **Add Attachments** | Embed one or more files into your PDF. | -| **Extract Attachments** | Extract all embedded files from PDF(s) as a ZIP. | -| **Edit Attachments** | View or remove attachments in your PDF. | -| **Divide Pages** | Divide pages horizontally or vertically. | -| **Combine to Single Page**| Stitch all pages into one continuous scroll. | -| **Add Blank Page** | Insert an empty page anywhere in your PDF. | -| **Reverse Pages** | Flip the order of all pages in your document. | -| **View Metadata** | Inspect the hidden properties of your PDF. | -| **PDFs to ZIP** | Package multiple PDF files into a ZIP archive. | -| **Compare PDFs** | Compare two PDFs side by side. | +| Tool Name | Description | +| :--------------------------- | :------------------------------------------------------------------------------------------------------ | +| **Merge PDFs** | Combine multiple PDF files into one. Preserves Bookmarks. | +| **Split PDFs** | Extract specific pages or divide a document into smaller files. | +| **Organize Pages** | Reorder, duplicate, or delete pages with a simple drag-and-drop interface. | +| **Extract Pages** | Save a specific range of pages as a new PDF. | +| **Delete Pages** | Remove unwanted pages from your document. | +| **Rotate PDF** | Rotate individual or all pages in a document. | +| **Rotate by Custom Degrees** | Rotate pages by any custom angle. | +| **N-Up PDF** | Combine multiple pages onto a single page. | +| **View PDF** | A powerful, integrated PDF viewer. | +| **Alternate & Mix Pages** | Merge pages by alternating pages from each PDF. Preserves Bookmarks. | +| **Posterize PDF** | Split a PDF into multiple smaller pages for print. | +| **PDF Multi Tool** | Merge, Split, Organize, Delete, Rotate, Add Blank Pages, Extract and Duplicate in an unified interface. | +| **PDF Booklet** | Rearrange pages for double-sided booklet printing. Fold and staple to create a booklet. | +| **Add Attachments** | Embed one or more files into your PDF. | +| **Extract Attachments** | Extract all embedded files from PDF(s) as a ZIP. | +| **Edit Attachments** | View or remove attachments in your PDF. | +| **Divide Pages** | Divide pages horizontally or vertically. | +| **Combine to Single Page** | Stitch all pages into one continuous scroll. | +| **Add Blank Page** | Insert an empty page anywhere in your PDF. | +| **Reverse Pages** | Flip the order of all pages in your document. | +| **View Metadata** | Inspect the hidden properties of your PDF. | +| **PDFs to ZIP** | Package multiple PDF files into a ZIP archive. | +| **Compare PDFs** | Compare two PDFs side by side. | ### Edit & Modify PDFs -| Tool Name | Description | -| :--------------------- | :---------------------------------------------------------- | -| **PDF Editor** | A comprehensive editor to modify your PDFs. | +| Tool Name | Description | +| :------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **PDF Editor** | Annotate, highlight, redact, comment, add shapes/images, search, and view PDFs. | | **Create Fillable Forms** | Create professional fillable PDF forms with text fields, checkboxes, dropdowns, radio buttons, signatures, and more. Fully compliant with PDF standards for compatibility with all PDF viewers. | -| **Add Page Numbers** | Easily add page numbers with customizable formatting. | -| **Add Watermark** | Add text or image watermarks to protect your documents. | -| **Header & Footer** | Add customizable headers and footers. | -| **Crop PDF** | Crop specific pages or the entire document. | -| **Invert Colors** | Invert the colors of your PDF pages for better readability. | -| **Change Background** | Modify the background color of your PDF. | -| **Change Text Color** | Change the color of text content within the PDF. | -| **Fill Forms** | Fill out PDF forms directly in your browser. | -| **Flatten PDF** | Flatten form fields and annotations into static content. | -| **Remove Annotations** | Remove comments, highlights, and other annotations. | -| **Remove Blank Pages** | Auto detect and remove blank pages in a PDF. | -| **Edit Bookmarks** | Add, Edit, Create, Import and Export PDF Bookmarks. | -| **Add Stamps** | Add image stamps to your PDF using the annotation toolbar. | -| **Table of Contents** | Generate a table of contents page from PDF bookmarks. | -| **Redact Content** | Permanently remove sensitive content from your PDFs. | +| **PDF Form Filler** | Fill in forms directly in the browser. Also supports XFA forms. | +| **Add Page Numbers** | Easily add page numbers with customizable formatting. | +| **Bates Numbering** | Add sequential Bates numbers across one or more PDF files. | +| **Add Watermark** | Add text or image watermarks to protect your documents. | +| **Header & Footer** | Add customizable headers and footers. | +| **Crop PDF** | Crop specific pages or the entire document. | +| **Deskew PDF** | Automatically straighten tilted scanned pages using OpenCV. | +| **Font to Outline** | Convert all fonts to vector outlines for consistent rendering across all devices. | +| **Invert Colors** | Invert the colors of your PDF pages for better readability. | +| **Change Background** | Modify the background color of your PDF. | +| **Change Text Color** | Change the color of text content within the PDF. | +| **Flatten PDF** | Flatten form fields and annotations into static content. | +| **Remove Annotations** | Remove comments, highlights, and other annotations. | +| **Remove Blank Pages** | Auto detect and remove blank pages in a PDF. | +| **Edit Bookmarks** | Add, Edit, Create, Import and Export PDF Bookmarks. | +| **Add Stamps** | Add image stamps to your PDF using the annotation toolbar. | +| **Table of Contents** | Generate a table of contents page from PDF bookmarks. | +| **Redact Content** | Permanently remove sensitive content from your PDFs. | +| **Scanner Effect** | Make your PDF look like a scanned document. | +| **Adjust Colors** | Fine-tune brightness, contrast, saturation and more. | + +### Automate + +| Tool Name | Description | +| :----------------------- | :--------------------------------------------------------------- | +| **PDF Workflow Builder** | Build custom PDF processing pipelines with a visual node editor. | ### Convert to PDF -| Tool Name | Description | -| :------------------ | :-------------------------------------------------------------- | -| **Image to PDF** | Convert JPG, PNG, WebP, SVG, BMP, HEIC, and TIFF images to PDF. | -| **JPG to PDF** | Convert JPG images to PDF. | -| **PNG to PDF** | Convert PNG images to PDF. | -| **WebP to PDF** | Convert WebP images to PDF. | -| **SVG to PDF** | Convert SVG images to PDF. | -| **BMP to PDF** | Convert BMP images to PDF. | -| **HEIC to PDF** | Convert HEIC images to PDF. | -| **TIFF to PDF** | Convert TIFF images to PDF. | -| **Markdown to PDF** | Convert `.md` files into professional PDF documents. | -| **Text to PDF** | Convert plain text files into a PDF. | -| **JSON to PDF** | Convert JSON to PDF. | +| Tool Name | Description | +| :-------------------- | :----------------------------------------------------------------------------------------------------- | +| **Image to PDF** | Convert JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP to PDF. | +| **JPG to PDF** | Convert JPG, JPEG, and JPEG2000 (JP2/JPX) images to PDF. | +| **PNG to PDF** | Convert PNG images to PDF. | +| **WebP to PDF** | Convert WebP images to PDF. | +| **SVG to PDF** | Convert SVG images to PDF. | +| **BMP to PDF** | Convert BMP images to PDF. | +| **HEIC to PDF** | Convert HEIC images to PDF. | +| **TIFF to PDF** | Convert TIFF images to PDF. | +| **PSD to PDF** | Convert Adobe Photoshop (PSD) files to PDF. | +| **Word to PDF** | Convert Word documents (DOCX, DOC, ODT, RTF) to PDF. | +| **Excel to PDF** | Convert Excel spreadsheets (XLSX, XLS, ODS, CSV) to PDF. | +| **PowerPoint to PDF** | Convert PowerPoint presentations (PPTX, PPT, ODP) to PDF. | +| **ODT to PDF** | Convert OpenDocument Text files to PDF. | +| **ODS to PDF** | Convert OpenDocument Spreadsheet (ODS) files to PDF. | +| **ODP to PDF** | Convert OpenDocument Presentation (ODP) files to PDF. | +| **ODG to PDF** | Convert OpenDocument Graphics (ODG) files to PDF. | +| **RTF to PDF** | Convert Rich Text Format documents to PDF. | +| **CSV to PDF** | Convert CSV spreadsheet files to PDF. | +| **Markdown to PDF** | Write or paste Markdown and export it as a beautifully formatted PDF. | +| **Text to PDF** | Convert plain text files into a PDF. | +| **JSON to PDF** | Convert JSON files to PDF. | +| **XML to PDF** | Convert XML documents to PDF. | +| **EPUB to PDF** | Convert EPUB e-books to PDF. | +| **MOBI to PDF** | Convert MOBI e-books to PDF. | +| **FB2 to PDF** | Convert FictionBook (FB2) e-books to PDF. | +| **CBZ to PDF** | Convert comic book archives (CBZ/CBR) to PDF. | +| **XPS to PDF** | Convert XPS/OXPS documents to PDF. | +| **Email to PDF** | Convert email files (EML, MSG) to PDF. Supports Outlook exports. | +| **Pages to PDF** | Convert Apple Pages documents to PDF. | +| **WPD to PDF** | Convert WordPerfect documents (WPD) to PDF. | +| **WPS to PDF** | Convert WPS Office documents to PDF. | +| **PUB to PDF** | Convert Microsoft Publisher (PUB) files to PDF. | +| **VSD to PDF** | Convert Microsoft Visio (VSD, VSDX) files to PDF. | ### Convert from PDF @@ -122,28 +248,35 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs. | **PDF to WebP** | Convert each PDF page into a WebP image. | | **PDF to BMP** | Convert each PDF page into a BMP image. | | **PDF to TIFF** | Convert each PDF page into a TIFF image. | +| **PDF to SVG** | Convert each page into a scalable vector graphic (SVG) for perfect quality. | | **PDF to Greyscale** | Convert a color PDF into a black-and-white version. | -| **OCR PDF** | Make scanned PDFs searchable and copyable using Optical Character Recognition. | +| **PDF to Text** | Extract text from PDF files and save as plain text (.txt). | | **PDF to JSON** | Convert PDF files to JSON format. | +| **PDF to CSV** | Extract tables from PDF and convert to CSV format. | +| **PDF to Excel** | Extract tables from PDF and convert to Excel (XLSX) format. | +| **Extract Tables** | Extract tables from PDF files and export as CSV, JSON, or Markdown. | +| **OCR PDF** | Make scanned PDFs searchable and copyable using Optical Character Recognition. | ### Secure & Optimize PDFs -| Tool Name | Description | -| :--------------------- | :----------------------------------------------------------------- | -| **Compress PDF** | Reduce file size while maintaining quality. | -| **Repair PDF** | Attempt to repair and recover data from a corrupted PDF. | -| **Encrypt PDF** | Add a password to protect your PDF from unauthorized access. | -| **Decrypt PDF** | Remove password protection from a PDF (password required). | -| **Change Permissions** | Set or modify user permissions for printing, copying, and editing. | -| **Sign PDF** | Add your digital signature to a document. | -| **Redact Content** | Permanently remove sensitive content from your PDFs. | -| **Edit Metadata** | View and modify PDF metadata (author, title, keywords, etc.). | -| **Remove Metadata** | Strip all metadata from your PDF for privacy. | -| **Linearize PDF** | Optimize PDF for fast web view. | -| **Sanitize PDF** | Remove potentially unwanted or malicous files from PDF. | -| **Fix Page Size** | Standardize all pages to a uniform size. | -| **Page Dimensions** | Analyze page size, orientation, and units. | -| **Remove Restrictions**| Remove password protection and security restrictions associated with digitally signed PDF files. | +| Tool Name | Description | +| :---------------------- | :--------------------------------------------------------------------------------------------------------- | +| **Compress PDF** | Reduce file size while maintaining quality. | +| **Repair PDF** | Attempt to repair and recover data from a corrupted PDF. | +| **Encrypt PDF** | Add a password to protect your PDF from unauthorized access. | +| **Decrypt PDF** | Remove password protection from a PDF (password required). | +| **Change Permissions** | Set or modify user permissions for printing, copying, and editing. | +| **Sign PDF** | Draw, type, or upload your signature. | +| **Digital Signature** | Add cryptographic digital signatures using X.509 certificates (PFX/PEM). Private key never leaves browser. | +| **Validate Signature** | Verify digital signatures, check certificate validity, and confirm document integrity. | +| **Redact Content** | Permanently remove sensitive content from your PDFs. | +| **Edit Metadata** | View and modify PDF metadata (author, title, keywords, etc.). | +| **Remove Metadata** | Strip all metadata from your PDF for privacy. | +| **Linearize PDF** | Optimize PDF for fast web viewing. | +| **Sanitize PDF** | Remove metadata, annotations, scripts, and more. | +| **Fix Page Size** | Standardize all pages to a uniform size. | +| **Page Dimensions** | Analyze page size, orientation, and units. | +| **Remove Restrictions** | Remove password protection and security restrictions associated with digitally signed PDF files. | --- @@ -151,11 +284,19 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs. BentoPDF is available in multiple languages: -| Language | Status | -|----------|--------| -| English | [![English](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/en/common.json) | -| German | [![German](https://img.shields.io/badge/In_Progress-yellow?style=flat-square)](public/locales/de/common.json) | -| Vietnamese | [![Vietnamese](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/vi/common.json) | +| Language | Status | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| English | [![English](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/en/common.json) | +| Chinese | [![Chinese](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/zh/common.json) | +| Traditional Chinese | [![Traditional Chinese](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/zh-TW/common.json) | +| French | [![French](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/fr/common.json) | +| German | [![German](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/de/common.json) | +| Indonesian | [![Indonesian](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/id/common.json) | +| Italian | [![Italian](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/it/common.json) | +| Portuguese | [![Portuguese](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/pt/common.json) | +| Turkish | [![Turkish](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/tr/common.json) | +| Vietnamese | [![Vietnamese](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/vi/common.json) | +| Korean | [![Korean](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/ko/common.json) | Want to help translate BentoPDF into your language? Check out our [Translation Guide](TRANSLATION.md)! @@ -171,22 +312,9 @@ You can run BentoPDF locally for development or personal use. - [npm](https://www.npmjs.com/) (or yarn/pnpm) - [Docker](https://www.docker.com/) & [Docker Compose](https://docs.docker.com/compose/install/) (for containerized setup) -### 🚀 Quick Start with Docker +### 🚀 Quick Start -[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/K4AU2B) - -You can run BentoPDF directly from Docker Hub or GitHub Container Registry without cloning the repository: - -You can also watch the video on how to set it up 👉 -[BentoPDF Docker Setup](https://drive.google.com/file/d/1C4eJ2nqeaH__1Tlad-xuBHaF2Ha4fSBf/view?usp=drive_link) - -**Using Docker Hub:** - -```bash -docker run -p 3000:8080 bentopdf/bentopdf:latest -``` - -**Using GitHub Container Registry:** +Run BentoPDF instantly from GitHub Container Registry (Recommended): ```bash docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest @@ -194,9 +322,37 @@ docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest Open your browser at: http://localhost:3000 -This is the fastest way to try BentoPDF without setting up a development environment. +
+Alternative: Using Docker Hub or Podman -### 🏠 Self-Hosting +**Docker Hub:** + +```bash +docker run -p 3000:8080 bentopdfteam/bentopdf:latest +``` + +**Podman (GHCR):** + +```bash +podman run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest +``` + +**Podman (Docker Hub):** + +```bash +podman run -p 3000:8080 docker.io/bentopdfteam/bentopdf:latest +``` + +> [!NOTE] +> All `docker` commands in this documentation work with Podman by replacing `docker` with `podman`. + +
+ +### Static Hosting using Netlify, Vercel, and GitHub Pages + +It is very straightforward to host your own instance of BentoPDF using a static web page hosting service. Plus, services such as Netlify, Vercel, and GitHub Pages all offer a free tier for getting started. See [Static Hosting](https://github.com/alam00000/bentopdf/blob/main/STATIC-HOSTING.md) for details. + +### 🏠 Self-Hosting Locally Since BentoPDF is fully client-side, all processing happens in the user's browser and no server-side processing is required. This means you can host BentoPDF as simple static files on any web server or hosting platform. @@ -208,7 +364,7 @@ The easiest way to self-host is to download the pre-built distribution file from 2. Download the latest `dist-{version}.zip` file 3. Extract the zip file 4. Serve the extracted folder with your preferred web server - + **Serve the extracted folder (requires Node.js):** ```bash @@ -221,7 +377,8 @@ npx http-server -c-1 The website will be accessible at: `http://localhost:8080/` -> **Note:** The `-c-1` flag disables caching for development. +> [!NOTE] +> The `-c-1` flag disables caching for development. **Build from Source (Advanced):** @@ -248,6 +405,213 @@ npm run preview ``` +**Compression Modes:** + +BentoPDF supports different compression modes for optimized builds: + +```bash +# Gzip only (smallest Docker image size) +npm run build:gzip +docker build --build-arg COMPRESSION_MODE=g -t bentopdf:gzip . + +# Brotli only (best compression ratio) +npm run build:brotli +docker build --build-arg COMPRESSION_MODE=b -t bentopdf:brotli . + +# No compression (fastest build time) +npm run build:original +docker build --build-arg COMPRESSION_MODE=o -t bentopdf:original . + +# All formats (default, maximum browser compatibility) +npm run build:all +docker build --build-arg COMPRESSION_MODE=all -t bentopdf:all . +``` + +| Mode | Files Kept | Use Case | +| ----- | ----------- | --------------------------------- | +| `g` | `.gz` only | Standard nginx or minimal size | +| `b` | `.br` only | Modern CDN with Brotli support | +| `o` | originals | Development or custom compression | +| `all` | all formats | Maximum compatibility (default) | + +**CDN Optimization:** + +BentoPDF can use jsDelivr CDN to serve large WASM files (LibreOffice, Ghostscript, PyMuPDF) for improved performance and reduced bandwidth costs: + +```bash +# Production build with CDN (Recommended) +VITE_USE_CDN=true npm run build + +# Standard build with local files only +npm run build +``` + +**How it works:** + +- When `VITE_USE_CDN=true`: Browser loads WASM files from jsDelivr CDN (fast, global delivery) +- Local files are **always included** as automatic fallback +- If CDN fails then it falls back to local files + +

⚙️ WASM Configuration

+ +Advanced PDF features (PyMuPDF, Ghostscript, CoherentPDF) are pre-configured to load from jsDelivr CDN via environment variables. This means **all features work out of the box** — no manual setup needed. + +The default URLs are set in `.env.production`: + +```bash +VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/ +VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/ +VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf/dist/ +``` + +To override via Docker build args: + +```bash +docker build \ + --build-arg VITE_WASM_PYMUPDF_URL=https://your-server.com/pymupdf/ \ + --build-arg VITE_WASM_GS_URL=https://your-server.com/gs/ \ + --build-arg VITE_WASM_CPDF_URL=https://your-server.com/cpdf/ \ + -t bentopdf . +``` + +To disable a module (require manual user config via Advanced Settings), set its variable to an empty string. + +Users can also override these defaults per-browser via **Advanced Settings** in the UI — user overrides take priority over the environment defaults. + +> [!IMPORTANT] +> These URLs are baked into the JavaScript at **build time**. The WASM files themselves are downloaded by the **user's browser** at runtime — Docker does not download them during the build. + +

🔒 Air-Gapped / Offline Deployment

+ +For networks with no internet access (government, healthcare, financial, etc.), you need to prepare everything on a machine **with** internet, then transfer the bundle into the isolated network. + +#### Automated Script (Recommended) + +The included `prepare-airgap.sh` script automates the entire process — downloading WASM packages, building the Docker image, exporting everything into a self-contained bundle with a setup script. + +```bash +git clone https://github.com/alam00000/bentopdf.git +cd bentopdf + +# Interactive mode — prompts for all options +bash scripts/prepare-airgap.sh + +# Or fully automated +bash scripts/prepare-airgap.sh --wasm-base-url https://internal.example.com/wasm +``` + +This produces a bundle directory containing: + +``` +bentopdf-airgap-bundle/ + bentopdf.tar # Docker image + *.tgz # WASM packages (PyMuPDF, Ghostscript, CoherentPDF) + setup.sh # Setup script for the air-gapped side + README.md # Instructions +``` + +**Transfer the bundle** into the air-gapped network via USB, internal artifact repo, or approved method. Then run the included setup script: + +```bash +cd bentopdf-airgap-bundle +bash setup.sh +``` + +The setup script loads the Docker image, extracts WASM files, and optionally starts the container. + +
+Script options + +| Flag | Description | Default | +| ----------------------- | ------------------------------------------------ | --------------------------------- | +| `--wasm-base-url ` | Where WASMs will be hosted internally | _(required, prompted if missing)_ | +| `--image-name ` | Docker image tag | `bentopdf` | +| `--output-dir ` | Output bundle directory | `./bentopdf-airgap-bundle` | +| `--simple-mode` | Enable Simple Mode | off | +| `--base-url ` | Subdirectory base URL (e.g. `/pdf/`) | `/` | +| `--language ` | Default UI language (e.g. `fr`, `de`) | _(none)_ | +| `--brand-name ` | Custom brand name | _(none)_ | +| `--brand-logo ` | Logo path relative to `public/` | _(none)_ | +| `--footer-text ` | Custom footer text | _(none)_ | +| `--dockerfile ` | Dockerfile to use | `Dockerfile` | +| `--skip-docker` | Skip Docker build and export | off | +| `--skip-wasm` | Skip WASM download (reuse existing `.tgz` files) | off | + +
+ +> [!IMPORTANT] +> WASM files must be served from the **same origin** as the BentoPDF app. Web Workers use `importScripts()` which cannot load scripts cross-origin. For example, if BentoPDF runs at `https://internal.example.com`, the WASM base URL should also be `https://internal.example.com/wasm`. + +#### Manual Steps + +
+If you prefer to do it manually without the script + +**Step 1: Download the WASM packages** (on a machine with internet) + +```bash +npm pack @bentopdf/pymupdf-wasm@0.11.16 +npm pack @bentopdf/gs-wasm +npm pack coherentpdf +``` + +**Step 2: Build the Docker image with internal URLs** + +```bash +git clone https://github.com/alam00000/bentopdf.git +cd bentopdf + +docker build \ + --build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \ + --build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \ + --build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \ + -t bentopdf . +``` + +**Step 3: Export the Docker image** + +```bash +docker save bentopdf -o bentopdf.tar +``` + +**Step 4: Transfer into the air-gapped network** + +Copy these files via USB drive, internal artifact repository, or approved transfer method: + +- `bentopdf.tar` — the Docker image +- `bentopdf-pymupdf-wasm-0.11.14.tgz` — PyMuPDF WASM package +- `bentopdf-gs-wasm-*.tgz` — Ghostscript WASM package +- `coherentpdf-*.tgz` — CoherentPDF WASM package + +**Step 5: Set up inside the air-gapped network** + +```bash +# Load the Docker image +docker load -i bentopdf.tar + +# Extract the WASM packages +mkdir -p ./wasm/pymupdf ./wasm/gs ./wasm/cpdf +tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C ./wasm/pymupdf --strip-components=1 +tar xzf bentopdf-gs-wasm-*.tgz -C ./wasm/gs --strip-components=1 +tar xzf coherentpdf-*.tgz -C ./wasm/cpdf --strip-components=1 + +# Run BentoPDF +docker run -d -p 3000:8080 --restart unless-stopped bentopdf +``` + +Make sure the WASM files are accessible at the URLs you configured in Step 2. + +
+ +> [!NOTE] +> If you're building from source instead of Docker, set the variables in `.env.production` before running `npm run build`: +> +> ```bash +> VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ +> VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ +> VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ +> ``` + **Subdirectory Hosting:** BentoPDF can also be hosted from a subdirectory (e.g., `example.com/tools/bentopdf/`): @@ -269,7 +633,7 @@ cp -r dist/* serve-test/tools/bentopdf/ npx serve serve-test ``` -The website can be accessible at: ```http://localhost:3000/tools/bentopdf/``` +The website can be accessible at: `http://localhost:3000/tools/bentopdf/` The `npm run package` command creates a `dist-{version}.zip` file that you can use for self-hosting. @@ -287,6 +651,14 @@ docker run -p 3000:8080 bentopdf # The app will be accessible at http://localhost:3000/bentopdf/ ``` +**Default Language:** + +Set the default UI language at build time. Users can still switch languages — this only changes the initial default. Supported: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. + +```bash +docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf . +``` + **Combined with Simple Mode:** ```bash @@ -299,11 +671,12 @@ docker build \ docker run -p 3000:8080 bentopdf-simple ``` -> **Important**: +> [!IMPORTANT] +> > - Always include trailing slashes in `BASE_URL` (e.g., `/bentopdf/` not `/bentopdf`) > - The default value is `/` for root deployment -### 🚀 Run with Docker Compose (Recommended) +### 🚀 Run with Docker Compose / Podman Compose (Recommended) For a more robust setup with auto-restart capabilities: @@ -312,7 +685,8 @@ For a more robust setup with auto-restart capabilities: ```yaml services: bentopdf: - image: bentopdf/bentopdf:latest + image: ghcr.io/alam00000/bentopdf:latest # Recommended + # image: bentopdfteam/bentopdf:latest # Alternative: Docker Hub container_name: bentopdf ports: - '3000:8080' @@ -322,11 +696,48 @@ services: 2. **Start the application**: ```bash +# Docker Compose docker-compose up -d + +# Podman Compose +podman-compose up -d ``` The application will be available at `http://localhost:3000`. +### 🐧 Podman Quadlet (Systemd Integration) + +For Linux production deployments, you can run BentoPDF as a systemd service using [Podman Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html). + +Create `~/.config/containers/systemd/bentopdf.container`: + +```ini +[Unit] +Description=BentoPDF - Privacy-first PDF toolkit +After=network-online.target + +[Container] +Image=ghcr.io/alam00000/bentopdf:latest +ContainerName=bentopdf +PublishPort=3000:8080 +AutoUpdate=registry + +[Service] +Restart=always + +[Install] +WantedBy=default.target +``` + +Then enable and start: + +```bash +systemctl --user daemon-reload +systemctl --user enable --now bentopdf +``` + +For detailed Quadlet configuration, see [Self-Hosting Docker Guide](https://bentopdf.com/docs/self-hosting/docker). + ### 🏢 Simple Mode for Internal Use For organizations that want a clean, distraction-free interface focused solely on PDF tools, BentoPDF supports a **Simple Mode** that hides all branding and marketing content. @@ -340,6 +751,42 @@ For organizations that want a clean, distraction-free interface focused solely o For more details, see [SIMPLE_MODE.md](SIMPLE_MODE.md). +### 🎨 Custom Branding + +Replace the default BentoPDF logo, name, and footer text with your own. Branding is configured via environment variables at **build time** and works across all deployment methods (Docker, static hosting, air-gapped VMs). + +| Variable | Description | Default | +| ------------------ | --------------------------------------- | --------------------------------------- | +| `VITE_BRAND_NAME` | Brand name shown in header and footer | `BentoPDF` | +| `VITE_BRAND_LOGO` | Path to logo file relative to `public/` | `images/favicon-no-bg.svg` | +| `VITE_FOOTER_TEXT` | Custom footer/copyright text | `© 2026 BentoPDF. All rights reserved.` | + +**Docker:** + +```bash +docker build \ + --build-arg VITE_BRAND_NAME="AcmePDF" \ + --build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \ + --build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \ + -t acmepdf . +``` + +**Building from source:** + +Place your logo in the `public/` folder, then build: + +```bash +VITE_BRAND_NAME="AcmePDF" \ +VITE_BRAND_LOGO="images/acme-logo.svg" \ +VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \ +npm run build +``` + +Or set the values in `.env.production` before building. + +> [!TIP] +> Branding works in both full mode and Simple Mode. You can combine it with other build-time options like `SIMPLE_MODE`, `BASE_URL`, and `VITE_DEFAULT_LANGUAGE`. + ### 🔒 Security Features BentoPDF runs as a non-root user using nginx-unprivileged for enhanced security: @@ -355,24 +802,161 @@ docker build -t bentopdf . docker run -p 8080:8080 bentopdf ``` +#### Custom User ID (PUID/PGID) + +For environments that require running as a specific non-root user (e.g., NAS devices, Kubernetes with security contexts), use the non-root Dockerfile: + +```bash +# Build the non-root image +docker build -f Dockerfile.nonroot -t bentopdf-nonroot . + +# Run with custom UID/GID +docker run -d -p 3000:8080 -e PUID=1000 -e PGID=1000 bentopdf-nonroot +``` + +| Variable | Description | Default | +| -------- | ------------------ | ------- | +| `PUID` | User ID to run as | `1000` | +| `PGID` | Group ID to run as | `1000` | + +> [!NOTE] +> The standard `Dockerfile` uses `nginx-unprivileged` (UID 101) and is recommended for most deployments. Use `Dockerfile.nonroot` only when you need a specific UID/GID. + For detailed security configuration, see [SECURITY.md](SECURITY.md). +### Digital Signature CORS Proxy (Required) + +The **Digital Signature** tool uses a signing library that may need to fetch certificate chain data from certificate authority providers. Since many certificate servers don't include CORS headers (and often serve over HTTP, which is blocked by browsers on HTTPS sites), a proxy is required for this feature to work. + +**When is the proxy needed?** + +- Only when using the Digital Signature tool +- Only if your certificate requires fetching issuer certificates from external URLs +- Self-signed certificates typically don't need this + +**Deploying the CORS Proxy (Cloudflare Workers):** + +1. **Navigate to the cloudflare directory:** + + ```bash + cd cloudflare + ``` + +2. **Login to Cloudflare (if not already):** + + ```bash + npx wrangler login + ``` + +3. **Update allowed origins** — open `cors-proxy-worker.js` and change `ALLOWED_ORIGINS` to your domain: + + ```js + const ALLOWED_ORIGINS = [ + 'https://your-domain.com', + 'https://www.your-domain.com', + ]; + ``` + + > [!IMPORTANT] + > Without this step, the proxy will reject all requests from your site with a 403 error. The default only allows `bentopdf.com`. + +4. **Deploy the worker:** + + ```bash + npx wrangler deploy + ``` + +5. **Note your worker URL** (e.g., `https://bentopdf-cors-proxy.your-subdomain.workers.dev`) + +6. **Set the environment variable when building:** + ```bash + VITE_CORS_PROXY_URL=https://your-worker-url.workers.dev npm run build + ``` + Or with Docker: + ```bash + export VITE_CORS_PROXY_URL="https://your-worker-url.workers.dev" + DOCKER_BUILDKIT=1 docker build \ + --secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \ + -t your-bentopdf . + ``` + +#### Production Security Features + +The CORS proxy includes several security measures: + +| Feature | Description | +| ----------------------- | -------------------------------------------------------------------------------------- | +| **Origin Validation** | Only allows requests from domains listed in `ALLOWED_ORIGINS` | +| **URL Restrictions** | Only allows certificate URLs (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) | +| **Private IP Blocking** | Blocks IPv4/IPv6 private ranges, link-local, loopback, decimal IPs, and cloud metadata | +| **Content-Type Safety** | Only returns safe certificate MIME types, blocks upstream content-type injection | +| **File Size Limit** | Streams response with 10MB limit, aborts mid-download if exceeded | +| **Rate Limiting** | 60 requests per IP per minute (requires KV) | +| **HMAC Signatures** | Optional client-side signing (deters casual abuse) | + +#### Enabling Rate Limiting (Recommended) + +Rate limiting requires Cloudflare KV storage: + +```bash +cd cloudflare + +# Create KV namespace +npx wrangler kv namespace create "RATE_LIMIT_KV" + +# Copy the returned ID and add to wrangler.toml: +# [[kv_namespaces]] +# binding = "RATE_LIMIT_KV" +# id = "YOUR_ID_HERE" + +# Redeploy +npx wrangler deploy +``` + +**Free tier limits:** 100,000 reads/day, 1,000 writes/day (~300-500 signatures/day) + +#### HMAC Signature Verification (Optional) + +> [!WARNING] +> Client-side secrets can be extracted from bundled JavaScript. For production deployments with sensitive requirements, use your own backend server to proxy requests instead of embedding secrets in frontend code. + +BentoPDF uses client-side HMAC as a deterrent against casual abuse, but accepts this tradeoff due to its fully client-side architecture. To enable: + +```bash +# Generate a secret +openssl rand -hex 32 + +# Set on Cloudflare Worker +npx wrangler secret put PROXY_SECRET + +# Set in build environment +VITE_CORS_PROXY_SECRET=your-secret npm run build + +# Or with Docker (optional; URL secret also shown for completeness) +export VITE_CORS_PROXY_URL="https://your-worker-url.workers.dev" +export VITE_CORS_PROXY_SECRET="your-secret" +DOCKER_BUILDKIT=1 docker build \ + --secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \ + --secret id=VITE_CORS_PROXY_SECRET,env=VITE_CORS_PROXY_SECRET \ + -t your-bentopdf . +``` + ### 📦 Version Management -BentoPDF supports semantic versioning with multiple Docker tags available on both Docker Hub and GitHub Container Registry: +BentoPDF supports semantic versioning with multiple container tags available: -**Docker Hub:** - -- **Latest**: `bentopdf/bentopdf:latest` -- **Specific Version**: `bentopdf/bentopdf:1.0.0` -- **Version with Prefix**: `bentopdf/bentopdf:v1.0.0` - -**GitHub Container Registry:** +**GitHub Container Registry (Recommended):** - **Latest**: `ghcr.io/alam00000/bentopdf:latest` - **Specific Version**: `ghcr.io/alam00000/bentopdf:1.0.0` - **Version with Prefix**: `ghcr.io/alam00000/bentopdf:v1.0.0` +**Docker Hub:** + +- **Latest**: `bentopdfteam/bentopdf:latest` +- **Specific Version**: `bentopdfteam/bentopdf:1.0.0` +- **Version with Prefix**: `bentopdfteam/bentopdf:v1.0.0` + #### Quick Release ```bash @@ -428,7 +1012,8 @@ For detailed release instructions, see [RELEASE.md](RELEASE.md). The application will be available at `http://localhost:3000`. - > **Note:** After making any local changes to the code, rebuild the Docker image using: + > [!NOTE] + > After making any local changes to the code, rebuild the Docker image using: ```bash docker-compose -f docker-compose.dev.yml up --build -d @@ -446,7 +1031,8 @@ BentoPDF was originally built using **HTML**, **CSS**, and **vanilla JavaScript* - **TypeScript**: For type safety and an improved developer experience. - **Tailwind CSS**: For rapid and consistent UI development. -> **Note:** Some parts of the codebase still use legacy structures from the original implementation. Contributors should expect gradual updates as testing and refactoring continue. +> [!NOTE] +> Some parts of the codebase still use legacy structures from the original implementation. Contributors should expect gradual updates as testing and refactoring continue. --- @@ -476,12 +1062,41 @@ We welcome contributions from the community! Here's how you can get started: Have an idea for a new tool or an improvement? [Open an issue](https://github.com/alam00000/bentopdf/issues) to discuss it first. +### 📖 Contributing to Documentation + +Our documentation is built with [VitePress](https://vitepress.dev/). Here's how to contribute: + +```bash +# Install dependencies +npm install + +# Start docs dev server +npm run docs:dev + +# Build docs for production +npm run docs:build + +# Preview the built docs +npm run docs:preview +``` + +Documentation files are in the `docs/` folder: + +- `docs/index.md` - Home page +- `docs/getting-started.md` - Getting started guide +- `docs/tools/` - Tools reference +- `docs/self-hosting/` - Self-hosting guides (Docker, Vercel, Netlify, Hostinger, etc.) +- `docs/contributing.md` - Contributing guide +- `docs/licensing.md` - Commercial license info + --- ## Special Thanks BentoPDF wouldn't be possible without the amazing open-source tools and libraries that power it. We'd like to extend our heartfelt thanks to the creators and maintainers of: +**Bundled Libraries:** + - **[PDFLib.js](https://pdf-lib.js.org/)** – For enabling powerful client-side PDF manipulation. - **[PDF.js](https://mozilla.github.io/pdf.js/)** – For the robust PDF rendering engine in the browser. - **[PDFKit](https://pdfkit.org/)** – For creating and editing PDFs with ease. @@ -489,7 +1104,16 @@ BentoPDF wouldn't be possible without the amazing open-source tools and librarie - **[Cropper.js](https://fengyuanchen.github.io/cropperjs/)** – For intuitive image cropping functionality. - **[Vite](https://vitejs.dev/)** – For lightning-fast development and build tooling. - **[Tailwind CSS](https://tailwindcss.com/)** – For rapid, flexible, and beautiful UI styling. -- **[qpdf](https://github.com/qpdf/qpdf)** and **[qpdf-wasm](https://github.com/neslinesli93/qpdf-wasm)**– A powerful command-line tool and library for inspecting, repairing, and transforming PDF file ported to wasm -- **[cpdf](https://www.coherentpdf.com/)** – For content preserving pdf operations. +- **[qpdf](https://github.com/qpdf/qpdf)** and **[qpdf-wasm](https://github.com/neslinesli93/qpdf-wasm)** – For inspecting, repairing, and transforming PDF files. +- **[LibreOffice](https://www.libreoffice.org/)** – For powerful document conversion capabilities. + +**AGPL Libraries (Pre-configured via CDN):** + +- **[CoherentPDF (cpdf)](https://www.coherentpdf.com/)** – For content-preserving PDF operations. _(AGPL-3.0)_ +- **[PyMuPDF](https://github.com/pymupdf/PyMuPDF)** – For high-performance PDF manipulation and data extraction. _(AGPL-3.0)_ +- **[Ghostscript (GhostPDL)](https://github.com/ArtifexSoftware/ghostpdl)** – For PDF/A conversion and font outlining. _(AGPL-3.0)_ + +> [!NOTE] +> AGPL-licensed libraries are not bundled in BentoPDF's source code. They are loaded at runtime from CDN (pre-configured) and can be overridden via environment variables or Advanced Settings. Your work inspires and empowers developers everywhere. Thank you for making open-source amazing! diff --git a/RELEASE.md b/RELEASE.md index 88d0c78..0aa21bc 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -38,7 +38,7 @@ npm run release:major # Major: 1.0.0 → 2.0.0 (breaking changes) **What Happens:** - ✅ Your feature commit stays as-is -- ✅ Version gets bumped in `package.json` +- ✅ Version gets bumped in `package.json` and `chart/Chart.yaml` - ✅ New release commit is created - ✅ Git tag is created (e.g., `v1.0.1`) - ✅ Everything gets pushed to GitHub @@ -215,16 +215,16 @@ git reset --hard HEAD~1 1. **GitHub Actions Triggered**: Workflow starts building Docker image 2. **Docker Build**: Multi-architecture image created 3. **Docker Push**: Images pushed to Docker Hub with tags: - - `bentopdf/bentopdf:latest` - - `bentopdf/bentopdf:1.0.1` - - `bentopdf/bentopdf:v1.0.1` + - `bentopdfteam/bentopdf:latest` + - `bentopdfteam/bentopdf:1.0.1` + - `bentopdfteam/bentopdf:v1.0.1` ### **End Result:** Users can immediately pull your new version: ```bash -docker pull bentopdf/bentopdf:1.0.1 +docker pull bentopdfteam/bentopdf:1.0.1 ``` --- diff --git a/SIMPLE_MODE.md b/SIMPLE_MODE.md index 996caef..4dd45d6 100644 --- a/SIMPLE_MODE.md +++ b/SIMPLE_MODE.md @@ -23,27 +23,35 @@ When enabled, Simple Mode will: Use the pre-built Simple Mode image directly: +**Using GitHub Container Registry (Recommended):** + +```bash +# Docker +docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest + +# Podman +podman run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest +``` + **Using Docker Hub:** ```bash -docker run -p 3000:8080 bentopdf/bentopdf-simple:latest +# Docker +docker run -p 3000:8080 bentopdfteam/bentopdf-simple:latest + +# Podman +podman run -p 3000:8080 docker.io/bentopdfteam/bentopdf-simple:latest ``` -**Using GitHub Container Registry:** - -```bash -docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest -``` - -Or with Docker Compose: +Or with Docker Compose / Podman Compose: ```yaml services: bentopdf: - # Using Docker Hub - image: bentopdf/bentopdf-simple:latest - # Or using GitHub Container Registry - # image: ghcr.io/alam00000/bentopdf-simple:latest + # Using GitHub Container Registry (Recommended) + image: ghcr.io/alam00000/bentopdf-simple:latest + # Or using Docker Hub + # image: bentopdfteam/bentopdf-simple:latest container_name: bentopdf restart: unless-stopped ports: @@ -105,9 +113,13 @@ This automatically builds and serves Simple Mode on `http://localhost:3000`. ### Method 2: Using Pre-built Image (Easiest for Production) ```bash -# Pull and run the Simple Mode image -docker pull bentopdf/bentopdf-simple:latest -docker run -p 3000:8080 bentopdf/bentopdf-simple:latest +# Docker - Pull and run the Simple Mode image +docker pull ghcr.io/alam00000/bentopdf-simple:latest +docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest + +# Podman +podman pull ghcr.io/alam00000/bentopdf-simple:latest +podman run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest ``` Open `http://localhost:3000` in your browser. @@ -127,11 +139,13 @@ Open `http://localhost:3000` in your browser. ### Method 4: Compare Both Modes ```bash -# Test Normal Mode -docker run -p 3000:8080 bentopdf/bentopdf:latest +# Test Normal Mode (Docker) +docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest -# Test Simple Mode -docker run -p 3001:8080 bentopdf/bentopdf-simple:latest +# Test Simple Mode (Docker) +docker run -p 3001:8080 ghcr.io/alam00000/bentopdf-simple:latest + +# Podman users: replace 'docker' with 'podman' ``` - Normal Mode: `http://localhost:3000` @@ -149,52 +163,82 @@ When Simple Mode is working correctly, you should see: - ❌ No hero section with "The PDF Toolkit built for privacy" - ❌ No features, FAQ, testimonials, or footer sections -## 📦 Available Docker Images +## 📦 Available Container Images ### Normal Mode (Full Branding) -**Docker Hub:** - -- `bentopdf/bentopdf:latest` -- `bentopdf/bentopdf:v1.0.0` (versioned) - -**GitHub Container Registry:** +**GitHub Container Registry (Recommended):** - `ghcr.io/alam00000/bentopdf:latest` - `ghcr.io/alam00000/bentopdf:v1.0.0` (versioned) -### Simple Mode (Clean Interface) - **Docker Hub:** -- `bentopdf/bentopdf-simple:latest` -- `bentopdf/bentopdf-simple:v1.0.0` (versioned) +- `bentopdfteam/bentopdf:latest` +- `bentopdfteam/bentopdf:v1.0.0` (versioned) -**GitHub Container Registry:** +### Simple Mode (Clean Interface) + +**GitHub Container Registry (Recommended):** - `ghcr.io/alam00000/bentopdf-simple:latest` - `ghcr.io/alam00000/bentopdf-simple:v1.0.0` (versioned) +**Docker Hub:** + +- `bentopdfteam/bentopdf-simple:latest` +- `bentopdfteam/bentopdf-simple:v1.0.0` (versioned) + ## 🚀 Production Deployment Examples -### Internal Company Tool +### Docker Compose / Podman Compose ```yaml services: bentopdf: - image: bentopdf/bentopdf-simple:latest + image: ghcr.io/alam00000/bentopdf-simple:latest # Recommended + # image: bentopdfteam/bentopdf-simple:latest # Alternative: Docker Hub container_name: bentopdf restart: unless-stopped ports: - - '80:80' + - '80:8080' environment: - PUID=1000 - PGID=1000 ``` +### Podman Quadlet (Linux Systemd) + +Create `~/.config/containers/systemd/bentopdf-simple.container`: + +```ini +[Unit] +Description=BentoPDF Simple Mode +After=network-online.target + +[Container] +Image=ghcr.io/alam00000/bentopdf-simple:latest +ContainerName=bentopdf-simple +PublishPort=80:8080 +AutoUpdate=registry + +[Service] +Restart=always + +[Install] +WantedBy=default.target +``` + +Enable and start: + +```bash +systemctl --user daemon-reload +systemctl --user enable --now bentopdf-simple +``` + ## ⚠️ Important Notes -- **Pre-built images**: Use `bentopdf/bentopdf-simple:latest` for Simple Mode +- **Pre-built images**: Use `ghcr.io/alam00000/bentopdf-simple:latest` for Simple Mode (recommended) - **Environment variables**: `SIMPLE_MODE=true` only works during build, not runtime - **Build-time optimization**: Simple Mode uses dead code elimination for smaller bundles - **Same functionality**: All PDF tools work identically in both modes diff --git a/STATIC-HOSTING.md b/STATIC-HOSTING.md new file mode 100644 index 0000000..9cbb82b --- /dev/null +++ b/STATIC-HOSTING.md @@ -0,0 +1,63 @@ +# Hosting BentoPDF as a static website + +As an alternative to runnning BenotPDF locally or in a Docker container, you can very easily host it as a set of static web pages. Here are a few examples: + +## Netlify + +### Netlify - static deployment + +One of simplest ways to host BentoPDF is to create a project at [Netlify](https://www.netlify.com/) and create a static deployment. You can accomplish this by first downloading the pre-built distribution file from our [GitHub releases](https://github.com/alam00000/bentopdf/releases). Each release includes a `dist-{version}.zip` file that contains all necessary files for self-hosting. + +1. Go to [BentoPDF Releases](https://github.com/alam00000/bentopdf/releases) +2. Download the latest `dist-{version}.zip` file +3. Next, if you have not already done so, create a Netlify account and log in. +4. From your Netlify projects page, add a new project and select "Deploy manually". +5. Drag and drop to upload the Bentopdf zip file you downloaded in step 3. +6. Your BentoPDF deployment should now be working! Optionally, you can go into Project Configuration and change the project name. + +When a new BentoPDF release becomes available, you will need to repeat steps 1-3, then go into "Deploys" and upload the new release. + +### Netlify - dynamic deployment + +Alternatively, you can configure a Netlify project to automatically deploy whenever your BentoPDF is updated. + +1. If you have not done so already, create a fork of BentoPDF into your own Github account. +2. If you have not already done so, create a Netlify account and log in. +3. From your Netlify projects page, add a new project and select 'Import an existing project'. +4. Select the GitHub button, authorize Netlify, then choose where to install the integration. You can choose 'All repositories' or 'Only select repositories' and choose your BentoPDF fork. +5. Select your repo and give it a project name. (add environment variables?) Then click the blue 'Deploy' button. +6. The Netlify build & deploy process will kick off. Once it finishes, you can click on the provided URL `https://[projectname]/netlify.app` to view your deployment of BentoPDF. + +Whenever the BentoPDF source code is updated, you can sync the changes into your repo. This will kick off a new build and deploy within Netlify. + +If you want to use BentoPDF's simple mode, go into Deploy Settings, then Environment Variables, and Add a variable. Add `SIMPLE_MODE` and set it to `true`. You will need to manually kick a new build to get this to take effect. + +## Vercel + +Vercel provides similar services to Netlify dynamic hosting. + +1. If you have not done so already, create a fork of BentoPDF into your own Github account. +2. If you have not already done so, create a Vercel account and log in. +3. From your Vercel Overview page, select 'Add new project' +4. Under 'Import Git Repository' and then choose 'Add GitHub Account'. Follow the prompts to authorize Vercel integration. You can choose 'All repositories' or 'Only select repositories' and choose your BentoPDF fork. +5. Select 'Import' to import the repo into Vercel. +6. Under 'Framework Preset', select 'Vite' and save the settings. +7. The Vercel build & deploy process will kick off. Once it finishes, you can click on the provided link to view your deployment of BentoPDF. + +Whenever the BentoPDF source code is updated, you can sync the changes into your repo. This will kick off a new build and deploy within BentoPDF. + +If you want to use BentoPDF's simple mode, go into Project Settings, then Environment Variables, and Create a new variable. Add `SIMPLE_MODE` and set it to `true`. You will need to redeploy to get this to take effect. + +## GitHub Pages + +You can also host your own instance of BentoPDF using GitHub pages. An advantage of this over the other options is you are able to do everything in GitHub without any third party service. + +1. If you have not done so already, create a fork of BentoPDF into your own Github account. +2. From your fork, go to `Settings->Pages`, and change the 'Source' to 'GitHub Actions' +3. Go to `Settings->Secrets and Variables > Actions`, then select 'Variables', and add the repository variable `BASE_URL`. Set the value to `/bentopdf`. *If you've renamed the repo to something other than bentopdf, put that here*. +4. Go to `Actions` in the top menu, and select 'I understand' to enable Actions +5. Within Actions, on the left, select 'Deploy static content to Pages', and then on the right select 'Run workflow', and in the dropdown, 'Run Workflow'. The action will not run to build BentoPDF and deploy it to GitHub Pages. + +When the build completes, you can find the website at `https://[your-github-username]/bentopdf` + +If/when you merge changes from the source BentoPDF repository, the build and deploy action will automatically be kicked off and the new version will be automatically deployed to GitHub Pages. \ No newline at end of file diff --git a/TRANSLATION.md b/TRANSLATION.md index 00354a5..26323f6 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -20,13 +20,33 @@ This guide will help you add new languages or improve existing translations for BentoPDF uses **i18next** for internationalization (i18n). Currently supported languages: - **English** (`en`) - Default +- **Belarusian** (`be`) - **German** (`de`) +- **Spanish** (`es`) +- **French** (`fr`) +- **Italian** (`it`) +- **Portuguese** (`pt`) +- **Turkish** (`tr`) - **Vietnamese** (`vi`) +- **Indonesian** (`id`) +- **Chinese** (`zh`) +- **Traditional Chinese (Taiwan)** (`zh-TW`) +- **Korean** (`ko`) The app automatically detects the language from the URL path: -- `/en/` → English + +- `/` or `/en/` → English (default) - `/de/` → German -- `/vi/` → Vietnamese +- `/fr/` → French +- etc. + +### Architecture + +BentoPDF uses a **static pre-rendering** approach for SEO-optimized i18n: + +1. **Build time**: `scripts/generate-i18n-pages.mjs` generates localized HTML files in `dist/{lang}/` +2. **Dev/Preview**: `languageRouterPlugin` in `vite.config.ts` handles URL rewriting +3. **Production**: Nginx serves static files directly from language directories --- @@ -34,50 +54,52 @@ The app automatically detects the language from the URL path: **To improve existing translations:** -1. Navigate to `public/locales/{language}/common.json` +1. Navigate to `public/locales/{language}/common.json` and `public/locales/{language}/tools.json` 2. Find the key you want to update 3. Change the translation value 4. Save and test -**To add a new language (e.g., Spanish):** +**To add a new language (e.g., Japanese `ja`):** -1. Copy `public/locales/en/common.json` to `public/locales/es/common.json` -2. Translate all values in `es/common.json` -3. Add Spanish to `supportedLanguages` in `src/js/i18n/i18n.ts` -4. Add Spanish name to `languageNames` in `src/js/i18n/i18n.ts` -5. Test thoroughly +1. Copy `public/locales/en/` to `public/locales/ja/` +2. Translate all values in both `ja/common.json` and `ja/tools.json` +3. Add Japanese to `supportedLanguages` and `languageNames` in `src/js/i18n/i18n.ts` +4. Add `'ja'` to `SUPPORTED_LANGUAGES` in `vite.config.ts` +5. Restart the dev server +6. Run `npm run build` to generate static language pages +7. Test thoroughly --- ## Adding a New Language -Let's add **French** as an example: +Let's add **Spanish** as an example: -### Step 1: Create Translation File +### Step 1: Create Translation Files ```bash # Create the directory -mkdir -p public/locales/fr +mkdir -p public/locales/es # Copy the English template -cp public/locales/en/common.json public/locales/fr/common.json +cp public/locales/en/common.json public/locales/es/common.json ``` -### Step 2: Translate the JSON File +### Step 2: Translate the JSON Files -Open `public/locales/fr/common.json` and translate all the values: +Open `public/locales/es/common.json` and translate all the values: ```json { "nav": { - "home": "Accueil", - "about": "À propos", - "contact": "Contact", - "allTools": "Tous les outils" + "home": "Inicio", + "about": "Acerca de", + "contact": "Contacto", + "allTools": "Todas las herramientas" }, "hero": { - "title": "Votre boîte à outils PDF gratuite et sécurisée", - "subtitle": "Fusionnez, divisez, compressez et modifiez des PDF directement dans votre navigateur." + "title": "Tu conjunto de herramientas PDF gratuito y seguro", + "subtitle": "Combina, divide, comprime y edita archivos PDF directamente en tu navegador." } // ... continue translating all keys } @@ -86,40 +108,78 @@ Open `public/locales/fr/common.json` and translate all the values: ⚠️ **Important**: Only translate the **values**, NOT the keys! ✅ **Correct:** + ```json -"home": "Accueil" +"home": "Inicio" ``` ❌ **Wrong:** + ```json -"accueil": "Accueil" +"inicio": "Inicio" ``` +Then do the same for `public/locales/es/tools.json` to translate all tool names and descriptions. + ### Step 3: Register the Language Edit `src/js/i18n/i18n.ts`: ```typescript // Add 'fr' to supported languages -export const supportedLanguages = ['en', 'de', 'fr'] as const; +export const supportedLanguages = ['en', 'de', 'es', 'fr', 'zh', 'vi'] as const; export type SupportedLanguage = (typeof supportedLanguages)[number]; // Add French display name export const languageNames: Record = { - en: 'English', - de: 'Deutsch', - fr: 'Français', // ← Add this + en: 'English', + de: 'Deutsch', + fr: 'Français', // ← Add this }; ``` -### Step 4: Test Your Translation +### Step 4: Update Vite Configuration + +In `vite.config.ts`, add your language to the `SUPPORTED_LANGUAGES` array: + +```typescript +const SUPPORTED_LANGUAGES = [ + 'en', + 'de', + 'es', + 'zh', + 'zh-TW', + 'vi', + 'it', + 'id', + 'tr', + 'fr', + 'pt', + 'ja', +] as const; +``` + +> **Important**: This is required for both dev server routing and the build-time i18n generation. + +### Step 5: Test Your Translation ```bash -# Start the dev server +# Restart the dev server npm run dev -# Visit the French version -# http://localhost:5173/fr/ +# Visit the Japanese version +# http://localhost:5173/ja/ +``` + +### Step 6: Build and Verify Static Files + +```bash +# Run build (includes i18n page generation) +npm run build + +# Verify files were created +ls dist/ja/ +# Should show: index.html, merge-pdf.html, etc. ``` --- @@ -207,6 +267,7 @@ Tool names and descriptions are defined in `src/js/config/tools.ts` and use a sp ``` In translations: + ```json { "tools": { @@ -234,14 +295,15 @@ console.log(message); // "Error" or "Fehler" depending on language For input placeholders: ```html - ``` In `common.json`: + ```json { "tools": { @@ -257,6 +319,7 @@ In `common.json`: ### Manual Testing 1. **Start development server:** + ```bash npm run dev ``` @@ -265,7 +328,11 @@ In `common.json`: - English: `http://localhost:5173/en/` - German: `http://localhost:5173/de/` - Vietnamese: `http://localhost:5173/vi/` - - Your new language: `http://localhost:5173/fr/` + - Indonesian: `http://localhost:5173/id/` + - Chinese: `http://localhost:5173/zh/` + - Traditional Chinese (Taiwan): `http://localhost:5173/zh-TW/` + - French: `http://localhost:5173/fr/` + - Your new language: `http://localhost:5173/es/` 3. **Check these pages:** - Homepage (`/`) @@ -289,11 +356,12 @@ Check for missing translations: node scripts/check-translations.js ``` -*(If this script doesn't exist, you may need to create it or manually compare JSON files)* +_(If this script doesn't exist, you may need to create it or manually compare JSON files)_ ### Browser Testing Test in different browsers: + - Chrome/Edge - Firefox - Safari @@ -307,11 +375,13 @@ Test in different browsers: BentoPDF is **friendly, clear, and professional**. Match this tone in your translations. ✅ **Good:** + ```json "hero.title": "Ihr kostenloses und sicheres PDF-Toolkit" ``` ❌ **Too formal:** + ```json "hero.title": "Ihr gebührenfreies und gesichertes Werkzeug für PDF-Dokumente" ``` @@ -339,6 +409,7 @@ When translating, **keep the HTML tags intact**: If your language has complex plural rules or gender distinctions, consult the [i18next pluralization guide](https://www.i18next.com/translation-function/plurals). Example: + ```json { "pages": "page", @@ -349,6 +420,7 @@ Example: ### 4. Don't Translate Brand Names or Legal Terms Keep these as-is: + - BentoPDF - PDF - GitHub @@ -361,6 +433,7 @@ Keep these as-is: ### 5. Technical Terms For technical terms, use commonly accepted translations in your language: + - "Merge" → "Fusionner" (French), "Zusammenführen" (German) - "Split" → "Diviser" (French), "Teilen" (German) - "Compress" → "Compresser" (French), "Komprimieren" (German) @@ -380,6 +453,7 @@ If a translation is much longer, test it visually to ensure it doesn't break the ### Issue: Translations Not Showing Up **Solution:** + 1. Clear your browser cache 2. Hard refresh (Ctrl+F5 or Cmd+Shift+R) 3. Check browser console for errors @@ -388,22 +462,26 @@ If a translation is much longer, test it visually to ensure it doesn't break the ### Issue: Some Text Still in English **Possible causes:** + 1. Missing translation key in your language file 2. Missing `data-i18n` attribute in HTML 3. Hardcoded text in JavaScript **Solution:** + - Compare your language file with `en/common.json` to find missing keys - Search the codebase for hardcoded strings ### Issue: JSON Syntax Error **Symptoms:** + ``` SyntaxError: Unexpected token } in JSON at position 1234 ``` **Solution:** + - Use a JSON validator: https://jsonlint.com/ - Common mistakes: - Trailing comma after last item @@ -414,15 +492,50 @@ SyntaxError: Unexpected token } in JSON at position 1234 **Solution:** Make sure you added the language to both arrays in `i18n.ts`: + ```typescript -export const supportedLanguages = ['en', 'de', 'fr']; // ← Add here +export const supportedLanguages = ['en', 'de', 'es', 'fr', 'zh', 'vi']; // ← Add here export const languageNames = { - en: 'English', - de: 'Deutsch', - fr: 'Français', // ← And here + en: 'English', + de: 'Deutsch', + es: 'Español', + fr: 'Français', // ← And here + zh: '中文', + vi: 'Tiếng Việt', }; ``` +### Issue: 404 Error When Accessing Language Pages + +**Symptoms:** +Visiting `http://localhost:5173/ja/about.html` shows a 404 error page. + +**Solution:** +You need to add your language code to `SUPPORTED_LANGUAGES` in `vite.config.ts`: + +```typescript +const SUPPORTED_LANGUAGES = [ + 'en', + 'de', + 'es', + 'zh', + 'zh-TW', + 'vi', + 'it', + 'id', + 'tr', + 'fr', + 'pt', + 'ja', +] as const; +``` + +After updating, restart the dev server: + +```bash +npm run dev +``` + --- ## File Checklist @@ -430,11 +543,15 @@ export const languageNames = { When adding a new language, make sure these files are updated: - [ ] `public/locales/{lang}/common.json` - Main translation file +- [ ] `public/locales/{lang}/tools.json` - Tools translation file - [ ] `src/js/i18n/i18n.ts` - Add to `supportedLanguages` and `languageNames` +- [ ] `vite.config.ts` - Add to `SUPPORTED_LANGUAGES` array - [ ] Test all pages: homepage, about, contact, FAQ, tool pages - [ ] Test settings modal and shortcuts - [ ] Test language switcher in footer - [ ] Verify URL routing works (`/{lang}/`) +- [ ] Run `npm run build` and verify `dist/{lang}/` folder is created +- [ ] Test that all tools load correctly --- @@ -470,13 +587,22 @@ Thank you for contributing to BentoPDF! 🎉 Current translation coverage: -| Language | Code | Status | Maintainer | -|----------|------|--------|------------| -| English | `en` | ✅ Complete | Core team | -| German | `de` | 🚧 In Progress | Core team | -| Vietnamese | `vi` | ✅ Complete | Community | -| Your Language | `??` | 🚧 In Progress | You? | +| Language | Code | Status | Maintainer | +| ------------------- | ------- | -------------- | ---------- | +| English | `en` | ✅ Complete | Core team | +| German | `de` | ✅ Complete | Community | +| Spanish | `es` | ✅ Complete | Community | +| French | `fr` | ✅ Complete | Community | +| Italian | `it` | ✅ Complete | Community | +| Portuguese | `pt` | ✅ Complete | Community | +| Turkish | `tr` | ✅ Complete | Community | +| Vietnamese | `vi` | ✅ Complete | Community | +| Indonesian | `id` | ✅ Complete | Community | +| Chinese | `zh` | ✅ Complete | Community | +| Traditional Chinese | `zh-TW` | ✅ Complete | Community | +| Korean | `ko` | ✅ Complete | Community | +| Your Language | `??` | 🚧 In Progress | You? | --- -**Last Updated**: December 2025 +**Last Updated**: January 2026 diff --git a/about.html b/about.html index 42bb2c6..76dd16b 100644 --- a/about.html +++ b/about.html @@ -1,290 +1,315 @@ + + + - - - - About Bentopdf - Fast, Private, and Free PDF Tools - - - - - - - - - - + + About BentoPDF - Privacy-First Free PDF Tools | Our Mission + + + + - - + -
-
-

- We believe PDF tools should be - fast, private, and free. -

-

No compromises.

-
+ + -
+ + + + + + + -
-
- -

- Our Mission -

-

- To provide the most comprehensive PDF toolbox that respects your - privacy and never asks for payment. We believe essential document - tools should be accessible to everyone, everywhere, without - barriers. + + {{> navbar }} + +

+
+

+ We believe PDF tools should be + fast, private, and free. +

+

+ No compromises.

-
-
+ -
-
-
- Our Core - Philosophy -

- Privacy First. Always. +
+ +
+
+ +

+ Our Mission

-

- In an era where data is a commodity, we take a different approach. - All processing for Bentopdf tools happens locally in your browser. - This means your files never touch our servers, we never see your - documents, and we don't track what you do. Your documents remain - completely and unequivocally private. It's not just a feature; - it's our foundation. +

+ To provide the most comprehensive PDF toolbox that respects your + privacy and never asks for payment. We believe essential document + tools should be accessible to everyone, everywhere, without + barriers.

-
-
-
-
- +
+ +
+
+
+ Our Core Philosophy +

+ Privacy First. Always. +

+

+ In an era where data is a commodity, we take a different approach. + All processing for Bentopdf tools happens locally in your browser. + This means your files never touch our servers, we never see your + documents, and we don't track what you do. Your documents remain + completely and unequivocally private. It's not just a feature; + it's our foundation. +

+
+
+
+
+
+ +
+ +
+

+ Why + Bentopdf? +

+
+
+ +
+

+ Built for Speed +

+

+ No waiting for uploads or downloads to a server. By processing + files directly in your browser using modern web technologies + like WebAssembly, we offer unparalleled speed for all our tools. +

+
+
+
+ +
+

+ Completely Free +

+

+ No trials, no subscriptions, no hidden fees, and no "premium" + features held hostage. We believe powerful PDF tools should be a + public utility, not a profit center. +

+
+
+
+ +
+

+ No Account Required +

+

+ Start using any tool immediately. We don't need your email, a + password, or any personal information. Your workflow should be + frictionless and anonymous. +

+
+
+
+ +
+

+ Open Source Spirit +

+

+ Built with transparency in mind. We leverage incredible + open-source libraries like PDF-lib and PDF.js, and believe in + the community-driven effort to make powerful tools accessible to + everyone. +

+
+
+
+
+ +
+ +
+

+ Ready to get started? +

+

+ Join thousands of users who trust Bentopdf for their daily document + needs. Experience the difference that privacy and performance can + make. +

+ + Explore All Tools + +

-
-

- Why Bentopdf? -

-
-
- -
-

Built for Speed

-

- No waiting for uploads or downloads to a server. By processing - files directly in your browser using modern web technologies - like WebAssembly, we offer unparalleled speed for all our tools. -

-
-
-
- -
-

Completely Free

-

- No trials, no subscriptions, no hidden fees, and no "premium" - features held hostage. We believe powerful PDF tools should be a - public utility, not a profit center. -

-
-
-
- -
-

No Account Required -

-

- Start using any tool immediately. We don't need your email, a - password, or any personal information. Your workflow should be - frictionless and anonymous. -

-
-
-
- -
-

Open Source Spirit -

-

- Built with transparency in mind. We leverage incredible - open-source libraries like PDF-lib and PDF.js, and believe in - the community-driven effort to make powerful tools accessible to - everyone. -

-
-
-
-
+ {{> footer }} -
+ + + + -
-

- Ready to get started? -

-

- Join thousands of users who trust Bentopdf for their daily document - needs. Experience the difference that privacy and performance can - make. -

- - Explore All Tools - -
-
- - - - - - - - - - \ No newline at end of file + + + + diff --git a/chart/.helmignore b/chart/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000..a8192a5 --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,16 @@ +apiVersion: v2 +name: bentopdf +description: BentoPDF static frontend served by NGINX +icon: https://raw.githubusercontent.com/spwoodcock/bentopdf/refs/heads/main/public/favicon.ico +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 1.0.2 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: '2.2.1' diff --git a/chart/README.md b/chart/README.md new file mode 100644 index 0000000..18b6367 --- /dev/null +++ b/chart/README.md @@ -0,0 +1,100 @@ +# BentoPDF Helm Chart + +Deploys **BentoPDF** as a **single NGINX container** serving the static frontend. + +## Quickstart + +### Option 1: Port-forward (testing) + +```bash +helm install bentopdf ./chart +kubectl port-forward deploy/bentopdf 8080:8080 +# open http://127.0.0.1:8080 +``` + +### Option 2: Ingress + +```yaml +ingress: + enabled: true + className: nginx + hosts: + - host: bentopdf.example.com + paths: + - path: / + pathType: Prefix +``` + +### Option 3: Gateway API (Gateway + HTTPRoute) + +```yaml +gateway: + enabled: true + gatewayClassName: 'cloudflare' # or your gateway class + +httpRoute: + enabled: true + hostnames: + - pdfs.example.com +``` + +**Note:** Both Gateway and HTTPRoute default to the release namespace. Omit `namespace` fields to use the release namespace automatically. + +If you have an existing Gateway, set `gateway.enabled=false` and configure `httpRoute.parentRefs`: + +```yaml +gateway: + enabled: false + +httpRoute: + enabled: true + parentRefs: + - name: existing-gateway + namespace: gateway-namespace + sectionName: http + hostnames: + - pdfs.example.com +``` + +## Configuration + +### Image + +- **`image.repository`**: container image repo (default: `ghcr.io/alam00000/bentopdf-simple`) +- **`image.tag`**: image tag (default: `Chart.appVersion`) +- **`image.pullPolicy`**: default `IfNotPresent` + +### Ports + +- **`containerPort`**: container listen port (**8080** for the BentoPDF nginx image) +- **`service.port`**: Service port exposed in-cluster (default **80**) + +### Environment Variables + +```yaml +env: + - name: DISABLE_IPV6 + value: 'true' +``` + +## Publish this chart to GHCR (OCI) for testing/deploying + +### Build And Push OCI + +```bash +echo "$GHCR_TOKEN" | helm registry login ghcr.io -u "$GHCR_USERNAME" --password-stdin + +cd chart +helm package . + +# produces bentopdf-.tgz +helm push bentopdf-*.tgz oci://ghcr.io/$GHCR_USERNAME/charts +``` + +This could be automated as part of a Github workflow. + +### Deploy + +```bash +helm upgrade --install bentopdf oci://ghcr.io/$GHCR_USERNAME/charts/bentopdf --version 1.0.0 +``` diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt new file mode 100644 index 0000000..fc3864e --- /dev/null +++ b/chart/templates/NOTES.txt @@ -0,0 +1,47 @@ +1. Get the application URL by running these commands: +{{- if .Values.httpRoute.enabled }} +{{- $defaultGatewayName := .Values.gateway.name -}} +{{- if not $defaultGatewayName -}} +{{- $defaultGatewayName = printf "%s-gateway" (include "bentopdf.name" .) -}} +{{- end -}} +{{- $defaultGatewayNs := .Release.Namespace -}} +{{- if .Values.gateway.enabled -}} +{{- $defaultGatewayNs = default .Release.Namespace .Values.gateway.namespace -}} +{{- end -}} +{{- $gatewayName := $defaultGatewayName -}} +{{- $gatewayNs := $defaultGatewayNs -}} +{{- if and .Values.httpRoute.parentRefs (first .Values.httpRoute.parentRefs) -}} +{{- $firstRef := first .Values.httpRoute.parentRefs -}} +{{- $gatewayName = default $defaultGatewayName $firstRef.name -}} +{{- $gatewayNs = default $defaultGatewayNs $firstRef.namespace -}} +{{- end -}} +{{- if .Values.httpRoute.hostnames }} + export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }} + echo "Visit http://$APP_HOSTNAME to use your application" +{{- else }} + echo "HTTPRoute is enabled. Configure httpRoute.hostnames to see your URL here." +{{- end }} + NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. + The rules can be set for path, method, header and query parameters. + You can check the gateway configuration with 'kubectl get --namespace {{ $gatewayNs }} gateway/{{ $gatewayName }} -o yaml' +{{- else if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "bentopdf.name" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "bentopdf.name" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "bentopdf.name" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "bentopdf.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 0000000..ba10597 --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,33 @@ +{{/* +Expand the name of the bentopdf +*/}} +{{- define "bentopdf.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "bentopdf.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "bentopdf.labels" -}} +helm.sh/chart: {{ include "bentopdf.chart" . }} +{{ include "bentopdf.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "bentopdf.selectorLabels" -}} +app.kubernetes.io/name: {{ include "bentopdf.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml new file mode 100644 index 0000000..737e68f --- /dev/null +++ b/chart/templates/deployment.yaml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "bentopdf.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "bentopdf.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "bentopdf.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "bentopdf.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.containerPort }} + protocol: TCP + {{- with .Values.env }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} diff --git a/chart/templates/gateway.yaml b/chart/templates/gateway.yaml new file mode 100644 index 0000000..9d0d6e9 --- /dev/null +++ b/chart/templates/gateway.yaml @@ -0,0 +1,22 @@ +{{- if .Values.gateway.enabled -}} +{{- $gatewayName := .Values.gateway.name -}} +{{- if not $gatewayName -}} +{{- $gatewayName = printf "%s-gateway" (include "bentopdf.name" .) -}} +{{- end -}} +{{- $gatewayNs := default .Release.Namespace .Values.gateway.namespace -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: {{ $gatewayName }} + namespace: {{ $gatewayNs }} + labels: + {{- include "bentopdf.labels" . | nindent 4 }} + {{- with .Values.gateway.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + gatewayClassName: {{ required "values.gateway.gatewayClassName is required when gateway.enabled=true" .Values.gateway.gatewayClassName }} + listeners: + {{- toYaml .Values.gateway.listeners | nindent 4 }} +{{- end }} diff --git a/chart/templates/httproute.yaml b/chart/templates/httproute.yaml new file mode 100644 index 0000000..70e033a --- /dev/null +++ b/chart/templates/httproute.yaml @@ -0,0 +1,52 @@ +{{- if .Values.httpRoute.enabled -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "bentopdf.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "bentopdf.labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- if .Values.httpRoute.parentRefs }} + {{- range $ref := .Values.httpRoute.parentRefs }} + - name: {{ $.Values.gateway.name | default $ref.name | quote }} + namespace: {{ $ref.namespace | default (default $.Release.Namespace $.Values.gateway.namespace) | quote }} + sectionName: {{ $ref.sectionName | default "http" | quote }} + {{- with $ref.kind }} + kind: {{ . | quote }} + {{- end }} + {{- with $ref.group }} + group: {{ . | quote }} + {{- end }} + {{- end }} + {{- else }} + # Default parentRef when parentRefs is empty or not set + - name: {{ .Values.gateway.name | default (printf "%s-gateway" (include "bentopdf.name" $)) | quote }} + namespace: {{ .Values.gateway.namespace | default .Release.Namespace | quote }} + sectionName: "http" + {{- end }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.httpRoute.rules }} + - {{- with .matches }} + matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - name: {{ include "bentopdf.name" $ }} + port: {{ $.Values.service.port }} + weight: 1 + {{- end }} +{{- end }} diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml new file mode 100644 index 0000000..04b8240 --- /dev/null +++ b/chart/templates/ingress.yaml @@ -0,0 +1,44 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "bentopdf.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "bentopdf.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ include "bentopdf.name" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml new file mode 100644 index 0000000..04c36d0 --- /dev/null +++ b/chart/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "bentopdf.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "bentopdf.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "bentopdf.selectorLabels" . | nindent 4 }} diff --git a/chart/templates/tests/test-connection.yaml b/chart/templates/tests/test-connection.yaml new file mode 100644 index 0000000..474132a --- /dev/null +++ b/chart/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "bentopdf.name" . }}-test-connection" + labels: + {{- include "bentopdf.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['-qO-', 'http://{{ include "bentopdf.name" . }}:{{ .Values.service.port }}/'] + restartPolicy: Never diff --git a/chart/values.yaml b/chart/values.yaml new file mode 100644 index 0000000..91ea545 --- /dev/null +++ b/chart/values.yaml @@ -0,0 +1,75 @@ +# Default values for the BentoPDF chart (single nginx static frontend). +replicaCount: 1 + +image: + # Image built from this repo's `Dockerfile` (nginx serving static frontend). + repository: ghcr.io/alam00000/bentopdf-simple + pullPolicy: IfNotPresent + tag: '' + +imagePullSecrets: [] +nameOverride: '' + +podAnnotations: {} +podLabels: {} + +service: + type: ClusterIP + port: 80 + +# Container listen port (BentoPDF nginx image listens on 8080). +containerPort: 8080 + +env: [] + +ingress: + enabled: false + className: '' + annotations: {} + hosts: + - host: bentopdf.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + +# Gateway API (optional) +gateway: + enabled: false + name: '' # default: release name + "-gateway" suffix + namespace: '' # to override the namespace + annotations: {} + gatewayClassName: '' # required when enabled=true + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same # Allow routes from the same namespace + +httpRoute: + enabled: false + annotations: {} + # parentRefs: # Omit entirely to use default (auto-references gateway in same namespace) + # - name: "" # default: gateway.name or {release-name}-gateway + # namespace: "" # to override the namespace + # sectionName: "http" + hostnames: + - bentopdf.local + rules: + - matches: + - path: + type: PathPrefix + value: / + +resources: {} + +livenessProbe: + httpGet: + path: / + port: http +readinessProbe: + httpGet: + path: / + port: http diff --git a/cloudflare/WASM-PROXY.md b/cloudflare/WASM-PROXY.md new file mode 100644 index 0000000..fa6963d --- /dev/null +++ b/cloudflare/WASM-PROXY.md @@ -0,0 +1,104 @@ +# WASM Proxy Setup Guide + +BentoPDF uses a Cloudflare Worker to proxy WASM library requests, bypassing CORS restrictions when loading AGPL-licensed components (PyMuPDF, Ghostscript, CoherentPDF) from external sources. + +## Quick Start + +### 1. Deploy the Worker + +```bash +cd cloudflare +npx wrangler login +npx wrangler deploy -c wasm-wrangler.toml +``` + +### 2. Configure Source URLs + +Set environment secrets with the base URLs for your WASM files: + +```bash +# Option A: Interactive prompts +npx wrangler secret put PYMUPDF_SOURCE -c wasm-wrangler.toml +npx wrangler secret put GS_SOURCE -c wasm-wrangler.toml +npx wrangler secret put CPDF_SOURCE -c wasm-wrangler.toml + +# Option B: Set via Cloudflare Dashboard +# Go to Workers & Pages > bentopdf-wasm-proxy > Settings > Variables +``` + +**Recommended Source URLs:** + +- PYMUPDF_SOURCE: `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` +- GS_SOURCE: `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/` +- CPDF_SOURCE: `https://cdn.jsdelivr.net/npm/coherentpdf/dist/` + +> **Note:** You can use your own hosted WASM files instead of the recommended URLs. Just ensure your files match the expected directory structure and file names that BentoPDF expects for each module. + +### 3. Configure BentoPDF + +**Option A: Environment variables (recommended — zero-config for users)** + +Set these in `.env.production` or pass as Docker build args: + +```bash +VITE_WASM_PYMUPDF_URL=https://bentopdf-wasm-proxy..workers.dev/pymupdf/ +VITE_WASM_GS_URL=https://bentopdf-wasm-proxy..workers.dev/gs/ +VITE_WASM_CPDF_URL=https://bentopdf-wasm-proxy..workers.dev/cpdf/ +``` + +**Option B: Manual per-user configuration** + +In BentoPDF's Advanced Settings (wasm-settings.html), enter: + +| Module | URL | +| ----------- | ------------------------------------------------------------------- | +| PyMuPDF | `https://bentopdf-wasm-proxy..workers.dev/pymupdf/` | +| Ghostscript | `https://bentopdf-wasm-proxy..workers.dev/gs/` | +| CoherentPDF | `https://bentopdf-wasm-proxy..workers.dev/cpdf/` | + +## Custom Domain (Optional) + +To use a custom domain like `wasm.bentopdf.com`: + +1. Add route in `wasm-wrangler.toml`: + +```toml +routes = [ + { pattern = "wasm.bentopdf.com/*", zone_name = "bentopdf.com" } +] +``` + +2. Add DNS record in Cloudflare: + - Type: AAAA + - Name: wasm + - Content: 100:: + - Proxied: Yes + +3. Redeploy: + +```bash +npx wrangler deploy -c wasm-wrangler.toml +``` + +## Security Features + +- **Origin validation**: Only allows requests from configured origins +- **Rate limiting**: 100 requests/minute per IP (requires KV namespace) +- **File type restrictions**: Only WASM-related files (.js, .wasm, .data, etc.) +- **Size limits**: Max 100MB per file +- **Caching**: Reduces origin requests and improves performance + +## Self-Hosting Notes + +1. Update `ALLOWED_ORIGINS` in `wasm-proxy-worker.js` to include your domain +2. Host your WASM files on any origin (R2, S3, or any CDN) +3. Set source URLs as secrets in your worker + +## Endpoints + +| Endpoint | Description | +| ------------ | -------------------------------------- | +| `/` | Health check, shows configured modules | +| `/pymupdf/*` | PyMuPDF WASM files | +| `/gs/*` | Ghostscript WASM files | +| `/cpdf/*` | CoherentPDF files | diff --git a/cloudflare/cors-proxy-worker.js b/cloudflare/cors-proxy-worker.js new file mode 100644 index 0000000..0d3e105 --- /dev/null +++ b/cloudflare/cors-proxy-worker.js @@ -0,0 +1,455 @@ +/** + * BentoPDF CORS Proxy Worker + * + * This Cloudflare Worker proxies certificate requests for the digital signing tool. + * It fetches certificates from external CAs that don't have CORS headers enabled + * and returns them with proper CORS headers. + * + * + * Deploy: npx wrangler deploy + * + * Required Environment Variables (set in wrangler.toml or Cloudflare dashboard): + * - PROXY_SECRET: Shared secret for HMAC signature verification + */ + +const ALLOWED_PATH_PATTERNS = [ + /\.crt$/i, + /\.cer$/i, + /\.pem$/i, + /\/certs\//i, + /\/ocsp/i, + /\/crl/i, + /caIssuers/i, +]; + +const ALLOWED_ORIGINS = ['https://www.bentopdf.com', 'https://bentopdf.com']; + +const SAFE_CONTENT_TYPES = [ + 'application/x-x509-ca-cert', + 'application/pkix-cert', + 'application/x-pem-file', + 'application/pkcs7-mime', + 'application/octet-stream', + 'text/plain', +]; + +const MAX_TIMESTAMP_AGE_MS = 5 * 60 * 1000; + +const RATE_LIMIT_MAX_REQUESTS = 60; +const RATE_LIMIT_WINDOW_MS = 60 * 1000; + +const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; + +async function verifySignature(message, signature, secret) { + try { + const encoder = new TextEncoder(); + const key = await crypto.subtle.importKey( + 'raw', + encoder.encode(secret), + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['verify'] + ); + + const signatureBytes = new Uint8Array( + signature.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)) + ); + + return await crypto.subtle.verify( + 'HMAC', + key, + signatureBytes, + encoder.encode(message) + ); + } catch (e) { + console.error('Signature verification error:', e); + return false; + } +} + +function isAllowedOrigin(origin) { + if (!origin) return false; + return ALLOWED_ORIGINS.includes(origin); +} + +function isPrivateOrReservedHost(hostname) { + if ( + /^10\./.test(hostname) || + /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname) || + /^192\.168\./.test(hostname) || + /^169\.254\./.test(hostname) || // link-local (cloud metadata) + /^100\.(6[4-9]|[7-9]\d|1[0-1]\d|12[0-7])\./.test(hostname) || // CGNAT + /^127\./.test(hostname) || + /^0\./.test(hostname) + ) { + return true; + } + + if (/^\d+$/.test(hostname)) { + return true; + } + + const lower = hostname.toLowerCase().replace(/^\[|\]$/g, ''); + if ( + lower === '::1' || + lower.startsWith('::ffff:') || + lower.startsWith('fe80') || + lower.startsWith('fc') || + lower.startsWith('fd') || + lower.startsWith('ff') + ) { + return true; + } + + const blockedNames = [ + 'localhost', + 'localhost.localdomain', + '0.0.0.0', + '[::1]', + ]; + if (blockedNames.includes(hostname.toLowerCase())) { + return true; + } + + return false; +} + +function isValidCertificateUrl(urlString) { + try { + const url = new URL(urlString); + + if (!['http:', 'https:'].includes(url.protocol)) { + return false; + } + + if (isPrivateOrReservedHost(url.hostname)) { + return false; + } + + return ALLOWED_PATH_PATTERNS.some((pattern) => pattern.test(url.pathname)); + } catch { + return false; + } +} + +function getSafeContentType(upstreamContentType) { + if (!upstreamContentType) return 'application/octet-stream'; + const match = SAFE_CONTENT_TYPES.find((ct) => + upstreamContentType.startsWith(ct) + ); + return match || 'application/octet-stream'; +} + +function corsHeaders(origin) { + return { + 'Access-Control-Allow-Origin': origin, + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + 'Access-Control-Max-Age': '86400', + }; +} + +function handleOptions(request) { + const origin = request.headers.get('Origin'); + if (!isAllowedOrigin(origin)) { + return new Response(null, { status: 403 }); + } + return new Response(null, { + status: 204, + headers: corsHeaders(origin), + }); +} + +export default { + async fetch(request, env, ctx) { + const url = new URL(request.url); + const origin = request.headers.get('Origin'); + + if (request.method === 'OPTIONS') { + return handleOptions(request); + } + + // NOTE: If you are selfhosting this proxy, you can remove this check, or can set it to only accept requests from your own domain + if (!isAllowedOrigin(origin)) { + return new Response( + JSON.stringify({ + error: 'Forbidden', + message: 'This proxy only accepts requests from allowed origins', + }), + { + status: 403, + headers: { + 'Content-Type': 'application/json', + }, + } + ); + } + + if (request.method !== 'GET') { + return new Response('Method not allowed', { + status: 405, + headers: corsHeaders(origin), + }); + } + + const targetUrl = url.searchParams.get('url'); + + if (!targetUrl) { + return new Response( + JSON.stringify({ + error: 'Missing url parameter', + usage: 'GET /?url=', + }), + { + status: 400, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + if (!isValidCertificateUrl(targetUrl)) { + return new Response( + JSON.stringify({ + error: 'Invalid or disallowed URL', + message: + 'Only certificate-related URLs are allowed (*.crt, *.cer, *.pem, /certs/, /ocsp, /crl)', + }), + { + status: 403, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + if (env.PROXY_SECRET) { + const timestamp = url.searchParams.get('t'); + const signature = url.searchParams.get('sig'); + + if (!timestamp || !signature) { + return new Response( + JSON.stringify({ + error: 'Missing authentication parameters', + message: + 'Request must include timestamp (t) and signature (sig) parameters', + }), + { + status: 401, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + const requestTime = parseInt(timestamp, 10); + const now = Date.now(); + if ( + isNaN(requestTime) || + Math.abs(now - requestTime) > MAX_TIMESTAMP_AGE_MS + ) { + return new Response( + JSON.stringify({ + error: 'Request expired or invalid timestamp', + message: 'Timestamp must be within 5 minutes of current time', + }), + { + status: 401, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + const message = `${targetUrl}${timestamp}`; + const isValid = await verifySignature( + message, + signature, + env.PROXY_SECRET + ); + + if (!isValid) { + return new Response( + JSON.stringify({ + error: 'Invalid signature', + message: 'Request signature verification failed', + }), + { + status: 401, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + } + + const clientIP = request.headers.get('CF-Connecting-IP') || 'unknown'; + const rateLimitKey = `ratelimit:${clientIP}`; + const now = Date.now(); + + if (env.RATE_LIMIT_KV) { + const rateLimitData = await env.RATE_LIMIT_KV.get(rateLimitKey, { + type: 'json', + }); + const requests = rateLimitData?.requests || []; + + const recentRequests = requests.filter( + (t) => now - t < RATE_LIMIT_WINDOW_MS + ); + + if (recentRequests.length >= RATE_LIMIT_MAX_REQUESTS) { + return new Response( + JSON.stringify({ + error: 'Rate limit exceeded', + message: `Maximum ${RATE_LIMIT_MAX_REQUESTS} requests per minute. Please try again later.`, + retryAfter: Math.ceil( + (recentRequests[0] + RATE_LIMIT_WINDOW_MS - now) / 1000 + ), + }), + { + status: 429, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + 'Retry-After': Math.ceil( + (recentRequests[0] + RATE_LIMIT_WINDOW_MS - now) / 1000 + ).toString(), + }, + } + ); + } + + recentRequests.push(now); + await env.RATE_LIMIT_KV.put( + rateLimitKey, + JSON.stringify({ requests: recentRequests }), + { + expirationTtl: 120, + } + ); + } else { + console.warn( + '[CORS Proxy] RATE_LIMIT_KV not configured — rate limiting is disabled' + ); + } + + try { + const response = await fetch(targetUrl, { + headers: { + 'User-Agent': 'BentoPDF-CertProxy/1.0', + }, + }); + + if (!response.ok) { + return new Response( + JSON.stringify({ + error: 'Failed to fetch certificate', + status: response.status, + }), + { + status: response.status, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + // Check Content-Length header first (fast reject for known-large responses) + const contentLength = parseInt( + response.headers.get('Content-Length') || '0', + 10 + ); + if (contentLength > MAX_FILE_SIZE_BYTES) { + return new Response( + JSON.stringify({ + error: 'File too large', + message: `Certificate file exceeds maximum size of ${MAX_FILE_SIZE_BYTES / 1024}KB`, + }), + { + status: 413, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + const reader = response.body.getReader(); + const chunks = []; + let totalSize = 0; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + totalSize += value.byteLength; + if (totalSize > MAX_FILE_SIZE_BYTES) { + reader.cancel(); + return new Response( + JSON.stringify({ + error: 'File too large', + message: `Certificate file exceeds maximum size of ${MAX_FILE_SIZE_BYTES / 1024}KB`, + }), + { + status: 413, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + chunks.push(value); + } + + const certData = new Uint8Array(totalSize); + let offset = 0; + for (const chunk of chunks) { + certData.set(chunk, offset); + offset += chunk.byteLength; + } + + return new Response(certData, { + status: 200, + headers: { + ...corsHeaders(origin), + 'Content-Type': getSafeContentType( + response.headers.get('Content-Type') + ), + 'Content-Length': totalSize.toString(), + 'Cache-Control': 'public, max-age=86400', + 'X-Content-Type-Options': 'nosniff', + }, + }); + } catch (error) { + console.error('Proxy fetch error:', error); + return new Response( + JSON.stringify({ + error: 'Proxy error', + message: 'Failed to fetch the requested certificate', + }), + { + status: 500, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + }, +}; diff --git a/cloudflare/wasm-proxy-worker.js b/cloudflare/wasm-proxy-worker.js new file mode 100644 index 0000000..a61346e --- /dev/null +++ b/cloudflare/wasm-proxy-worker.js @@ -0,0 +1,356 @@ +/** + * BentoPDF WASM Proxy Worker + * + * This Cloudflare Worker proxies WASM module requests to bypass CORS restrictions. + * It fetches WASM libraries (PyMuPDF, Ghostscript, CoherentPDF) from configured sources + * and serves them with proper CORS headers. + * + * Endpoints: + * - /pymupdf/* - Proxies to PyMuPDF WASM source + * - /gs/* - Proxies to Ghostscript WASM source + * - /cpdf/* - Proxies to CoherentPDF WASM source + * + * Deploy: cd cloudflare && npx wrangler deploy -c wasm-wrangler.toml + * + * Required Environment Variables (set in Cloudflare dashboard): + * - PYMUPDF_SOURCE: Base URL for PyMuPDF WASM files (e.g., https://cdn.example.com/pymupdf) + * - GS_SOURCE: Base URL for Ghostscript WASM files (e.g., https://cdn.example.com/gs) + * - CPDF_SOURCE: Base URL for CoherentPDF files (e.g., https://cdn.example.com/cpdf) + */ + +const ALLOWED_ORIGINS = ['https://www.bentopdf.com', 'https://bentopdf.com']; + +const MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024; + +const RATE_LIMIT_MAX_REQUESTS = 100; +const RATE_LIMIT_WINDOW_MS = 60 * 1000; + +const CACHE_TTL_SECONDS = 604800; + +const ALLOWED_EXTENSIONS = [ + '.js', + '.mjs', + '.wasm', + '.data', + '.py', + '.so', + '.zip', + '.json', + '.mem', + '.asm.js', + '.worker.js', + '.html', +]; + +function isAllowedOrigin(origin) { + if (!origin) return true; // Allow no-origin requests (e.g., direct browser navigation) + return ALLOWED_ORIGINS.some((allowed) => + origin.startsWith(allowed.replace(/\/$/, '')) + ); +} + +function isAllowedFile(pathname) { + const ext = pathname.substring(pathname.lastIndexOf('.')).toLowerCase(); + if (ALLOWED_EXTENSIONS.includes(ext)) return true; + + if (!pathname.includes('.') || pathname.endsWith('/')) return true; + + return false; +} + +function corsHeaders(origin) { + return { + 'Access-Control-Allow-Origin': origin || '*', + 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Range, Cache-Control', + 'Access-Control-Expose-Headers': + 'Content-Length, Content-Range, Content-Type', + 'Access-Control-Max-Age': '86400', + }; +} + +function handleOptions(request) { + const origin = request.headers.get('Origin'); + return new Response(null, { + status: 204, + headers: corsHeaders(origin), + }); +} + +function getContentType(pathname) { + const ext = pathname.substring(pathname.lastIndexOf('.')).toLowerCase(); + const contentTypes = { + '.js': 'application/javascript', + '.mjs': 'application/javascript', + '.wasm': 'application/wasm', + '.json': 'application/json', + '.data': 'application/octet-stream', + '.py': 'text/x-python', + '.so': 'application/octet-stream', + '.zip': 'application/zip', + '.mem': 'application/octet-stream', + '.html': 'text/html', + }; + return contentTypes[ext] || 'application/octet-stream'; +} + +async function proxyRequest(request, env, sourceBaseUrl, subpath, origin) { + if (!sourceBaseUrl) { + return new Response( + JSON.stringify({ + error: 'Source not configured', + message: 'This WASM module source URL has not been configured.', + }), + { + status: 503, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + const normalizedBase = sourceBaseUrl.endsWith('/') + ? sourceBaseUrl.slice(0, -1) + : sourceBaseUrl; + const normalizedPath = subpath.startsWith('/') ? subpath : `/${subpath}`; + const targetUrl = `${normalizedBase}${normalizedPath}`; + + if (!isAllowedFile(normalizedPath)) { + return new Response( + JSON.stringify({ + error: 'Forbidden file type', + message: 'Only WASM-related file types are allowed.', + }), + { + status: 403, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + try { + const cacheKey = new Request(targetUrl, request); + const cache = caches.default; + let response = await cache.match(cacheKey); + + if (!response) { + response = await fetch(targetUrl, { + headers: { + 'User-Agent': 'BentoPDF-WASM-Proxy/1.0', + Accept: '*/*', + }, + }); + + if (!response.ok) { + return new Response( + JSON.stringify({ + error: 'Failed to fetch resource', + status: response.status, + statusText: response.statusText, + targetUrl: targetUrl, + }), + { + status: response.status, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + const contentLength = parseInt( + response.headers.get('Content-Length') || '0', + 10 + ); + if (contentLength > MAX_FILE_SIZE_BYTES) { + return new Response( + JSON.stringify({ + error: 'File too large', + message: `File exceeds maximum size of ${MAX_FILE_SIZE_BYTES / 1024 / 1024}MB`, + }), + { + status: 413, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + response = new Response(response.body, response); + response.headers.set( + 'Cache-Control', + `public, max-age=${CACHE_TTL_SECONDS}` + ); + + if (response.status === 200) { + await cache.put(cacheKey, response.clone()); + } + } + + const bodyData = await response.arrayBuffer(); + + return new Response(bodyData, { + status: 200, + headers: { + ...corsHeaders(origin), + 'Content-Type': getContentType(normalizedPath), + 'Content-Length': bodyData.byteLength.toString(), + 'Cache-Control': `public, max-age=${CACHE_TTL_SECONDS}`, + 'X-Proxied-From': new URL(targetUrl).hostname, + }, + }); + } catch (error) { + return new Response( + JSON.stringify({ + error: 'Proxy error', + message: error.message, + }), + { + status: 500, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } +} + +export default { + async fetch(request, env, ctx) { + const url = new URL(request.url); + const pathname = url.pathname; + const origin = request.headers.get('Origin'); + + if (request.method === 'OPTIONS') { + return handleOptions(request); + } + + if (!isAllowedOrigin(origin)) { + return new Response( + JSON.stringify({ + error: 'Forbidden', + message: + 'Origin not allowed. Add your domain to ALLOWED_ORIGINS if self-hosting.', + }), + { + status: 403, + headers: { + 'Content-Type': 'application/json', + ...corsHeaders(origin), + }, + } + ); + } + + if (request.method !== 'GET' && request.method !== 'HEAD') { + return new Response('Method not allowed', { + status: 405, + headers: corsHeaders(origin), + }); + } + + if (env.RATE_LIMIT_KV) { + const clientIP = request.headers.get('CF-Connecting-IP') || 'unknown'; + const rateLimitKey = `wasm-ratelimit:${clientIP}`; + const now = Date.now(); + + const rateLimitData = await env.RATE_LIMIT_KV.get(rateLimitKey, { + type: 'json', + }); + const requests = rateLimitData?.requests || []; + const recentRequests = requests.filter( + (t) => now - t < RATE_LIMIT_WINDOW_MS + ); + + if (recentRequests.length >= RATE_LIMIT_MAX_REQUESTS) { + return new Response( + JSON.stringify({ + error: 'Rate limit exceeded', + message: `Maximum ${RATE_LIMIT_MAX_REQUESTS} requests per minute.`, + }), + { + status: 429, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + 'Retry-After': '60', + }, + } + ); + } + + recentRequests.push(now); + await env.RATE_LIMIT_KV.put( + rateLimitKey, + JSON.stringify({ requests: recentRequests }), + { + expirationTtl: 120, + } + ); + } + + if (pathname.startsWith('/pymupdf/')) { + const subpath = pathname.replace('/pymupdf', ''); + return proxyRequest(request, env, env.PYMUPDF_SOURCE, subpath, origin); + } + + if (pathname.startsWith('/gs/')) { + const subpath = pathname.replace('/gs', ''); + return proxyRequest(request, env, env.GS_SOURCE, subpath, origin); + } + + if (pathname.startsWith('/cpdf/')) { + const subpath = pathname.replace('/cpdf', ''); + return proxyRequest(request, env, env.CPDF_SOURCE, subpath, origin); + } + + if (pathname === '/' || pathname === '/health') { + return new Response( + JSON.stringify({ + service: 'BentoPDF WASM Proxy', + version: '1.0.0', + endpoints: { + pymupdf: '/pymupdf/*', + gs: '/gs/*', + cpdf: '/cpdf/*', + }, + configured: { + pymupdf: !!env.PYMUPDF_SOURCE, + gs: !!env.GS_SOURCE, + cpdf: !!env.CPDF_SOURCE, + }, + }), + { + status: 200, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + } + + return new Response( + JSON.stringify({ + error: 'Not Found', + message: 'Use /pymupdf/*, /gs/*, or /cpdf/* endpoints', + }), + { + status: 404, + headers: { + ...corsHeaders(origin), + 'Content-Type': 'application/json', + }, + } + ); + }, +}; diff --git a/cloudflare/wasm-wrangler.toml b/cloudflare/wasm-wrangler.toml new file mode 100644 index 0000000..c994f32 --- /dev/null +++ b/cloudflare/wasm-wrangler.toml @@ -0,0 +1,69 @@ +name = "bentopdf-wasm-proxy" +main = "wasm-proxy-worker.js" +compatibility_date = "2024-01-01" + +# ============================================================================= +# DEPLOYMENT +# ============================================================================= +# Deploy this worker: +# cd cloudflare +# npx wrangler deploy -c wasm-wrangler.toml +# +# Set environment secrets (one of the following methods): +# Option A: Cloudflare Dashboard +# Go to Workers & Pages > bentopdf-wasm-proxy > Settings > Variables +# Add: PYMUPDF_SOURCE, GS_SOURCE, CPDF_SOURCE +# +# Option B: Wrangler CLI +# npx wrangler secret put PYMUPDF_SOURCE -c wasm-wrangler.toml +# npx wrangler secret put GS_SOURCE -c wasm-wrangler.toml +# npx wrangler secret put CPDF_SOURCE -c wasm-wrangler.toml + +# ============================================================================= +# WASM SOURCE URLS +# ============================================================================= +# Set these as secrets in the Cloudflare dashboard or via wrangler: +# +# PYMUPDF_SOURCE: Base URL to PyMuPDF WASM files +# Example: https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm/assets +# https://your-bucket.r2.cloudflarestorage.com/pymupdf +# +# GS_SOURCE: Base URL to Ghostscript WASM files +# Example: https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets +# https://your-bucket.r2.cloudflarestorage.com/gs +# +# CPDF_SOURCE: Base URL to CoherentPDF files +# Example: https://cdn.jsdelivr.net/npm/coherentpdf/cpdf +# https://your-bucket.r2.cloudflarestorage.com/cpdf + +# ============================================================================= +# USAGE FROM BENTOPDF +# ============================================================================= +# In BentoPDF's WASM Settings page, configure URLs like: +# PyMuPDF: https://wasm.bentopdf.com/pymupdf/ +# Ghostscript: https://wasm.bentopdf.com/gs/ +# CoherentPDF: https://wasm.bentopdf.com/cpdf/ + +# ============================================================================= +# RATE LIMITING (Optional but recommended) +# ============================================================================= +# Create KV namespace: +# npx wrangler kv namespace create "RATE_LIMIT_KV" +# +# Then uncomment and update the ID below: +# [[kv_namespaces]] +# binding = "RATE_LIMIT_KV" +# id = "" + +# Use the same KV namespace as the CORS proxy if you want shared rate limiting +[[kv_namespaces]] +binding = "RATE_LIMIT_KV" +id = "b88e030b308941118cd484e3fcb3ae49" + +# ============================================================================= +# CUSTOM DOMAIN (Optional) +# ============================================================================= +# If you want a custom domain like wasm.bentopdf.com: +# routes = [ +# { pattern = "wasm.bentopdf.com/*", zone_name = "bentopdf.com" } +# ] diff --git a/cloudflare/wrangler.toml b/cloudflare/wrangler.toml new file mode 100644 index 0000000..491b4ae --- /dev/null +++ b/cloudflare/wrangler.toml @@ -0,0 +1,45 @@ +name = "bentopdf-cors-proxy" +main = "cors-proxy-worker.js" +compatibility_date = "2024-01-01" + +# Deploy to Cloudflare's global network +# If you are self hosting change the name to your worker name +# Run: npx wrangler deploy + +# ============================================================================= +# SECURITY FEATURES +# ============================================================================= +# +# 1. SIGNATURE VERIFICATION (Optional - for anti-spoofing) +# - Generate secret: openssl rand -hex 32 +# - Set secret: npx wrangler secret put PROXY_SECRET +# - Note: Secret is visible in frontend JS, so provides limited protection +# +# 2. RATE LIMITING (Recommended - requires KV) +# - Create KV namespace: npx wrangler kv namespace create "RATE_LIMIT_KV" +# - Uncomment the kv_namespaces section below with the returned ID +# - Limits: 60 requests per IP per minute +# +# 3. FILE SIZE LIMIT +# - Automatic: Rejects files larger than 1MB +# - Certificates are typically <10KB, so this prevents abuse +# +# 4. URL RESTRICTIONS +# - Only certificate URLs allowed (*.crt, *.cer, *.pem, /certs/, etc.) +# - Blocks private IPs (localhost, 10.x, 192.168.x, 172.16-31.x) + +# ============================================================================= +# KV NAMESPACE FOR RATE LIMITING +# ============================================================================= +[[kv_namespaces]] +binding = "RATE_LIMIT_KV" +id = "b88e030b308941118cd484e3fcb3ae49" + +# Optional: Custom domain routing +# routes = [ +# { pattern = "cors-proxy.bentopdf.com/*", zone_name = "bentopdf.com" } +# ] + +# Optional: Environment variables (for non-secret config) +# [vars] +# ALLOWED_ORIGINS = "https://www.bentopdf.com,https://bentopdf.com" \ No newline at end of file diff --git a/contact.html b/contact.html index 2932254..1e9c012 100644 --- a/contact.html +++ b/contact.html @@ -1,183 +1,130 @@ + + + + + Contact BentoPDF - Get in Touch | Support + + + - - - - Contact Us - BentoPDF - - - - - - - - - - + + - - + {{> footer }} -
-
-

- Get in Touch -

-

- We'd love to hear from you. Whether you have a question, feedback, or - a feature request, please don't hesitate to reach out. -

-
- -
-

- You can reach us directly by email at: - contact@bentopdf.com -

-
-
- - - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 2dc72bf..1e6b4ab 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -9,3 +9,6 @@ services: ports: - '8080:8080' restart: unless-stopped + # For IPv4-only environments + #environment: + # - DISABLE_IPV6=true diff --git a/docker-compose.yml b/docker-compose.yml index 494eff5..95dbe02 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,16 @@ services: bentopdf: - # simple mode - bentopdf/bentopdf-simple:latest - # default mode - bentopdf/bentopdf:latest - image: bentopdf/bentopdf-simple:latest + # GitHub Container Registry (Recommended) + # simple mode - ghcr.io/alam00000/bentopdf-simple:latest + # default mode - ghcr.io/alam00000/bentopdf:latest + # Docker Hub (Alternative) + # simple mode - bentopdfteam/bentopdf-simple:latest + # default mode - bentopdfteam/bentopdf:latest + image: ghcr.io/alam00000/bentopdf-simple:latest container_name: bentopdf restart: unless-stopped ports: - '8080:8080' + # For IPv4-only environments + #environment: + # - DISABLE_IPV6=true diff --git a/docs/.vitepress/cache/deps/@theme_index.js b/docs/.vitepress/cache/deps/@theme_index.js new file mode 100644 index 0000000..991f721 --- /dev/null +++ b/docs/.vitepress/cache/deps/@theme_index.js @@ -0,0 +1,275 @@ +import { + useMediaQuery +} from "./chunk-2CLQ7TTZ.js"; +import { + computed, + ref, + shallowRef, + watch +} from "./chunk-LE5NDSFD.js"; + +// node_modules/vitepress/dist/client/theme-default/index.js +import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/fonts.css"; + +// node_modules/vitepress/dist/client/theme-default/without-fonts.js +import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/vars.css"; +import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/base.css"; +import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/icons.css"; +import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/utils.css"; +import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css"; +import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css"; +import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css"; +import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css"; +import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css"; +import VPBadge from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue"; +import Layout from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/Layout.vue"; +import { default as default2 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue"; +import { default as default3 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue"; +import { default as default4 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue"; +import { default as default5 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPFeatures.vue"; +import { default as default6 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPHomeContent.vue"; +import { default as default7 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue"; +import { default as default8 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue"; +import { default as default9 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue"; +import { default as default10 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue"; +import { default as default11 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPLink.vue"; +import { default as default12 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPNavBarSearch.vue"; +import { default as default13 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPSocialLink.vue"; +import { default as default14 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPSocialLinks.vue"; +import { default as default15 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue"; +import { default as default16 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue"; +import { default as default17 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue"; +import { default as default18 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue"; +import { default as default19 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue"; + +// node_modules/vitepress/dist/client/theme-default/composables/local-nav.js +import { onContentUpdated } from "vitepress"; + +// node_modules/vitepress/dist/client/theme-default/composables/outline.js +import { getScrollOffset } from "vitepress"; + +// node_modules/vitepress/dist/client/theme-default/support/utils.js +import { withBase } from "vitepress"; + +// node_modules/vitepress/dist/client/theme-default/composables/data.js +import { useData as useData$ } from "vitepress"; +var useData = useData$; + +// node_modules/vitepress/dist/client/theme-default/support/utils.js +function ensureStartingSlash(path) { + return path.startsWith("/") ? path : `/${path}`; +} + +// node_modules/vitepress/dist/client/theme-default/support/sidebar.js +function getSidebar(_sidebar, path) { + if (Array.isArray(_sidebar)) + return addBase(_sidebar); + if (_sidebar == null) + return []; + path = ensureStartingSlash(path); + const dir = Object.keys(_sidebar).sort((a, b) => { + return b.split("/").length - a.split("/").length; + }).find((dir2) => { + return path.startsWith(ensureStartingSlash(dir2)); + }); + const sidebar = dir ? _sidebar[dir] : []; + return Array.isArray(sidebar) ? addBase(sidebar) : addBase(sidebar.items, sidebar.base); +} +function getSidebarGroups(sidebar) { + const groups = []; + let lastGroupIndex = 0; + for (const index in sidebar) { + const item = sidebar[index]; + if (item.items) { + lastGroupIndex = groups.push(item); + continue; + } + if (!groups[lastGroupIndex]) { + groups.push({ items: [] }); + } + groups[lastGroupIndex].items.push(item); + } + return groups; +} +function addBase(items, _base) { + return [...items].map((_item) => { + const item = { ..._item }; + const base = item.base || _base; + if (base && item.link) + item.link = base + item.link; + if (item.items) + item.items = addBase(item.items, base); + return item; + }); +} + +// node_modules/vitepress/dist/client/theme-default/composables/sidebar.js +function useSidebar() { + const { frontmatter, page, theme: theme2 } = useData(); + const is960 = useMediaQuery("(min-width: 960px)"); + const isOpen = ref(false); + const _sidebar = computed(() => { + const sidebarConfig = theme2.value.sidebar; + const relativePath = page.value.relativePath; + return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : []; + }); + const sidebar = ref(_sidebar.value); + watch(_sidebar, (next, prev) => { + if (JSON.stringify(next) !== JSON.stringify(prev)) + sidebar.value = _sidebar.value; + }); + const hasSidebar = computed(() => { + return frontmatter.value.sidebar !== false && sidebar.value.length > 0 && frontmatter.value.layout !== "home"; + }); + const leftAside = computed(() => { + if (hasAside) + return frontmatter.value.aside == null ? theme2.value.aside === "left" : frontmatter.value.aside === "left"; + return false; + }); + const hasAside = computed(() => { + if (frontmatter.value.layout === "home") + return false; + if (frontmatter.value.aside != null) + return !!frontmatter.value.aside; + return theme2.value.aside !== false; + }); + const isSidebarEnabled = computed(() => hasSidebar.value && is960.value); + const sidebarGroups = computed(() => { + return hasSidebar.value ? getSidebarGroups(sidebar.value) : []; + }); + function open() { + isOpen.value = true; + } + function close() { + isOpen.value = false; + } + function toggle() { + isOpen.value ? close() : open(); + } + return { + isOpen, + sidebar, + sidebarGroups, + hasSidebar, + hasAside, + leftAside, + isSidebarEnabled, + open, + close, + toggle + }; +} + +// node_modules/vitepress/dist/client/theme-default/composables/outline.js +var ignoreRE = /\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/; +var resolvedHeaders = []; +function getHeaders(range) { + const headers = [ + ...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)") + ].filter((el) => el.id && el.hasChildNodes()).map((el) => { + const level = Number(el.tagName[1]); + return { + element: el, + title: serializeHeader(el), + link: "#" + el.id, + level + }; + }); + return resolveHeaders(headers, range); +} +function serializeHeader(h) { + let ret = ""; + for (const node of h.childNodes) { + if (node.nodeType === 1) { + if (ignoreRE.test(node.className)) + continue; + ret += node.textContent; + } else if (node.nodeType === 3) { + ret += node.textContent; + } + } + return ret.trim(); +} +function resolveHeaders(headers, range) { + if (range === false) { + return []; + } + const levelsRange = (typeof range === "object" && !Array.isArray(range) ? range.level : range) || 2; + const [high, low] = typeof levelsRange === "number" ? [levelsRange, levelsRange] : levelsRange === "deep" ? [2, 6] : levelsRange; + return buildTree(headers, high, low); +} +function buildTree(data, min, max) { + resolvedHeaders.length = 0; + const result = []; + const stack = []; + data.forEach((item) => { + const node = { ...item, children: [] }; + let parent = stack[stack.length - 1]; + while (parent && parent.level >= node.level) { + stack.pop(); + parent = stack[stack.length - 1]; + } + if (node.element.classList.contains("ignore-header") || parent && "shouldIgnore" in parent) { + stack.push({ level: node.level, shouldIgnore: true }); + return; + } + if (node.level > max || node.level < min) + return; + resolvedHeaders.push({ element: node.element, link: node.link }); + if (parent) + parent.children.push(node); + else + result.push(node); + stack.push(node); + }); + return result; +} + +// node_modules/vitepress/dist/client/theme-default/composables/local-nav.js +function useLocalNav() { + const { theme: theme2, frontmatter } = useData(); + const headers = shallowRef([]); + const hasLocalNav = computed(() => { + return headers.value.length > 0; + }); + onContentUpdated(() => { + headers.value = getHeaders(frontmatter.value.outline ?? theme2.value.outline); + }); + return { + headers, + hasLocalNav + }; +} + +// node_modules/vitepress/dist/client/theme-default/without-fonts.js +var theme = { + Layout, + enhanceApp: ({ app }) => { + app.component("Badge", VPBadge); + } +}; +var without_fonts_default = theme; +export { + default2 as VPBadge, + default3 as VPButton, + default4 as VPDocAsideSponsors, + default5 as VPFeatures, + default6 as VPHomeContent, + default7 as VPHomeFeatures, + default8 as VPHomeHero, + default9 as VPHomeSponsors, + default10 as VPImage, + default11 as VPLink, + default12 as VPNavBarSearch, + default13 as VPSocialLink, + default14 as VPSocialLinks, + default15 as VPSponsors, + default16 as VPTeamMembers, + default17 as VPTeamPage, + default18 as VPTeamPageSection, + default19 as VPTeamPageTitle, + without_fonts_default as default, + useLocalNav, + useSidebar +}; +//# sourceMappingURL=@theme_index.js.map diff --git a/docs/.vitepress/cache/deps/@theme_index.js.map b/docs/.vitepress/cache/deps/@theme_index.js.map new file mode 100644 index 0000000..76de7c1 --- /dev/null +++ b/docs/.vitepress/cache/deps/@theme_index.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../../../../node_modules/vitepress/dist/client/theme-default/index.js", "../../../../node_modules/vitepress/dist/client/theme-default/without-fonts.js", "../../../../node_modules/vitepress/dist/client/theme-default/composables/local-nav.js", "../../../../node_modules/vitepress/dist/client/theme-default/composables/outline.js", "../../../../node_modules/vitepress/dist/client/theme-default/support/utils.js", "../../../../node_modules/vitepress/dist/client/theme-default/composables/data.js", "../../../../node_modules/vitepress/dist/client/theme-default/support/sidebar.js", "../../../../node_modules/vitepress/dist/client/theme-default/composables/sidebar.js"], + "sourcesContent": ["import './styles/fonts.css';\nexport * from './without-fonts';\nexport { default as default } from './without-fonts';\n", "import './styles/vars.css';\nimport './styles/base.css';\nimport './styles/icons.css';\nimport './styles/utils.css';\nimport './styles/components/custom-block.css';\nimport './styles/components/vp-code.css';\nimport './styles/components/vp-code-group.css';\nimport './styles/components/vp-doc.css';\nimport './styles/components/vp-sponsor.css';\nimport VPBadge from './components/VPBadge.vue';\nimport Layout from './Layout.vue';\nexport { default as VPBadge } from './components/VPBadge.vue';\nexport { default as VPButton } from './components/VPButton.vue';\nexport { default as VPDocAsideSponsors } from './components/VPDocAsideSponsors.vue';\nexport { default as VPFeatures } from './components/VPFeatures.vue';\nexport { default as VPHomeContent } from './components/VPHomeContent.vue';\nexport { default as VPHomeFeatures } from './components/VPHomeFeatures.vue';\nexport { default as VPHomeHero } from './components/VPHomeHero.vue';\nexport { default as VPHomeSponsors } from './components/VPHomeSponsors.vue';\nexport { default as VPImage } from './components/VPImage.vue';\nexport { default as VPLink } from './components/VPLink.vue';\nexport { default as VPNavBarSearch } from './components/VPNavBarSearch.vue';\nexport { default as VPSocialLink } from './components/VPSocialLink.vue';\nexport { default as VPSocialLinks } from './components/VPSocialLinks.vue';\nexport { default as VPSponsors } from './components/VPSponsors.vue';\nexport { default as VPTeamMembers } from './components/VPTeamMembers.vue';\nexport { default as VPTeamPage } from './components/VPTeamPage.vue';\nexport { default as VPTeamPageSection } from './components/VPTeamPageSection.vue';\nexport { default as VPTeamPageTitle } from './components/VPTeamPageTitle.vue';\nexport { useLocalNav } from './composables/local-nav';\nexport { useSidebar } from './composables/sidebar';\nconst theme = {\n Layout,\n enhanceApp: ({ app }) => {\n app.component('Badge', VPBadge);\n }\n};\nexport default theme;\n", "import { onContentUpdated } from 'vitepress';\nimport { computed, shallowRef } from 'vue';\nimport { getHeaders } from '../composables/outline';\nimport { useData } from './data';\nexport function useLocalNav() {\n const { theme, frontmatter } = useData();\n const headers = shallowRef([]);\n const hasLocalNav = computed(() => {\n return headers.value.length > 0;\n });\n onContentUpdated(() => {\n headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline);\n });\n return {\n headers,\n hasLocalNav\n };\n}\n", "import { getScrollOffset } from 'vitepress';\nimport { onMounted, onUnmounted, onUpdated } from 'vue';\nimport { throttleAndDebounce } from '../support/utils';\nimport { useAside } from './aside';\nconst ignoreRE = /\\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\\b/;\n// cached list of anchor elements from resolveHeaders\nconst resolvedHeaders = [];\nexport function resolveTitle(theme) {\n return ((typeof theme.outline === 'object' &&\n !Array.isArray(theme.outline) &&\n theme.outline.label) ||\n theme.outlineTitle ||\n 'On this page');\n}\nexport function getHeaders(range) {\n const headers = [\n ...document.querySelectorAll('.VPDoc :where(h1,h2,h3,h4,h5,h6)')\n ]\n .filter((el) => el.id && el.hasChildNodes())\n .map((el) => {\n const level = Number(el.tagName[1]);\n return {\n element: el,\n title: serializeHeader(el),\n link: '#' + el.id,\n level\n };\n });\n return resolveHeaders(headers, range);\n}\nfunction serializeHeader(h) {\n let ret = '';\n for (const node of h.childNodes) {\n if (node.nodeType === 1) {\n if (ignoreRE.test(node.className))\n continue;\n ret += node.textContent;\n }\n else if (node.nodeType === 3) {\n ret += node.textContent;\n }\n }\n return ret.trim();\n}\nexport function resolveHeaders(headers, range) {\n if (range === false) {\n return [];\n }\n const levelsRange = (typeof range === 'object' && !Array.isArray(range)\n ? range.level\n : range) || 2;\n const [high, low] = typeof levelsRange === 'number'\n ? [levelsRange, levelsRange]\n : levelsRange === 'deep'\n ? [2, 6]\n : levelsRange;\n return buildTree(headers, high, low);\n}\nexport function useActiveAnchor(container, marker) {\n const { isAsideEnabled } = useAside();\n const onScroll = throttleAndDebounce(setActiveLink, 100);\n let prevActiveLink = null;\n onMounted(() => {\n requestAnimationFrame(setActiveLink);\n window.addEventListener('scroll', onScroll);\n });\n onUpdated(() => {\n // sidebar update means a route change\n activateLink(location.hash);\n });\n onUnmounted(() => {\n window.removeEventListener('scroll', onScroll);\n });\n function setActiveLink() {\n if (!isAsideEnabled.value) {\n return;\n }\n const scrollY = window.scrollY;\n const innerHeight = window.innerHeight;\n const offsetHeight = document.body.offsetHeight;\n const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1;\n // resolvedHeaders may be repositioned, hidden or fix positioned\n const headers = resolvedHeaders\n .map(({ element, link }) => ({\n link,\n top: getAbsoluteTop(element)\n }))\n .filter(({ top }) => !Number.isNaN(top))\n .sort((a, b) => a.top - b.top);\n // no headers available for active link\n if (!headers.length) {\n activateLink(null);\n return;\n }\n // page top\n if (scrollY < 1) {\n activateLink(null);\n return;\n }\n // page bottom - highlight last link\n if (isBottom) {\n activateLink(headers[headers.length - 1].link);\n return;\n }\n // find the last header above the top of viewport\n let activeLink = null;\n for (const { link, top } of headers) {\n if (top > scrollY + getScrollOffset() + 4) {\n break;\n }\n activeLink = link;\n }\n activateLink(activeLink);\n }\n function activateLink(hash) {\n if (prevActiveLink) {\n prevActiveLink.classList.remove('active');\n }\n if (hash == null) {\n prevActiveLink = null;\n }\n else {\n prevActiveLink = container.value.querySelector(`a[href=\"${decodeURIComponent(hash)}\"]`);\n }\n const activeLink = prevActiveLink;\n if (activeLink) {\n activeLink.classList.add('active');\n marker.value.style.top = activeLink.offsetTop + 39 + 'px';\n marker.value.style.opacity = '1';\n }\n else {\n marker.value.style.top = '33px';\n marker.value.style.opacity = '0';\n }\n }\n}\nfunction getAbsoluteTop(element) {\n let offsetTop = 0;\n while (element !== document.body) {\n if (element === null) {\n // child element is:\n // - not attached to the DOM (display: none)\n // - set to fixed position (not scrollable)\n // - body or html element (null offsetParent)\n return NaN;\n }\n offsetTop += element.offsetTop;\n element = element.offsetParent;\n }\n return offsetTop;\n}\nfunction buildTree(data, min, max) {\n resolvedHeaders.length = 0;\n const result = [];\n const stack = [];\n data.forEach((item) => {\n const node = { ...item, children: [] };\n let parent = stack[stack.length - 1];\n while (parent && parent.level >= node.level) {\n stack.pop();\n parent = stack[stack.length - 1];\n }\n if (node.element.classList.contains('ignore-header') ||\n (parent && 'shouldIgnore' in parent)) {\n stack.push({ level: node.level, shouldIgnore: true });\n return;\n }\n if (node.level > max || node.level < min)\n return;\n resolvedHeaders.push({ element: node.element, link: node.link });\n if (parent)\n parent.children.push(node);\n else\n result.push(node);\n stack.push(node);\n });\n return result;\n}\n", "import { withBase } from 'vitepress';\nimport { isExternal, treatAsHtml } from '../../shared';\nimport { useData } from '../composables/data';\nexport function throttleAndDebounce(fn, delay) {\n let timeoutId;\n let called = false;\n return () => {\n if (timeoutId)\n clearTimeout(timeoutId);\n if (!called) {\n fn();\n (called = true) && setTimeout(() => (called = false), delay);\n }\n else\n timeoutId = setTimeout(fn, delay);\n };\n}\nexport function ensureStartingSlash(path) {\n return path.startsWith('/') ? path : `/${path}`;\n}\nexport function normalizeLink(url) {\n const { pathname, search, hash, protocol } = new URL(url, 'http://a.com');\n if (isExternal(url) ||\n url.startsWith('#') ||\n !protocol.startsWith('http') ||\n !treatAsHtml(pathname))\n return url;\n const { site } = useData();\n const normalizedPath = pathname.endsWith('/') || pathname.endsWith('.html')\n ? url\n : url.replace(/(?:(^\\.+)\\/)?.*$/, `$1${pathname.replace(/(\\.md)?$/, site.value.cleanUrls ? '' : '.html')}${search}${hash}`);\n return withBase(normalizedPath);\n}\n", "import { useData as useData$ } from 'vitepress';\nexport const useData = useData$;\n", "import { isActive } from '../../shared';\nimport { ensureStartingSlash } from './utils';\n/**\n * Get the `Sidebar` from sidebar option. This method will ensure to get correct\n * sidebar config from `MultiSideBarConfig` with various path combinations such\n * as matching `guide/` and `/guide/`. If no matching config was found, it will\n * return empty array.\n */\nexport function getSidebar(_sidebar, path) {\n if (Array.isArray(_sidebar))\n return addBase(_sidebar);\n if (_sidebar == null)\n return [];\n path = ensureStartingSlash(path);\n const dir = Object.keys(_sidebar)\n .sort((a, b) => {\n return b.split('/').length - a.split('/').length;\n })\n .find((dir) => {\n // make sure the multi sidebar key starts with slash too\n return path.startsWith(ensureStartingSlash(dir));\n });\n const sidebar = dir ? _sidebar[dir] : [];\n return Array.isArray(sidebar)\n ? addBase(sidebar)\n : addBase(sidebar.items, sidebar.base);\n}\n/**\n * Get or generate sidebar group from the given sidebar items.\n */\nexport function getSidebarGroups(sidebar) {\n const groups = [];\n let lastGroupIndex = 0;\n for (const index in sidebar) {\n const item = sidebar[index];\n if (item.items) {\n lastGroupIndex = groups.push(item);\n continue;\n }\n if (!groups[lastGroupIndex]) {\n groups.push({ items: [] });\n }\n groups[lastGroupIndex].items.push(item);\n }\n return groups;\n}\nexport function getFlatSideBarLinks(sidebar) {\n const links = [];\n function recursivelyExtractLinks(items) {\n for (const item of items) {\n if (item.text && item.link) {\n links.push({\n text: item.text,\n link: item.link,\n docFooterText: item.docFooterText\n });\n }\n if (item.items) {\n recursivelyExtractLinks(item.items);\n }\n }\n }\n recursivelyExtractLinks(sidebar);\n return links;\n}\n/**\n * Check if the given sidebar item contains any active link.\n */\nexport function hasActiveLink(path, items) {\n if (Array.isArray(items)) {\n return items.some((item) => hasActiveLink(path, item));\n }\n return isActive(path, items.link)\n ? true\n : items.items\n ? hasActiveLink(path, items.items)\n : false;\n}\nfunction addBase(items, _base) {\n return [...items].map((_item) => {\n const item = { ..._item };\n const base = item.base || _base;\n if (base && item.link)\n item.link = base + item.link;\n if (item.items)\n item.items = addBase(item.items, base);\n return item;\n });\n}\n", "import { useMediaQuery } from '@vueuse/core';\nimport { computed, onMounted, onUnmounted, ref, watch, watchEffect, watchPostEffect } from 'vue';\nimport { isActive } from '../../shared';\nimport { hasActiveLink as containsActiveLink, getSidebar, getSidebarGroups } from '../support/sidebar';\nimport { useData } from './data';\nexport function useSidebar() {\n const { frontmatter, page, theme } = useData();\n const is960 = useMediaQuery('(min-width: 960px)');\n const isOpen = ref(false);\n const _sidebar = computed(() => {\n const sidebarConfig = theme.value.sidebar;\n const relativePath = page.value.relativePath;\n return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : [];\n });\n const sidebar = ref(_sidebar.value);\n watch(_sidebar, (next, prev) => {\n if (JSON.stringify(next) !== JSON.stringify(prev))\n sidebar.value = _sidebar.value;\n });\n const hasSidebar = computed(() => {\n return (frontmatter.value.sidebar !== false &&\n sidebar.value.length > 0 &&\n frontmatter.value.layout !== 'home');\n });\n const leftAside = computed(() => {\n if (hasAside)\n return frontmatter.value.aside == null\n ? theme.value.aside === 'left'\n : frontmatter.value.aside === 'left';\n return false;\n });\n const hasAside = computed(() => {\n if (frontmatter.value.layout === 'home')\n return false;\n if (frontmatter.value.aside != null)\n return !!frontmatter.value.aside;\n return theme.value.aside !== false;\n });\n const isSidebarEnabled = computed(() => hasSidebar.value && is960.value);\n const sidebarGroups = computed(() => {\n return hasSidebar.value ? getSidebarGroups(sidebar.value) : [];\n });\n function open() {\n isOpen.value = true;\n }\n function close() {\n isOpen.value = false;\n }\n function toggle() {\n isOpen.value ? close() : open();\n }\n return {\n isOpen,\n sidebar,\n sidebarGroups,\n hasSidebar,\n hasAside,\n leftAside,\n isSidebarEnabled,\n open,\n close,\n toggle\n };\n}\n/**\n * a11y: cache the element that opened the Sidebar (the menu button) then\n * focus that button again when Menu is closed with Escape key.\n */\nexport function useCloseSidebarOnEscape(isOpen, close) {\n let triggerElement;\n watchEffect(() => {\n triggerElement = isOpen.value\n ? document.activeElement\n : undefined;\n });\n onMounted(() => {\n window.addEventListener('keyup', onEscape);\n });\n onUnmounted(() => {\n window.removeEventListener('keyup', onEscape);\n });\n function onEscape(e) {\n if (e.key === 'Escape' && isOpen.value) {\n close();\n triggerElement?.focus();\n }\n }\n}\nexport function useSidebarControl(item) {\n const { page, hash } = useData();\n const collapsed = ref(false);\n const collapsible = computed(() => {\n return item.value.collapsed != null;\n });\n const isLink = computed(() => {\n return !!item.value.link;\n });\n const isActiveLink = ref(false);\n const updateIsActiveLink = () => {\n isActiveLink.value = isActive(page.value.relativePath, item.value.link);\n };\n watch([page, item, hash], updateIsActiveLink);\n onMounted(updateIsActiveLink);\n const hasActiveLink = computed(() => {\n if (isActiveLink.value) {\n return true;\n }\n return item.value.items\n ? containsActiveLink(page.value.relativePath, item.value.items)\n : false;\n });\n const hasChildren = computed(() => {\n return !!(item.value.items && item.value.items.length);\n });\n watchEffect(() => {\n collapsed.value = !!(collapsible.value && item.value.collapsed);\n });\n watchPostEffect(() => {\n ;\n (isActiveLink.value || hasActiveLink.value) && (collapsed.value = false);\n });\n function toggle() {\n if (collapsible.value) {\n collapsed.value = !collapsed.value;\n }\n }\n return {\n collapsed,\n collapsible,\n isLink,\n isActiveLink,\n hasActiveLink,\n hasChildren,\n toggle\n };\n}\n"], + "mappings": "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", + "names": ["default", "dir", "theme", "theme"] +} diff --git a/docs/.vitepress/cache/deps/_metadata.json b/docs/.vitepress/cache/deps/_metadata.json new file mode 100644 index 0000000..ead6f83 --- /dev/null +++ b/docs/.vitepress/cache/deps/_metadata.json @@ -0,0 +1,58 @@ +{ + "hash": "b8473a4c", + "configHash": "de601e5a", + "lockfileHash": "97b635bf", + "browserHash": "6dc79743", + "optimized": { + "vue": { + "src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js", + "file": "vue.js", + "fileHash": "82d9bd70", + "needsInterop": false + }, + "vitepress > @vue/devtools-api": { + "src": "../../../../node_modules/@vue/devtools-api/dist/index.js", + "file": "vitepress___@vue_devtools-api.js", + "fileHash": "776db5f7", + "needsInterop": false + }, + "vitepress > @vueuse/core": { + "src": "../../../../node_modules/@vueuse/core/index.mjs", + "file": "vitepress___@vueuse_core.js", + "fileHash": "5b890910", + "needsInterop": false + }, + "vitepress > @vueuse/integrations/useFocusTrap": { + "src": "../../../../node_modules/@vueuse/integrations/useFocusTrap.mjs", + "file": "vitepress___@vueuse_integrations_useFocusTrap.js", + "fileHash": "0798c805", + "needsInterop": false + }, + "vitepress > mark.js/src/vanilla.js": { + "src": "../../../../node_modules/mark.js/src/vanilla.js", + "file": "vitepress___mark__js_src_vanilla__js.js", + "fileHash": "ce575f5c", + "needsInterop": false + }, + "vitepress > minisearch": { + "src": "../../../../node_modules/minisearch/dist/es/index.js", + "file": "vitepress___minisearch.js", + "fileHash": "97a74ec0", + "needsInterop": false + }, + "@theme/index": { + "src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js", + "file": "@theme_index.js", + "fileHash": "725ded83", + "needsInterop": false + } + }, + "chunks": { + "chunk-2CLQ7TTZ": { + "file": "chunk-2CLQ7TTZ.js" + }, + "chunk-LE5NDSFD": { + "file": "chunk-LE5NDSFD.js" + } + } +} \ No newline at end of file diff --git a/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js b/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js new file mode 100644 index 0000000..8cd09fb --- /dev/null +++ b/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js @@ -0,0 +1,9719 @@ +import { + Fragment, + TransitionGroup, + computed, + customRef, + defineComponent, + effectScope, + getCurrentInstance, + getCurrentScope, + h, + hasInjectionContext, + inject, + isReactive, + isReadonly, + isRef, + markRaw, + nextTick, + onBeforeMount, + onBeforeUnmount, + onBeforeUpdate, + onMounted, + onScopeDispose, + onUnmounted, + onUpdated, + provide, + reactive, + readonly, + ref, + shallowReactive, + shallowRef, + toRaw, + toRef, + toRefs, + toValue, + unref, + watch, + watchEffect +} from "./chunk-LE5NDSFD.js"; + +// node_modules/@vueuse/shared/index.mjs +function computedEager(fn, options) { + var _a; + const result = shallowRef(); + watchEffect(() => { + result.value = fn(); + }, { + ...options, + flush: (_a = options == null ? void 0 : options.flush) != null ? _a : "sync" + }); + return readonly(result); +} +function computedWithControl(source, fn) { + let v = void 0; + let track; + let trigger; + const dirty = shallowRef(true); + const update = () => { + dirty.value = true; + trigger(); + }; + watch(source, update, { flush: "sync" }); + const get2 = typeof fn === "function" ? fn : fn.get; + const set2 = typeof fn === "function" ? void 0 : fn.set; + const result = customRef((_track, _trigger) => { + track = _track; + trigger = _trigger; + return { + get() { + if (dirty.value) { + v = get2(v); + dirty.value = false; + } + track(); + return v; + }, + set(v2) { + set2 == null ? void 0 : set2(v2); + } + }; + }); + if (Object.isExtensible(result)) + result.trigger = update; + return result; +} +function tryOnScopeDispose(fn) { + if (getCurrentScope()) { + onScopeDispose(fn); + return true; + } + return false; +} +function createEventHook() { + const fns = /* @__PURE__ */ new Set(); + const off = (fn) => { + fns.delete(fn); + }; + const clear = () => { + fns.clear(); + }; + const on = (fn) => { + fns.add(fn); + const offFn = () => off(fn); + tryOnScopeDispose(offFn); + return { + off: offFn + }; + }; + const trigger = (...args) => { + return Promise.all(Array.from(fns).map((fn) => fn(...args))); + }; + return { + on, + off, + trigger, + clear + }; +} +function createGlobalState(stateFactory) { + let initialized = false; + let state; + const scope = effectScope(true); + return (...args) => { + if (!initialized) { + state = scope.run(() => stateFactory(...args)); + initialized = true; + } + return state; + }; +} +var localProvidedStateMap = /* @__PURE__ */ new WeakMap(); +var injectLocal = (...args) => { + var _a; + const key = args[0]; + const instance = (_a = getCurrentInstance()) == null ? void 0 : _a.proxy; + if (instance == null && !hasInjectionContext()) + throw new Error("injectLocal must be called in setup"); + if (instance && localProvidedStateMap.has(instance) && key in localProvidedStateMap.get(instance)) + return localProvidedStateMap.get(instance)[key]; + return inject(...args); +}; +var provideLocal = (key, value) => { + var _a; + const instance = (_a = getCurrentInstance()) == null ? void 0 : _a.proxy; + if (instance == null) + throw new Error("provideLocal must be called in setup"); + if (!localProvidedStateMap.has(instance)) + localProvidedStateMap.set(instance, /* @__PURE__ */ Object.create(null)); + const localProvidedState = localProvidedStateMap.get(instance); + localProvidedState[key] = value; + provide(key, value); +}; +function createInjectionState(composable, options) { + const key = (options == null ? void 0 : options.injectionKey) || Symbol(composable.name || "InjectionState"); + const defaultValue = options == null ? void 0 : options.defaultValue; + const useProvidingState = (...args) => { + const state = composable(...args); + provideLocal(key, state); + return state; + }; + const useInjectedState = () => injectLocal(key, defaultValue); + return [useProvidingState, useInjectedState]; +} +function createRef(value, deep) { + if (deep === true) { + return ref(value); + } else { + return shallowRef(value); + } +} +function createSharedComposable(composable) { + let subscribers = 0; + let state; + let scope; + const dispose = () => { + subscribers -= 1; + if (scope && subscribers <= 0) { + scope.stop(); + state = void 0; + scope = void 0; + } + }; + return (...args) => { + subscribers += 1; + if (!scope) { + scope = effectScope(true); + state = scope.run(() => composable(...args)); + } + tryOnScopeDispose(dispose); + return state; + }; +} +function extendRef(ref2, extend, { enumerable = false, unwrap = true } = {}) { + for (const [key, value] of Object.entries(extend)) { + if (key === "value") + continue; + if (isRef(value) && unwrap) { + Object.defineProperty(ref2, key, { + get() { + return value.value; + }, + set(v) { + value.value = v; + }, + enumerable + }); + } else { + Object.defineProperty(ref2, key, { value, enumerable }); + } + } + return ref2; +} +function get(obj, key) { + if (key == null) + return unref(obj); + return unref(obj)[key]; +} +function isDefined(v) { + return unref(v) != null; +} +function makeDestructurable(obj, arr) { + if (typeof Symbol !== "undefined") { + const clone = { ...obj }; + Object.defineProperty(clone, Symbol.iterator, { + enumerable: false, + value() { + let index = 0; + return { + next: () => ({ + value: arr[index++], + done: index > arr.length + }) + }; + } + }); + return clone; + } else { + return Object.assign([...arr], obj); + } +} +function reactify(fn, options) { + const unrefFn = (options == null ? void 0 : options.computedGetter) === false ? unref : toValue; + return function(...args) { + return computed(() => fn.apply(this, args.map((i) => unrefFn(i)))); + }; +} +function reactifyObject(obj, optionsOrKeys = {}) { + let keys2 = []; + let options; + if (Array.isArray(optionsOrKeys)) { + keys2 = optionsOrKeys; + } else { + options = optionsOrKeys; + const { includeOwnProperties = true } = optionsOrKeys; + keys2.push(...Object.keys(obj)); + if (includeOwnProperties) + keys2.push(...Object.getOwnPropertyNames(obj)); + } + return Object.fromEntries( + keys2.map((key) => { + const value = obj[key]; + return [ + key, + typeof value === "function" ? reactify(value.bind(obj), options) : value + ]; + }) + ); +} +function toReactive(objectRef) { + if (!isRef(objectRef)) + return reactive(objectRef); + const proxy = new Proxy({}, { + get(_, p, receiver) { + return unref(Reflect.get(objectRef.value, p, receiver)); + }, + set(_, p, value) { + if (isRef(objectRef.value[p]) && !isRef(value)) + objectRef.value[p].value = value; + else + objectRef.value[p] = value; + return true; + }, + deleteProperty(_, p) { + return Reflect.deleteProperty(objectRef.value, p); + }, + has(_, p) { + return Reflect.has(objectRef.value, p); + }, + ownKeys() { + return Object.keys(objectRef.value); + }, + getOwnPropertyDescriptor() { + return { + enumerable: true, + configurable: true + }; + } + }); + return reactive(proxy); +} +function reactiveComputed(fn) { + return toReactive(computed(fn)); +} +function reactiveOmit(obj, ...keys2) { + const flatKeys = keys2.flat(); + const predicate = flatKeys[0]; + return reactiveComputed(() => typeof predicate === "function" ? Object.fromEntries(Object.entries(toRefs(obj)).filter(([k, v]) => !predicate(toValue(v), k))) : Object.fromEntries(Object.entries(toRefs(obj)).filter((e) => !flatKeys.includes(e[0])))); +} +var isClient = typeof window !== "undefined" && typeof document !== "undefined"; +var isWorker = typeof WorkerGlobalScope !== "undefined" && globalThis instanceof WorkerGlobalScope; +var isDef = (val) => typeof val !== "undefined"; +var notNullish = (val) => val != null; +var assert = (condition, ...infos) => { + if (!condition) + console.warn(...infos); +}; +var toString = Object.prototype.toString; +var isObject = (val) => toString.call(val) === "[object Object]"; +var now = () => Date.now(); +var timestamp = () => +Date.now(); +var clamp = (n, min, max) => Math.min(max, Math.max(min, n)); +var noop = () => { +}; +var rand = (min, max) => { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +}; +var hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key); +var isIOS = getIsIOS(); +function getIsIOS() { + var _a, _b; + return isClient && ((_a = window == null ? void 0 : window.navigator) == null ? void 0 : _a.userAgent) && (/iP(?:ad|hone|od)/.test(window.navigator.userAgent) || ((_b = window == null ? void 0 : window.navigator) == null ? void 0 : _b.maxTouchPoints) > 2 && /iPad|Macintosh/.test(window == null ? void 0 : window.navigator.userAgent)); +} +function createFilterWrapper(filter, fn) { + function wrapper(...args) { + return new Promise((resolve, reject) => { + Promise.resolve(filter(() => fn.apply(this, args), { fn, thisArg: this, args })).then(resolve).catch(reject); + }); + } + return wrapper; +} +var bypassFilter = (invoke2) => { + return invoke2(); +}; +function debounceFilter(ms, options = {}) { + let timer; + let maxTimer; + let lastRejector = noop; + const _clearTimeout = (timer2) => { + clearTimeout(timer2); + lastRejector(); + lastRejector = noop; + }; + let lastInvoker; + const filter = (invoke2) => { + const duration = toValue(ms); + const maxDuration = toValue(options.maxWait); + if (timer) + _clearTimeout(timer); + if (duration <= 0 || maxDuration !== void 0 && maxDuration <= 0) { + if (maxTimer) { + _clearTimeout(maxTimer); + maxTimer = null; + } + return Promise.resolve(invoke2()); + } + return new Promise((resolve, reject) => { + lastRejector = options.rejectOnCancel ? reject : resolve; + lastInvoker = invoke2; + if (maxDuration && !maxTimer) { + maxTimer = setTimeout(() => { + if (timer) + _clearTimeout(timer); + maxTimer = null; + resolve(lastInvoker()); + }, maxDuration); + } + timer = setTimeout(() => { + if (maxTimer) + _clearTimeout(maxTimer); + maxTimer = null; + resolve(invoke2()); + }, duration); + }); + }; + return filter; +} +function throttleFilter(...args) { + let lastExec = 0; + let timer; + let isLeading = true; + let lastRejector = noop; + let lastValue; + let ms; + let trailing; + let leading; + let rejectOnCancel; + if (!isRef(args[0]) && typeof args[0] === "object") + ({ delay: ms, trailing = true, leading = true, rejectOnCancel = false } = args[0]); + else + [ms, trailing = true, leading = true, rejectOnCancel = false] = args; + const clear = () => { + if (timer) { + clearTimeout(timer); + timer = void 0; + lastRejector(); + lastRejector = noop; + } + }; + const filter = (_invoke) => { + const duration = toValue(ms); + const elapsed = Date.now() - lastExec; + const invoke2 = () => { + return lastValue = _invoke(); + }; + clear(); + if (duration <= 0) { + lastExec = Date.now(); + return invoke2(); + } + if (elapsed > duration && (leading || !isLeading)) { + lastExec = Date.now(); + invoke2(); + } else if (trailing) { + lastValue = new Promise((resolve, reject) => { + lastRejector = rejectOnCancel ? reject : resolve; + timer = setTimeout(() => { + lastExec = Date.now(); + isLeading = true; + resolve(invoke2()); + clear(); + }, Math.max(0, duration - elapsed)); + }); + } + if (!leading && !timer) + timer = setTimeout(() => isLeading = true, duration); + isLeading = false; + return lastValue; + }; + return filter; +} +function pausableFilter(extendFilter = bypassFilter, options = {}) { + const { + initialState = "active" + } = options; + const isActive = toRef2(initialState === "active"); + function pause() { + isActive.value = false; + } + function resume() { + isActive.value = true; + } + const eventFilter = (...args) => { + if (isActive.value) + extendFilter(...args); + }; + return { isActive: readonly(isActive), pause, resume, eventFilter }; +} +function cacheStringFunction(fn) { + const cache = /* @__PURE__ */ Object.create(null); + return (str) => { + const hit = cache[str]; + return hit || (cache[str] = fn(str)); + }; +} +var hyphenateRE = /\B([A-Z])/g; +var hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, "-$1").toLowerCase()); +var camelizeRE = /-(\w)/g; +var camelize = cacheStringFunction((str) => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ""); +}); +function promiseTimeout(ms, throwOnTimeout = false, reason = "Timeout") { + return new Promise((resolve, reject) => { + if (throwOnTimeout) + setTimeout(() => reject(reason), ms); + else + setTimeout(resolve, ms); + }); +} +function identity(arg) { + return arg; +} +function createSingletonPromise(fn) { + let _promise; + function wrapper() { + if (!_promise) + _promise = fn(); + return _promise; + } + wrapper.reset = async () => { + const _prev = _promise; + _promise = void 0; + if (_prev) + await _prev; + }; + return wrapper; +} +function invoke(fn) { + return fn(); +} +function containsProp(obj, ...props) { + return props.some((k) => k in obj); +} +function increaseWithUnit(target, delta) { + var _a; + if (typeof target === "number") + return target + delta; + const value = ((_a = target.match(/^-?\d+\.?\d*/)) == null ? void 0 : _a[0]) || ""; + const unit = target.slice(value.length); + const result = Number.parseFloat(value) + delta; + if (Number.isNaN(result)) + return target; + return result + unit; +} +function pxValue(px) { + return px.endsWith("rem") ? Number.parseFloat(px) * 16 : Number.parseFloat(px); +} +function objectPick(obj, keys2, omitUndefined = false) { + return keys2.reduce((n, k) => { + if (k in obj) { + if (!omitUndefined || obj[k] !== void 0) + n[k] = obj[k]; + } + return n; + }, {}); +} +function objectOmit(obj, keys2, omitUndefined = false) { + return Object.fromEntries(Object.entries(obj).filter(([key, value]) => { + return (!omitUndefined || value !== void 0) && !keys2.includes(key); + })); +} +function objectEntries(obj) { + return Object.entries(obj); +} +function getLifeCycleTarget(target) { + return target || getCurrentInstance(); +} +function toArray(value) { + return Array.isArray(value) ? value : [value]; +} +function toRef2(...args) { + if (args.length !== 1) + return toRef(...args); + const r = args[0]; + return typeof r === "function" ? readonly(customRef(() => ({ get: r, set: noop }))) : ref(r); +} +var resolveRef = toRef2; +function reactivePick(obj, ...keys2) { + const flatKeys = keys2.flat(); + const predicate = flatKeys[0]; + return reactiveComputed(() => typeof predicate === "function" ? Object.fromEntries(Object.entries(toRefs(obj)).filter(([k, v]) => predicate(toValue(v), k))) : Object.fromEntries(flatKeys.map((k) => [k, toRef2(obj, k)]))); +} +function refAutoReset(defaultValue, afterMs = 1e4) { + return customRef((track, trigger) => { + let value = toValue(defaultValue); + let timer; + const resetAfter = () => setTimeout(() => { + value = toValue(defaultValue); + trigger(); + }, toValue(afterMs)); + tryOnScopeDispose(() => { + clearTimeout(timer); + }); + return { + get() { + track(); + return value; + }, + set(newValue) { + value = newValue; + trigger(); + clearTimeout(timer); + timer = resetAfter(); + } + }; + }); +} +function useDebounceFn(fn, ms = 200, options = {}) { + return createFilterWrapper( + debounceFilter(ms, options), + fn + ); +} +function refDebounced(value, ms = 200, options = {}) { + const debounced = ref(value.value); + const updater = useDebounceFn(() => { + debounced.value = value.value; + }, ms, options); + watch(value, () => updater()); + return debounced; +} +function refDefault(source, defaultValue) { + return computed({ + get() { + var _a; + return (_a = source.value) != null ? _a : defaultValue; + }, + set(value) { + source.value = value; + } + }); +} +function useThrottleFn(fn, ms = 200, trailing = false, leading = true, rejectOnCancel = false) { + return createFilterWrapper( + throttleFilter(ms, trailing, leading, rejectOnCancel), + fn + ); +} +function refThrottled(value, delay = 200, trailing = true, leading = true) { + if (delay <= 0) + return value; + const throttled = ref(value.value); + const updater = useThrottleFn(() => { + throttled.value = value.value; + }, delay, trailing, leading); + watch(value, () => updater()); + return throttled; +} +function refWithControl(initial, options = {}) { + let source = initial; + let track; + let trigger; + const ref2 = customRef((_track, _trigger) => { + track = _track; + trigger = _trigger; + return { + get() { + return get2(); + }, + set(v) { + set2(v); + } + }; + }); + function get2(tracking = true) { + if (tracking) + track(); + return source; + } + function set2(value, triggering = true) { + var _a, _b; + if (value === source) + return; + const old = source; + if (((_a = options.onBeforeChange) == null ? void 0 : _a.call(options, value, old)) === false) + return; + source = value; + (_b = options.onChanged) == null ? void 0 : _b.call(options, value, old); + if (triggering) + trigger(); + } + const untrackedGet = () => get2(false); + const silentSet = (v) => set2(v, false); + const peek = () => get2(false); + const lay = (v) => set2(v, false); + return extendRef( + ref2, + { + get: get2, + set: set2, + untrackedGet, + silentSet, + peek, + lay + }, + { enumerable: true } + ); +} +var controlledRef = refWithControl; +function set(...args) { + if (args.length === 2) { + const [ref2, value] = args; + ref2.value = value; + } + if (args.length === 3) { + const [target, key, value] = args; + target[key] = value; + } +} +function watchWithFilter(source, cb, options = {}) { + const { + eventFilter = bypassFilter, + ...watchOptions + } = options; + return watch( + source, + createFilterWrapper( + eventFilter, + cb + ), + watchOptions + ); +} +function watchPausable(source, cb, options = {}) { + const { + eventFilter: filter, + initialState = "active", + ...watchOptions + } = options; + const { eventFilter, pause, resume, isActive } = pausableFilter(filter, { initialState }); + const stop = watchWithFilter( + source, + cb, + { + ...watchOptions, + eventFilter + } + ); + return { stop, pause, resume, isActive }; +} +function syncRef(left, right, ...[options]) { + const { + flush = "sync", + deep = false, + immediate = true, + direction = "both", + transform = {} + } = options || {}; + const watchers = []; + const transformLTR = "ltr" in transform && transform.ltr || ((v) => v); + const transformRTL = "rtl" in transform && transform.rtl || ((v) => v); + if (direction === "both" || direction === "ltr") { + watchers.push(watchPausable( + left, + (newValue) => { + watchers.forEach((w) => w.pause()); + right.value = transformLTR(newValue); + watchers.forEach((w) => w.resume()); + }, + { flush, deep, immediate } + )); + } + if (direction === "both" || direction === "rtl") { + watchers.push(watchPausable( + right, + (newValue) => { + watchers.forEach((w) => w.pause()); + left.value = transformRTL(newValue); + watchers.forEach((w) => w.resume()); + }, + { flush, deep, immediate } + )); + } + const stop = () => { + watchers.forEach((w) => w.stop()); + }; + return stop; +} +function syncRefs(source, targets, options = {}) { + const { + flush = "sync", + deep = false, + immediate = true + } = options; + const targetsArray = toArray(targets); + return watch( + source, + (newValue) => targetsArray.forEach((target) => target.value = newValue), + { flush, deep, immediate } + ); +} +function toRefs2(objectRef, options = {}) { + if (!isRef(objectRef)) + return toRefs(objectRef); + const result = Array.isArray(objectRef.value) ? Array.from({ length: objectRef.value.length }) : {}; + for (const key in objectRef.value) { + result[key] = customRef(() => ({ + get() { + return objectRef.value[key]; + }, + set(v) { + var _a; + const replaceRef = (_a = toValue(options.replaceRef)) != null ? _a : true; + if (replaceRef) { + if (Array.isArray(objectRef.value)) { + const copy = [...objectRef.value]; + copy[key] = v; + objectRef.value = copy; + } else { + const newObject = { ...objectRef.value, [key]: v }; + Object.setPrototypeOf(newObject, Object.getPrototypeOf(objectRef.value)); + objectRef.value = newObject; + } + } else { + objectRef.value[key] = v; + } + } + })); + } + return result; +} +var toValue2 = toValue; +var resolveUnref = toValue; +function tryOnBeforeMount(fn, sync = true, target) { + const instance = getLifeCycleTarget(target); + if (instance) + onBeforeMount(fn, target); + else if (sync) + fn(); + else + nextTick(fn); +} +function tryOnBeforeUnmount(fn, target) { + const instance = getLifeCycleTarget(target); + if (instance) + onBeforeUnmount(fn, target); +} +function tryOnMounted(fn, sync = true, target) { + const instance = getLifeCycleTarget(); + if (instance) + onMounted(fn, target); + else if (sync) + fn(); + else + nextTick(fn); +} +function tryOnUnmounted(fn, target) { + const instance = getLifeCycleTarget(target); + if (instance) + onUnmounted(fn, target); +} +function createUntil(r, isNot = false) { + function toMatch(condition, { flush = "sync", deep = false, timeout, throwOnTimeout } = {}) { + let stop = null; + const watcher = new Promise((resolve) => { + stop = watch( + r, + (v) => { + if (condition(v) !== isNot) { + if (stop) + stop(); + else + nextTick(() => stop == null ? void 0 : stop()); + resolve(v); + } + }, + { + flush, + deep, + immediate: true + } + ); + }); + const promises = [watcher]; + if (timeout != null) { + promises.push( + promiseTimeout(timeout, throwOnTimeout).then(() => toValue(r)).finally(() => stop == null ? void 0 : stop()) + ); + } + return Promise.race(promises); + } + function toBe(value, options) { + if (!isRef(value)) + return toMatch((v) => v === value, options); + const { flush = "sync", deep = false, timeout, throwOnTimeout } = options != null ? options : {}; + let stop = null; + const watcher = new Promise((resolve) => { + stop = watch( + [r, value], + ([v1, v2]) => { + if (isNot !== (v1 === v2)) { + if (stop) + stop(); + else + nextTick(() => stop == null ? void 0 : stop()); + resolve(v1); + } + }, + { + flush, + deep, + immediate: true + } + ); + }); + const promises = [watcher]; + if (timeout != null) { + promises.push( + promiseTimeout(timeout, throwOnTimeout).then(() => toValue(r)).finally(() => { + stop == null ? void 0 : stop(); + return toValue(r); + }) + ); + } + return Promise.race(promises); + } + function toBeTruthy(options) { + return toMatch((v) => Boolean(v), options); + } + function toBeNull(options) { + return toBe(null, options); + } + function toBeUndefined(options) { + return toBe(void 0, options); + } + function toBeNaN(options) { + return toMatch(Number.isNaN, options); + } + function toContains(value, options) { + return toMatch((v) => { + const array = Array.from(v); + return array.includes(value) || array.includes(toValue(value)); + }, options); + } + function changed(options) { + return changedTimes(1, options); + } + function changedTimes(n = 1, options) { + let count = -1; + return toMatch(() => { + count += 1; + return count >= n; + }, options); + } + if (Array.isArray(toValue(r))) { + const instance = { + toMatch, + toContains, + changed, + changedTimes, + get not() { + return createUntil(r, !isNot); + } + }; + return instance; + } else { + const instance = { + toMatch, + toBe, + toBeTruthy, + toBeNull, + toBeNaN, + toBeUndefined, + changed, + changedTimes, + get not() { + return createUntil(r, !isNot); + } + }; + return instance; + } +} +function until(r) { + return createUntil(r); +} +function defaultComparator(value, othVal) { + return value === othVal; +} +function useArrayDifference(...args) { + var _a, _b; + const list = args[0]; + const values = args[1]; + let compareFn = (_a = args[2]) != null ? _a : defaultComparator; + const { + symmetric = false + } = (_b = args[3]) != null ? _b : {}; + if (typeof compareFn === "string") { + const key = compareFn; + compareFn = (value, othVal) => value[key] === othVal[key]; + } + const diff1 = computed(() => toValue(list).filter((x) => toValue(values).findIndex((y) => compareFn(x, y)) === -1)); + if (symmetric) { + const diff2 = computed(() => toValue(values).filter((x) => toValue(list).findIndex((y) => compareFn(x, y)) === -1)); + return computed(() => symmetric ? [...toValue(diff1), ...toValue(diff2)] : toValue(diff1)); + } else { + return diff1; + } +} +function useArrayEvery(list, fn) { + return computed(() => toValue(list).every((element, index, array) => fn(toValue(element), index, array))); +} +function useArrayFilter(list, fn) { + return computed(() => toValue(list).map((i) => toValue(i)).filter(fn)); +} +function useArrayFind(list, fn) { + return computed(() => toValue( + toValue(list).find((element, index, array) => fn(toValue(element), index, array)) + )); +} +function useArrayFindIndex(list, fn) { + return computed(() => toValue(list).findIndex((element, index, array) => fn(toValue(element), index, array))); +} +function findLast(arr, cb) { + let index = arr.length; + while (index-- > 0) { + if (cb(arr[index], index, arr)) + return arr[index]; + } + return void 0; +} +function useArrayFindLast(list, fn) { + return computed(() => toValue( + !Array.prototype.findLast ? findLast(toValue(list), (element, index, array) => fn(toValue(element), index, array)) : toValue(list).findLast((element, index, array) => fn(toValue(element), index, array)) + )); +} +function isArrayIncludesOptions(obj) { + return isObject(obj) && containsProp(obj, "formIndex", "comparator"); +} +function useArrayIncludes(...args) { + var _a; + const list = args[0]; + const value = args[1]; + let comparator = args[2]; + let formIndex = 0; + if (isArrayIncludesOptions(comparator)) { + formIndex = (_a = comparator.fromIndex) != null ? _a : 0; + comparator = comparator.comparator; + } + if (typeof comparator === "string") { + const key = comparator; + comparator = (element, value2) => element[key] === toValue(value2); + } + comparator = comparator != null ? comparator : (element, value2) => element === toValue(value2); + return computed(() => toValue(list).slice(formIndex).some((element, index, array) => comparator( + toValue(element), + toValue(value), + index, + toValue(array) + ))); +} +function useArrayJoin(list, separator) { + return computed(() => toValue(list).map((i) => toValue(i)).join(toValue(separator))); +} +function useArrayMap(list, fn) { + return computed(() => toValue(list).map((i) => toValue(i)).map(fn)); +} +function useArrayReduce(list, reducer, ...args) { + const reduceCallback = (sum, value, index) => reducer(toValue(sum), toValue(value), index); + return computed(() => { + const resolved = toValue(list); + return args.length ? resolved.reduce(reduceCallback, typeof args[0] === "function" ? toValue(args[0]()) : toValue(args[0])) : resolved.reduce(reduceCallback); + }); +} +function useArraySome(list, fn) { + return computed(() => toValue(list).some((element, index, array) => fn(toValue(element), index, array))); +} +function uniq(array) { + return Array.from(new Set(array)); +} +function uniqueElementsBy(array, fn) { + return array.reduce((acc, v) => { + if (!acc.some((x) => fn(v, x, array))) + acc.push(v); + return acc; + }, []); +} +function useArrayUnique(list, compareFn) { + return computed(() => { + const resolvedList = toValue(list).map((element) => toValue(element)); + return compareFn ? uniqueElementsBy(resolvedList, compareFn) : uniq(resolvedList); + }); +} +function useCounter(initialValue = 0, options = {}) { + let _initialValue = unref(initialValue); + const count = shallowRef(initialValue); + const { + max = Number.POSITIVE_INFINITY, + min = Number.NEGATIVE_INFINITY + } = options; + const inc = (delta = 1) => count.value = Math.max(Math.min(max, count.value + delta), min); + const dec = (delta = 1) => count.value = Math.min(Math.max(min, count.value - delta), max); + const get2 = () => count.value; + const set2 = (val) => count.value = Math.max(min, Math.min(max, val)); + const reset = (val = _initialValue) => { + _initialValue = val; + return set2(val); + }; + return { count, inc, dec, get: get2, set: set2, reset }; +} +var REGEX_PARSE = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[T\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/i; +var REGEX_FORMAT = /[YMDHhms]o|\[([^\]]+)\]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a{1,2}|A{1,2}|m{1,2}|s{1,2}|Z{1,2}|z{1,4}|SSS/g; +function defaultMeridiem(hours, minutes, isLowercase, hasPeriod) { + let m = hours < 12 ? "AM" : "PM"; + if (hasPeriod) + m = m.split("").reduce((acc, curr) => acc += `${curr}.`, ""); + return isLowercase ? m.toLowerCase() : m; +} +function formatOrdinal(num) { + const suffixes = ["th", "st", "nd", "rd"]; + const v = num % 100; + return num + (suffixes[(v - 20) % 10] || suffixes[v] || suffixes[0]); +} +function formatDate(date, formatStr, options = {}) { + var _a; + const years = date.getFullYear(); + const month = date.getMonth(); + const days = date.getDate(); + const hours = date.getHours(); + const minutes = date.getMinutes(); + const seconds = date.getSeconds(); + const milliseconds = date.getMilliseconds(); + const day = date.getDay(); + const meridiem = (_a = options.customMeridiem) != null ? _a : defaultMeridiem; + const stripTimeZone = (dateString) => { + var _a2; + return (_a2 = dateString.split(" ")[1]) != null ? _a2 : ""; + }; + const matches = { + Yo: () => formatOrdinal(years), + YY: () => String(years).slice(-2), + YYYY: () => years, + M: () => month + 1, + Mo: () => formatOrdinal(month + 1), + MM: () => `${month + 1}`.padStart(2, "0"), + MMM: () => date.toLocaleDateString(toValue(options.locales), { month: "short" }), + MMMM: () => date.toLocaleDateString(toValue(options.locales), { month: "long" }), + D: () => String(days), + Do: () => formatOrdinal(days), + DD: () => `${days}`.padStart(2, "0"), + H: () => String(hours), + Ho: () => formatOrdinal(hours), + HH: () => `${hours}`.padStart(2, "0"), + h: () => `${hours % 12 || 12}`.padStart(1, "0"), + ho: () => formatOrdinal(hours % 12 || 12), + hh: () => `${hours % 12 || 12}`.padStart(2, "0"), + m: () => String(minutes), + mo: () => formatOrdinal(minutes), + mm: () => `${minutes}`.padStart(2, "0"), + s: () => String(seconds), + so: () => formatOrdinal(seconds), + ss: () => `${seconds}`.padStart(2, "0"), + SSS: () => `${milliseconds}`.padStart(3, "0"), + d: () => day, + dd: () => date.toLocaleDateString(toValue(options.locales), { weekday: "narrow" }), + ddd: () => date.toLocaleDateString(toValue(options.locales), { weekday: "short" }), + dddd: () => date.toLocaleDateString(toValue(options.locales), { weekday: "long" }), + A: () => meridiem(hours, minutes), + AA: () => meridiem(hours, minutes, false, true), + a: () => meridiem(hours, minutes, true), + aa: () => meridiem(hours, minutes, true, true), + z: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: "shortOffset" })), + zz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: "shortOffset" })), + zzz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: "shortOffset" })), + zzzz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: "longOffset" })) + }; + return formatStr.replace(REGEX_FORMAT, (match, $1) => { + var _a2, _b; + return (_b = $1 != null ? $1 : (_a2 = matches[match]) == null ? void 0 : _a2.call(matches)) != null ? _b : match; + }); +} +function normalizeDate(date) { + if (date === null) + return new Date(Number.NaN); + if (date === void 0) + return /* @__PURE__ */ new Date(); + if (date instanceof Date) + return new Date(date); + if (typeof date === "string" && !/Z$/i.test(date)) { + const d = date.match(REGEX_PARSE); + if (d) { + const m = d[2] - 1 || 0; + const ms = (d[7] || "0").substring(0, 3); + return new Date(d[1], m, d[3] || 1, d[4] || 0, d[5] || 0, d[6] || 0, ms); + } + } + return new Date(date); +} +function useDateFormat(date, formatStr = "HH:mm:ss", options = {}) { + return computed(() => formatDate(normalizeDate(toValue(date)), toValue(formatStr), options)); +} +function useIntervalFn(cb, interval = 1e3, options = {}) { + const { + immediate = true, + immediateCallback = false + } = options; + let timer = null; + const isActive = shallowRef(false); + function clean() { + if (timer) { + clearInterval(timer); + timer = null; + } + } + function pause() { + isActive.value = false; + clean(); + } + function resume() { + const intervalValue = toValue(interval); + if (intervalValue <= 0) + return; + isActive.value = true; + if (immediateCallback) + cb(); + clean(); + if (isActive.value) + timer = setInterval(cb, intervalValue); + } + if (immediate && isClient) + resume(); + if (isRef(interval) || typeof interval === "function") { + const stopWatch = watch(interval, () => { + if (isActive.value && isClient) + resume(); + }); + tryOnScopeDispose(stopWatch); + } + tryOnScopeDispose(pause); + return { + isActive, + pause, + resume + }; +} +function useInterval(interval = 1e3, options = {}) { + const { + controls: exposeControls = false, + immediate = true, + callback + } = options; + const counter = shallowRef(0); + const update = () => counter.value += 1; + const reset = () => { + counter.value = 0; + }; + const controls = useIntervalFn( + callback ? () => { + update(); + callback(counter.value); + } : update, + interval, + { immediate } + ); + if (exposeControls) { + return { + counter, + reset, + ...controls + }; + } else { + return counter; + } +} +function useLastChanged(source, options = {}) { + var _a; + const ms = shallowRef((_a = options.initialValue) != null ? _a : null); + watch( + source, + () => ms.value = timestamp(), + options + ); + return ms; +} +function useTimeoutFn(cb, interval, options = {}) { + const { + immediate = true, + immediateCallback = false + } = options; + const isPending = shallowRef(false); + let timer = null; + function clear() { + if (timer) { + clearTimeout(timer); + timer = null; + } + } + function stop() { + isPending.value = false; + clear(); + } + function start(...args) { + if (immediateCallback) + cb(); + clear(); + isPending.value = true; + timer = setTimeout(() => { + isPending.value = false; + timer = null; + cb(...args); + }, toValue(interval)); + } + if (immediate) { + isPending.value = true; + if (isClient) + start(); + } + tryOnScopeDispose(stop); + return { + isPending: readonly(isPending), + start, + stop + }; +} +function useTimeout(interval = 1e3, options = {}) { + const { + controls: exposeControls = false, + callback + } = options; + const controls = useTimeoutFn( + callback != null ? callback : noop, + interval, + options + ); + const ready = computed(() => !controls.isPending.value); + if (exposeControls) { + return { + ready, + ...controls + }; + } else { + return ready; + } +} +function useToNumber(value, options = {}) { + const { + method = "parseFloat", + radix, + nanToZero + } = options; + return computed(() => { + let resolved = toValue(value); + if (typeof method === "function") + resolved = method(resolved); + else if (typeof resolved === "string") + resolved = Number[method](resolved, radix); + if (nanToZero && Number.isNaN(resolved)) + resolved = 0; + return resolved; + }); +} +function useToString(value) { + return computed(() => `${toValue(value)}`); +} +function useToggle(initialValue = false, options = {}) { + const { + truthyValue = true, + falsyValue = false + } = options; + const valueIsRef = isRef(initialValue); + const _value = shallowRef(initialValue); + function toggle(value) { + if (arguments.length) { + _value.value = value; + return _value.value; + } else { + const truthy = toValue(truthyValue); + _value.value = _value.value === truthy ? toValue(falsyValue) : truthy; + return _value.value; + } + } + if (valueIsRef) + return toggle; + else + return [_value, toggle]; +} +function watchArray(source, cb, options) { + let oldList = (options == null ? void 0 : options.immediate) ? [] : [...typeof source === "function" ? source() : Array.isArray(source) ? source : toValue(source)]; + return watch(source, (newList, _, onCleanup) => { + const oldListRemains = Array.from({ length: oldList.length }); + const added = []; + for (const obj of newList) { + let found = false; + for (let i = 0; i < oldList.length; i++) { + if (!oldListRemains[i] && obj === oldList[i]) { + oldListRemains[i] = true; + found = true; + break; + } + } + if (!found) + added.push(obj); + } + const removed = oldList.filter((_2, i) => !oldListRemains[i]); + cb(newList, oldList, added, removed, onCleanup); + oldList = [...newList]; + }, options); +} +function watchAtMost(source, cb, options) { + const { + count, + ...watchOptions + } = options; + const current = shallowRef(0); + const stop = watchWithFilter( + source, + (...args) => { + current.value += 1; + if (current.value >= toValue(count)) + nextTick(() => stop()); + cb(...args); + }, + watchOptions + ); + return { count: current, stop }; +} +function watchDebounced(source, cb, options = {}) { + const { + debounce = 0, + maxWait = void 0, + ...watchOptions + } = options; + return watchWithFilter( + source, + cb, + { + ...watchOptions, + eventFilter: debounceFilter(debounce, { maxWait }) + } + ); +} +function watchDeep(source, cb, options) { + return watch( + source, + cb, + { + ...options, + deep: true + } + ); +} +function watchIgnorable(source, cb, options = {}) { + const { + eventFilter = bypassFilter, + ...watchOptions + } = options; + const filteredCb = createFilterWrapper( + eventFilter, + cb + ); + let ignoreUpdates; + let ignorePrevAsyncUpdates; + let stop; + if (watchOptions.flush === "sync") { + const ignore = shallowRef(false); + ignorePrevAsyncUpdates = () => { + }; + ignoreUpdates = (updater) => { + ignore.value = true; + updater(); + ignore.value = false; + }; + stop = watch( + source, + (...args) => { + if (!ignore.value) + filteredCb(...args); + }, + watchOptions + ); + } else { + const disposables = []; + const ignoreCounter = shallowRef(0); + const syncCounter = shallowRef(0); + ignorePrevAsyncUpdates = () => { + ignoreCounter.value = syncCounter.value; + }; + disposables.push( + watch( + source, + () => { + syncCounter.value++; + }, + { ...watchOptions, flush: "sync" } + ) + ); + ignoreUpdates = (updater) => { + const syncCounterPrev = syncCounter.value; + updater(); + ignoreCounter.value += syncCounter.value - syncCounterPrev; + }; + disposables.push( + watch( + source, + (...args) => { + const ignore = ignoreCounter.value > 0 && ignoreCounter.value === syncCounter.value; + ignoreCounter.value = 0; + syncCounter.value = 0; + if (ignore) + return; + filteredCb(...args); + }, + watchOptions + ) + ); + stop = () => { + disposables.forEach((fn) => fn()); + }; + } + return { stop, ignoreUpdates, ignorePrevAsyncUpdates }; +} +function watchImmediate(source, cb, options) { + return watch( + source, + cb, + { + ...options, + immediate: true + } + ); +} +function watchOnce(source, cb, options) { + const stop = watch(source, (...args) => { + nextTick(() => stop()); + return cb(...args); + }, options); + return stop; +} +function watchThrottled(source, cb, options = {}) { + const { + throttle = 0, + trailing = true, + leading = true, + ...watchOptions + } = options; + return watchWithFilter( + source, + cb, + { + ...watchOptions, + eventFilter: throttleFilter(throttle, trailing, leading) + } + ); +} +function watchTriggerable(source, cb, options = {}) { + let cleanupFn; + function onEffect() { + if (!cleanupFn) + return; + const fn = cleanupFn; + cleanupFn = void 0; + fn(); + } + function onCleanup(callback) { + cleanupFn = callback; + } + const _cb = (value, oldValue) => { + onEffect(); + return cb(value, oldValue, onCleanup); + }; + const res = watchIgnorable(source, _cb, options); + const { ignoreUpdates } = res; + const trigger = () => { + let res2; + ignoreUpdates(() => { + res2 = _cb(getWatchSources(source), getOldValue(source)); + }); + return res2; + }; + return { + ...res, + trigger + }; +} +function getWatchSources(sources) { + if (isReactive(sources)) + return sources; + if (Array.isArray(sources)) + return sources.map((item) => toValue(item)); + return toValue(sources); +} +function getOldValue(source) { + return Array.isArray(source) ? source.map(() => void 0) : void 0; +} +function whenever(source, cb, options) { + const stop = watch( + source, + (v, ov, onInvalidate) => { + if (v) { + if (options == null ? void 0 : options.once) + nextTick(() => stop()); + cb(v, ov, onInvalidate); + } + }, + { + ...options, + once: false + } + ); + return stop; +} + +// node_modules/@vueuse/core/index.mjs +function computedAsync(evaluationCallback, initialState, optionsOrRef) { + let options; + if (isRef(optionsOrRef)) { + options = { + evaluating: optionsOrRef + }; + } else { + options = optionsOrRef || {}; + } + const { + lazy = false, + evaluating = void 0, + shallow = true, + onError = noop + } = options; + const started = shallowRef(!lazy); + const current = shallow ? shallowRef(initialState) : ref(initialState); + let counter = 0; + watchEffect(async (onInvalidate) => { + if (!started.value) + return; + counter++; + const counterAtBeginning = counter; + let hasFinished = false; + if (evaluating) { + Promise.resolve().then(() => { + evaluating.value = true; + }); + } + try { + const result = await evaluationCallback((cancelCallback) => { + onInvalidate(() => { + if (evaluating) + evaluating.value = false; + if (!hasFinished) + cancelCallback(); + }); + }); + if (counterAtBeginning === counter) + current.value = result; + } catch (e) { + onError(e); + } finally { + if (evaluating && counterAtBeginning === counter) + evaluating.value = false; + hasFinished = true; + } + }); + if (lazy) { + return computed(() => { + started.value = true; + return current.value; + }); + } else { + return current; + } +} +function computedInject(key, options, defaultSource, treatDefaultAsFactory) { + let source = inject(key); + if (defaultSource) + source = inject(key, defaultSource); + if (treatDefaultAsFactory) + source = inject(key, defaultSource, treatDefaultAsFactory); + if (typeof options === "function") { + return computed((ctx) => options(source, ctx)); + } else { + return computed({ + get: (ctx) => options.get(source, ctx), + set: options.set + }); + } +} +function createReusableTemplate(options = {}) { + const { + inheritAttrs = true + } = options; + const render = shallowRef(); + const define = defineComponent({ + setup(_, { slots }) { + return () => { + render.value = slots.default; + }; + } + }); + const reuse = defineComponent({ + inheritAttrs, + props: options.props, + setup(props, { attrs, slots }) { + return () => { + var _a; + if (!render.value && true) + throw new Error("[VueUse] Failed to find the definition of reusable template"); + const vnode = (_a = render.value) == null ? void 0 : _a.call(render, { + ...options.props == null ? keysToCamelKebabCase(attrs) : props, + $slots: slots + }); + return inheritAttrs && (vnode == null ? void 0 : vnode.length) === 1 ? vnode[0] : vnode; + }; + } + }); + return makeDestructurable( + { define, reuse }, + [define, reuse] + ); +} +function keysToCamelKebabCase(obj) { + const newObj = {}; + for (const key in obj) + newObj[camelize(key)] = obj[key]; + return newObj; +} +function createTemplatePromise(options = {}) { + let index = 0; + const instances = ref([]); + function create(...args) { + const props = shallowReactive({ + key: index++, + args, + promise: void 0, + resolve: () => { + }, + reject: () => { + }, + isResolving: false, + options + }); + instances.value.push(props); + props.promise = new Promise((_resolve, _reject) => { + props.resolve = (v) => { + props.isResolving = true; + return _resolve(v); + }; + props.reject = _reject; + }).finally(() => { + props.promise = void 0; + const index2 = instances.value.indexOf(props); + if (index2 !== -1) + instances.value.splice(index2, 1); + }); + return props.promise; + } + function start(...args) { + if (options.singleton && instances.value.length > 0) + return instances.value[0].promise; + return create(...args); + } + const component = defineComponent((_, { slots }) => { + const renderList = () => instances.value.map((props) => { + var _a; + return h(Fragment, { key: props.key }, (_a = slots.default) == null ? void 0 : _a.call(slots, props)); + }); + if (options.transition) + return () => h(TransitionGroup, options.transition, renderList); + return renderList; + }); + component.start = start; + return component; +} +function createUnrefFn(fn) { + return function(...args) { + return fn.apply(this, args.map((i) => toValue(i))); + }; +} +var defaultWindow = isClient ? window : void 0; +var defaultDocument = isClient ? window.document : void 0; +var defaultNavigator = isClient ? window.navigator : void 0; +var defaultLocation = isClient ? window.location : void 0; +function unrefElement(elRef) { + var _a; + const plain = toValue(elRef); + return (_a = plain == null ? void 0 : plain.$el) != null ? _a : plain; +} +function useEventListener(...args) { + const cleanups = []; + const cleanup = () => { + cleanups.forEach((fn) => fn()); + cleanups.length = 0; + }; + const register = (el, event, listener, options) => { + el.addEventListener(event, listener, options); + return () => el.removeEventListener(event, listener, options); + }; + const firstParamTargets = computed(() => { + const test = toArray(toValue(args[0])).filter((e) => e != null); + return test.every((e) => typeof e !== "string") ? test : void 0; + }); + const stopWatch = watchImmediate( + () => { + var _a, _b; + return [ + (_b = (_a = firstParamTargets.value) == null ? void 0 : _a.map((e) => unrefElement(e))) != null ? _b : [defaultWindow].filter((e) => e != null), + toArray(toValue(firstParamTargets.value ? args[1] : args[0])), + toArray(unref(firstParamTargets.value ? args[2] : args[1])), + // @ts-expect-error - TypeScript gets the correct types, but somehow still complains + toValue(firstParamTargets.value ? args[3] : args[2]) + ]; + }, + ([raw_targets, raw_events, raw_listeners, raw_options]) => { + cleanup(); + if (!(raw_targets == null ? void 0 : raw_targets.length) || !(raw_events == null ? void 0 : raw_events.length) || !(raw_listeners == null ? void 0 : raw_listeners.length)) + return; + const optionsClone = isObject(raw_options) ? { ...raw_options } : raw_options; + cleanups.push( + ...raw_targets.flatMap( + (el) => raw_events.flatMap( + (event) => raw_listeners.map((listener) => register(el, event, listener, optionsClone)) + ) + ) + ); + }, + { flush: "post" } + ); + const stop = () => { + stopWatch(); + cleanup(); + }; + tryOnScopeDispose(cleanup); + return stop; +} +var _iOSWorkaround = false; +function onClickOutside(target, handler, options = {}) { + const { window: window2 = defaultWindow, ignore = [], capture = true, detectIframe = false, controls = false } = options; + if (!window2) { + return controls ? { stop: noop, cancel: noop, trigger: noop } : noop; + } + if (isIOS && !_iOSWorkaround) { + _iOSWorkaround = true; + const listenerOptions = { passive: true }; + Array.from(window2.document.body.children).forEach((el) => useEventListener(el, "click", noop, listenerOptions)); + useEventListener(window2.document.documentElement, "click", noop, listenerOptions); + } + let shouldListen = true; + const shouldIgnore = (event) => { + return toValue(ignore).some((target2) => { + if (typeof target2 === "string") { + return Array.from(window2.document.querySelectorAll(target2)).some((el) => el === event.target || event.composedPath().includes(el)); + } else { + const el = unrefElement(target2); + return el && (event.target === el || event.composedPath().includes(el)); + } + }); + }; + function hasMultipleRoots(target2) { + const vm = toValue(target2); + return vm && vm.$.subTree.shapeFlag === 16; + } + function checkMultipleRoots(target2, event) { + const vm = toValue(target2); + const children = vm.$.subTree && vm.$.subTree.children; + if (children == null || !Array.isArray(children)) + return false; + return children.some((child) => child.el === event.target || event.composedPath().includes(child.el)); + } + const listener = (event) => { + const el = unrefElement(target); + if (event.target == null) + return; + if (!(el instanceof Element) && hasMultipleRoots(target) && checkMultipleRoots(target, event)) + return; + if (!el || el === event.target || event.composedPath().includes(el)) + return; + if ("detail" in event && event.detail === 0) + shouldListen = !shouldIgnore(event); + if (!shouldListen) { + shouldListen = true; + return; + } + handler(event); + }; + let isProcessingClick = false; + const cleanup = [ + useEventListener(window2, "click", (event) => { + if (!isProcessingClick) { + isProcessingClick = true; + setTimeout(() => { + isProcessingClick = false; + }, 0); + listener(event); + } + }, { passive: true, capture }), + useEventListener(window2, "pointerdown", (e) => { + const el = unrefElement(target); + shouldListen = !shouldIgnore(e) && !!(el && !e.composedPath().includes(el)); + }, { passive: true }), + detectIframe && useEventListener(window2, "blur", (event) => { + setTimeout(() => { + var _a; + const el = unrefElement(target); + if (((_a = window2.document.activeElement) == null ? void 0 : _a.tagName) === "IFRAME" && !(el == null ? void 0 : el.contains(window2.document.activeElement))) { + handler(event); + } + }, 0); + }, { passive: true }) + ].filter(Boolean); + const stop = () => cleanup.forEach((fn) => fn()); + if (controls) { + return { + stop, + cancel: () => { + shouldListen = false; + }, + trigger: (event) => { + shouldListen = true; + listener(event); + shouldListen = false; + } + }; + } + return stop; +} +function useMounted() { + const isMounted = shallowRef(false); + const instance = getCurrentInstance(); + if (instance) { + onMounted(() => { + isMounted.value = true; + }, instance); + } + return isMounted; +} +function useSupported(callback) { + const isMounted = useMounted(); + return computed(() => { + isMounted.value; + return Boolean(callback()); + }); +} +function useMutationObserver(target, callback, options = {}) { + const { window: window2 = defaultWindow, ...mutationOptions } = options; + let observer; + const isSupported = useSupported(() => window2 && "MutationObserver" in window2); + const cleanup = () => { + if (observer) { + observer.disconnect(); + observer = void 0; + } + }; + const targets = computed(() => { + const value = toValue(target); + const items = toArray(value).map(unrefElement).filter(notNullish); + return new Set(items); + }); + const stopWatch = watch( + () => targets.value, + (targets2) => { + cleanup(); + if (isSupported.value && targets2.size) { + observer = new MutationObserver(callback); + targets2.forEach((el) => observer.observe(el, mutationOptions)); + } + }, + { immediate: true, flush: "post" } + ); + const takeRecords = () => { + return observer == null ? void 0 : observer.takeRecords(); + }; + const stop = () => { + stopWatch(); + cleanup(); + }; + tryOnScopeDispose(stop); + return { + isSupported, + stop, + takeRecords + }; +} +function onElementRemoval(target, callback, options = {}) { + const { + window: window2 = defaultWindow, + document: document2 = window2 == null ? void 0 : window2.document, + flush = "sync" + } = options; + if (!window2 || !document2) + return noop; + let stopFn; + const cleanupAndUpdate = (fn) => { + stopFn == null ? void 0 : stopFn(); + stopFn = fn; + }; + const stopWatch = watchEffect(() => { + const el = unrefElement(target); + if (el) { + const { stop } = useMutationObserver( + document2, + (mutationsList) => { + const targetRemoved = mutationsList.map((mutation) => [...mutation.removedNodes]).flat().some((node) => node === el || node.contains(el)); + if (targetRemoved) { + callback(mutationsList); + } + }, + { + window: window2, + childList: true, + subtree: true + } + ); + cleanupAndUpdate(stop); + } + }, { flush }); + const stopHandle = () => { + stopWatch(); + cleanupAndUpdate(); + }; + tryOnScopeDispose(stopHandle); + return stopHandle; +} +function createKeyPredicate(keyFilter) { + if (typeof keyFilter === "function") + return keyFilter; + else if (typeof keyFilter === "string") + return (event) => event.key === keyFilter; + else if (Array.isArray(keyFilter)) + return (event) => keyFilter.includes(event.key); + return () => true; +} +function onKeyStroke(...args) { + let key; + let handler; + let options = {}; + if (args.length === 3) { + key = args[0]; + handler = args[1]; + options = args[2]; + } else if (args.length === 2) { + if (typeof args[1] === "object") { + key = true; + handler = args[0]; + options = args[1]; + } else { + key = args[0]; + handler = args[1]; + } + } else { + key = true; + handler = args[0]; + } + const { + target = defaultWindow, + eventName = "keydown", + passive = false, + dedupe = false + } = options; + const predicate = createKeyPredicate(key); + const listener = (e) => { + if (e.repeat && toValue(dedupe)) + return; + if (predicate(e)) + handler(e); + }; + return useEventListener(target, eventName, listener, passive); +} +function onKeyDown(key, handler, options = {}) { + return onKeyStroke(key, handler, { ...options, eventName: "keydown" }); +} +function onKeyPressed(key, handler, options = {}) { + return onKeyStroke(key, handler, { ...options, eventName: "keypress" }); +} +function onKeyUp(key, handler, options = {}) { + return onKeyStroke(key, handler, { ...options, eventName: "keyup" }); +} +var DEFAULT_DELAY = 500; +var DEFAULT_THRESHOLD = 10; +function onLongPress(target, handler, options) { + var _a, _b; + const elementRef = computed(() => unrefElement(target)); + let timeout; + let posStart; + let startTimestamp; + let hasLongPressed = false; + function clear() { + if (timeout) { + clearTimeout(timeout); + timeout = void 0; + } + posStart = void 0; + startTimestamp = void 0; + hasLongPressed = false; + } + function onRelease(ev) { + var _a2, _b2, _c; + const [_startTimestamp, _posStart, _hasLongPressed] = [startTimestamp, posStart, hasLongPressed]; + clear(); + if (!(options == null ? void 0 : options.onMouseUp) || !_posStart || !_startTimestamp) + return; + if (((_a2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _a2.self) && ev.target !== elementRef.value) + return; + if ((_b2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _b2.prevent) + ev.preventDefault(); + if ((_c = options == null ? void 0 : options.modifiers) == null ? void 0 : _c.stop) + ev.stopPropagation(); + const dx = ev.x - _posStart.x; + const dy = ev.y - _posStart.y; + const distance = Math.sqrt(dx * dx + dy * dy); + options.onMouseUp(ev.timeStamp - _startTimestamp, distance, _hasLongPressed); + } + function onDown(ev) { + var _a2, _b2, _c, _d; + if (((_a2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _a2.self) && ev.target !== elementRef.value) + return; + clear(); + if ((_b2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _b2.prevent) + ev.preventDefault(); + if ((_c = options == null ? void 0 : options.modifiers) == null ? void 0 : _c.stop) + ev.stopPropagation(); + posStart = { + x: ev.x, + y: ev.y + }; + startTimestamp = ev.timeStamp; + timeout = setTimeout( + () => { + hasLongPressed = true; + handler(ev); + }, + (_d = options == null ? void 0 : options.delay) != null ? _d : DEFAULT_DELAY + ); + } + function onMove(ev) { + var _a2, _b2, _c, _d; + if (((_a2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _a2.self) && ev.target !== elementRef.value) + return; + if (!posStart || (options == null ? void 0 : options.distanceThreshold) === false) + return; + if ((_b2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _b2.prevent) + ev.preventDefault(); + if ((_c = options == null ? void 0 : options.modifiers) == null ? void 0 : _c.stop) + ev.stopPropagation(); + const dx = ev.x - posStart.x; + const dy = ev.y - posStart.y; + const distance = Math.sqrt(dx * dx + dy * dy); + if (distance >= ((_d = options == null ? void 0 : options.distanceThreshold) != null ? _d : DEFAULT_THRESHOLD)) + clear(); + } + const listenerOptions = { + capture: (_a = options == null ? void 0 : options.modifiers) == null ? void 0 : _a.capture, + once: (_b = options == null ? void 0 : options.modifiers) == null ? void 0 : _b.once + }; + const cleanup = [ + useEventListener(elementRef, "pointerdown", onDown, listenerOptions), + useEventListener(elementRef, "pointermove", onMove, listenerOptions), + useEventListener(elementRef, ["pointerup", "pointerleave"], onRelease, listenerOptions) + ]; + const stop = () => cleanup.forEach((fn) => fn()); + return stop; +} +function isFocusedElementEditable() { + const { activeElement, body } = document; + if (!activeElement) + return false; + if (activeElement === body) + return false; + switch (activeElement.tagName) { + case "INPUT": + case "TEXTAREA": + return true; + } + return activeElement.hasAttribute("contenteditable"); +} +function isTypedCharValid({ + keyCode, + metaKey, + ctrlKey, + altKey +}) { + if (metaKey || ctrlKey || altKey) + return false; + if (keyCode >= 48 && keyCode <= 57 || keyCode >= 96 && keyCode <= 105) + return true; + if (keyCode >= 65 && keyCode <= 90) + return true; + return false; +} +function onStartTyping(callback, options = {}) { + const { document: document2 = defaultDocument } = options; + const keydown = (event) => { + if (!isFocusedElementEditable() && isTypedCharValid(event)) { + callback(event); + } + }; + if (document2) + useEventListener(document2, "keydown", keydown, { passive: true }); +} +function templateRef(key, initialValue = null) { + const instance = getCurrentInstance(); + let _trigger = () => { + }; + const element = customRef((track, trigger) => { + _trigger = trigger; + return { + get() { + var _a, _b; + track(); + return (_b = (_a = instance == null ? void 0 : instance.proxy) == null ? void 0 : _a.$refs[key]) != null ? _b : initialValue; + }, + set() { + } + }; + }); + tryOnMounted(_trigger); + onUpdated(_trigger); + return element; +} +function useActiveElement(options = {}) { + var _a; + const { + window: window2 = defaultWindow, + deep = true, + triggerOnRemoval = false + } = options; + const document2 = (_a = options.document) != null ? _a : window2 == null ? void 0 : window2.document; + const getDeepActiveElement = () => { + var _a2; + let element = document2 == null ? void 0 : document2.activeElement; + if (deep) { + while (element == null ? void 0 : element.shadowRoot) + element = (_a2 = element == null ? void 0 : element.shadowRoot) == null ? void 0 : _a2.activeElement; + } + return element; + }; + const activeElement = shallowRef(); + const trigger = () => { + activeElement.value = getDeepActiveElement(); + }; + if (window2) { + const listenerOptions = { + capture: true, + passive: true + }; + useEventListener( + window2, + "blur", + (event) => { + if (event.relatedTarget !== null) + return; + trigger(); + }, + listenerOptions + ); + useEventListener( + window2, + "focus", + trigger, + listenerOptions + ); + } + if (triggerOnRemoval) { + onElementRemoval(activeElement, trigger, { document: document2 }); + } + trigger(); + return activeElement; +} +function useRafFn(fn, options = {}) { + const { + immediate = true, + fpsLimit = void 0, + window: window2 = defaultWindow, + once = false + } = options; + const isActive = shallowRef(false); + const intervalLimit = computed(() => { + return fpsLimit ? 1e3 / toValue(fpsLimit) : null; + }); + let previousFrameTimestamp = 0; + let rafId = null; + function loop(timestamp2) { + if (!isActive.value || !window2) + return; + if (!previousFrameTimestamp) + previousFrameTimestamp = timestamp2; + const delta = timestamp2 - previousFrameTimestamp; + if (intervalLimit.value && delta < intervalLimit.value) { + rafId = window2.requestAnimationFrame(loop); + return; + } + previousFrameTimestamp = timestamp2; + fn({ delta, timestamp: timestamp2 }); + if (once) { + isActive.value = false; + rafId = null; + return; + } + rafId = window2.requestAnimationFrame(loop); + } + function resume() { + if (!isActive.value && window2) { + isActive.value = true; + previousFrameTimestamp = 0; + rafId = window2.requestAnimationFrame(loop); + } + } + function pause() { + isActive.value = false; + if (rafId != null && window2) { + window2.cancelAnimationFrame(rafId); + rafId = null; + } + } + if (immediate) + resume(); + tryOnScopeDispose(pause); + return { + isActive: readonly(isActive), + pause, + resume + }; +} +function useAnimate(target, keyframes, options) { + let config; + let animateOptions; + if (isObject(options)) { + config = options; + animateOptions = objectOmit(options, ["window", "immediate", "commitStyles", "persist", "onReady", "onError"]); + } else { + config = { duration: options }; + animateOptions = options; + } + const { + window: window2 = defaultWindow, + immediate = true, + commitStyles, + persist, + playbackRate: _playbackRate = 1, + onReady, + onError = (e) => { + console.error(e); + } + } = config; + const isSupported = useSupported(() => window2 && HTMLElement && "animate" in HTMLElement.prototype); + const animate = shallowRef(void 0); + const store = shallowReactive({ + startTime: null, + currentTime: null, + timeline: null, + playbackRate: _playbackRate, + pending: false, + playState: immediate ? "idle" : "paused", + replaceState: "active" + }); + const pending = computed(() => store.pending); + const playState = computed(() => store.playState); + const replaceState = computed(() => store.replaceState); + const startTime = computed({ + get() { + return store.startTime; + }, + set(value) { + store.startTime = value; + if (animate.value) + animate.value.startTime = value; + } + }); + const currentTime = computed({ + get() { + return store.currentTime; + }, + set(value) { + store.currentTime = value; + if (animate.value) { + animate.value.currentTime = value; + syncResume(); + } + } + }); + const timeline = computed({ + get() { + return store.timeline; + }, + set(value) { + store.timeline = value; + if (animate.value) + animate.value.timeline = value; + } + }); + const playbackRate = computed({ + get() { + return store.playbackRate; + }, + set(value) { + store.playbackRate = value; + if (animate.value) + animate.value.playbackRate = value; + } + }); + const play = () => { + if (animate.value) { + try { + animate.value.play(); + syncResume(); + } catch (e) { + syncPause(); + onError(e); + } + } else { + update(); + } + }; + const pause = () => { + var _a; + try { + (_a = animate.value) == null ? void 0 : _a.pause(); + syncPause(); + } catch (e) { + onError(e); + } + }; + const reverse = () => { + var _a; + if (!animate.value) + update(); + try { + (_a = animate.value) == null ? void 0 : _a.reverse(); + syncResume(); + } catch (e) { + syncPause(); + onError(e); + } + }; + const finish = () => { + var _a; + try { + (_a = animate.value) == null ? void 0 : _a.finish(); + syncPause(); + } catch (e) { + onError(e); + } + }; + const cancel = () => { + var _a; + try { + (_a = animate.value) == null ? void 0 : _a.cancel(); + syncPause(); + } catch (e) { + onError(e); + } + }; + watch(() => unrefElement(target), (el) => { + if (el) { + update(); + } else { + animate.value = void 0; + } + }); + watch(() => keyframes, (value) => { + if (animate.value) { + update(); + const targetEl = unrefElement(target); + if (targetEl) { + animate.value.effect = new KeyframeEffect( + targetEl, + toValue(value), + animateOptions + ); + } + } + }, { deep: true }); + tryOnMounted(() => update(true), false); + tryOnScopeDispose(cancel); + function update(init) { + const el = unrefElement(target); + if (!isSupported.value || !el) + return; + if (!animate.value) + animate.value = el.animate(toValue(keyframes), animateOptions); + if (persist) + animate.value.persist(); + if (_playbackRate !== 1) + animate.value.playbackRate = _playbackRate; + if (init && !immediate) + animate.value.pause(); + else + syncResume(); + onReady == null ? void 0 : onReady(animate.value); + } + const listenerOptions = { passive: true }; + useEventListener(animate, ["cancel", "finish", "remove"], syncPause, listenerOptions); + useEventListener(animate, "finish", () => { + var _a; + if (commitStyles) + (_a = animate.value) == null ? void 0 : _a.commitStyles(); + }, listenerOptions); + const { resume: resumeRef, pause: pauseRef } = useRafFn(() => { + if (!animate.value) + return; + store.pending = animate.value.pending; + store.playState = animate.value.playState; + store.replaceState = animate.value.replaceState; + store.startTime = animate.value.startTime; + store.currentTime = animate.value.currentTime; + store.timeline = animate.value.timeline; + store.playbackRate = animate.value.playbackRate; + }, { immediate: false }); + function syncResume() { + if (isSupported.value) + resumeRef(); + } + function syncPause() { + if (isSupported.value && window2) + window2.requestAnimationFrame(pauseRef); + } + return { + isSupported, + animate, + // actions + play, + pause, + reverse, + finish, + cancel, + // state + pending, + playState, + replaceState, + startTime, + currentTime, + timeline, + playbackRate + }; +} +function useAsyncQueue(tasks, options) { + const { + interrupt = true, + onError = noop, + onFinished = noop, + signal + } = options || {}; + const promiseState = { + aborted: "aborted", + fulfilled: "fulfilled", + pending: "pending", + rejected: "rejected" + }; + const initialResult = Array.from(Array.from({ length: tasks.length }), () => ({ state: promiseState.pending, data: null })); + const result = reactive(initialResult); + const activeIndex = shallowRef(-1); + if (!tasks || tasks.length === 0) { + onFinished(); + return { + activeIndex, + result + }; + } + function updateResult(state, res) { + activeIndex.value++; + result[activeIndex.value].data = res; + result[activeIndex.value].state = state; + } + tasks.reduce((prev, curr) => { + return prev.then((prevRes) => { + var _a; + if (signal == null ? void 0 : signal.aborted) { + updateResult(promiseState.aborted, new Error("aborted")); + return; + } + if (((_a = result[activeIndex.value]) == null ? void 0 : _a.state) === promiseState.rejected && interrupt) { + onFinished(); + return; + } + const done = curr(prevRes).then((currentRes) => { + updateResult(promiseState.fulfilled, currentRes); + if (activeIndex.value === tasks.length - 1) + onFinished(); + return currentRes; + }); + if (!signal) + return done; + return Promise.race([done, whenAborted(signal)]); + }).catch((e) => { + if (signal == null ? void 0 : signal.aborted) { + updateResult(promiseState.aborted, e); + return e; + } + updateResult(promiseState.rejected, e); + onError(); + return e; + }); + }, Promise.resolve()); + return { + activeIndex, + result + }; +} +function whenAborted(signal) { + return new Promise((resolve, reject) => { + const error = new Error("aborted"); + if (signal.aborted) + reject(error); + else + signal.addEventListener("abort", () => reject(error), { once: true }); + }); +} +function useAsyncState(promise, initialState, options) { + const { + immediate = true, + delay = 0, + onError = noop, + onSuccess = noop, + resetOnExecute = true, + shallow = true, + throwError + } = options != null ? options : {}; + const state = shallow ? shallowRef(initialState) : ref(initialState); + const isReady = shallowRef(false); + const isLoading = shallowRef(false); + const error = shallowRef(void 0); + async function execute(delay2 = 0, ...args) { + if (resetOnExecute) + state.value = initialState; + error.value = void 0; + isReady.value = false; + isLoading.value = true; + if (delay2 > 0) + await promiseTimeout(delay2); + const _promise = typeof promise === "function" ? promise(...args) : promise; + try { + const data = await _promise; + state.value = data; + isReady.value = true; + onSuccess(data); + } catch (e) { + error.value = e; + onError(e); + if (throwError) + throw e; + } finally { + isLoading.value = false; + } + return state.value; + } + if (immediate) { + execute(delay); + } + const shell = { + state, + isReady, + isLoading, + error, + execute + }; + function waitUntilIsLoaded() { + return new Promise((resolve, reject) => { + until(isLoading).toBe(false).then(() => resolve(shell)).catch(reject); + }); + } + return { + ...shell, + then(onFulfilled, onRejected) { + return waitUntilIsLoaded().then(onFulfilled, onRejected); + } + }; +} +var defaults = { + array: (v) => JSON.stringify(v), + object: (v) => JSON.stringify(v), + set: (v) => JSON.stringify(Array.from(v)), + map: (v) => JSON.stringify(Object.fromEntries(v)), + null: () => "" +}; +function getDefaultSerialization(target) { + if (!target) + return defaults.null; + if (target instanceof Map) + return defaults.map; + else if (target instanceof Set) + return defaults.set; + else if (Array.isArray(target)) + return defaults.array; + else + return defaults.object; +} +function useBase64(target, options) { + const base64 = shallowRef(""); + const promise = shallowRef(); + function execute() { + if (!isClient) + return; + promise.value = new Promise((resolve, reject) => { + try { + const _target = toValue(target); + if (_target == null) { + resolve(""); + } else if (typeof _target === "string") { + resolve(blobToBase64(new Blob([_target], { type: "text/plain" }))); + } else if (_target instanceof Blob) { + resolve(blobToBase64(_target)); + } else if (_target instanceof ArrayBuffer) { + resolve(window.btoa(String.fromCharCode(...new Uint8Array(_target)))); + } else if (_target instanceof HTMLCanvasElement) { + resolve(_target.toDataURL(options == null ? void 0 : options.type, options == null ? void 0 : options.quality)); + } else if (_target instanceof HTMLImageElement) { + const img = _target.cloneNode(false); + img.crossOrigin = "Anonymous"; + imgLoaded(img).then(() => { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + resolve(canvas.toDataURL(options == null ? void 0 : options.type, options == null ? void 0 : options.quality)); + }).catch(reject); + } else if (typeof _target === "object") { + const _serializeFn = (options == null ? void 0 : options.serializer) || getDefaultSerialization(_target); + const serialized = _serializeFn(_target); + return resolve(blobToBase64(new Blob([serialized], { type: "application/json" }))); + } else { + reject(new Error("target is unsupported types")); + } + } catch (error) { + reject(error); + } + }); + promise.value.then((res) => { + base64.value = (options == null ? void 0 : options.dataUrl) === false ? res.replace(/^data:.*?;base64,/, "") : res; + }); + return promise.value; + } + if (isRef(target) || typeof target === "function") + watch(target, execute, { immediate: true }); + else + execute(); + return { + base64, + promise, + execute + }; +} +function imgLoaded(img) { + return new Promise((resolve, reject) => { + if (!img.complete) { + img.onload = () => { + resolve(); + }; + img.onerror = reject; + } else { + resolve(); + } + }); +} +function blobToBase64(blob) { + return new Promise((resolve, reject) => { + const fr = new FileReader(); + fr.onload = (e) => { + resolve(e.target.result); + }; + fr.onerror = reject; + fr.readAsDataURL(blob); + }); +} +function useBattery(options = {}) { + const { navigator: navigator2 = defaultNavigator } = options; + const events2 = ["chargingchange", "chargingtimechange", "dischargingtimechange", "levelchange"]; + const isSupported = useSupported(() => navigator2 && "getBattery" in navigator2 && typeof navigator2.getBattery === "function"); + const charging = shallowRef(false); + const chargingTime = shallowRef(0); + const dischargingTime = shallowRef(0); + const level = shallowRef(1); + let battery; + function updateBatteryInfo() { + charging.value = this.charging; + chargingTime.value = this.chargingTime || 0; + dischargingTime.value = this.dischargingTime || 0; + level.value = this.level; + } + if (isSupported.value) { + navigator2.getBattery().then((_battery) => { + battery = _battery; + updateBatteryInfo.call(battery); + useEventListener(battery, events2, updateBatteryInfo, { passive: true }); + }); + } + return { + isSupported, + charging, + chargingTime, + dischargingTime, + level + }; +} +function useBluetooth(options) { + let { + acceptAllDevices = false + } = options || {}; + const { + filters = void 0, + optionalServices = void 0, + navigator: navigator2 = defaultNavigator + } = options || {}; + const isSupported = useSupported(() => navigator2 && "bluetooth" in navigator2); + const device = shallowRef(); + const error = shallowRef(null); + watch(device, () => { + connectToBluetoothGATTServer(); + }); + async function requestDevice() { + if (!isSupported.value) + return; + error.value = null; + if (filters && filters.length > 0) + acceptAllDevices = false; + try { + device.value = await (navigator2 == null ? void 0 : navigator2.bluetooth.requestDevice({ + acceptAllDevices, + filters, + optionalServices + })); + } catch (err) { + error.value = err; + } + } + const server = shallowRef(); + const isConnected = shallowRef(false); + function reset() { + isConnected.value = false; + device.value = void 0; + server.value = void 0; + } + async function connectToBluetoothGATTServer() { + error.value = null; + if (device.value && device.value.gatt) { + useEventListener(device, "gattserverdisconnected", reset, { passive: true }); + try { + server.value = await device.value.gatt.connect(); + isConnected.value = server.value.connected; + } catch (err) { + error.value = err; + } + } + } + tryOnMounted(() => { + var _a; + if (device.value) + (_a = device.value.gatt) == null ? void 0 : _a.connect(); + }); + tryOnScopeDispose(() => { + var _a; + if (device.value) + (_a = device.value.gatt) == null ? void 0 : _a.disconnect(); + }); + return { + isSupported, + isConnected: readonly(isConnected), + // Device: + device, + requestDevice, + // Server: + server, + // Errors: + error + }; +} +var ssrWidthSymbol = Symbol("vueuse-ssr-width"); +function useSSRWidth() { + const ssrWidth = hasInjectionContext() ? injectLocal(ssrWidthSymbol, null) : null; + return typeof ssrWidth === "number" ? ssrWidth : void 0; +} +function provideSSRWidth(width, app) { + if (app !== void 0) { + app.provide(ssrWidthSymbol, width); + } else { + provideLocal(ssrWidthSymbol, width); + } +} +function useMediaQuery(query, options = {}) { + const { window: window2 = defaultWindow, ssrWidth = useSSRWidth() } = options; + const isSupported = useSupported(() => window2 && "matchMedia" in window2 && typeof window2.matchMedia === "function"); + const ssrSupport = shallowRef(typeof ssrWidth === "number"); + const mediaQuery = shallowRef(); + const matches = shallowRef(false); + const handler = (event) => { + matches.value = event.matches; + }; + watchEffect(() => { + if (ssrSupport.value) { + ssrSupport.value = !isSupported.value; + const queryStrings = toValue(query).split(","); + matches.value = queryStrings.some((queryString) => { + const not = queryString.includes("not all"); + const minWidth = queryString.match(/\(\s*min-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/); + const maxWidth = queryString.match(/\(\s*max-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/); + let res = Boolean(minWidth || maxWidth); + if (minWidth && res) { + res = ssrWidth >= pxValue(minWidth[1]); + } + if (maxWidth && res) { + res = ssrWidth <= pxValue(maxWidth[1]); + } + return not ? !res : res; + }); + return; + } + if (!isSupported.value) + return; + mediaQuery.value = window2.matchMedia(toValue(query)); + matches.value = mediaQuery.value.matches; + }); + useEventListener(mediaQuery, "change", handler, { passive: true }); + return computed(() => matches.value); +} +var breakpointsTailwind = { + "sm": 640, + "md": 768, + "lg": 1024, + "xl": 1280, + "2xl": 1536 +}; +var breakpointsBootstrapV5 = { + xs: 0, + sm: 576, + md: 768, + lg: 992, + xl: 1200, + xxl: 1400 +}; +var breakpointsVuetifyV2 = { + xs: 0, + sm: 600, + md: 960, + lg: 1264, + xl: 1904 +}; +var breakpointsVuetifyV3 = { + xs: 0, + sm: 600, + md: 960, + lg: 1280, + xl: 1920, + xxl: 2560 +}; +var breakpointsVuetify = breakpointsVuetifyV2; +var breakpointsAntDesign = { + xs: 480, + sm: 576, + md: 768, + lg: 992, + xl: 1200, + xxl: 1600 +}; +var breakpointsQuasar = { + xs: 0, + sm: 600, + md: 1024, + lg: 1440, + xl: 1920 +}; +var breakpointsSematic = { + mobileS: 320, + mobileM: 375, + mobileL: 425, + tablet: 768, + laptop: 1024, + laptopL: 1440, + desktop4K: 2560 +}; +var breakpointsMasterCss = { + "3xs": 360, + "2xs": 480, + "xs": 600, + "sm": 768, + "md": 1024, + "lg": 1280, + "xl": 1440, + "2xl": 1600, + "3xl": 1920, + "4xl": 2560 +}; +var breakpointsPrimeFlex = { + sm: 576, + md: 768, + lg: 992, + xl: 1200 +}; +var breakpointsElement = { + xs: 0, + sm: 768, + md: 992, + lg: 1200, + xl: 1920 +}; +function useBreakpoints(breakpoints, options = {}) { + function getValue2(k, delta) { + let v = toValue(breakpoints[toValue(k)]); + if (delta != null) + v = increaseWithUnit(v, delta); + if (typeof v === "number") + v = `${v}px`; + return v; + } + const { window: window2 = defaultWindow, strategy = "min-width", ssrWidth = useSSRWidth() } = options; + const ssrSupport = typeof ssrWidth === "number"; + const mounted = ssrSupport ? shallowRef(false) : { value: true }; + if (ssrSupport) { + tryOnMounted(() => mounted.value = !!window2); + } + function match(query, size) { + if (!mounted.value && ssrSupport) { + return query === "min" ? ssrWidth >= pxValue(size) : ssrWidth <= pxValue(size); + } + if (!window2) + return false; + return window2.matchMedia(`(${query}-width: ${size})`).matches; + } + const greaterOrEqual = (k) => { + return useMediaQuery(() => `(min-width: ${getValue2(k)})`, options); + }; + const smallerOrEqual = (k) => { + return useMediaQuery(() => `(max-width: ${getValue2(k)})`, options); + }; + const shortcutMethods = Object.keys(breakpoints).reduce((shortcuts, k) => { + Object.defineProperty(shortcuts, k, { + get: () => strategy === "min-width" ? greaterOrEqual(k) : smallerOrEqual(k), + enumerable: true, + configurable: true + }); + return shortcuts; + }, {}); + function current() { + const points = Object.keys(breakpoints).map((k) => [k, shortcutMethods[k], pxValue(getValue2(k))]).sort((a, b) => a[2] - b[2]); + return computed(() => points.filter(([, v]) => v.value).map(([k]) => k)); + } + return Object.assign(shortcutMethods, { + greaterOrEqual, + smallerOrEqual, + greater(k) { + return useMediaQuery(() => `(min-width: ${getValue2(k, 0.1)})`, options); + }, + smaller(k) { + return useMediaQuery(() => `(max-width: ${getValue2(k, -0.1)})`, options); + }, + between(a, b) { + return useMediaQuery(() => `(min-width: ${getValue2(a)}) and (max-width: ${getValue2(b, -0.1)})`, options); + }, + isGreater(k) { + return match("min", getValue2(k, 0.1)); + }, + isGreaterOrEqual(k) { + return match("min", getValue2(k)); + }, + isSmaller(k) { + return match("max", getValue2(k, -0.1)); + }, + isSmallerOrEqual(k) { + return match("max", getValue2(k)); + }, + isInBetween(a, b) { + return match("min", getValue2(a)) && match("max", getValue2(b, -0.1)); + }, + current, + active() { + const bps = current(); + return computed(() => bps.value.length === 0 ? "" : bps.value.at(strategy === "min-width" ? -1 : 0)); + } + }); +} +function useBroadcastChannel(options) { + const { + name, + window: window2 = defaultWindow + } = options; + const isSupported = useSupported(() => window2 && "BroadcastChannel" in window2); + const isClosed = shallowRef(false); + const channel = ref(); + const data = ref(); + const error = shallowRef(null); + const post = (data2) => { + if (channel.value) + channel.value.postMessage(data2); + }; + const close = () => { + if (channel.value) + channel.value.close(); + isClosed.value = true; + }; + if (isSupported.value) { + tryOnMounted(() => { + error.value = null; + channel.value = new BroadcastChannel(name); + const listenerOptions = { + passive: true + }; + useEventListener(channel, "message", (e) => { + data.value = e.data; + }, listenerOptions); + useEventListener(channel, "messageerror", (e) => { + error.value = e; + }, listenerOptions); + useEventListener(channel, "close", () => { + isClosed.value = true; + }, listenerOptions); + }); + } + tryOnScopeDispose(() => { + close(); + }); + return { + isSupported, + channel, + data, + post, + close, + error, + isClosed + }; +} +var WRITABLE_PROPERTIES = [ + "hash", + "host", + "hostname", + "href", + "pathname", + "port", + "protocol", + "search" +]; +function useBrowserLocation(options = {}) { + const { window: window2 = defaultWindow } = options; + const refs = Object.fromEntries( + WRITABLE_PROPERTIES.map((key) => [key, ref()]) + ); + for (const [key, ref2] of objectEntries(refs)) { + watch(ref2, (value) => { + if (!(window2 == null ? void 0 : window2.location) || window2.location[key] === value) + return; + window2.location[key] = value; + }); + } + const buildState = (trigger) => { + var _a; + const { state: state2, length } = (window2 == null ? void 0 : window2.history) || {}; + const { origin } = (window2 == null ? void 0 : window2.location) || {}; + for (const key of WRITABLE_PROPERTIES) + refs[key].value = (_a = window2 == null ? void 0 : window2.location) == null ? void 0 : _a[key]; + return reactive({ + trigger, + state: state2, + length, + origin, + ...refs + }); + }; + const state = ref(buildState("load")); + if (window2) { + const listenerOptions = { passive: true }; + useEventListener(window2, "popstate", () => state.value = buildState("popstate"), listenerOptions); + useEventListener(window2, "hashchange", () => state.value = buildState("hashchange"), listenerOptions); + } + return state; +} +function useCached(refValue, comparator = (a, b) => a === b, options) { + const { deepRefs = true, ...watchOptions } = options || {}; + const cachedValue = createRef(refValue.value, deepRefs); + watch(() => refValue.value, (value) => { + if (!comparator(value, cachedValue.value)) + cachedValue.value = value; + }, watchOptions); + return cachedValue; +} +function usePermission(permissionDesc, options = {}) { + const { + controls = false, + navigator: navigator2 = defaultNavigator + } = options; + const isSupported = useSupported(() => navigator2 && "permissions" in navigator2); + const permissionStatus = shallowRef(); + const desc = typeof permissionDesc === "string" ? { name: permissionDesc } : permissionDesc; + const state = shallowRef(); + const update = () => { + var _a, _b; + state.value = (_b = (_a = permissionStatus.value) == null ? void 0 : _a.state) != null ? _b : "prompt"; + }; + useEventListener(permissionStatus, "change", update, { passive: true }); + const query = createSingletonPromise(async () => { + if (!isSupported.value) + return; + if (!permissionStatus.value) { + try { + permissionStatus.value = await navigator2.permissions.query(desc); + } catch (e) { + permissionStatus.value = void 0; + } finally { + update(); + } + } + if (controls) + return toRaw(permissionStatus.value); + }); + query(); + if (controls) { + return { + state, + isSupported, + query + }; + } else { + return state; + } +} +function useClipboard(options = {}) { + const { + navigator: navigator2 = defaultNavigator, + read = false, + source, + copiedDuring = 1500, + legacy = false + } = options; + const isClipboardApiSupported = useSupported(() => navigator2 && "clipboard" in navigator2); + const permissionRead = usePermission("clipboard-read"); + const permissionWrite = usePermission("clipboard-write"); + const isSupported = computed(() => isClipboardApiSupported.value || legacy); + const text = shallowRef(""); + const copied = shallowRef(false); + const timeout = useTimeoutFn(() => copied.value = false, copiedDuring, { immediate: false }); + async function updateText() { + let useLegacy = !(isClipboardApiSupported.value && isAllowed(permissionRead.value)); + if (!useLegacy) { + try { + text.value = await navigator2.clipboard.readText(); + } catch (e) { + useLegacy = true; + } + } + if (useLegacy) { + text.value = legacyRead(); + } + } + if (isSupported.value && read) + useEventListener(["copy", "cut"], updateText, { passive: true }); + async function copy(value = toValue(source)) { + if (isSupported.value && value != null) { + let useLegacy = !(isClipboardApiSupported.value && isAllowed(permissionWrite.value)); + if (!useLegacy) { + try { + await navigator2.clipboard.writeText(value); + } catch (e) { + useLegacy = true; + } + } + if (useLegacy) + legacyCopy(value); + text.value = value; + copied.value = true; + timeout.start(); + } + } + function legacyCopy(value) { + const ta = document.createElement("textarea"); + ta.value = value != null ? value : ""; + ta.style.position = "absolute"; + ta.style.opacity = "0"; + document.body.appendChild(ta); + ta.select(); + document.execCommand("copy"); + ta.remove(); + } + function legacyRead() { + var _a, _b, _c; + return (_c = (_b = (_a = document == null ? void 0 : document.getSelection) == null ? void 0 : _a.call(document)) == null ? void 0 : _b.toString()) != null ? _c : ""; + } + function isAllowed(status) { + return status === "granted" || status === "prompt"; + } + return { + isSupported, + text, + copied, + copy + }; +} +function useClipboardItems(options = {}) { + const { + navigator: navigator2 = defaultNavigator, + read = false, + source, + copiedDuring = 1500 + } = options; + const isSupported = useSupported(() => navigator2 && "clipboard" in navigator2); + const content = ref([]); + const copied = shallowRef(false); + const timeout = useTimeoutFn(() => copied.value = false, copiedDuring, { immediate: false }); + function updateContent() { + if (isSupported.value) { + navigator2.clipboard.read().then((items) => { + content.value = items; + }); + } + } + if (isSupported.value && read) + useEventListener(["copy", "cut"], updateContent, { passive: true }); + async function copy(value = toValue(source)) { + if (isSupported.value && value != null) { + await navigator2.clipboard.write(value); + content.value = value; + copied.value = true; + timeout.start(); + } + } + return { + isSupported, + content, + copied, + copy + }; +} +function cloneFnJSON(source) { + return JSON.parse(JSON.stringify(source)); +} +function useCloned(source, options = {}) { + const cloned = ref({}); + const isModified = shallowRef(false); + let _lastSync = false; + const { + manual, + clone = cloneFnJSON, + // watch options + deep = true, + immediate = true + } = options; + watch(cloned, () => { + if (_lastSync) { + _lastSync = false; + return; + } + isModified.value = true; + }, { + deep: true, + flush: "sync" + }); + function sync() { + _lastSync = true; + isModified.value = false; + cloned.value = clone(toValue(source)); + } + if (!manual && (isRef(source) || typeof source === "function")) { + watch(source, sync, { + ...options, + deep, + immediate + }); + } else { + sync(); + } + return { cloned, isModified, sync }; +} +var _global = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {}; +var globalKey = "__vueuse_ssr_handlers__"; +var handlers = getHandlers(); +function getHandlers() { + if (!(globalKey in _global)) + _global[globalKey] = _global[globalKey] || {}; + return _global[globalKey]; +} +function getSSRHandler(key, fallback) { + return handlers[key] || fallback; +} +function setSSRHandler(key, fn) { + handlers[key] = fn; +} +function usePreferredDark(options) { + return useMediaQuery("(prefers-color-scheme: dark)", options); +} +function guessSerializerType(rawInit) { + return rawInit == null ? "any" : rawInit instanceof Set ? "set" : rawInit instanceof Map ? "map" : rawInit instanceof Date ? "date" : typeof rawInit === "boolean" ? "boolean" : typeof rawInit === "string" ? "string" : typeof rawInit === "object" ? "object" : !Number.isNaN(rawInit) ? "number" : "any"; +} +var StorageSerializers = { + boolean: { + read: (v) => v === "true", + write: (v) => String(v) + }, + object: { + read: (v) => JSON.parse(v), + write: (v) => JSON.stringify(v) + }, + number: { + read: (v) => Number.parseFloat(v), + write: (v) => String(v) + }, + any: { + read: (v) => v, + write: (v) => String(v) + }, + string: { + read: (v) => v, + write: (v) => String(v) + }, + map: { + read: (v) => new Map(JSON.parse(v)), + write: (v) => JSON.stringify(Array.from(v.entries())) + }, + set: { + read: (v) => new Set(JSON.parse(v)), + write: (v) => JSON.stringify(Array.from(v)) + }, + date: { + read: (v) => new Date(v), + write: (v) => v.toISOString() + } +}; +var customStorageEventName = "vueuse-storage"; +function useStorage(key, defaults2, storage, options = {}) { + var _a; + const { + flush = "pre", + deep = true, + listenToStorageChanges = true, + writeDefaults = true, + mergeDefaults = false, + shallow, + window: window2 = defaultWindow, + eventFilter, + onError = (e) => { + console.error(e); + }, + initOnMounted + } = options; + const data = (shallow ? shallowRef : ref)(typeof defaults2 === "function" ? defaults2() : defaults2); + const keyComputed = computed(() => toValue(key)); + if (!storage) { + try { + storage = getSSRHandler("getDefaultStorage", () => { + var _a2; + return (_a2 = defaultWindow) == null ? void 0 : _a2.localStorage; + })(); + } catch (e) { + onError(e); + } + } + if (!storage) + return data; + const rawInit = toValue(defaults2); + const type = guessSerializerType(rawInit); + const serializer = (_a = options.serializer) != null ? _a : StorageSerializers[type]; + const { pause: pauseWatch, resume: resumeWatch } = watchPausable( + data, + () => write(data.value), + { flush, deep, eventFilter } + ); + watch(keyComputed, () => update(), { flush }); + if (window2 && listenToStorageChanges) { + tryOnMounted(() => { + if (storage instanceof Storage) + useEventListener(window2, "storage", update, { passive: true }); + else + useEventListener(window2, customStorageEventName, updateFromCustomEvent); + if (initOnMounted) + update(); + }); + } + if (!initOnMounted) + update(); + function dispatchWriteEvent(oldValue, newValue) { + if (window2) { + const payload = { + key: keyComputed.value, + oldValue, + newValue, + storageArea: storage + }; + window2.dispatchEvent(storage instanceof Storage ? new StorageEvent("storage", payload) : new CustomEvent(customStorageEventName, { + detail: payload + })); + } + } + function write(v) { + try { + const oldValue = storage.getItem(keyComputed.value); + if (v == null) { + dispatchWriteEvent(oldValue, null); + storage.removeItem(keyComputed.value); + } else { + const serialized = serializer.write(v); + if (oldValue !== serialized) { + storage.setItem(keyComputed.value, serialized); + dispatchWriteEvent(oldValue, serialized); + } + } + } catch (e) { + onError(e); + } + } + function read(event) { + const rawValue = event ? event.newValue : storage.getItem(keyComputed.value); + if (rawValue == null) { + if (writeDefaults && rawInit != null) + storage.setItem(keyComputed.value, serializer.write(rawInit)); + return rawInit; + } else if (!event && mergeDefaults) { + const value = serializer.read(rawValue); + if (typeof mergeDefaults === "function") + return mergeDefaults(value, rawInit); + else if (type === "object" && !Array.isArray(value)) + return { ...rawInit, ...value }; + return value; + } else if (typeof rawValue !== "string") { + return rawValue; + } else { + return serializer.read(rawValue); + } + } + function update(event) { + if (event && event.storageArea !== storage) + return; + if (event && event.key == null) { + data.value = rawInit; + return; + } + if (event && event.key !== keyComputed.value) + return; + pauseWatch(); + try { + if ((event == null ? void 0 : event.newValue) !== serializer.write(data.value)) + data.value = read(event); + } catch (e) { + onError(e); + } finally { + if (event) + nextTick(resumeWatch); + else + resumeWatch(); + } + } + function updateFromCustomEvent(event) { + update(event.detail); + } + return data; +} +var CSS_DISABLE_TRANS = "*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}"; +function useColorMode(options = {}) { + const { + selector = "html", + attribute = "class", + initialValue = "auto", + window: window2 = defaultWindow, + storage, + storageKey = "vueuse-color-scheme", + listenToStorageChanges = true, + storageRef, + emitAuto, + disableTransition = true + } = options; + const modes = { + auto: "", + light: "light", + dark: "dark", + ...options.modes || {} + }; + const preferredDark = usePreferredDark({ window: window2 }); + const system = computed(() => preferredDark.value ? "dark" : "light"); + const store = storageRef || (storageKey == null ? toRef2(initialValue) : useStorage(storageKey, initialValue, storage, { window: window2, listenToStorageChanges })); + const state = computed(() => store.value === "auto" ? system.value : store.value); + const updateHTMLAttrs = getSSRHandler( + "updateHTMLAttrs", + (selector2, attribute2, value) => { + const el = typeof selector2 === "string" ? window2 == null ? void 0 : window2.document.querySelector(selector2) : unrefElement(selector2); + if (!el) + return; + const classesToAdd = /* @__PURE__ */ new Set(); + const classesToRemove = /* @__PURE__ */ new Set(); + let attributeToChange = null; + if (attribute2 === "class") { + const current = value.split(/\s/g); + Object.values(modes).flatMap((i) => (i || "").split(/\s/g)).filter(Boolean).forEach((v) => { + if (current.includes(v)) + classesToAdd.add(v); + else + classesToRemove.add(v); + }); + } else { + attributeToChange = { key: attribute2, value }; + } + if (classesToAdd.size === 0 && classesToRemove.size === 0 && attributeToChange === null) + return; + let style; + if (disableTransition) { + style = window2.document.createElement("style"); + style.appendChild(document.createTextNode(CSS_DISABLE_TRANS)); + window2.document.head.appendChild(style); + } + for (const c of classesToAdd) { + el.classList.add(c); + } + for (const c of classesToRemove) { + el.classList.remove(c); + } + if (attributeToChange) { + el.setAttribute(attributeToChange.key, attributeToChange.value); + } + if (disableTransition) { + window2.getComputedStyle(style).opacity; + document.head.removeChild(style); + } + } + ); + function defaultOnChanged(mode) { + var _a; + updateHTMLAttrs(selector, attribute, (_a = modes[mode]) != null ? _a : mode); + } + function onChanged(mode) { + if (options.onChanged) + options.onChanged(mode, defaultOnChanged); + else + defaultOnChanged(mode); + } + watch(state, onChanged, { flush: "post", immediate: true }); + tryOnMounted(() => onChanged(state.value)); + const auto = computed({ + get() { + return emitAuto ? store.value : state.value; + }, + set(v) { + store.value = v; + } + }); + return Object.assign(auto, { store, system, state }); +} +function useConfirmDialog(revealed = shallowRef(false)) { + const confirmHook = createEventHook(); + const cancelHook = createEventHook(); + const revealHook = createEventHook(); + let _resolve = noop; + const reveal = (data) => { + revealHook.trigger(data); + revealed.value = true; + return new Promise((resolve) => { + _resolve = resolve; + }); + }; + const confirm = (data) => { + revealed.value = false; + confirmHook.trigger(data); + _resolve({ data, isCanceled: false }); + }; + const cancel = (data) => { + revealed.value = false; + cancelHook.trigger(data); + _resolve({ data, isCanceled: true }); + }; + return { + isRevealed: computed(() => revealed.value), + reveal, + confirm, + cancel, + onReveal: revealHook.on, + onConfirm: confirmHook.on, + onCancel: cancelHook.on + }; +} +function useCountdown(initialCountdown, options) { + var _a, _b; + const remaining = shallowRef(toValue(initialCountdown)); + const intervalController = useIntervalFn(() => { + var _a2, _b2; + const value = remaining.value - 1; + remaining.value = value < 0 ? 0 : value; + (_a2 = options == null ? void 0 : options.onTick) == null ? void 0 : _a2.call(options); + if (remaining.value <= 0) { + intervalController.pause(); + (_b2 = options == null ? void 0 : options.onComplete) == null ? void 0 : _b2.call(options); + } + }, (_a = options == null ? void 0 : options.interval) != null ? _a : 1e3, { immediate: (_b = options == null ? void 0 : options.immediate) != null ? _b : false }); + const reset = (countdown) => { + var _a2; + remaining.value = (_a2 = toValue(countdown)) != null ? _a2 : toValue(initialCountdown); + }; + const stop = () => { + intervalController.pause(); + reset(); + }; + const resume = () => { + if (!intervalController.isActive.value) { + if (remaining.value > 0) { + intervalController.resume(); + } + } + }; + const start = (countdown) => { + reset(countdown); + intervalController.resume(); + }; + return { + remaining, + reset, + stop, + start, + pause: intervalController.pause, + resume, + isActive: intervalController.isActive + }; +} +function useCssVar(prop, target, options = {}) { + const { window: window2 = defaultWindow, initialValue, observe = false } = options; + const variable = shallowRef(initialValue); + const elRef = computed(() => { + var _a; + return unrefElement(target) || ((_a = window2 == null ? void 0 : window2.document) == null ? void 0 : _a.documentElement); + }); + function updateCssVar() { + var _a; + const key = toValue(prop); + const el = toValue(elRef); + if (el && window2 && key) { + const value = (_a = window2.getComputedStyle(el).getPropertyValue(key)) == null ? void 0 : _a.trim(); + variable.value = value || variable.value || initialValue; + } + } + if (observe) { + useMutationObserver(elRef, updateCssVar, { + attributeFilter: ["style", "class"], + window: window2 + }); + } + watch( + [elRef, () => toValue(prop)], + (_, old) => { + if (old[0] && old[1]) + old[0].style.removeProperty(old[1]); + updateCssVar(); + }, + { immediate: true } + ); + watch( + [variable, elRef], + ([val, el]) => { + const raw_prop = toValue(prop); + if ((el == null ? void 0 : el.style) && raw_prop) { + if (val == null) + el.style.removeProperty(raw_prop); + else + el.style.setProperty(raw_prop, val); + } + }, + { immediate: true } + ); + return variable; +} +function useCurrentElement(rootComponent) { + const vm = getCurrentInstance(); + const currentElement = computedWithControl( + () => null, + () => rootComponent ? unrefElement(rootComponent) : vm.proxy.$el + ); + onUpdated(currentElement.trigger); + onMounted(currentElement.trigger); + return currentElement; +} +function useCycleList(list, options) { + const state = shallowRef(getInitialValue()); + const listRef = toRef2(list); + const index = computed({ + get() { + var _a; + const targetList = listRef.value; + let index2 = (options == null ? void 0 : options.getIndexOf) ? options.getIndexOf(state.value, targetList) : targetList.indexOf(state.value); + if (index2 < 0) + index2 = (_a = options == null ? void 0 : options.fallbackIndex) != null ? _a : 0; + return index2; + }, + set(v) { + set2(v); + } + }); + function set2(i) { + const targetList = listRef.value; + const length = targetList.length; + const index2 = (i % length + length) % length; + const value = targetList[index2]; + state.value = value; + return value; + } + function shift(delta = 1) { + return set2(index.value + delta); + } + function next(n = 1) { + return shift(n); + } + function prev(n = 1) { + return shift(-n); + } + function getInitialValue() { + var _a, _b; + return (_b = toValue((_a = options == null ? void 0 : options.initialValue) != null ? _a : toValue(list)[0])) != null ? _b : void 0; + } + watch(listRef, () => set2(index.value)); + return { + state, + index, + next, + prev, + go: set2 + }; +} +function useDark(options = {}) { + const { + valueDark = "dark", + valueLight = "" + } = options; + const mode = useColorMode({ + ...options, + onChanged: (mode2, defaultHandler) => { + var _a; + if (options.onChanged) + (_a = options.onChanged) == null ? void 0 : _a.call(options, mode2 === "dark", defaultHandler, mode2); + else + defaultHandler(mode2); + }, + modes: { + dark: valueDark, + light: valueLight + } + }); + const system = computed(() => mode.system.value); + const isDark = computed({ + get() { + return mode.value === "dark"; + }, + set(v) { + const modeVal = v ? "dark" : "light"; + if (system.value === modeVal) + mode.value = "auto"; + else + mode.value = modeVal; + } + }); + return isDark; +} +function fnBypass(v) { + return v; +} +function fnSetSource(source, value) { + return source.value = value; +} +function defaultDump(clone) { + return clone ? typeof clone === "function" ? clone : cloneFnJSON : fnBypass; +} +function defaultParse(clone) { + return clone ? typeof clone === "function" ? clone : cloneFnJSON : fnBypass; +} +function useManualRefHistory(source, options = {}) { + const { + clone = false, + dump = defaultDump(clone), + parse = defaultParse(clone), + setSource = fnSetSource + } = options; + function _createHistoryRecord() { + return markRaw({ + snapshot: dump(source.value), + timestamp: timestamp() + }); + } + const last = ref(_createHistoryRecord()); + const undoStack = ref([]); + const redoStack = ref([]); + const _setSource = (record) => { + setSource(source, parse(record.snapshot)); + last.value = record; + }; + const commit = () => { + undoStack.value.unshift(last.value); + last.value = _createHistoryRecord(); + if (options.capacity && undoStack.value.length > options.capacity) + undoStack.value.splice(options.capacity, Number.POSITIVE_INFINITY); + if (redoStack.value.length) + redoStack.value.splice(0, redoStack.value.length); + }; + const clear = () => { + undoStack.value.splice(0, undoStack.value.length); + redoStack.value.splice(0, redoStack.value.length); + }; + const undo = () => { + const state = undoStack.value.shift(); + if (state) { + redoStack.value.unshift(last.value); + _setSource(state); + } + }; + const redo = () => { + const state = redoStack.value.shift(); + if (state) { + undoStack.value.unshift(last.value); + _setSource(state); + } + }; + const reset = () => { + _setSource(last.value); + }; + const history = computed(() => [last.value, ...undoStack.value]); + const canUndo = computed(() => undoStack.value.length > 0); + const canRedo = computed(() => redoStack.value.length > 0); + return { + source, + undoStack, + redoStack, + last, + history, + canUndo, + canRedo, + clear, + commit, + reset, + undo, + redo + }; +} +function useRefHistory(source, options = {}) { + const { + deep = false, + flush = "pre", + eventFilter + } = options; + const { + eventFilter: composedFilter, + pause, + resume: resumeTracking, + isActive: isTracking + } = pausableFilter(eventFilter); + const { + ignoreUpdates, + ignorePrevAsyncUpdates, + stop + } = watchIgnorable( + source, + commit, + { deep, flush, eventFilter: composedFilter } + ); + function setSource(source2, value) { + ignorePrevAsyncUpdates(); + ignoreUpdates(() => { + source2.value = value; + }); + } + const manualHistory = useManualRefHistory(source, { ...options, clone: options.clone || deep, setSource }); + const { clear, commit: manualCommit } = manualHistory; + function commit() { + ignorePrevAsyncUpdates(); + manualCommit(); + } + function resume(commitNow) { + resumeTracking(); + if (commitNow) + commit(); + } + function batch(fn) { + let canceled = false; + const cancel = () => canceled = true; + ignoreUpdates(() => { + fn(cancel); + }); + if (!canceled) + commit(); + } + function dispose() { + stop(); + clear(); + } + return { + ...manualHistory, + isTracking, + pause, + resume, + commit, + batch, + dispose + }; +} +function useDebouncedRefHistory(source, options = {}) { + const filter = options.debounce ? debounceFilter(options.debounce) : void 0; + const history = useRefHistory(source, { ...options, eventFilter: filter }); + return { + ...history + }; +} +function useDeviceMotion(options = {}) { + const { + window: window2 = defaultWindow, + requestPermissions = false, + eventFilter = bypassFilter + } = options; + const isSupported = useSupported(() => typeof DeviceMotionEvent !== "undefined"); + const requirePermissions = useSupported(() => isSupported.value && "requestPermission" in DeviceMotionEvent && typeof DeviceMotionEvent.requestPermission === "function"); + const permissionGranted = shallowRef(false); + const acceleration = ref({ x: null, y: null, z: null }); + const rotationRate = ref({ alpha: null, beta: null, gamma: null }); + const interval = shallowRef(0); + const accelerationIncludingGravity = ref({ + x: null, + y: null, + z: null + }); + function init() { + if (window2) { + const onDeviceMotion = createFilterWrapper( + eventFilter, + (event) => { + var _a, _b, _c, _d, _e, _f, _g, _h, _i; + acceleration.value = { + x: ((_a = event.acceleration) == null ? void 0 : _a.x) || null, + y: ((_b = event.acceleration) == null ? void 0 : _b.y) || null, + z: ((_c = event.acceleration) == null ? void 0 : _c.z) || null + }; + accelerationIncludingGravity.value = { + x: ((_d = event.accelerationIncludingGravity) == null ? void 0 : _d.x) || null, + y: ((_e = event.accelerationIncludingGravity) == null ? void 0 : _e.y) || null, + z: ((_f = event.accelerationIncludingGravity) == null ? void 0 : _f.z) || null + }; + rotationRate.value = { + alpha: ((_g = event.rotationRate) == null ? void 0 : _g.alpha) || null, + beta: ((_h = event.rotationRate) == null ? void 0 : _h.beta) || null, + gamma: ((_i = event.rotationRate) == null ? void 0 : _i.gamma) || null + }; + interval.value = event.interval; + } + ); + useEventListener(window2, "devicemotion", onDeviceMotion, { passive: true }); + } + } + const ensurePermissions = async () => { + if (!requirePermissions.value) + permissionGranted.value = true; + if (permissionGranted.value) + return; + if (requirePermissions.value) { + const requestPermission = DeviceMotionEvent.requestPermission; + try { + const response = await requestPermission(); + if (response === "granted") { + permissionGranted.value = true; + init(); + } + } catch (error) { + console.error(error); + } + } + }; + if (isSupported.value) { + if (requestPermissions && requirePermissions.value) { + ensurePermissions().then(() => init()); + } else { + init(); + } + } + return { + acceleration, + accelerationIncludingGravity, + rotationRate, + interval, + isSupported, + requirePermissions, + ensurePermissions, + permissionGranted + }; +} +function useDeviceOrientation(options = {}) { + const { window: window2 = defaultWindow } = options; + const isSupported = useSupported(() => window2 && "DeviceOrientationEvent" in window2); + const isAbsolute = shallowRef(false); + const alpha = shallowRef(null); + const beta = shallowRef(null); + const gamma = shallowRef(null); + if (window2 && isSupported.value) { + useEventListener(window2, "deviceorientation", (event) => { + isAbsolute.value = event.absolute; + alpha.value = event.alpha; + beta.value = event.beta; + gamma.value = event.gamma; + }, { passive: true }); + } + return { + isSupported, + isAbsolute, + alpha, + beta, + gamma + }; +} +function useDevicePixelRatio(options = {}) { + const { + window: window2 = defaultWindow + } = options; + const pixelRatio = shallowRef(1); + const query = useMediaQuery(() => `(resolution: ${pixelRatio.value}dppx)`, options); + let stop = noop; + if (window2) { + stop = watchImmediate(query, () => pixelRatio.value = window2.devicePixelRatio); + } + return { + pixelRatio: readonly(pixelRatio), + stop + }; +} +function useDevicesList(options = {}) { + const { + navigator: navigator2 = defaultNavigator, + requestPermissions = false, + constraints = { audio: true, video: true }, + onUpdated: onUpdated2 + } = options; + const devices = ref([]); + const videoInputs = computed(() => devices.value.filter((i) => i.kind === "videoinput")); + const audioInputs = computed(() => devices.value.filter((i) => i.kind === "audioinput")); + const audioOutputs = computed(() => devices.value.filter((i) => i.kind === "audiooutput")); + const isSupported = useSupported(() => navigator2 && navigator2.mediaDevices && navigator2.mediaDevices.enumerateDevices); + const permissionGranted = shallowRef(false); + let stream; + async function update() { + if (!isSupported.value) + return; + devices.value = await navigator2.mediaDevices.enumerateDevices(); + onUpdated2 == null ? void 0 : onUpdated2(devices.value); + if (stream) { + stream.getTracks().forEach((t) => t.stop()); + stream = null; + } + } + async function ensurePermissions() { + const deviceName = constraints.video ? "camera" : "microphone"; + if (!isSupported.value) + return false; + if (permissionGranted.value) + return true; + const { state, query } = usePermission(deviceName, { controls: true }); + await query(); + if (state.value !== "granted") { + let granted = true; + try { + stream = await navigator2.mediaDevices.getUserMedia(constraints); + } catch (e) { + stream = null; + granted = false; + } + update(); + permissionGranted.value = granted; + } else { + permissionGranted.value = true; + } + return permissionGranted.value; + } + if (isSupported.value) { + if (requestPermissions) + ensurePermissions(); + useEventListener(navigator2.mediaDevices, "devicechange", update, { passive: true }); + update(); + } + return { + devices, + ensurePermissions, + permissionGranted, + videoInputs, + audioInputs, + audioOutputs, + isSupported + }; +} +function useDisplayMedia(options = {}) { + var _a; + const enabled = shallowRef((_a = options.enabled) != null ? _a : false); + const video = options.video; + const audio = options.audio; + const { navigator: navigator2 = defaultNavigator } = options; + const isSupported = useSupported(() => { + var _a2; + return (_a2 = navigator2 == null ? void 0 : navigator2.mediaDevices) == null ? void 0 : _a2.getDisplayMedia; + }); + const constraint = { audio, video }; + const stream = shallowRef(); + async function _start() { + var _a2; + if (!isSupported.value || stream.value) + return; + stream.value = await navigator2.mediaDevices.getDisplayMedia(constraint); + (_a2 = stream.value) == null ? void 0 : _a2.getTracks().forEach((t) => useEventListener(t, "ended", stop, { passive: true })); + return stream.value; + } + async function _stop() { + var _a2; + (_a2 = stream.value) == null ? void 0 : _a2.getTracks().forEach((t) => t.stop()); + stream.value = void 0; + } + function stop() { + _stop(); + enabled.value = false; + } + async function start() { + await _start(); + if (stream.value) + enabled.value = true; + return stream.value; + } + watch( + enabled, + (v) => { + if (v) + _start(); + else + _stop(); + }, + { immediate: true } + ); + return { + isSupported, + stream, + start, + stop, + enabled + }; +} +function useDocumentVisibility(options = {}) { + const { document: document2 = defaultDocument } = options; + if (!document2) + return shallowRef("visible"); + const visibility = shallowRef(document2.visibilityState); + useEventListener(document2, "visibilitychange", () => { + visibility.value = document2.visibilityState; + }, { passive: true }); + return visibility; +} +function useDraggable(target, options = {}) { + var _a; + const { + pointerTypes, + preventDefault: preventDefault2, + stopPropagation, + exact, + onMove, + onEnd, + onStart, + initialValue, + axis = "both", + draggingElement = defaultWindow, + containerElement, + handle: draggingHandle = target, + buttons = [0] + } = options; + const position = ref( + (_a = toValue(initialValue)) != null ? _a : { x: 0, y: 0 } + ); + const pressedDelta = ref(); + const filterEvent = (e) => { + if (pointerTypes) + return pointerTypes.includes(e.pointerType); + return true; + }; + const handleEvent = (e) => { + if (toValue(preventDefault2)) + e.preventDefault(); + if (toValue(stopPropagation)) + e.stopPropagation(); + }; + const start = (e) => { + var _a2; + if (!toValue(buttons).includes(e.button)) + return; + if (toValue(options.disabled) || !filterEvent(e)) + return; + if (toValue(exact) && e.target !== toValue(target)) + return; + const container = toValue(containerElement); + const containerRect = (_a2 = container == null ? void 0 : container.getBoundingClientRect) == null ? void 0 : _a2.call(container); + const targetRect = toValue(target).getBoundingClientRect(); + const pos = { + x: e.clientX - (container ? targetRect.left - containerRect.left + container.scrollLeft : targetRect.left), + y: e.clientY - (container ? targetRect.top - containerRect.top + container.scrollTop : targetRect.top) + }; + if ((onStart == null ? void 0 : onStart(pos, e)) === false) + return; + pressedDelta.value = pos; + handleEvent(e); + }; + const move = (e) => { + if (toValue(options.disabled) || !filterEvent(e)) + return; + if (!pressedDelta.value) + return; + const container = toValue(containerElement); + const targetRect = toValue(target).getBoundingClientRect(); + let { x, y } = position.value; + if (axis === "x" || axis === "both") { + x = e.clientX - pressedDelta.value.x; + if (container) + x = Math.min(Math.max(0, x), container.scrollWidth - targetRect.width); + } + if (axis === "y" || axis === "both") { + y = e.clientY - pressedDelta.value.y; + if (container) + y = Math.min(Math.max(0, y), container.scrollHeight - targetRect.height); + } + position.value = { + x, + y + }; + onMove == null ? void 0 : onMove(position.value, e); + handleEvent(e); + }; + const end = (e) => { + if (toValue(options.disabled) || !filterEvent(e)) + return; + if (!pressedDelta.value) + return; + pressedDelta.value = void 0; + onEnd == null ? void 0 : onEnd(position.value, e); + handleEvent(e); + }; + if (isClient) { + const config = () => { + var _a2; + return { + capture: (_a2 = options.capture) != null ? _a2 : true, + passive: !toValue(preventDefault2) + }; + }; + useEventListener(draggingHandle, "pointerdown", start, config); + useEventListener(draggingElement, "pointermove", move, config); + useEventListener(draggingElement, "pointerup", end, config); + } + return { + ...toRefs2(position), + position, + isDragging: computed(() => !!pressedDelta.value), + style: computed( + () => `left:${position.value.x}px;top:${position.value.y}px;` + ) + }; +} +function useDropZone(target, options = {}) { + var _a, _b; + const isOverDropZone = shallowRef(false); + const files = shallowRef(null); + let counter = 0; + let isValid = true; + if (isClient) { + const _options = typeof options === "function" ? { onDrop: options } : options; + const multiple = (_a = _options.multiple) != null ? _a : true; + const preventDefaultForUnhandled = (_b = _options.preventDefaultForUnhandled) != null ? _b : false; + const getFiles = (event) => { + var _a2, _b2; + const list = Array.from((_b2 = (_a2 = event.dataTransfer) == null ? void 0 : _a2.files) != null ? _b2 : []); + return list.length === 0 ? null : multiple ? list : [list[0]]; + }; + const checkDataTypes = (types) => { + const dataTypes = unref(_options.dataTypes); + if (typeof dataTypes === "function") + return dataTypes(types); + if (!(dataTypes == null ? void 0 : dataTypes.length)) + return true; + if (types.length === 0) + return false; + return types.every( + (type) => dataTypes.some((allowedType) => type.includes(allowedType)) + ); + }; + const checkValidity = (items) => { + const types = Array.from(items != null ? items : []).map((item) => item.type); + const dataTypesValid = checkDataTypes(types); + const multipleFilesValid = multiple || items.length <= 1; + return dataTypesValid && multipleFilesValid; + }; + const isSafari = () => /^(?:(?!chrome|android).)*safari/i.test(navigator.userAgent) && !("chrome" in window); + const handleDragEvent = (event, eventType) => { + var _a2, _b2, _c, _d, _e, _f; + const dataTransferItemList = (_a2 = event.dataTransfer) == null ? void 0 : _a2.items; + isValid = (_b2 = dataTransferItemList && checkValidity(dataTransferItemList)) != null ? _b2 : false; + if (preventDefaultForUnhandled) { + event.preventDefault(); + } + if (!isSafari() && !isValid) { + if (event.dataTransfer) { + event.dataTransfer.dropEffect = "none"; + } + return; + } + event.preventDefault(); + if (event.dataTransfer) { + event.dataTransfer.dropEffect = "copy"; + } + const currentFiles = getFiles(event); + switch (eventType) { + case "enter": + counter += 1; + isOverDropZone.value = true; + (_c = _options.onEnter) == null ? void 0 : _c.call(_options, null, event); + break; + case "over": + (_d = _options.onOver) == null ? void 0 : _d.call(_options, null, event); + break; + case "leave": + counter -= 1; + if (counter === 0) + isOverDropZone.value = false; + (_e = _options.onLeave) == null ? void 0 : _e.call(_options, null, event); + break; + case "drop": + counter = 0; + isOverDropZone.value = false; + if (isValid) { + files.value = currentFiles; + (_f = _options.onDrop) == null ? void 0 : _f.call(_options, currentFiles, event); + } + break; + } + }; + useEventListener(target, "dragenter", (event) => handleDragEvent(event, "enter")); + useEventListener(target, "dragover", (event) => handleDragEvent(event, "over")); + useEventListener(target, "dragleave", (event) => handleDragEvent(event, "leave")); + useEventListener(target, "drop", (event) => handleDragEvent(event, "drop")); + } + return { + files, + isOverDropZone + }; +} +function useResizeObserver(target, callback, options = {}) { + const { window: window2 = defaultWindow, ...observerOptions } = options; + let observer; + const isSupported = useSupported(() => window2 && "ResizeObserver" in window2); + const cleanup = () => { + if (observer) { + observer.disconnect(); + observer = void 0; + } + }; + const targets = computed(() => { + const _targets = toValue(target); + return Array.isArray(_targets) ? _targets.map((el) => unrefElement(el)) : [unrefElement(_targets)]; + }); + const stopWatch = watch( + targets, + (els) => { + cleanup(); + if (isSupported.value && window2) { + observer = new ResizeObserver(callback); + for (const _el of els) { + if (_el) + observer.observe(_el, observerOptions); + } + } + }, + { immediate: true, flush: "post" } + ); + const stop = () => { + cleanup(); + stopWatch(); + }; + tryOnScopeDispose(stop); + return { + isSupported, + stop + }; +} +function useElementBounding(target, options = {}) { + const { + reset = true, + windowResize = true, + windowScroll = true, + immediate = true, + updateTiming = "sync" + } = options; + const height = shallowRef(0); + const bottom = shallowRef(0); + const left = shallowRef(0); + const right = shallowRef(0); + const top = shallowRef(0); + const width = shallowRef(0); + const x = shallowRef(0); + const y = shallowRef(0); + function recalculate() { + const el = unrefElement(target); + if (!el) { + if (reset) { + height.value = 0; + bottom.value = 0; + left.value = 0; + right.value = 0; + top.value = 0; + width.value = 0; + x.value = 0; + y.value = 0; + } + return; + } + const rect = el.getBoundingClientRect(); + height.value = rect.height; + bottom.value = rect.bottom; + left.value = rect.left; + right.value = rect.right; + top.value = rect.top; + width.value = rect.width; + x.value = rect.x; + y.value = rect.y; + } + function update() { + if (updateTiming === "sync") + recalculate(); + else if (updateTiming === "next-frame") + requestAnimationFrame(() => recalculate()); + } + useResizeObserver(target, update); + watch(() => unrefElement(target), (ele) => !ele && update()); + useMutationObserver(target, update, { + attributeFilter: ["style", "class"] + }); + if (windowScroll) + useEventListener("scroll", update, { capture: true, passive: true }); + if (windowResize) + useEventListener("resize", update, { passive: true }); + tryOnMounted(() => { + if (immediate) + update(); + }); + return { + height, + bottom, + left, + right, + top, + width, + x, + y, + update + }; +} +function useElementByPoint(options) { + const { + x, + y, + document: document2 = defaultDocument, + multiple, + interval = "requestAnimationFrame", + immediate = true + } = options; + const isSupported = useSupported(() => { + if (toValue(multiple)) + return document2 && "elementsFromPoint" in document2; + return document2 && "elementFromPoint" in document2; + }); + const element = shallowRef(null); + const cb = () => { + var _a, _b; + element.value = toValue(multiple) ? (_a = document2 == null ? void 0 : document2.elementsFromPoint(toValue(x), toValue(y))) != null ? _a : [] : (_b = document2 == null ? void 0 : document2.elementFromPoint(toValue(x), toValue(y))) != null ? _b : null; + }; + const controls = interval === "requestAnimationFrame" ? useRafFn(cb, { immediate }) : useIntervalFn(cb, interval, { immediate }); + return { + isSupported, + element, + ...controls + }; +} +function useElementHover(el, options = {}) { + const { + delayEnter = 0, + delayLeave = 0, + triggerOnRemoval = false, + window: window2 = defaultWindow + } = options; + const isHovered = shallowRef(false); + let timer; + const toggle = (entering) => { + const delay = entering ? delayEnter : delayLeave; + if (timer) { + clearTimeout(timer); + timer = void 0; + } + if (delay) + timer = setTimeout(() => isHovered.value = entering, delay); + else + isHovered.value = entering; + }; + if (!window2) + return isHovered; + useEventListener(el, "mouseenter", () => toggle(true), { passive: true }); + useEventListener(el, "mouseleave", () => toggle(false), { passive: true }); + if (triggerOnRemoval) { + onElementRemoval( + computed(() => unrefElement(el)), + () => toggle(false) + ); + } + return isHovered; +} +function useElementSize(target, initialSize = { width: 0, height: 0 }, options = {}) { + const { window: window2 = defaultWindow, box = "content-box" } = options; + const isSVG = computed(() => { + var _a, _b; + return (_b = (_a = unrefElement(target)) == null ? void 0 : _a.namespaceURI) == null ? void 0 : _b.includes("svg"); + }); + const width = shallowRef(initialSize.width); + const height = shallowRef(initialSize.height); + const { stop: stop1 } = useResizeObserver( + target, + ([entry]) => { + const boxSize = box === "border-box" ? entry.borderBoxSize : box === "content-box" ? entry.contentBoxSize : entry.devicePixelContentBoxSize; + if (window2 && isSVG.value) { + const $elem = unrefElement(target); + if ($elem) { + const rect = $elem.getBoundingClientRect(); + width.value = rect.width; + height.value = rect.height; + } + } else { + if (boxSize) { + const formatBoxSize = toArray(boxSize); + width.value = formatBoxSize.reduce((acc, { inlineSize }) => acc + inlineSize, 0); + height.value = formatBoxSize.reduce((acc, { blockSize }) => acc + blockSize, 0); + } else { + width.value = entry.contentRect.width; + height.value = entry.contentRect.height; + } + } + }, + options + ); + tryOnMounted(() => { + const ele = unrefElement(target); + if (ele) { + width.value = "offsetWidth" in ele ? ele.offsetWidth : initialSize.width; + height.value = "offsetHeight" in ele ? ele.offsetHeight : initialSize.height; + } + }); + const stop2 = watch( + () => unrefElement(target), + (ele) => { + width.value = ele ? initialSize.width : 0; + height.value = ele ? initialSize.height : 0; + } + ); + function stop() { + stop1(); + stop2(); + } + return { + width, + height, + stop + }; +} +function useIntersectionObserver(target, callback, options = {}) { + const { + root, + rootMargin = "0px", + threshold = 0, + window: window2 = defaultWindow, + immediate = true + } = options; + const isSupported = useSupported(() => window2 && "IntersectionObserver" in window2); + const targets = computed(() => { + const _target = toValue(target); + return toArray(_target).map(unrefElement).filter(notNullish); + }); + let cleanup = noop; + const isActive = shallowRef(immediate); + const stopWatch = isSupported.value ? watch( + () => [targets.value, unrefElement(root), isActive.value], + ([targets2, root2]) => { + cleanup(); + if (!isActive.value) + return; + if (!targets2.length) + return; + const observer = new IntersectionObserver( + callback, + { + root: unrefElement(root2), + rootMargin, + threshold + } + ); + targets2.forEach((el) => el && observer.observe(el)); + cleanup = () => { + observer.disconnect(); + cleanup = noop; + }; + }, + { immediate, flush: "post" } + ) : noop; + const stop = () => { + cleanup(); + stopWatch(); + isActive.value = false; + }; + tryOnScopeDispose(stop); + return { + isSupported, + isActive, + pause() { + cleanup(); + isActive.value = false; + }, + resume() { + isActive.value = true; + }, + stop + }; +} +function useElementVisibility(element, options = {}) { + const { + window: window2 = defaultWindow, + scrollTarget, + threshold = 0, + rootMargin, + once = false + } = options; + const elementIsVisible = shallowRef(false); + const { stop } = useIntersectionObserver( + element, + (intersectionObserverEntries) => { + let isIntersecting = elementIsVisible.value; + let latestTime = 0; + for (const entry of intersectionObserverEntries) { + if (entry.time >= latestTime) { + latestTime = entry.time; + isIntersecting = entry.isIntersecting; + } + } + elementIsVisible.value = isIntersecting; + if (once) { + watchOnce(elementIsVisible, () => { + stop(); + }); + } + }, + { + root: scrollTarget, + window: window2, + threshold, + rootMargin: toValue(rootMargin) + } + ); + return elementIsVisible; +} +var events = /* @__PURE__ */ new Map(); +function useEventBus(key) { + const scope = getCurrentScope(); + function on(listener) { + var _a; + const listeners = events.get(key) || /* @__PURE__ */ new Set(); + listeners.add(listener); + events.set(key, listeners); + const _off = () => off(listener); + (_a = scope == null ? void 0 : scope.cleanups) == null ? void 0 : _a.push(_off); + return _off; + } + function once(listener) { + function _listener(...args) { + off(_listener); + listener(...args); + } + return on(_listener); + } + function off(listener) { + const listeners = events.get(key); + if (!listeners) + return; + listeners.delete(listener); + if (!listeners.size) + reset(); + } + function reset() { + events.delete(key); + } + function emit(event, payload) { + var _a; + (_a = events.get(key)) == null ? void 0 : _a.forEach((v) => v(event, payload)); + } + return { on, once, off, emit, reset }; +} +function resolveNestedOptions$1(options) { + if (options === true) + return {}; + return options; +} +function useEventSource(url, events2 = [], options = {}) { + const event = shallowRef(null); + const data = shallowRef(null); + const status = shallowRef("CONNECTING"); + const eventSource = ref(null); + const error = shallowRef(null); + const urlRef = toRef2(url); + const lastEventId = shallowRef(null); + let explicitlyClosed = false; + let retried = 0; + const { + withCredentials = false, + immediate = true, + autoConnect = true, + autoReconnect + } = options; + const close = () => { + if (isClient && eventSource.value) { + eventSource.value.close(); + eventSource.value = null; + status.value = "CLOSED"; + explicitlyClosed = true; + } + }; + const _init = () => { + if (explicitlyClosed || typeof urlRef.value === "undefined") + return; + const es = new EventSource(urlRef.value, { withCredentials }); + status.value = "CONNECTING"; + eventSource.value = es; + es.onopen = () => { + status.value = "OPEN"; + error.value = null; + }; + es.onerror = (e) => { + status.value = "CLOSED"; + error.value = e; + if (es.readyState === 2 && !explicitlyClosed && autoReconnect) { + es.close(); + const { + retries = -1, + delay = 1e3, + onFailed + } = resolveNestedOptions$1(autoReconnect); + retried += 1; + if (typeof retries === "number" && (retries < 0 || retried < retries)) + setTimeout(_init, delay); + else if (typeof retries === "function" && retries()) + setTimeout(_init, delay); + else + onFailed == null ? void 0 : onFailed(); + } + }; + es.onmessage = (e) => { + event.value = null; + data.value = e.data; + lastEventId.value = e.lastEventId; + }; + for (const event_name of events2) { + useEventListener(es, event_name, (e) => { + event.value = event_name; + data.value = e.data || null; + }, { passive: true }); + } + }; + const open = () => { + if (!isClient) + return; + close(); + explicitlyClosed = false; + retried = 0; + _init(); + }; + if (immediate) + open(); + if (autoConnect) + watch(urlRef, open); + tryOnScopeDispose(close); + return { + eventSource, + event, + data, + status, + error, + open, + close, + lastEventId + }; +} +function useEyeDropper(options = {}) { + const { initialValue = "" } = options; + const isSupported = useSupported(() => typeof window !== "undefined" && "EyeDropper" in window); + const sRGBHex = shallowRef(initialValue); + async function open(openOptions) { + if (!isSupported.value) + return; + const eyeDropper = new window.EyeDropper(); + const result = await eyeDropper.open(openOptions); + sRGBHex.value = result.sRGBHex; + return result; + } + return { isSupported, sRGBHex, open }; +} +function useFavicon(newIcon = null, options = {}) { + const { + baseUrl = "", + rel = "icon", + document: document2 = defaultDocument + } = options; + const favicon = toRef2(newIcon); + const applyIcon = (icon) => { + const elements = document2 == null ? void 0 : document2.head.querySelectorAll(`link[rel*="${rel}"]`); + if (!elements || elements.length === 0) { + const link = document2 == null ? void 0 : document2.createElement("link"); + if (link) { + link.rel = rel; + link.href = `${baseUrl}${icon}`; + link.type = `image/${icon.split(".").pop()}`; + document2 == null ? void 0 : document2.head.append(link); + } + return; + } + elements == null ? void 0 : elements.forEach((el) => el.href = `${baseUrl}${icon}`); + }; + watch( + favicon, + (i, o) => { + if (typeof i === "string" && i !== o) + applyIcon(i); + }, + { immediate: true } + ); + return favicon; +} +var payloadMapping = { + json: "application/json", + text: "text/plain" +}; +function isFetchOptions(obj) { + return obj && containsProp(obj, "immediate", "refetch", "initialData", "timeout", "beforeFetch", "afterFetch", "onFetchError", "fetch", "updateDataOnError"); +} +var reAbsolute = /^(?:[a-z][a-z\d+\-.]*:)?\/\//i; +function isAbsoluteURL(url) { + return reAbsolute.test(url); +} +function headersToObject(headers) { + if (typeof Headers !== "undefined" && headers instanceof Headers) + return Object.fromEntries(headers.entries()); + return headers; +} +function combineCallbacks(combination, ...callbacks) { + if (combination === "overwrite") { + return async (ctx) => { + let callback; + for (let i = callbacks.length - 1; i >= 0; i--) { + if (callbacks[i] != null) { + callback = callbacks[i]; + break; + } + } + if (callback) + return { ...ctx, ...await callback(ctx) }; + return ctx; + }; + } else { + return async (ctx) => { + for (const callback of callbacks) { + if (callback) + ctx = { ...ctx, ...await callback(ctx) }; + } + return ctx; + }; + } +} +function createFetch(config = {}) { + const _combination = config.combination || "chain"; + const _options = config.options || {}; + const _fetchOptions = config.fetchOptions || {}; + function useFactoryFetch(url, ...args) { + const computedUrl = computed(() => { + const baseUrl = toValue(config.baseUrl); + const targetUrl = toValue(url); + return baseUrl && !isAbsoluteURL(targetUrl) ? joinPaths(baseUrl, targetUrl) : targetUrl; + }); + let options = _options; + let fetchOptions = _fetchOptions; + if (args.length > 0) { + if (isFetchOptions(args[0])) { + options = { + ...options, + ...args[0], + beforeFetch: combineCallbacks(_combination, _options.beforeFetch, args[0].beforeFetch), + afterFetch: combineCallbacks(_combination, _options.afterFetch, args[0].afterFetch), + onFetchError: combineCallbacks(_combination, _options.onFetchError, args[0].onFetchError) + }; + } else { + fetchOptions = { + ...fetchOptions, + ...args[0], + headers: { + ...headersToObject(fetchOptions.headers) || {}, + ...headersToObject(args[0].headers) || {} + } + }; + } + } + if (args.length > 1 && isFetchOptions(args[1])) { + options = { + ...options, + ...args[1], + beforeFetch: combineCallbacks(_combination, _options.beforeFetch, args[1].beforeFetch), + afterFetch: combineCallbacks(_combination, _options.afterFetch, args[1].afterFetch), + onFetchError: combineCallbacks(_combination, _options.onFetchError, args[1].onFetchError) + }; + } + return useFetch(computedUrl, fetchOptions, options); + } + return useFactoryFetch; +} +function useFetch(url, ...args) { + var _a; + const supportsAbort = typeof AbortController === "function"; + let fetchOptions = {}; + let options = { + immediate: true, + refetch: false, + timeout: 0, + updateDataOnError: false + }; + const config = { + method: "GET", + type: "text", + payload: void 0 + }; + if (args.length > 0) { + if (isFetchOptions(args[0])) + options = { ...options, ...args[0] }; + else + fetchOptions = args[0]; + } + if (args.length > 1) { + if (isFetchOptions(args[1])) + options = { ...options, ...args[1] }; + } + const { + fetch = (_a = defaultWindow) == null ? void 0 : _a.fetch, + initialData, + timeout + } = options; + const responseEvent = createEventHook(); + const errorEvent = createEventHook(); + const finallyEvent = createEventHook(); + const isFinished = shallowRef(false); + const isFetching = shallowRef(false); + const aborted = shallowRef(false); + const statusCode = shallowRef(null); + const response = shallowRef(null); + const error = shallowRef(null); + const data = shallowRef(initialData || null); + const canAbort = computed(() => supportsAbort && isFetching.value); + let controller; + let timer; + const abort = () => { + if (supportsAbort) { + controller == null ? void 0 : controller.abort(); + controller = new AbortController(); + controller.signal.onabort = () => aborted.value = true; + fetchOptions = { + ...fetchOptions, + signal: controller.signal + }; + } + }; + const loading = (isLoading) => { + isFetching.value = isLoading; + isFinished.value = !isLoading; + }; + if (timeout) + timer = useTimeoutFn(abort, timeout, { immediate: false }); + let executeCounter = 0; + const execute = async (throwOnFailed = false) => { + var _a2, _b; + abort(); + loading(true); + error.value = null; + statusCode.value = null; + aborted.value = false; + executeCounter += 1; + const currentExecuteCounter = executeCounter; + const defaultFetchOptions = { + method: config.method, + headers: {} + }; + const payload = toValue(config.payload); + if (payload) { + const headers = headersToObject(defaultFetchOptions.headers); + const proto = Object.getPrototypeOf(payload); + if (!config.payloadType && payload && (proto === Object.prototype || Array.isArray(proto)) && !(payload instanceof FormData)) + config.payloadType = "json"; + if (config.payloadType) + headers["Content-Type"] = (_a2 = payloadMapping[config.payloadType]) != null ? _a2 : config.payloadType; + defaultFetchOptions.body = config.payloadType === "json" ? JSON.stringify(payload) : payload; + } + let isCanceled = false; + const context = { + url: toValue(url), + options: { + ...defaultFetchOptions, + ...fetchOptions + }, + cancel: () => { + isCanceled = true; + } + }; + if (options.beforeFetch) + Object.assign(context, await options.beforeFetch(context)); + if (isCanceled || !fetch) { + loading(false); + return Promise.resolve(null); + } + let responseData = null; + if (timer) + timer.start(); + return fetch( + context.url, + { + ...defaultFetchOptions, + ...context.options, + headers: { + ...headersToObject(defaultFetchOptions.headers), + ...headersToObject((_b = context.options) == null ? void 0 : _b.headers) + } + } + ).then(async (fetchResponse) => { + response.value = fetchResponse; + statusCode.value = fetchResponse.status; + responseData = await fetchResponse.clone()[config.type](); + if (!fetchResponse.ok) { + data.value = initialData || null; + throw new Error(fetchResponse.statusText); + } + if (options.afterFetch) { + ({ data: responseData } = await options.afterFetch({ + data: responseData, + response: fetchResponse, + context, + execute + })); + } + data.value = responseData; + responseEvent.trigger(fetchResponse); + return fetchResponse; + }).catch(async (fetchError) => { + let errorData = fetchError.message || fetchError.name; + if (options.onFetchError) { + ({ error: errorData, data: responseData } = await options.onFetchError({ + data: responseData, + error: fetchError, + response: response.value, + context, + execute + })); + } + error.value = errorData; + if (options.updateDataOnError) + data.value = responseData; + errorEvent.trigger(fetchError); + if (throwOnFailed) + throw fetchError; + return null; + }).finally(() => { + if (currentExecuteCounter === executeCounter) + loading(false); + if (timer) + timer.stop(); + finallyEvent.trigger(null); + }); + }; + const refetch = toRef2(options.refetch); + watch( + [ + refetch, + toRef2(url) + ], + ([refetch2]) => refetch2 && execute(), + { deep: true } + ); + const shell = { + isFinished: readonly(isFinished), + isFetching: readonly(isFetching), + statusCode, + response, + error, + data, + canAbort, + aborted, + abort, + execute, + onFetchResponse: responseEvent.on, + onFetchError: errorEvent.on, + onFetchFinally: finallyEvent.on, + // method + get: setMethod("GET"), + put: setMethod("PUT"), + post: setMethod("POST"), + delete: setMethod("DELETE"), + patch: setMethod("PATCH"), + head: setMethod("HEAD"), + options: setMethod("OPTIONS"), + // type + json: setType("json"), + text: setType("text"), + blob: setType("blob"), + arrayBuffer: setType("arrayBuffer"), + formData: setType("formData") + }; + function setMethod(method) { + return (payload, payloadType) => { + if (!isFetching.value) { + config.method = method; + config.payload = payload; + config.payloadType = payloadType; + if (isRef(config.payload)) { + watch( + [ + refetch, + toRef2(config.payload) + ], + ([refetch2]) => refetch2 && execute(), + { deep: true } + ); + } + return { + ...shell, + then(onFulfilled, onRejected) { + return waitUntilFinished().then(onFulfilled, onRejected); + } + }; + } + return void 0; + }; + } + function waitUntilFinished() { + return new Promise((resolve, reject) => { + until(isFinished).toBe(true).then(() => resolve(shell)).catch(reject); + }); + } + function setType(type) { + return () => { + if (!isFetching.value) { + config.type = type; + return { + ...shell, + then(onFulfilled, onRejected) { + return waitUntilFinished().then(onFulfilled, onRejected); + } + }; + } + return void 0; + }; + } + if (options.immediate) + Promise.resolve().then(() => execute()); + return { + ...shell, + then(onFulfilled, onRejected) { + return waitUntilFinished().then(onFulfilled, onRejected); + } + }; +} +function joinPaths(start, end) { + if (!start.endsWith("/") && !end.startsWith("/")) { + return `${start}/${end}`; + } + if (start.endsWith("/") && end.startsWith("/")) { + return `${start.slice(0, -1)}${end}`; + } + return `${start}${end}`; +} +var DEFAULT_OPTIONS = { + multiple: true, + accept: "*", + reset: false, + directory: false +}; +function prepareInitialFiles(files) { + if (!files) + return null; + if (files instanceof FileList) + return files; + const dt = new DataTransfer(); + for (const file of files) { + dt.items.add(file); + } + return dt.files; +} +function useFileDialog(options = {}) { + const { + document: document2 = defaultDocument + } = options; + const files = ref(prepareInitialFiles(options.initialFiles)); + const { on: onChange, trigger: changeTrigger } = createEventHook(); + const { on: onCancel, trigger: cancelTrigger } = createEventHook(); + let input; + if (document2) { + input = document2.createElement("input"); + input.type = "file"; + input.onchange = (event) => { + const result = event.target; + files.value = result.files; + changeTrigger(files.value); + }; + input.oncancel = () => { + cancelTrigger(); + }; + } + const reset = () => { + files.value = null; + if (input && input.value) { + input.value = ""; + changeTrigger(null); + } + }; + const open = (localOptions) => { + if (!input) + return; + const _options = { + ...DEFAULT_OPTIONS, + ...options, + ...localOptions + }; + input.multiple = _options.multiple; + input.accept = _options.accept; + input.webkitdirectory = _options.directory; + if (hasOwn(_options, "capture")) + input.capture = _options.capture; + if (_options.reset) + reset(); + input.click(); + }; + return { + files: readonly(files), + open, + reset, + onCancel, + onChange + }; +} +function useFileSystemAccess(options = {}) { + const { + window: _window = defaultWindow, + dataType = "Text" + } = options; + const window2 = _window; + const isSupported = useSupported(() => window2 && "showSaveFilePicker" in window2 && "showOpenFilePicker" in window2); + const fileHandle = shallowRef(); + const data = shallowRef(); + const file = shallowRef(); + const fileName = computed(() => { + var _a, _b; + return (_b = (_a = file.value) == null ? void 0 : _a.name) != null ? _b : ""; + }); + const fileMIME = computed(() => { + var _a, _b; + return (_b = (_a = file.value) == null ? void 0 : _a.type) != null ? _b : ""; + }); + const fileSize = computed(() => { + var _a, _b; + return (_b = (_a = file.value) == null ? void 0 : _a.size) != null ? _b : 0; + }); + const fileLastModified = computed(() => { + var _a, _b; + return (_b = (_a = file.value) == null ? void 0 : _a.lastModified) != null ? _b : 0; + }); + async function open(_options = {}) { + if (!isSupported.value) + return; + const [handle] = await window2.showOpenFilePicker({ ...toValue(options), ..._options }); + fileHandle.value = handle; + await updateData(); + } + async function create(_options = {}) { + if (!isSupported.value) + return; + fileHandle.value = await window2.showSaveFilePicker({ ...options, ..._options }); + data.value = void 0; + await updateData(); + } + async function save(_options = {}) { + if (!isSupported.value) + return; + if (!fileHandle.value) + return saveAs(_options); + if (data.value) { + const writableStream = await fileHandle.value.createWritable(); + await writableStream.write(data.value); + await writableStream.close(); + } + await updateFile(); + } + async function saveAs(_options = {}) { + if (!isSupported.value) + return; + fileHandle.value = await window2.showSaveFilePicker({ ...options, ..._options }); + if (data.value) { + const writableStream = await fileHandle.value.createWritable(); + await writableStream.write(data.value); + await writableStream.close(); + } + await updateFile(); + } + async function updateFile() { + var _a; + file.value = await ((_a = fileHandle.value) == null ? void 0 : _a.getFile()); + } + async function updateData() { + var _a, _b; + await updateFile(); + const type = toValue(dataType); + if (type === "Text") + data.value = await ((_a = file.value) == null ? void 0 : _a.text()); + else if (type === "ArrayBuffer") + data.value = await ((_b = file.value) == null ? void 0 : _b.arrayBuffer()); + else if (type === "Blob") + data.value = file.value; + } + watch(() => toValue(dataType), updateData); + return { + isSupported, + data, + file, + fileName, + fileMIME, + fileSize, + fileLastModified, + open, + create, + save, + saveAs, + updateData + }; +} +function useFocus(target, options = {}) { + const { initialValue = false, focusVisible = false, preventScroll = false } = options; + const innerFocused = shallowRef(false); + const targetElement = computed(() => unrefElement(target)); + const listenerOptions = { passive: true }; + useEventListener(targetElement, "focus", (event) => { + var _a, _b; + if (!focusVisible || ((_b = (_a = event.target).matches) == null ? void 0 : _b.call(_a, ":focus-visible"))) + innerFocused.value = true; + }, listenerOptions); + useEventListener(targetElement, "blur", () => innerFocused.value = false, listenerOptions); + const focused = computed({ + get: () => innerFocused.value, + set(value) { + var _a, _b; + if (!value && innerFocused.value) + (_a = targetElement.value) == null ? void 0 : _a.blur(); + else if (value && !innerFocused.value) + (_b = targetElement.value) == null ? void 0 : _b.focus({ preventScroll }); + } + }); + watch( + targetElement, + () => { + focused.value = initialValue; + }, + { immediate: true, flush: "post" } + ); + return { focused }; +} +var EVENT_FOCUS_IN = "focusin"; +var EVENT_FOCUS_OUT = "focusout"; +var PSEUDO_CLASS_FOCUS_WITHIN = ":focus-within"; +function useFocusWithin(target, options = {}) { + const { window: window2 = defaultWindow } = options; + const targetElement = computed(() => unrefElement(target)); + const _focused = shallowRef(false); + const focused = computed(() => _focused.value); + const activeElement = useActiveElement(options); + if (!window2 || !activeElement.value) { + return { focused }; + } + const listenerOptions = { passive: true }; + useEventListener(targetElement, EVENT_FOCUS_IN, () => _focused.value = true, listenerOptions); + useEventListener(targetElement, EVENT_FOCUS_OUT, () => { + var _a, _b, _c; + return _focused.value = (_c = (_b = (_a = targetElement.value) == null ? void 0 : _a.matches) == null ? void 0 : _b.call(_a, PSEUDO_CLASS_FOCUS_WITHIN)) != null ? _c : false; + }, listenerOptions); + return { focused }; +} +function useFps(options) { + var _a; + const fps = shallowRef(0); + if (typeof performance === "undefined") + return fps; + const every = (_a = options == null ? void 0 : options.every) != null ? _a : 10; + let last = performance.now(); + let ticks = 0; + useRafFn(() => { + ticks += 1; + if (ticks >= every) { + const now2 = performance.now(); + const diff = now2 - last; + fps.value = Math.round(1e3 / (diff / ticks)); + last = now2; + ticks = 0; + } + }); + return fps; +} +var eventHandlers = [ + "fullscreenchange", + "webkitfullscreenchange", + "webkitendfullscreen", + "mozfullscreenchange", + "MSFullscreenChange" +]; +function useFullscreen(target, options = {}) { + const { + document: document2 = defaultDocument, + autoExit = false + } = options; + const targetRef = computed(() => { + var _a; + return (_a = unrefElement(target)) != null ? _a : document2 == null ? void 0 : document2.documentElement; + }); + const isFullscreen = shallowRef(false); + const requestMethod = computed(() => { + return [ + "requestFullscreen", + "webkitRequestFullscreen", + "webkitEnterFullscreen", + "webkitEnterFullScreen", + "webkitRequestFullScreen", + "mozRequestFullScreen", + "msRequestFullscreen" + ].find((m) => document2 && m in document2 || targetRef.value && m in targetRef.value); + }); + const exitMethod = computed(() => { + return [ + "exitFullscreen", + "webkitExitFullscreen", + "webkitExitFullScreen", + "webkitCancelFullScreen", + "mozCancelFullScreen", + "msExitFullscreen" + ].find((m) => document2 && m in document2 || targetRef.value && m in targetRef.value); + }); + const fullscreenEnabled = computed(() => { + return [ + "fullScreen", + "webkitIsFullScreen", + "webkitDisplayingFullscreen", + "mozFullScreen", + "msFullscreenElement" + ].find((m) => document2 && m in document2 || targetRef.value && m in targetRef.value); + }); + const fullscreenElementMethod = [ + "fullscreenElement", + "webkitFullscreenElement", + "mozFullScreenElement", + "msFullscreenElement" + ].find((m) => document2 && m in document2); + const isSupported = useSupported(() => targetRef.value && document2 && requestMethod.value !== void 0 && exitMethod.value !== void 0 && fullscreenEnabled.value !== void 0); + const isCurrentElementFullScreen = () => { + if (fullscreenElementMethod) + return (document2 == null ? void 0 : document2[fullscreenElementMethod]) === targetRef.value; + return false; + }; + const isElementFullScreen = () => { + if (fullscreenEnabled.value) { + if (document2 && document2[fullscreenEnabled.value] != null) { + return document2[fullscreenEnabled.value]; + } else { + const target2 = targetRef.value; + if ((target2 == null ? void 0 : target2[fullscreenEnabled.value]) != null) { + return Boolean(target2[fullscreenEnabled.value]); + } + } + } + return false; + }; + async function exit() { + if (!isSupported.value || !isFullscreen.value) + return; + if (exitMethod.value) { + if ((document2 == null ? void 0 : document2[exitMethod.value]) != null) { + await document2[exitMethod.value](); + } else { + const target2 = targetRef.value; + if ((target2 == null ? void 0 : target2[exitMethod.value]) != null) + await target2[exitMethod.value](); + } + } + isFullscreen.value = false; + } + async function enter() { + if (!isSupported.value || isFullscreen.value) + return; + if (isElementFullScreen()) + await exit(); + const target2 = targetRef.value; + if (requestMethod.value && (target2 == null ? void 0 : target2[requestMethod.value]) != null) { + await target2[requestMethod.value](); + isFullscreen.value = true; + } + } + async function toggle() { + await (isFullscreen.value ? exit() : enter()); + } + const handlerCallback = () => { + const isElementFullScreenValue = isElementFullScreen(); + if (!isElementFullScreenValue || isElementFullScreenValue && isCurrentElementFullScreen()) + isFullscreen.value = isElementFullScreenValue; + }; + const listenerOptions = { capture: false, passive: true }; + useEventListener(document2, eventHandlers, handlerCallback, listenerOptions); + useEventListener(() => unrefElement(targetRef), eventHandlers, handlerCallback, listenerOptions); + if (autoExit) + tryOnScopeDispose(exit); + return { + isSupported, + isFullscreen, + enter, + exit, + toggle + }; +} +function mapGamepadToXbox360Controller(gamepad) { + return computed(() => { + if (gamepad.value) { + return { + buttons: { + a: gamepad.value.buttons[0], + b: gamepad.value.buttons[1], + x: gamepad.value.buttons[2], + y: gamepad.value.buttons[3] + }, + bumper: { + left: gamepad.value.buttons[4], + right: gamepad.value.buttons[5] + }, + triggers: { + left: gamepad.value.buttons[6], + right: gamepad.value.buttons[7] + }, + stick: { + left: { + horizontal: gamepad.value.axes[0], + vertical: gamepad.value.axes[1], + button: gamepad.value.buttons[10] + }, + right: { + horizontal: gamepad.value.axes[2], + vertical: gamepad.value.axes[3], + button: gamepad.value.buttons[11] + } + }, + dpad: { + up: gamepad.value.buttons[12], + down: gamepad.value.buttons[13], + left: gamepad.value.buttons[14], + right: gamepad.value.buttons[15] + }, + back: gamepad.value.buttons[8], + start: gamepad.value.buttons[9] + }; + } + return null; + }); +} +function useGamepad(options = {}) { + const { + navigator: navigator2 = defaultNavigator + } = options; + const isSupported = useSupported(() => navigator2 && "getGamepads" in navigator2); + const gamepads = ref([]); + const onConnectedHook = createEventHook(); + const onDisconnectedHook = createEventHook(); + const stateFromGamepad = (gamepad) => { + const hapticActuators = []; + const vibrationActuator = "vibrationActuator" in gamepad ? gamepad.vibrationActuator : null; + if (vibrationActuator) + hapticActuators.push(vibrationActuator); + if (gamepad.hapticActuators) + hapticActuators.push(...gamepad.hapticActuators); + return { + id: gamepad.id, + index: gamepad.index, + connected: gamepad.connected, + mapping: gamepad.mapping, + timestamp: gamepad.timestamp, + vibrationActuator: gamepad.vibrationActuator, + hapticActuators, + axes: gamepad.axes.map((axes) => axes), + buttons: gamepad.buttons.map((button) => ({ pressed: button.pressed, touched: button.touched, value: button.value })) + }; + }; + const updateGamepadState = () => { + const _gamepads = (navigator2 == null ? void 0 : navigator2.getGamepads()) || []; + for (const gamepad of _gamepads) { + if (gamepad && gamepads.value[gamepad.index]) + gamepads.value[gamepad.index] = stateFromGamepad(gamepad); + } + }; + const { isActive, pause, resume } = useRafFn(updateGamepadState); + const onGamepadConnected = (gamepad) => { + if (!gamepads.value.some(({ index }) => index === gamepad.index)) { + gamepads.value.push(stateFromGamepad(gamepad)); + onConnectedHook.trigger(gamepad.index); + } + resume(); + }; + const onGamepadDisconnected = (gamepad) => { + gamepads.value = gamepads.value.filter((x) => x.index !== gamepad.index); + onDisconnectedHook.trigger(gamepad.index); + }; + const listenerOptions = { passive: true }; + useEventListener("gamepadconnected", (e) => onGamepadConnected(e.gamepad), listenerOptions); + useEventListener("gamepaddisconnected", (e) => onGamepadDisconnected(e.gamepad), listenerOptions); + tryOnMounted(() => { + const _gamepads = (navigator2 == null ? void 0 : navigator2.getGamepads()) || []; + for (const gamepad of _gamepads) { + if (gamepad && gamepads.value[gamepad.index]) + onGamepadConnected(gamepad); + } + }); + pause(); + return { + isSupported, + onConnected: onConnectedHook.on, + onDisconnected: onDisconnectedHook.on, + gamepads, + pause, + resume, + isActive + }; +} +function useGeolocation(options = {}) { + const { + enableHighAccuracy = true, + maximumAge = 3e4, + timeout = 27e3, + navigator: navigator2 = defaultNavigator, + immediate = true + } = options; + const isSupported = useSupported(() => navigator2 && "geolocation" in navigator2); + const locatedAt = shallowRef(null); + const error = shallowRef(null); + const coords = ref({ + accuracy: 0, + latitude: Number.POSITIVE_INFINITY, + longitude: Number.POSITIVE_INFINITY, + altitude: null, + altitudeAccuracy: null, + heading: null, + speed: null + }); + function updatePosition(position) { + locatedAt.value = position.timestamp; + coords.value = position.coords; + error.value = null; + } + let watcher; + function resume() { + if (isSupported.value) { + watcher = navigator2.geolocation.watchPosition( + updatePosition, + (err) => error.value = err, + { + enableHighAccuracy, + maximumAge, + timeout + } + ); + } + } + if (immediate) + resume(); + function pause() { + if (watcher && navigator2) + navigator2.geolocation.clearWatch(watcher); + } + tryOnScopeDispose(() => { + pause(); + }); + return { + isSupported, + coords, + locatedAt, + error, + resume, + pause + }; +} +var defaultEvents$1 = ["mousemove", "mousedown", "resize", "keydown", "touchstart", "wheel"]; +var oneMinute = 6e4; +function useIdle(timeout = oneMinute, options = {}) { + const { + initialState = false, + listenForVisibilityChange = true, + events: events2 = defaultEvents$1, + window: window2 = defaultWindow, + eventFilter = throttleFilter(50) + } = options; + const idle = shallowRef(initialState); + const lastActive = shallowRef(timestamp()); + let timer; + const reset = () => { + idle.value = false; + clearTimeout(timer); + timer = setTimeout(() => idle.value = true, timeout); + }; + const onEvent = createFilterWrapper( + eventFilter, + () => { + lastActive.value = timestamp(); + reset(); + } + ); + if (window2) { + const document2 = window2.document; + const listenerOptions = { passive: true }; + for (const event of events2) + useEventListener(window2, event, onEvent, listenerOptions); + if (listenForVisibilityChange) { + useEventListener(document2, "visibilitychange", () => { + if (!document2.hidden) + onEvent(); + }, listenerOptions); + } + reset(); + } + return { + idle, + lastActive, + reset + }; +} +async function loadImage(options) { + return new Promise((resolve, reject) => { + const img = new Image(); + const { src, srcset, sizes, class: clazz, loading, crossorigin, referrerPolicy, width, height, decoding, fetchPriority, ismap, usemap } = options; + img.src = src; + if (srcset != null) + img.srcset = srcset; + if (sizes != null) + img.sizes = sizes; + if (clazz != null) + img.className = clazz; + if (loading != null) + img.loading = loading; + if (crossorigin != null) + img.crossOrigin = crossorigin; + if (referrerPolicy != null) + img.referrerPolicy = referrerPolicy; + if (width != null) + img.width = width; + if (height != null) + img.height = height; + if (decoding != null) + img.decoding = decoding; + if (fetchPriority != null) + img.fetchPriority = fetchPriority; + if (ismap != null) + img.isMap = ismap; + if (usemap != null) + img.useMap = usemap; + img.onload = () => resolve(img); + img.onerror = reject; + }); +} +function useImage(options, asyncStateOptions = {}) { + const state = useAsyncState( + () => loadImage(toValue(options)), + void 0, + { + resetOnExecute: true, + ...asyncStateOptions + } + ); + watch( + () => toValue(options), + () => state.execute(asyncStateOptions.delay), + { deep: true } + ); + return state; +} +function resolveElement(el) { + if (typeof Window !== "undefined" && el instanceof Window) + return el.document.documentElement; + if (typeof Document !== "undefined" && el instanceof Document) + return el.documentElement; + return el; +} +var ARRIVED_STATE_THRESHOLD_PIXELS = 1; +function useScroll(element, options = {}) { + const { + throttle = 0, + idle = 200, + onStop = noop, + onScroll = noop, + offset = { + left: 0, + right: 0, + top: 0, + bottom: 0 + }, + eventListenerOptions = { + capture: false, + passive: true + }, + behavior = "auto", + window: window2 = defaultWindow, + onError = (e) => { + console.error(e); + } + } = options; + const internalX = shallowRef(0); + const internalY = shallowRef(0); + const x = computed({ + get() { + return internalX.value; + }, + set(x2) { + scrollTo(x2, void 0); + } + }); + const y = computed({ + get() { + return internalY.value; + }, + set(y2) { + scrollTo(void 0, y2); + } + }); + function scrollTo(_x, _y) { + var _a, _b, _c, _d; + if (!window2) + return; + const _element = toValue(element); + if (!_element) + return; + (_c = _element instanceof Document ? window2.document.body : _element) == null ? void 0 : _c.scrollTo({ + top: (_a = toValue(_y)) != null ? _a : y.value, + left: (_b = toValue(_x)) != null ? _b : x.value, + behavior: toValue(behavior) + }); + const scrollContainer = ((_d = _element == null ? void 0 : _element.document) == null ? void 0 : _d.documentElement) || (_element == null ? void 0 : _element.documentElement) || _element; + if (x != null) + internalX.value = scrollContainer.scrollLeft; + if (y != null) + internalY.value = scrollContainer.scrollTop; + } + const isScrolling = shallowRef(false); + const arrivedState = reactive({ + left: true, + right: false, + top: true, + bottom: false + }); + const directions = reactive({ + left: false, + right: false, + top: false, + bottom: false + }); + const onScrollEnd = (e) => { + if (!isScrolling.value) + return; + isScrolling.value = false; + directions.left = false; + directions.right = false; + directions.top = false; + directions.bottom = false; + onStop(e); + }; + const onScrollEndDebounced = useDebounceFn(onScrollEnd, throttle + idle); + const setArrivedState = (target) => { + var _a; + if (!window2) + return; + const el = ((_a = target == null ? void 0 : target.document) == null ? void 0 : _a.documentElement) || (target == null ? void 0 : target.documentElement) || unrefElement(target); + const { display, flexDirection, direction } = getComputedStyle(el); + const directionMultipler = direction === "rtl" ? -1 : 1; + const scrollLeft = el.scrollLeft; + directions.left = scrollLeft < internalX.value; + directions.right = scrollLeft > internalX.value; + const left = Math.abs(scrollLeft * directionMultipler) <= (offset.left || 0); + const right = Math.abs(scrollLeft * directionMultipler) + el.clientWidth >= el.scrollWidth - (offset.right || 0) - ARRIVED_STATE_THRESHOLD_PIXELS; + if (display === "flex" && flexDirection === "row-reverse") { + arrivedState.left = right; + arrivedState.right = left; + } else { + arrivedState.left = left; + arrivedState.right = right; + } + internalX.value = scrollLeft; + let scrollTop = el.scrollTop; + if (target === window2.document && !scrollTop) + scrollTop = window2.document.body.scrollTop; + directions.top = scrollTop < internalY.value; + directions.bottom = scrollTop > internalY.value; + const top = Math.abs(scrollTop) <= (offset.top || 0); + const bottom = Math.abs(scrollTop) + el.clientHeight >= el.scrollHeight - (offset.bottom || 0) - ARRIVED_STATE_THRESHOLD_PIXELS; + if (display === "flex" && flexDirection === "column-reverse") { + arrivedState.top = bottom; + arrivedState.bottom = top; + } else { + arrivedState.top = top; + arrivedState.bottom = bottom; + } + internalY.value = scrollTop; + }; + const onScrollHandler = (e) => { + var _a; + if (!window2) + return; + const eventTarget = (_a = e.target.documentElement) != null ? _a : e.target; + setArrivedState(eventTarget); + isScrolling.value = true; + onScrollEndDebounced(e); + onScroll(e); + }; + useEventListener( + element, + "scroll", + throttle ? useThrottleFn(onScrollHandler, throttle, true, false) : onScrollHandler, + eventListenerOptions + ); + tryOnMounted(() => { + try { + const _element = toValue(element); + if (!_element) + return; + setArrivedState(_element); + } catch (e) { + onError(e); + } + }); + useEventListener( + element, + "scrollend", + onScrollEnd, + eventListenerOptions + ); + return { + x, + y, + isScrolling, + arrivedState, + directions, + measure() { + const _element = toValue(element); + if (window2 && _element) + setArrivedState(_element); + } + }; +} +function useInfiniteScroll(element, onLoadMore, options = {}) { + var _a; + const { + direction = "bottom", + interval = 100, + canLoadMore = () => true + } = options; + const state = reactive(useScroll( + element, + { + ...options, + offset: { + [direction]: (_a = options.distance) != null ? _a : 0, + ...options.offset + } + } + )); + const promise = ref(); + const isLoading = computed(() => !!promise.value); + const observedElement = computed(() => { + return resolveElement(toValue(element)); + }); + const isElementVisible = useElementVisibility(observedElement); + function checkAndLoad() { + state.measure(); + if (!observedElement.value || !isElementVisible.value || !canLoadMore(observedElement.value)) + return; + const { scrollHeight, clientHeight, scrollWidth, clientWidth } = observedElement.value; + const isNarrower = direction === "bottom" || direction === "top" ? scrollHeight <= clientHeight : scrollWidth <= clientWidth; + if (state.arrivedState[direction] || isNarrower) { + if (!promise.value) { + promise.value = Promise.all([ + onLoadMore(state), + new Promise((resolve) => setTimeout(resolve, interval)) + ]).finally(() => { + promise.value = null; + nextTick(() => checkAndLoad()); + }); + } + } + } + const stop = watch( + () => [state.arrivedState[direction], isElementVisible.value], + checkAndLoad, + { immediate: true } + ); + tryOnUnmounted(stop); + return { + isLoading, + reset() { + nextTick(() => checkAndLoad()); + } + }; +} +var defaultEvents = ["mousedown", "mouseup", "keydown", "keyup"]; +function useKeyModifier(modifier, options = {}) { + const { + events: events2 = defaultEvents, + document: document2 = defaultDocument, + initial = null + } = options; + const state = shallowRef(initial); + if (document2) { + events2.forEach((listenerEvent) => { + useEventListener(document2, listenerEvent, (evt) => { + if (typeof evt.getModifierState === "function") + state.value = evt.getModifierState(modifier); + }, { passive: true }); + }); + } + return state; +} +function useLocalStorage(key, initialValue, options = {}) { + const { window: window2 = defaultWindow } = options; + return useStorage(key, initialValue, window2 == null ? void 0 : window2.localStorage, options); +} +var DefaultMagicKeysAliasMap = { + ctrl: "control", + command: "meta", + cmd: "meta", + option: "alt", + up: "arrowup", + down: "arrowdown", + left: "arrowleft", + right: "arrowright" +}; +function useMagicKeys(options = {}) { + const { + reactive: useReactive = false, + target = defaultWindow, + aliasMap = DefaultMagicKeysAliasMap, + passive = true, + onEventFired = noop + } = options; + const current = reactive(/* @__PURE__ */ new Set()); + const obj = { + toJSON() { + return {}; + }, + current + }; + const refs = useReactive ? reactive(obj) : obj; + const metaDeps = /* @__PURE__ */ new Set(); + const usedKeys = /* @__PURE__ */ new Set(); + function setRefs(key, value) { + if (key in refs) { + if (useReactive) + refs[key] = value; + else + refs[key].value = value; + } + } + function reset() { + current.clear(); + for (const key of usedKeys) + setRefs(key, false); + } + function updateRefs(e, value) { + var _a, _b; + const key = (_a = e.key) == null ? void 0 : _a.toLowerCase(); + const code = (_b = e.code) == null ? void 0 : _b.toLowerCase(); + const values = [code, key].filter(Boolean); + if (key) { + if (value) + current.add(key); + else + current.delete(key); + } + for (const key2 of values) { + usedKeys.add(key2); + setRefs(key2, value); + } + if (key === "meta" && !value) { + metaDeps.forEach((key2) => { + current.delete(key2); + setRefs(key2, false); + }); + metaDeps.clear(); + } else if (typeof e.getModifierState === "function" && e.getModifierState("Meta") && value) { + [...current, ...values].forEach((key2) => metaDeps.add(key2)); + } + } + useEventListener(target, "keydown", (e) => { + updateRefs(e, true); + return onEventFired(e); + }, { passive }); + useEventListener(target, "keyup", (e) => { + updateRefs(e, false); + return onEventFired(e); + }, { passive }); + useEventListener("blur", reset, { passive }); + useEventListener("focus", reset, { passive }); + const proxy = new Proxy( + refs, + { + get(target2, prop, rec) { + if (typeof prop !== "string") + return Reflect.get(target2, prop, rec); + prop = prop.toLowerCase(); + if (prop in aliasMap) + prop = aliasMap[prop]; + if (!(prop in refs)) { + if (/[+_-]/.test(prop)) { + const keys2 = prop.split(/[+_-]/g).map((i) => i.trim()); + refs[prop] = computed(() => keys2.map((key) => toValue(proxy[key])).every(Boolean)); + } else { + refs[prop] = shallowRef(false); + } + } + const r = Reflect.get(target2, prop, rec); + return useReactive ? toValue(r) : r; + } + } + ); + return proxy; +} +function usingElRef(source, cb) { + if (toValue(source)) + cb(toValue(source)); +} +function timeRangeToArray(timeRanges) { + let ranges = []; + for (let i = 0; i < timeRanges.length; ++i) + ranges = [...ranges, [timeRanges.start(i), timeRanges.end(i)]]; + return ranges; +} +function tracksToArray(tracks) { + return Array.from(tracks).map(({ label, kind, language, mode, activeCues, cues, inBandMetadataTrackDispatchType }, id) => ({ id, label, kind, language, mode, activeCues, cues, inBandMetadataTrackDispatchType })); +} +var defaultOptions = { + src: "", + tracks: [] +}; +function useMediaControls(target, options = {}) { + target = toRef2(target); + options = { + ...defaultOptions, + ...options + }; + const { + document: document2 = defaultDocument + } = options; + const listenerOptions = { passive: true }; + const currentTime = shallowRef(0); + const duration = shallowRef(0); + const seeking = shallowRef(false); + const volume = shallowRef(1); + const waiting = shallowRef(false); + const ended = shallowRef(false); + const playing = shallowRef(false); + const rate = shallowRef(1); + const stalled = shallowRef(false); + const buffered = ref([]); + const tracks = ref([]); + const selectedTrack = shallowRef(-1); + const isPictureInPicture = shallowRef(false); + const muted = shallowRef(false); + const supportsPictureInPicture = document2 && "pictureInPictureEnabled" in document2; + const sourceErrorEvent = createEventHook(); + const playbackErrorEvent = createEventHook(); + const disableTrack = (track) => { + usingElRef(target, (el) => { + if (track) { + const id = typeof track === "number" ? track : track.id; + el.textTracks[id].mode = "disabled"; + } else { + for (let i = 0; i < el.textTracks.length; ++i) + el.textTracks[i].mode = "disabled"; + } + selectedTrack.value = -1; + }); + }; + const enableTrack = (track, disableTracks = true) => { + usingElRef(target, (el) => { + const id = typeof track === "number" ? track : track.id; + if (disableTracks) + disableTrack(); + el.textTracks[id].mode = "showing"; + selectedTrack.value = id; + }); + }; + const togglePictureInPicture = () => { + return new Promise((resolve, reject) => { + usingElRef(target, async (el) => { + if (supportsPictureInPicture) { + if (!isPictureInPicture.value) { + el.requestPictureInPicture().then(resolve).catch(reject); + } else { + document2.exitPictureInPicture().then(resolve).catch(reject); + } + } + }); + }); + }; + watchEffect(() => { + if (!document2) + return; + const el = toValue(target); + if (!el) + return; + const src = toValue(options.src); + let sources = []; + if (!src) + return; + if (typeof src === "string") + sources = [{ src }]; + else if (Array.isArray(src)) + sources = src; + else if (isObject(src)) + sources = [src]; + el.querySelectorAll("source").forEach((e) => { + e.remove(); + }); + sources.forEach(({ src: src2, type, media }) => { + const source = document2.createElement("source"); + source.setAttribute("src", src2); + source.setAttribute("type", type || ""); + source.setAttribute("media", media || ""); + useEventListener(source, "error", sourceErrorEvent.trigger, listenerOptions); + el.appendChild(source); + }); + el.load(); + }); + watch([target, volume], () => { + const el = toValue(target); + if (!el) + return; + el.volume = volume.value; + }); + watch([target, muted], () => { + const el = toValue(target); + if (!el) + return; + el.muted = muted.value; + }); + watch([target, rate], () => { + const el = toValue(target); + if (!el) + return; + el.playbackRate = rate.value; + }); + watchEffect(() => { + if (!document2) + return; + const textTracks = toValue(options.tracks); + const el = toValue(target); + if (!textTracks || !textTracks.length || !el) + return; + el.querySelectorAll("track").forEach((e) => e.remove()); + textTracks.forEach(({ default: isDefault, kind, label, src, srcLang }, i) => { + const track = document2.createElement("track"); + track.default = isDefault || false; + track.kind = kind; + track.label = label; + track.src = src; + track.srclang = srcLang; + if (track.default) + selectedTrack.value = i; + el.appendChild(track); + }); + }); + const { ignoreUpdates: ignoreCurrentTimeUpdates } = watchIgnorable(currentTime, (time) => { + const el = toValue(target); + if (!el) + return; + el.currentTime = time; + }); + const { ignoreUpdates: ignorePlayingUpdates } = watchIgnorable(playing, (isPlaying) => { + const el = toValue(target); + if (!el) + return; + if (isPlaying) { + el.play().catch((e) => { + playbackErrorEvent.trigger(e); + throw e; + }); + } else { + el.pause(); + } + }); + useEventListener( + target, + "timeupdate", + () => ignoreCurrentTimeUpdates(() => currentTime.value = toValue(target).currentTime), + listenerOptions + ); + useEventListener( + target, + "durationchange", + () => duration.value = toValue(target).duration, + listenerOptions + ); + useEventListener( + target, + "progress", + () => buffered.value = timeRangeToArray(toValue(target).buffered), + listenerOptions + ); + useEventListener( + target, + "seeking", + () => seeking.value = true, + listenerOptions + ); + useEventListener( + target, + "seeked", + () => seeking.value = false, + listenerOptions + ); + useEventListener( + target, + ["waiting", "loadstart"], + () => { + waiting.value = true; + ignorePlayingUpdates(() => playing.value = false); + }, + listenerOptions + ); + useEventListener( + target, + "loadeddata", + () => waiting.value = false, + listenerOptions + ); + useEventListener( + target, + "playing", + () => { + waiting.value = false; + ended.value = false; + ignorePlayingUpdates(() => playing.value = true); + }, + listenerOptions + ); + useEventListener( + target, + "ratechange", + () => rate.value = toValue(target).playbackRate, + listenerOptions + ); + useEventListener( + target, + "stalled", + () => stalled.value = true, + listenerOptions + ); + useEventListener( + target, + "ended", + () => ended.value = true, + listenerOptions + ); + useEventListener( + target, + "pause", + () => ignorePlayingUpdates(() => playing.value = false), + listenerOptions + ); + useEventListener( + target, + "play", + () => ignorePlayingUpdates(() => playing.value = true), + listenerOptions + ); + useEventListener( + target, + "enterpictureinpicture", + () => isPictureInPicture.value = true, + listenerOptions + ); + useEventListener( + target, + "leavepictureinpicture", + () => isPictureInPicture.value = false, + listenerOptions + ); + useEventListener( + target, + "volumechange", + () => { + const el = toValue(target); + if (!el) + return; + volume.value = el.volume; + muted.value = el.muted; + }, + listenerOptions + ); + const listeners = []; + const stop = watch([target], () => { + const el = toValue(target); + if (!el) + return; + stop(); + listeners[0] = useEventListener(el.textTracks, "addtrack", () => tracks.value = tracksToArray(el.textTracks), listenerOptions); + listeners[1] = useEventListener(el.textTracks, "removetrack", () => tracks.value = tracksToArray(el.textTracks), listenerOptions); + listeners[2] = useEventListener(el.textTracks, "change", () => tracks.value = tracksToArray(el.textTracks), listenerOptions); + }); + tryOnScopeDispose(() => listeners.forEach((listener) => listener())); + return { + currentTime, + duration, + waiting, + seeking, + ended, + stalled, + buffered, + playing, + rate, + // Volume + volume, + muted, + // Tracks + tracks, + selectedTrack, + enableTrack, + disableTrack, + // Picture in Picture + supportsPictureInPicture, + togglePictureInPicture, + isPictureInPicture, + // Events + onSourceError: sourceErrorEvent.on, + onPlaybackError: playbackErrorEvent.on + }; +} +function useMemoize(resolver, options) { + const initCache = () => { + if (options == null ? void 0 : options.cache) + return shallowReactive(options.cache); + return shallowReactive(/* @__PURE__ */ new Map()); + }; + const cache = initCache(); + const generateKey = (...args) => (options == null ? void 0 : options.getKey) ? options.getKey(...args) : JSON.stringify(args); + const _loadData = (key, ...args) => { + cache.set(key, resolver(...args)); + return cache.get(key); + }; + const loadData = (...args) => _loadData(generateKey(...args), ...args); + const deleteData = (...args) => { + cache.delete(generateKey(...args)); + }; + const clearData = () => { + cache.clear(); + }; + const memoized = (...args) => { + const key = generateKey(...args); + if (cache.has(key)) + return cache.get(key); + return _loadData(key, ...args); + }; + memoized.load = loadData; + memoized.delete = deleteData; + memoized.clear = clearData; + memoized.generateKey = generateKey; + memoized.cache = cache; + return memoized; +} +function useMemory(options = {}) { + const memory = ref(); + const isSupported = useSupported(() => typeof performance !== "undefined" && "memory" in performance); + if (isSupported.value) { + const { interval = 1e3 } = options; + useIntervalFn(() => { + memory.value = performance.memory; + }, interval, { immediate: options.immediate, immediateCallback: options.immediateCallback }); + } + return { isSupported, memory }; +} +var UseMouseBuiltinExtractors = { + page: (event) => [event.pageX, event.pageY], + client: (event) => [event.clientX, event.clientY], + screen: (event) => [event.screenX, event.screenY], + movement: (event) => event instanceof MouseEvent ? [event.movementX, event.movementY] : null +}; +function useMouse(options = {}) { + const { + type = "page", + touch = true, + resetOnTouchEnds = false, + initialValue = { x: 0, y: 0 }, + window: window2 = defaultWindow, + target = window2, + scroll = true, + eventFilter + } = options; + let _prevMouseEvent = null; + let _prevScrollX = 0; + let _prevScrollY = 0; + const x = shallowRef(initialValue.x); + const y = shallowRef(initialValue.y); + const sourceType = shallowRef(null); + const extractor = typeof type === "function" ? type : UseMouseBuiltinExtractors[type]; + const mouseHandler = (event) => { + const result = extractor(event); + _prevMouseEvent = event; + if (result) { + [x.value, y.value] = result; + sourceType.value = "mouse"; + } + if (window2) { + _prevScrollX = window2.scrollX; + _prevScrollY = window2.scrollY; + } + }; + const touchHandler = (event) => { + if (event.touches.length > 0) { + const result = extractor(event.touches[0]); + if (result) { + [x.value, y.value] = result; + sourceType.value = "touch"; + } + } + }; + const scrollHandler = () => { + if (!_prevMouseEvent || !window2) + return; + const pos = extractor(_prevMouseEvent); + if (_prevMouseEvent instanceof MouseEvent && pos) { + x.value = pos[0] + window2.scrollX - _prevScrollX; + y.value = pos[1] + window2.scrollY - _prevScrollY; + } + }; + const reset = () => { + x.value = initialValue.x; + y.value = initialValue.y; + }; + const mouseHandlerWrapper = eventFilter ? (event) => eventFilter(() => mouseHandler(event), {}) : (event) => mouseHandler(event); + const touchHandlerWrapper = eventFilter ? (event) => eventFilter(() => touchHandler(event), {}) : (event) => touchHandler(event); + const scrollHandlerWrapper = eventFilter ? () => eventFilter(() => scrollHandler(), {}) : () => scrollHandler(); + if (target) { + const listenerOptions = { passive: true }; + useEventListener(target, ["mousemove", "dragover"], mouseHandlerWrapper, listenerOptions); + if (touch && type !== "movement") { + useEventListener(target, ["touchstart", "touchmove"], touchHandlerWrapper, listenerOptions); + if (resetOnTouchEnds) + useEventListener(target, "touchend", reset, listenerOptions); + } + if (scroll && type === "page") + useEventListener(window2, "scroll", scrollHandlerWrapper, listenerOptions); + } + return { + x, + y, + sourceType + }; +} +function useMouseInElement(target, options = {}) { + const { + handleOutside = true, + window: window2 = defaultWindow + } = options; + const type = options.type || "page"; + const { x, y, sourceType } = useMouse(options); + const targetRef = shallowRef(target != null ? target : window2 == null ? void 0 : window2.document.body); + const elementX = shallowRef(0); + const elementY = shallowRef(0); + const elementPositionX = shallowRef(0); + const elementPositionY = shallowRef(0); + const elementHeight = shallowRef(0); + const elementWidth = shallowRef(0); + const isOutside = shallowRef(true); + let stop = () => { + }; + if (window2) { + stop = watch( + [targetRef, x, y], + () => { + const el = unrefElement(targetRef); + if (!el || !(el instanceof Element)) + return; + const { + left, + top, + width, + height + } = el.getBoundingClientRect(); + elementPositionX.value = left + (type === "page" ? window2.pageXOffset : 0); + elementPositionY.value = top + (type === "page" ? window2.pageYOffset : 0); + elementHeight.value = height; + elementWidth.value = width; + const elX = x.value - elementPositionX.value; + const elY = y.value - elementPositionY.value; + isOutside.value = width === 0 || height === 0 || elX < 0 || elY < 0 || elX > width || elY > height; + if (handleOutside || !isOutside.value) { + elementX.value = elX; + elementY.value = elY; + } + }, + { immediate: true } + ); + useEventListener( + document, + "mouseleave", + () => isOutside.value = true, + { passive: true } + ); + } + return { + x, + y, + sourceType, + elementX, + elementY, + elementPositionX, + elementPositionY, + elementHeight, + elementWidth, + isOutside, + stop + }; +} +function useMousePressed(options = {}) { + const { + touch = true, + drag = true, + capture = false, + initialValue = false, + window: window2 = defaultWindow + } = options; + const pressed = shallowRef(initialValue); + const sourceType = shallowRef(null); + if (!window2) { + return { + pressed, + sourceType + }; + } + const onPressed = (srcType) => (event) => { + var _a; + pressed.value = true; + sourceType.value = srcType; + (_a = options.onPressed) == null ? void 0 : _a.call(options, event); + }; + const onReleased = (event) => { + var _a; + pressed.value = false; + sourceType.value = null; + (_a = options.onReleased) == null ? void 0 : _a.call(options, event); + }; + const target = computed(() => unrefElement(options.target) || window2); + const listenerOptions = { passive: true, capture }; + useEventListener(target, "mousedown", onPressed("mouse"), listenerOptions); + useEventListener(window2, "mouseleave", onReleased, listenerOptions); + useEventListener(window2, "mouseup", onReleased, listenerOptions); + if (drag) { + useEventListener(target, "dragstart", onPressed("mouse"), listenerOptions); + useEventListener(window2, "drop", onReleased, listenerOptions); + useEventListener(window2, "dragend", onReleased, listenerOptions); + } + if (touch) { + useEventListener(target, "touchstart", onPressed("touch"), listenerOptions); + useEventListener(window2, "touchend", onReleased, listenerOptions); + useEventListener(window2, "touchcancel", onReleased, listenerOptions); + } + return { + pressed, + sourceType + }; +} +function useNavigatorLanguage(options = {}) { + const { window: window2 = defaultWindow } = options; + const navigator2 = window2 == null ? void 0 : window2.navigator; + const isSupported = useSupported(() => navigator2 && "language" in navigator2); + const language = shallowRef(navigator2 == null ? void 0 : navigator2.language); + useEventListener(window2, "languagechange", () => { + if (navigator2) + language.value = navigator2.language; + }, { passive: true }); + return { + isSupported, + language + }; +} +function useNetwork(options = {}) { + const { window: window2 = defaultWindow } = options; + const navigator2 = window2 == null ? void 0 : window2.navigator; + const isSupported = useSupported(() => navigator2 && "connection" in navigator2); + const isOnline = shallowRef(true); + const saveData = shallowRef(false); + const offlineAt = shallowRef(void 0); + const onlineAt = shallowRef(void 0); + const downlink = shallowRef(void 0); + const downlinkMax = shallowRef(void 0); + const rtt = shallowRef(void 0); + const effectiveType = shallowRef(void 0); + const type = shallowRef("unknown"); + const connection = isSupported.value && navigator2.connection; + function updateNetworkInformation() { + if (!navigator2) + return; + isOnline.value = navigator2.onLine; + offlineAt.value = isOnline.value ? void 0 : Date.now(); + onlineAt.value = isOnline.value ? Date.now() : void 0; + if (connection) { + downlink.value = connection.downlink; + downlinkMax.value = connection.downlinkMax; + effectiveType.value = connection.effectiveType; + rtt.value = connection.rtt; + saveData.value = connection.saveData; + type.value = connection.type; + } + } + const listenerOptions = { passive: true }; + if (window2) { + useEventListener(window2, "offline", () => { + isOnline.value = false; + offlineAt.value = Date.now(); + }, listenerOptions); + useEventListener(window2, "online", () => { + isOnline.value = true; + onlineAt.value = Date.now(); + }, listenerOptions); + } + if (connection) + useEventListener(connection, "change", updateNetworkInformation, listenerOptions); + updateNetworkInformation(); + return { + isSupported, + isOnline: readonly(isOnline), + saveData: readonly(saveData), + offlineAt: readonly(offlineAt), + onlineAt: readonly(onlineAt), + downlink: readonly(downlink), + downlinkMax: readonly(downlinkMax), + effectiveType: readonly(effectiveType), + rtt: readonly(rtt), + type: readonly(type) + }; +} +function useNow(options = {}) { + const { + controls: exposeControls = false, + interval = "requestAnimationFrame" + } = options; + const now2 = ref(/* @__PURE__ */ new Date()); + const update = () => now2.value = /* @__PURE__ */ new Date(); + const controls = interval === "requestAnimationFrame" ? useRafFn(update, { immediate: true }) : useIntervalFn(update, interval, { immediate: true }); + if (exposeControls) { + return { + now: now2, + ...controls + }; + } else { + return now2; + } +} +function useObjectUrl(object) { + const url = shallowRef(); + const release = () => { + if (url.value) + URL.revokeObjectURL(url.value); + url.value = void 0; + }; + watch( + () => toValue(object), + (newObject) => { + release(); + if (newObject) + url.value = URL.createObjectURL(newObject); + }, + { immediate: true } + ); + tryOnScopeDispose(release); + return readonly(url); +} +function useClamp(value, min, max) { + if (typeof value === "function" || isReadonly(value)) + return computed(() => clamp(toValue(value), toValue(min), toValue(max))); + const _value = ref(value); + return computed({ + get() { + return _value.value = clamp(_value.value, toValue(min), toValue(max)); + }, + set(value2) { + _value.value = clamp(value2, toValue(min), toValue(max)); + } + }); +} +function useOffsetPagination(options) { + const { + total = Number.POSITIVE_INFINITY, + pageSize = 10, + page = 1, + onPageChange = noop, + onPageSizeChange = noop, + onPageCountChange = noop + } = options; + const currentPageSize = useClamp(pageSize, 1, Number.POSITIVE_INFINITY); + const pageCount = computed(() => Math.max( + 1, + Math.ceil(toValue(total) / toValue(currentPageSize)) + )); + const currentPage = useClamp(page, 1, pageCount); + const isFirstPage = computed(() => currentPage.value === 1); + const isLastPage = computed(() => currentPage.value === pageCount.value); + if (isRef(page)) { + syncRef(page, currentPage, { + direction: isReadonly(page) ? "ltr" : "both" + }); + } + if (isRef(pageSize)) { + syncRef(pageSize, currentPageSize, { + direction: isReadonly(pageSize) ? "ltr" : "both" + }); + } + function prev() { + currentPage.value--; + } + function next() { + currentPage.value++; + } + const returnValue = { + currentPage, + currentPageSize, + pageCount, + isFirstPage, + isLastPage, + prev, + next + }; + watch(currentPage, () => { + onPageChange(reactive(returnValue)); + }); + watch(currentPageSize, () => { + onPageSizeChange(reactive(returnValue)); + }); + watch(pageCount, () => { + onPageCountChange(reactive(returnValue)); + }); + return returnValue; +} +function useOnline(options = {}) { + const { isOnline } = useNetwork(options); + return isOnline; +} +function usePageLeave(options = {}) { + const { window: window2 = defaultWindow } = options; + const isLeft = shallowRef(false); + const handler = (event) => { + if (!window2) + return; + event = event || window2.event; + const from = event.relatedTarget || event.toElement; + isLeft.value = !from; + }; + if (window2) { + const listenerOptions = { passive: true }; + useEventListener(window2, "mouseout", handler, listenerOptions); + useEventListener(window2.document, "mouseleave", handler, listenerOptions); + useEventListener(window2.document, "mouseenter", handler, listenerOptions); + } + return isLeft; +} +function useScreenOrientation(options = {}) { + const { + window: window2 = defaultWindow + } = options; + const isSupported = useSupported(() => window2 && "screen" in window2 && "orientation" in window2.screen); + const screenOrientation = isSupported.value ? window2.screen.orientation : {}; + const orientation = ref(screenOrientation.type); + const angle = shallowRef(screenOrientation.angle || 0); + if (isSupported.value) { + useEventListener(window2, "orientationchange", () => { + orientation.value = screenOrientation.type; + angle.value = screenOrientation.angle; + }, { passive: true }); + } + const lockOrientation = (type) => { + if (isSupported.value && typeof screenOrientation.lock === "function") + return screenOrientation.lock(type); + return Promise.reject(new Error("Not supported")); + }; + const unlockOrientation = () => { + if (isSupported.value && typeof screenOrientation.unlock === "function") + screenOrientation.unlock(); + }; + return { + isSupported, + orientation, + angle, + lockOrientation, + unlockOrientation + }; +} +function useParallax(target, options = {}) { + const { + deviceOrientationTiltAdjust = (i) => i, + deviceOrientationRollAdjust = (i) => i, + mouseTiltAdjust = (i) => i, + mouseRollAdjust = (i) => i, + window: window2 = defaultWindow + } = options; + const orientation = reactive(useDeviceOrientation({ window: window2 })); + const screenOrientation = reactive(useScreenOrientation({ window: window2 })); + const { + elementX: x, + elementY: y, + elementWidth: width, + elementHeight: height + } = useMouseInElement(target, { handleOutside: false, window: window2 }); + const source = computed(() => { + if (orientation.isSupported && (orientation.alpha != null && orientation.alpha !== 0 || orientation.gamma != null && orientation.gamma !== 0)) { + return "deviceOrientation"; + } + return "mouse"; + }); + const roll = computed(() => { + if (source.value === "deviceOrientation") { + let value; + switch (screenOrientation.orientation) { + case "landscape-primary": + value = orientation.gamma / 90; + break; + case "landscape-secondary": + value = -orientation.gamma / 90; + break; + case "portrait-primary": + value = -orientation.beta / 90; + break; + case "portrait-secondary": + value = orientation.beta / 90; + break; + default: + value = -orientation.beta / 90; + } + return deviceOrientationRollAdjust(value); + } else { + const value = -(y.value - height.value / 2) / height.value; + return mouseRollAdjust(value); + } + }); + const tilt = computed(() => { + if (source.value === "deviceOrientation") { + let value; + switch (screenOrientation.orientation) { + case "landscape-primary": + value = orientation.beta / 90; + break; + case "landscape-secondary": + value = -orientation.beta / 90; + break; + case "portrait-primary": + value = orientation.gamma / 90; + break; + case "portrait-secondary": + value = -orientation.gamma / 90; + break; + default: + value = orientation.gamma / 90; + } + return deviceOrientationTiltAdjust(value); + } else { + const value = (x.value - width.value / 2) / width.value; + return mouseTiltAdjust(value); + } + }); + return { roll, tilt, source }; +} +function useParentElement(element = useCurrentElement()) { + const parentElement = shallowRef(); + const update = () => { + const el = unrefElement(element); + if (el) + parentElement.value = el.parentElement; + }; + tryOnMounted(update); + watch(() => toValue(element), update); + return parentElement; +} +function usePerformanceObserver(options, callback) { + const { + window: window2 = defaultWindow, + immediate = true, + ...performanceOptions + } = options; + const isSupported = useSupported(() => window2 && "PerformanceObserver" in window2); + let observer; + const stop = () => { + observer == null ? void 0 : observer.disconnect(); + }; + const start = () => { + if (isSupported.value) { + stop(); + observer = new PerformanceObserver(callback); + observer.observe(performanceOptions); + } + }; + tryOnScopeDispose(stop); + if (immediate) + start(); + return { + isSupported, + start, + stop + }; +} +var defaultState = { + x: 0, + y: 0, + pointerId: 0, + pressure: 0, + tiltX: 0, + tiltY: 0, + width: 0, + height: 0, + twist: 0, + pointerType: null +}; +var keys = Object.keys(defaultState); +function usePointer(options = {}) { + const { + target = defaultWindow + } = options; + const isInside = shallowRef(false); + const state = ref(options.initialValue || {}); + Object.assign(state.value, defaultState, state.value); + const handler = (event) => { + isInside.value = true; + if (options.pointerTypes && !options.pointerTypes.includes(event.pointerType)) + return; + state.value = objectPick(event, keys, false); + }; + if (target) { + const listenerOptions = { passive: true }; + useEventListener(target, ["pointerdown", "pointermove", "pointerup"], handler, listenerOptions); + useEventListener(target, "pointerleave", () => isInside.value = false, listenerOptions); + } + return { + ...toRefs2(state), + isInside + }; +} +function usePointerLock(target, options = {}) { + const { document: document2 = defaultDocument } = options; + const isSupported = useSupported(() => document2 && "pointerLockElement" in document2); + const element = shallowRef(); + const triggerElement = shallowRef(); + let targetElement; + if (isSupported.value) { + const listenerOptions = { passive: true }; + useEventListener(document2, "pointerlockchange", () => { + var _a; + const currentElement = (_a = document2.pointerLockElement) != null ? _a : element.value; + if (targetElement && currentElement === targetElement) { + element.value = document2.pointerLockElement; + if (!element.value) + targetElement = triggerElement.value = null; + } + }, listenerOptions); + useEventListener(document2, "pointerlockerror", () => { + var _a; + const currentElement = (_a = document2.pointerLockElement) != null ? _a : element.value; + if (targetElement && currentElement === targetElement) { + const action = document2.pointerLockElement ? "release" : "acquire"; + throw new Error(`Failed to ${action} pointer lock.`); + } + }, listenerOptions); + } + async function lock(e) { + var _a; + if (!isSupported.value) + throw new Error("Pointer Lock API is not supported by your browser."); + triggerElement.value = e instanceof Event ? e.currentTarget : null; + targetElement = e instanceof Event ? (_a = unrefElement(target)) != null ? _a : triggerElement.value : unrefElement(e); + if (!targetElement) + throw new Error("Target element undefined."); + targetElement.requestPointerLock(); + return await until(element).toBe(targetElement); + } + async function unlock() { + if (!element.value) + return false; + document2.exitPointerLock(); + await until(element).toBeNull(); + return true; + } + return { + isSupported, + element, + triggerElement, + lock, + unlock + }; +} +function usePointerSwipe(target, options = {}) { + const targetRef = toRef2(target); + const { + threshold = 50, + onSwipe, + onSwipeEnd, + onSwipeStart, + disableTextSelect = false + } = options; + const posStart = reactive({ x: 0, y: 0 }); + const updatePosStart = (x, y) => { + posStart.x = x; + posStart.y = y; + }; + const posEnd = reactive({ x: 0, y: 0 }); + const updatePosEnd = (x, y) => { + posEnd.x = x; + posEnd.y = y; + }; + const distanceX = computed(() => posStart.x - posEnd.x); + const distanceY = computed(() => posStart.y - posEnd.y); + const { max, abs } = Math; + const isThresholdExceeded = computed(() => max(abs(distanceX.value), abs(distanceY.value)) >= threshold); + const isSwiping = shallowRef(false); + const isPointerDown = shallowRef(false); + const direction = computed(() => { + if (!isThresholdExceeded.value) + return "none"; + if (abs(distanceX.value) > abs(distanceY.value)) { + return distanceX.value > 0 ? "left" : "right"; + } else { + return distanceY.value > 0 ? "up" : "down"; + } + }); + const eventIsAllowed = (e) => { + var _a, _b, _c; + const isReleasingButton = e.buttons === 0; + const isPrimaryButton = e.buttons === 1; + return (_c = (_b = (_a = options.pointerTypes) == null ? void 0 : _a.includes(e.pointerType)) != null ? _b : isReleasingButton || isPrimaryButton) != null ? _c : true; + }; + const listenerOptions = { passive: true }; + const stops = [ + useEventListener(target, "pointerdown", (e) => { + if (!eventIsAllowed(e)) + return; + isPointerDown.value = true; + const eventTarget = e.target; + eventTarget == null ? void 0 : eventTarget.setPointerCapture(e.pointerId); + const { clientX: x, clientY: y } = e; + updatePosStart(x, y); + updatePosEnd(x, y); + onSwipeStart == null ? void 0 : onSwipeStart(e); + }, listenerOptions), + useEventListener(target, "pointermove", (e) => { + if (!eventIsAllowed(e)) + return; + if (!isPointerDown.value) + return; + const { clientX: x, clientY: y } = e; + updatePosEnd(x, y); + if (!isSwiping.value && isThresholdExceeded.value) + isSwiping.value = true; + if (isSwiping.value) + onSwipe == null ? void 0 : onSwipe(e); + }, listenerOptions), + useEventListener(target, "pointerup", (e) => { + if (!eventIsAllowed(e)) + return; + if (isSwiping.value) + onSwipeEnd == null ? void 0 : onSwipeEnd(e, direction.value); + isPointerDown.value = false; + isSwiping.value = false; + }, listenerOptions) + ]; + tryOnMounted(() => { + var _a, _b, _c, _d, _e, _f, _g, _h; + (_b = (_a = targetRef.value) == null ? void 0 : _a.style) == null ? void 0 : _b.setProperty("touch-action", "none"); + if (disableTextSelect) { + (_d = (_c = targetRef.value) == null ? void 0 : _c.style) == null ? void 0 : _d.setProperty("-webkit-user-select", "none"); + (_f = (_e = targetRef.value) == null ? void 0 : _e.style) == null ? void 0 : _f.setProperty("-ms-user-select", "none"); + (_h = (_g = targetRef.value) == null ? void 0 : _g.style) == null ? void 0 : _h.setProperty("user-select", "none"); + } + }); + const stop = () => stops.forEach((s) => s()); + return { + isSwiping: readonly(isSwiping), + direction: readonly(direction), + posStart: readonly(posStart), + posEnd: readonly(posEnd), + distanceX, + distanceY, + stop + }; +} +function usePreferredColorScheme(options) { + const isLight = useMediaQuery("(prefers-color-scheme: light)", options); + const isDark = useMediaQuery("(prefers-color-scheme: dark)", options); + return computed(() => { + if (isDark.value) + return "dark"; + if (isLight.value) + return "light"; + return "no-preference"; + }); +} +function usePreferredContrast(options) { + const isMore = useMediaQuery("(prefers-contrast: more)", options); + const isLess = useMediaQuery("(prefers-contrast: less)", options); + const isCustom = useMediaQuery("(prefers-contrast: custom)", options); + return computed(() => { + if (isMore.value) + return "more"; + if (isLess.value) + return "less"; + if (isCustom.value) + return "custom"; + return "no-preference"; + }); +} +function usePreferredLanguages(options = {}) { + const { window: window2 = defaultWindow } = options; + if (!window2) + return ref(["en"]); + const navigator2 = window2.navigator; + const value = ref(navigator2.languages); + useEventListener(window2, "languagechange", () => { + value.value = navigator2.languages; + }, { passive: true }); + return value; +} +function usePreferredReducedMotion(options) { + const isReduced = useMediaQuery("(prefers-reduced-motion: reduce)", options); + return computed(() => { + if (isReduced.value) + return "reduce"; + return "no-preference"; + }); +} +function usePreferredReducedTransparency(options) { + const isReduced = useMediaQuery("(prefers-reduced-transparency: reduce)", options); + return computed(() => { + if (isReduced.value) + return "reduce"; + return "no-preference"; + }); +} +function usePrevious(value, initialValue) { + const previous = shallowRef(initialValue); + watch( + toRef2(value), + (_, oldValue) => { + previous.value = oldValue; + }, + { flush: "sync" } + ); + return readonly(previous); +} +var topVarName = "--vueuse-safe-area-top"; +var rightVarName = "--vueuse-safe-area-right"; +var bottomVarName = "--vueuse-safe-area-bottom"; +var leftVarName = "--vueuse-safe-area-left"; +function useScreenSafeArea() { + const top = shallowRef(""); + const right = shallowRef(""); + const bottom = shallowRef(""); + const left = shallowRef(""); + if (isClient) { + const topCssVar = useCssVar(topVarName); + const rightCssVar = useCssVar(rightVarName); + const bottomCssVar = useCssVar(bottomVarName); + const leftCssVar = useCssVar(leftVarName); + topCssVar.value = "env(safe-area-inset-top, 0px)"; + rightCssVar.value = "env(safe-area-inset-right, 0px)"; + bottomCssVar.value = "env(safe-area-inset-bottom, 0px)"; + leftCssVar.value = "env(safe-area-inset-left, 0px)"; + update(); + useEventListener("resize", useDebounceFn(update), { passive: true }); + } + function update() { + top.value = getValue(topVarName); + right.value = getValue(rightVarName); + bottom.value = getValue(bottomVarName); + left.value = getValue(leftVarName); + } + return { + top, + right, + bottom, + left, + update + }; +} +function getValue(position) { + return getComputedStyle(document.documentElement).getPropertyValue(position); +} +function useScriptTag(src, onLoaded = noop, options = {}) { + const { + immediate = true, + manual = false, + type = "text/javascript", + async = true, + crossOrigin, + referrerPolicy, + noModule, + defer, + document: document2 = defaultDocument, + attrs = {} + } = options; + const scriptTag = shallowRef(null); + let _promise = null; + const loadScript = (waitForScriptLoad) => new Promise((resolve, reject) => { + const resolveWithElement = (el2) => { + scriptTag.value = el2; + resolve(el2); + return el2; + }; + if (!document2) { + resolve(false); + return; + } + let shouldAppend = false; + let el = document2.querySelector(`script[src="${toValue(src)}"]`); + if (!el) { + el = document2.createElement("script"); + el.type = type; + el.async = async; + el.src = toValue(src); + if (defer) + el.defer = defer; + if (crossOrigin) + el.crossOrigin = crossOrigin; + if (noModule) + el.noModule = noModule; + if (referrerPolicy) + el.referrerPolicy = referrerPolicy; + Object.entries(attrs).forEach(([name, value]) => el == null ? void 0 : el.setAttribute(name, value)); + shouldAppend = true; + } else if (el.hasAttribute("data-loaded")) { + resolveWithElement(el); + } + const listenerOptions = { + passive: true + }; + useEventListener(el, "error", (event) => reject(event), listenerOptions); + useEventListener(el, "abort", (event) => reject(event), listenerOptions); + useEventListener(el, "load", () => { + el.setAttribute("data-loaded", "true"); + onLoaded(el); + resolveWithElement(el); + }, listenerOptions); + if (shouldAppend) + el = document2.head.appendChild(el); + if (!waitForScriptLoad) + resolveWithElement(el); + }); + const load = (waitForScriptLoad = true) => { + if (!_promise) + _promise = loadScript(waitForScriptLoad); + return _promise; + }; + const unload = () => { + if (!document2) + return; + _promise = null; + if (scriptTag.value) + scriptTag.value = null; + const el = document2.querySelector(`script[src="${toValue(src)}"]`); + if (el) + document2.head.removeChild(el); + }; + if (immediate && !manual) + tryOnMounted(load); + if (!manual) + tryOnUnmounted(unload); + return { scriptTag, load, unload }; +} +function checkOverflowScroll(ele) { + const style = window.getComputedStyle(ele); + if (style.overflowX === "scroll" || style.overflowY === "scroll" || style.overflowX === "auto" && ele.clientWidth < ele.scrollWidth || style.overflowY === "auto" && ele.clientHeight < ele.scrollHeight) { + return true; + } else { + const parent = ele.parentNode; + if (!parent || parent.tagName === "BODY") + return false; + return checkOverflowScroll(parent); + } +} +function preventDefault(rawEvent) { + const e = rawEvent || window.event; + const _target = e.target; + if (checkOverflowScroll(_target)) + return false; + if (e.touches.length > 1) + return true; + if (e.preventDefault) + e.preventDefault(); + return false; +} +var elInitialOverflow = /* @__PURE__ */ new WeakMap(); +function useScrollLock(element, initialState = false) { + const isLocked = shallowRef(initialState); + let stopTouchMoveListener = null; + let initialOverflow = ""; + watch(toRef2(element), (el) => { + const target = resolveElement(toValue(el)); + if (target) { + const ele = target; + if (!elInitialOverflow.get(ele)) + elInitialOverflow.set(ele, ele.style.overflow); + if (ele.style.overflow !== "hidden") + initialOverflow = ele.style.overflow; + if (ele.style.overflow === "hidden") + return isLocked.value = true; + if (isLocked.value) + return ele.style.overflow = "hidden"; + } + }, { + immediate: true + }); + const lock = () => { + const el = resolveElement(toValue(element)); + if (!el || isLocked.value) + return; + if (isIOS) { + stopTouchMoveListener = useEventListener( + el, + "touchmove", + (e) => { + preventDefault(e); + }, + { passive: false } + ); + } + el.style.overflow = "hidden"; + isLocked.value = true; + }; + const unlock = () => { + const el = resolveElement(toValue(element)); + if (!el || !isLocked.value) + return; + if (isIOS) + stopTouchMoveListener == null ? void 0 : stopTouchMoveListener(); + el.style.overflow = initialOverflow; + elInitialOverflow.delete(el); + isLocked.value = false; + }; + tryOnScopeDispose(unlock); + return computed({ + get() { + return isLocked.value; + }, + set(v) { + if (v) + lock(); + else unlock(); + } + }); +} +function useSessionStorage(key, initialValue, options = {}) { + const { window: window2 = defaultWindow } = options; + return useStorage(key, initialValue, window2 == null ? void 0 : window2.sessionStorage, options); +} +function useShare(shareOptions = {}, options = {}) { + const { navigator: navigator2 = defaultNavigator } = options; + const _navigator = navigator2; + const isSupported = useSupported(() => _navigator && "canShare" in _navigator); + const share = async (overrideOptions = {}) => { + if (isSupported.value) { + const data = { + ...toValue(shareOptions), + ...toValue(overrideOptions) + }; + let granted = true; + if (data.files && _navigator.canShare) + granted = _navigator.canShare({ files: data.files }); + if (granted) + return _navigator.share(data); + } + }; + return { + isSupported, + share + }; +} +var defaultSortFn = (source, compareFn) => source.sort(compareFn); +var defaultCompare = (a, b) => a - b; +function useSorted(...args) { + var _a, _b, _c, _d; + const [source] = args; + let compareFn = defaultCompare; + let options = {}; + if (args.length === 2) { + if (typeof args[1] === "object") { + options = args[1]; + compareFn = (_a = options.compareFn) != null ? _a : defaultCompare; + } else { + compareFn = (_b = args[1]) != null ? _b : defaultCompare; + } + } else if (args.length > 2) { + compareFn = (_c = args[1]) != null ? _c : defaultCompare; + options = (_d = args[2]) != null ? _d : {}; + } + const { + dirty = false, + sortFn = defaultSortFn + } = options; + if (!dirty) + return computed(() => sortFn([...toValue(source)], compareFn)); + watchEffect(() => { + const result = sortFn(toValue(source), compareFn); + if (isRef(source)) + source.value = result; + else + source.splice(0, source.length, ...result); + }); + return source; +} +function useSpeechRecognition(options = {}) { + const { + interimResults = true, + continuous = true, + maxAlternatives = 1, + window: window2 = defaultWindow + } = options; + const lang = toRef2(options.lang || "en-US"); + const isListening = shallowRef(false); + const isFinal = shallowRef(false); + const result = shallowRef(""); + const error = shallowRef(void 0); + let recognition; + const start = () => { + isListening.value = true; + }; + const stop = () => { + isListening.value = false; + }; + const toggle = (value = !isListening.value) => { + if (value) { + start(); + } else { + stop(); + } + }; + const SpeechRecognition = window2 && (window2.SpeechRecognition || window2.webkitSpeechRecognition); + const isSupported = useSupported(() => SpeechRecognition); + if (isSupported.value) { + recognition = new SpeechRecognition(); + recognition.continuous = continuous; + recognition.interimResults = interimResults; + recognition.lang = toValue(lang); + recognition.maxAlternatives = maxAlternatives; + recognition.onstart = () => { + isListening.value = true; + isFinal.value = false; + }; + watch(lang, (lang2) => { + if (recognition && !isListening.value) + recognition.lang = lang2; + }); + recognition.onresult = (event) => { + const currentResult = event.results[event.resultIndex]; + const { transcript } = currentResult[0]; + isFinal.value = currentResult.isFinal; + result.value = transcript; + error.value = void 0; + }; + recognition.onerror = (event) => { + error.value = event; + }; + recognition.onend = () => { + isListening.value = false; + recognition.lang = toValue(lang); + }; + watch(isListening, (newValue, oldValue) => { + if (newValue === oldValue) + return; + if (newValue) + recognition.start(); + else + recognition.stop(); + }); + } + tryOnScopeDispose(() => { + stop(); + }); + return { + isSupported, + isListening, + isFinal, + recognition, + result, + error, + toggle, + start, + stop + }; +} +function useSpeechSynthesis(text, options = {}) { + const { + pitch = 1, + rate = 1, + volume = 1, + window: window2 = defaultWindow + } = options; + const synth = window2 && window2.speechSynthesis; + const isSupported = useSupported(() => synth); + const isPlaying = shallowRef(false); + const status = shallowRef("init"); + const spokenText = toRef2(text || ""); + const lang = toRef2(options.lang || "en-US"); + const error = shallowRef(void 0); + const toggle = (value = !isPlaying.value) => { + isPlaying.value = value; + }; + const bindEventsForUtterance = (utterance2) => { + utterance2.lang = toValue(lang); + utterance2.voice = toValue(options.voice) || null; + utterance2.pitch = toValue(pitch); + utterance2.rate = toValue(rate); + utterance2.volume = volume; + utterance2.onstart = () => { + isPlaying.value = true; + status.value = "play"; + }; + utterance2.onpause = () => { + isPlaying.value = false; + status.value = "pause"; + }; + utterance2.onresume = () => { + isPlaying.value = true; + status.value = "play"; + }; + utterance2.onend = () => { + isPlaying.value = false; + status.value = "end"; + }; + utterance2.onerror = (event) => { + error.value = event; + }; + }; + const utterance = computed(() => { + isPlaying.value = false; + status.value = "init"; + const newUtterance = new SpeechSynthesisUtterance(spokenText.value); + bindEventsForUtterance(newUtterance); + return newUtterance; + }); + const speak = () => { + synth.cancel(); + if (utterance) + synth.speak(utterance.value); + }; + const stop = () => { + synth.cancel(); + isPlaying.value = false; + }; + if (isSupported.value) { + bindEventsForUtterance(utterance.value); + watch(lang, (lang2) => { + if (utterance.value && !isPlaying.value) + utterance.value.lang = lang2; + }); + if (options.voice) { + watch(options.voice, () => { + synth.cancel(); + }); + } + watch(isPlaying, () => { + if (isPlaying.value) + synth.resume(); + else + synth.pause(); + }); + } + tryOnScopeDispose(() => { + isPlaying.value = false; + }); + return { + isSupported, + isPlaying, + status, + utterance, + error, + stop, + toggle, + speak + }; +} +function useStepper(steps, initialStep) { + const stepsRef = ref(steps); + const stepNames = computed(() => Array.isArray(stepsRef.value) ? stepsRef.value : Object.keys(stepsRef.value)); + const index = ref(stepNames.value.indexOf(initialStep != null ? initialStep : stepNames.value[0])); + const current = computed(() => at(index.value)); + const isFirst = computed(() => index.value === 0); + const isLast = computed(() => index.value === stepNames.value.length - 1); + const next = computed(() => stepNames.value[index.value + 1]); + const previous = computed(() => stepNames.value[index.value - 1]); + function at(index2) { + if (Array.isArray(stepsRef.value)) + return stepsRef.value[index2]; + return stepsRef.value[stepNames.value[index2]]; + } + function get2(step) { + if (!stepNames.value.includes(step)) + return; + return at(stepNames.value.indexOf(step)); + } + function goTo(step) { + if (stepNames.value.includes(step)) + index.value = stepNames.value.indexOf(step); + } + function goToNext() { + if (isLast.value) + return; + index.value++; + } + function goToPrevious() { + if (isFirst.value) + return; + index.value--; + } + function goBackTo(step) { + if (isAfter(step)) + goTo(step); + } + function isNext(step) { + return stepNames.value.indexOf(step) === index.value + 1; + } + function isPrevious(step) { + return stepNames.value.indexOf(step) === index.value - 1; + } + function isCurrent(step) { + return stepNames.value.indexOf(step) === index.value; + } + function isBefore(step) { + return index.value < stepNames.value.indexOf(step); + } + function isAfter(step) { + return index.value > stepNames.value.indexOf(step); + } + return { + steps: stepsRef, + stepNames, + index, + current, + next, + previous, + isFirst, + isLast, + at, + get: get2, + goTo, + goToNext, + goToPrevious, + goBackTo, + isNext, + isPrevious, + isCurrent, + isBefore, + isAfter + }; +} +function useStorageAsync(key, initialValue, storage, options = {}) { + var _a; + const { + flush = "pre", + deep = true, + listenToStorageChanges = true, + writeDefaults = true, + mergeDefaults = false, + shallow, + window: window2 = defaultWindow, + eventFilter, + onError = (e) => { + console.error(e); + } + } = options; + const rawInit = toValue(initialValue); + const type = guessSerializerType(rawInit); + const data = (shallow ? shallowRef : ref)(toValue(initialValue)); + const serializer = (_a = options.serializer) != null ? _a : StorageSerializers[type]; + if (!storage) { + try { + storage = getSSRHandler("getDefaultStorageAsync", () => { + var _a2; + return (_a2 = defaultWindow) == null ? void 0 : _a2.localStorage; + })(); + } catch (e) { + onError(e); + } + } + async function read(event) { + if (!storage || event && event.key !== key) + return; + try { + const rawValue = event ? event.newValue : await storage.getItem(key); + if (rawValue == null) { + data.value = rawInit; + if (writeDefaults && rawInit !== null) + await storage.setItem(key, await serializer.write(rawInit)); + } else if (mergeDefaults) { + const value = await serializer.read(rawValue); + if (typeof mergeDefaults === "function") + data.value = mergeDefaults(value, rawInit); + else if (type === "object" && !Array.isArray(value)) + data.value = { ...rawInit, ...value }; + else data.value = value; + } else { + data.value = await serializer.read(rawValue); + } + } catch (e) { + onError(e); + } + } + read(); + if (window2 && listenToStorageChanges) + useEventListener(window2, "storage", (e) => Promise.resolve().then(() => read(e)), { passive: true }); + if (storage) { + watchWithFilter( + data, + async () => { + try { + if (data.value == null) + await storage.removeItem(key); + else + await storage.setItem(key, await serializer.write(data.value)); + } catch (e) { + onError(e); + } + }, + { + flush, + deep, + eventFilter + } + ); + } + return data; +} +var _id = 0; +function useStyleTag(css, options = {}) { + const isLoaded = shallowRef(false); + const { + document: document2 = defaultDocument, + immediate = true, + manual = false, + id = `vueuse_styletag_${++_id}` + } = options; + const cssRef = shallowRef(css); + let stop = () => { + }; + const load = () => { + if (!document2) + return; + const el = document2.getElementById(id) || document2.createElement("style"); + if (!el.isConnected) { + el.id = id; + if (options.media) + el.media = options.media; + document2.head.appendChild(el); + } + if (isLoaded.value) + return; + stop = watch( + cssRef, + (value) => { + el.textContent = value; + }, + { immediate: true } + ); + isLoaded.value = true; + }; + const unload = () => { + if (!document2 || !isLoaded.value) + return; + stop(); + document2.head.removeChild(document2.getElementById(id)); + isLoaded.value = false; + }; + if (immediate && !manual) + tryOnMounted(load); + if (!manual) + tryOnScopeDispose(unload); + return { + id, + css: cssRef, + unload, + load, + isLoaded: readonly(isLoaded) + }; +} +function useSwipe(target, options = {}) { + const { + threshold = 50, + onSwipe, + onSwipeEnd, + onSwipeStart, + passive = true + } = options; + const coordsStart = reactive({ x: 0, y: 0 }); + const coordsEnd = reactive({ x: 0, y: 0 }); + const diffX = computed(() => coordsStart.x - coordsEnd.x); + const diffY = computed(() => coordsStart.y - coordsEnd.y); + const { max, abs } = Math; + const isThresholdExceeded = computed(() => max(abs(diffX.value), abs(diffY.value)) >= threshold); + const isSwiping = shallowRef(false); + const direction = computed(() => { + if (!isThresholdExceeded.value) + return "none"; + if (abs(diffX.value) > abs(diffY.value)) { + return diffX.value > 0 ? "left" : "right"; + } else { + return diffY.value > 0 ? "up" : "down"; + } + }); + const getTouchEventCoords = (e) => [e.touches[0].clientX, e.touches[0].clientY]; + const updateCoordsStart = (x, y) => { + coordsStart.x = x; + coordsStart.y = y; + }; + const updateCoordsEnd = (x, y) => { + coordsEnd.x = x; + coordsEnd.y = y; + }; + const listenerOptions = { passive, capture: !passive }; + const onTouchEnd = (e) => { + if (isSwiping.value) + onSwipeEnd == null ? void 0 : onSwipeEnd(e, direction.value); + isSwiping.value = false; + }; + const stops = [ + useEventListener(target, "touchstart", (e) => { + if (e.touches.length !== 1) + return; + const [x, y] = getTouchEventCoords(e); + updateCoordsStart(x, y); + updateCoordsEnd(x, y); + onSwipeStart == null ? void 0 : onSwipeStart(e); + }, listenerOptions), + useEventListener(target, "touchmove", (e) => { + if (e.touches.length !== 1) + return; + const [x, y] = getTouchEventCoords(e); + updateCoordsEnd(x, y); + if (listenerOptions.capture && !listenerOptions.passive && Math.abs(diffX.value) > Math.abs(diffY.value)) + e.preventDefault(); + if (!isSwiping.value && isThresholdExceeded.value) + isSwiping.value = true; + if (isSwiping.value) + onSwipe == null ? void 0 : onSwipe(e); + }, listenerOptions), + useEventListener(target, ["touchend", "touchcancel"], onTouchEnd, listenerOptions) + ]; + const stop = () => stops.forEach((s) => s()); + return { + isSwiping, + direction, + coordsStart, + coordsEnd, + lengthX: diffX, + lengthY: diffY, + stop, + // TODO: Remove in the next major version + isPassiveEventSupported: true + }; +} +function useTemplateRefsList() { + const refs = ref([]); + refs.value.set = (el) => { + if (el) + refs.value.push(el); + }; + onBeforeUpdate(() => { + refs.value.length = 0; + }); + return refs; +} +function useTextDirection(options = {}) { + const { + document: document2 = defaultDocument, + selector = "html", + observe = false, + initialValue = "ltr" + } = options; + function getValue2() { + var _a, _b; + return (_b = (_a = document2 == null ? void 0 : document2.querySelector(selector)) == null ? void 0 : _a.getAttribute("dir")) != null ? _b : initialValue; + } + const dir = ref(getValue2()); + tryOnMounted(() => dir.value = getValue2()); + if (observe && document2) { + useMutationObserver( + document2.querySelector(selector), + () => dir.value = getValue2(), + { attributes: true } + ); + } + return computed({ + get() { + return dir.value; + }, + set(v) { + var _a, _b; + dir.value = v; + if (!document2) + return; + if (dir.value) + (_a = document2.querySelector(selector)) == null ? void 0 : _a.setAttribute("dir", dir.value); + else + (_b = document2.querySelector(selector)) == null ? void 0 : _b.removeAttribute("dir"); + } + }); +} +function getRangesFromSelection(selection) { + var _a; + const rangeCount = (_a = selection.rangeCount) != null ? _a : 0; + return Array.from({ length: rangeCount }, (_, i) => selection.getRangeAt(i)); +} +function useTextSelection(options = {}) { + const { + window: window2 = defaultWindow + } = options; + const selection = ref(null); + const text = computed(() => { + var _a, _b; + return (_b = (_a = selection.value) == null ? void 0 : _a.toString()) != null ? _b : ""; + }); + const ranges = computed(() => selection.value ? getRangesFromSelection(selection.value) : []); + const rects = computed(() => ranges.value.map((range) => range.getBoundingClientRect())); + function onSelectionChange() { + selection.value = null; + if (window2) + selection.value = window2.getSelection(); + } + if (window2) + useEventListener(window2.document, "selectionchange", onSelectionChange, { passive: true }); + return { + text, + rects, + ranges, + selection + }; +} +function tryRequestAnimationFrame(window2 = defaultWindow, fn) { + if (window2 && typeof window2.requestAnimationFrame === "function") { + window2.requestAnimationFrame(fn); + } else { + fn(); + } +} +function useTextareaAutosize(options = {}) { + var _a, _b; + const { window: window2 = defaultWindow } = options; + const textarea = toRef2(options == null ? void 0 : options.element); + const input = toRef2((_a = options == null ? void 0 : options.input) != null ? _a : ""); + const styleProp = (_b = options == null ? void 0 : options.styleProp) != null ? _b : "height"; + const textareaScrollHeight = shallowRef(1); + const textareaOldWidth = shallowRef(0); + function triggerResize() { + var _a2; + if (!textarea.value) + return; + let height = ""; + textarea.value.style[styleProp] = "1px"; + textareaScrollHeight.value = (_a2 = textarea.value) == null ? void 0 : _a2.scrollHeight; + const _styleTarget = toValue(options == null ? void 0 : options.styleTarget); + if (_styleTarget) + _styleTarget.style[styleProp] = `${textareaScrollHeight.value}px`; + else + height = `${textareaScrollHeight.value}px`; + textarea.value.style[styleProp] = height; + } + watch([input, textarea], () => nextTick(triggerResize), { immediate: true }); + watch(textareaScrollHeight, () => { + var _a2; + return (_a2 = options == null ? void 0 : options.onResize) == null ? void 0 : _a2.call(options); + }); + useResizeObserver(textarea, ([{ contentRect }]) => { + if (textareaOldWidth.value === contentRect.width) + return; + tryRequestAnimationFrame(window2, () => { + textareaOldWidth.value = contentRect.width; + triggerResize(); + }); + }); + if (options == null ? void 0 : options.watch) + watch(options.watch, triggerResize, { immediate: true, deep: true }); + return { + textarea, + input, + triggerResize + }; +} +function useThrottledRefHistory(source, options = {}) { + const { throttle = 200, trailing = true } = options; + const filter = throttleFilter(throttle, trailing); + const history = useRefHistory(source, { ...options, eventFilter: filter }); + return { + ...history + }; +} +var DEFAULT_UNITS = [ + { max: 6e4, value: 1e3, name: "second" }, + { max: 276e4, value: 6e4, name: "minute" }, + { max: 72e6, value: 36e5, name: "hour" }, + { max: 5184e5, value: 864e5, name: "day" }, + { max: 24192e5, value: 6048e5, name: "week" }, + { max: 28512e6, value: 2592e6, name: "month" }, + { max: Number.POSITIVE_INFINITY, value: 31536e6, name: "year" } +]; +var DEFAULT_MESSAGES = { + justNow: "just now", + past: (n) => n.match(/\d/) ? `${n} ago` : n, + future: (n) => n.match(/\d/) ? `in ${n}` : n, + month: (n, past) => n === 1 ? past ? "last month" : "next month" : `${n} month${n > 1 ? "s" : ""}`, + year: (n, past) => n === 1 ? past ? "last year" : "next year" : `${n} year${n > 1 ? "s" : ""}`, + day: (n, past) => n === 1 ? past ? "yesterday" : "tomorrow" : `${n} day${n > 1 ? "s" : ""}`, + week: (n, past) => n === 1 ? past ? "last week" : "next week" : `${n} week${n > 1 ? "s" : ""}`, + hour: (n) => `${n} hour${n > 1 ? "s" : ""}`, + minute: (n) => `${n} minute${n > 1 ? "s" : ""}`, + second: (n) => `${n} second${n > 1 ? "s" : ""}`, + invalid: "" +}; +function DEFAULT_FORMATTER(date) { + return date.toISOString().slice(0, 10); +} +function useTimeAgo(time, options = {}) { + const { + controls: exposeControls = false, + updateInterval = 3e4 + } = options; + const { now: now2, ...controls } = useNow({ interval: updateInterval, controls: true }); + const timeAgo = computed(() => formatTimeAgo(new Date(toValue(time)), options, toValue(now2))); + if (exposeControls) { + return { + timeAgo, + ...controls + }; + } else { + return timeAgo; + } +} +function formatTimeAgo(from, options = {}, now2 = Date.now()) { + var _a; + const { + max, + messages = DEFAULT_MESSAGES, + fullDateFormatter = DEFAULT_FORMATTER, + units = DEFAULT_UNITS, + showSecond = false, + rounding = "round" + } = options; + const roundFn = typeof rounding === "number" ? (n) => +n.toFixed(rounding) : Math[rounding]; + const diff = +now2 - +from; + const absDiff = Math.abs(diff); + function getValue2(diff2, unit) { + return roundFn(Math.abs(diff2) / unit.value); + } + function format(diff2, unit) { + const val = getValue2(diff2, unit); + const past = diff2 > 0; + const str = applyFormat(unit.name, val, past); + return applyFormat(past ? "past" : "future", str, past); + } + function applyFormat(name, val, isPast) { + const formatter = messages[name]; + if (typeof formatter === "function") + return formatter(val, isPast); + return formatter.replace("{0}", val.toString()); + } + if (absDiff < 6e4 && !showSecond) + return messages.justNow; + if (typeof max === "number" && absDiff > max) + return fullDateFormatter(new Date(from)); + if (typeof max === "string") { + const unitMax = (_a = units.find((i) => i.name === max)) == null ? void 0 : _a.max; + if (unitMax && absDiff > unitMax) + return fullDateFormatter(new Date(from)); + } + for (const [idx, unit] of units.entries()) { + const val = getValue2(diff, unit); + if (val <= 0 && units[idx - 1]) + return format(diff, units[idx - 1]); + if (absDiff < unit.max) + return format(diff, unit); + } + return messages.invalid; +} +function useTimeoutPoll(fn, interval, options = {}) { + const { + immediate = true, + immediateCallback = false + } = options; + const { start } = useTimeoutFn(loop, interval, { immediate }); + const isActive = shallowRef(false); + async function loop() { + if (!isActive.value) + return; + await fn(); + start(); + } + function resume() { + if (!isActive.value) { + isActive.value = true; + if (immediateCallback) + fn(); + start(); + } + } + function pause() { + isActive.value = false; + } + if (immediate && isClient) + resume(); + tryOnScopeDispose(pause); + return { + isActive, + pause, + resume + }; +} +function useTimestamp(options = {}) { + const { + controls: exposeControls = false, + offset = 0, + immediate = true, + interval = "requestAnimationFrame", + callback + } = options; + const ts = shallowRef(timestamp() + offset); + const update = () => ts.value = timestamp() + offset; + const cb = callback ? () => { + update(); + callback(ts.value); + } : update; + const controls = interval === "requestAnimationFrame" ? useRafFn(cb, { immediate }) : useIntervalFn(cb, interval, { immediate }); + if (exposeControls) { + return { + timestamp: ts, + ...controls + }; + } else { + return ts; + } +} +function useTitle(newTitle = null, options = {}) { + var _a, _b, _c; + const { + document: document2 = defaultDocument, + restoreOnUnmount = (t) => t + } = options; + const originalTitle = (_a = document2 == null ? void 0 : document2.title) != null ? _a : ""; + const title = toRef2((_b = newTitle != null ? newTitle : document2 == null ? void 0 : document2.title) != null ? _b : null); + const isReadonly2 = !!(newTitle && typeof newTitle === "function"); + function format(t) { + if (!("titleTemplate" in options)) + return t; + const template = options.titleTemplate || "%s"; + return typeof template === "function" ? template(t) : toValue(template).replace(/%s/g, t); + } + watch( + title, + (newValue, oldValue) => { + if (newValue !== oldValue && document2) + document2.title = format(newValue != null ? newValue : ""); + }, + { immediate: true } + ); + if (options.observe && !options.titleTemplate && document2 && !isReadonly2) { + useMutationObserver( + (_c = document2.head) == null ? void 0 : _c.querySelector("title"), + () => { + if (document2 && document2.title !== title.value) + title.value = format(document2.title); + }, + { childList: true } + ); + } + tryOnScopeDispose(() => { + if (restoreOnUnmount) { + const restoredTitle = restoreOnUnmount(originalTitle, title.value || ""); + if (restoredTitle != null && document2) + document2.title = restoredTitle; + } + }); + return title; +} +var _TransitionPresets = { + easeInSine: [0.12, 0, 0.39, 0], + easeOutSine: [0.61, 1, 0.88, 1], + easeInOutSine: [0.37, 0, 0.63, 1], + easeInQuad: [0.11, 0, 0.5, 0], + easeOutQuad: [0.5, 1, 0.89, 1], + easeInOutQuad: [0.45, 0, 0.55, 1], + easeInCubic: [0.32, 0, 0.67, 0], + easeOutCubic: [0.33, 1, 0.68, 1], + easeInOutCubic: [0.65, 0, 0.35, 1], + easeInQuart: [0.5, 0, 0.75, 0], + easeOutQuart: [0.25, 1, 0.5, 1], + easeInOutQuart: [0.76, 0, 0.24, 1], + easeInQuint: [0.64, 0, 0.78, 0], + easeOutQuint: [0.22, 1, 0.36, 1], + easeInOutQuint: [0.83, 0, 0.17, 1], + easeInExpo: [0.7, 0, 0.84, 0], + easeOutExpo: [0.16, 1, 0.3, 1], + easeInOutExpo: [0.87, 0, 0.13, 1], + easeInCirc: [0.55, 0, 1, 0.45], + easeOutCirc: [0, 0.55, 0.45, 1], + easeInOutCirc: [0.85, 0, 0.15, 1], + easeInBack: [0.36, 0, 0.66, -0.56], + easeOutBack: [0.34, 1.56, 0.64, 1], + easeInOutBack: [0.68, -0.6, 0.32, 1.6] +}; +var TransitionPresets = Object.assign({}, { linear: identity }, _TransitionPresets); +function createEasingFunction([p0, p1, p2, p3]) { + const a = (a1, a2) => 1 - 3 * a2 + 3 * a1; + const b = (a1, a2) => 3 * a2 - 6 * a1; + const c = (a1) => 3 * a1; + const calcBezier = (t, a1, a2) => ((a(a1, a2) * t + b(a1, a2)) * t + c(a1)) * t; + const getSlope = (t, a1, a2) => 3 * a(a1, a2) * t * t + 2 * b(a1, a2) * t + c(a1); + const getTforX = (x) => { + let aGuessT = x; + for (let i = 0; i < 4; ++i) { + const currentSlope = getSlope(aGuessT, p0, p2); + if (currentSlope === 0) + return aGuessT; + const currentX = calcBezier(aGuessT, p0, p2) - x; + aGuessT -= currentX / currentSlope; + } + return aGuessT; + }; + return (x) => p0 === p1 && p2 === p3 ? x : calcBezier(getTforX(x), p1, p3); +} +function lerp(a, b, alpha) { + return a + alpha * (b - a); +} +function toVec(t) { + return (typeof t === "number" ? [t] : t) || []; +} +function executeTransition(source, from, to, options = {}) { + var _a, _b; + const fromVal = toValue(from); + const toVal = toValue(to); + const v1 = toVec(fromVal); + const v2 = toVec(toVal); + const duration = (_a = toValue(options.duration)) != null ? _a : 1e3; + const startedAt = Date.now(); + const endAt = Date.now() + duration; + const trans = typeof options.transition === "function" ? options.transition : (_b = toValue(options.transition)) != null ? _b : identity; + const ease = typeof trans === "function" ? trans : createEasingFunction(trans); + return new Promise((resolve) => { + source.value = fromVal; + const tick = () => { + var _a2; + if ((_a2 = options.abort) == null ? void 0 : _a2.call(options)) { + resolve(); + return; + } + const now2 = Date.now(); + const alpha = ease((now2 - startedAt) / duration); + const arr = toVec(source.value).map((n, i) => lerp(v1[i], v2[i], alpha)); + if (Array.isArray(source.value)) + source.value = arr.map((n, i) => { + var _a3, _b2; + return lerp((_a3 = v1[i]) != null ? _a3 : 0, (_b2 = v2[i]) != null ? _b2 : 0, alpha); + }); + else if (typeof source.value === "number") + source.value = arr[0]; + if (now2 < endAt) { + requestAnimationFrame(tick); + } else { + source.value = toVal; + resolve(); + } + }; + tick(); + }); +} +function useTransition(source, options = {}) { + let currentId = 0; + const sourceVal = () => { + const v = toValue(source); + return typeof v === "number" ? v : v.map(toValue); + }; + const outputRef = ref(sourceVal()); + watch(sourceVal, async (to) => { + var _a, _b; + if (toValue(options.disabled)) + return; + const id = ++currentId; + if (options.delay) + await promiseTimeout(toValue(options.delay)); + if (id !== currentId) + return; + const toVal = Array.isArray(to) ? to.map(toValue) : toValue(to); + (_a = options.onStarted) == null ? void 0 : _a.call(options); + await executeTransition(outputRef, outputRef.value, toVal, { + ...options, + abort: () => { + var _a2; + return id !== currentId || ((_a2 = options.abort) == null ? void 0 : _a2.call(options)); + } + }); + (_b = options.onFinished) == null ? void 0 : _b.call(options); + }, { deep: true }); + watch(() => toValue(options.disabled), (disabled) => { + if (disabled) { + currentId++; + outputRef.value = sourceVal(); + } + }); + tryOnScopeDispose(() => { + currentId++; + }); + return computed(() => toValue(options.disabled) ? sourceVal() : outputRef.value); +} +function useUrlSearchParams(mode = "history", options = {}) { + const { + initialValue = {}, + removeNullishValues = true, + removeFalsyValues = false, + write: enableWrite = true, + writeMode = "replace", + window: window2 = defaultWindow + } = options; + if (!window2) + return reactive(initialValue); + const state = reactive({}); + function getRawParams() { + if (mode === "history") { + return window2.location.search || ""; + } else if (mode === "hash") { + const hash = window2.location.hash || ""; + const index = hash.indexOf("?"); + return index > 0 ? hash.slice(index) : ""; + } else { + return (window2.location.hash || "").replace(/^#/, ""); + } + } + function constructQuery(params) { + const stringified = params.toString(); + if (mode === "history") + return `${stringified ? `?${stringified}` : ""}${window2.location.hash || ""}`; + if (mode === "hash-params") + return `${window2.location.search || ""}${stringified ? `#${stringified}` : ""}`; + const hash = window2.location.hash || "#"; + const index = hash.indexOf("?"); + if (index > 0) + return `${window2.location.search || ""}${hash.slice(0, index)}${stringified ? `?${stringified}` : ""}`; + return `${window2.location.search || ""}${hash}${stringified ? `?${stringified}` : ""}`; + } + function read() { + return new URLSearchParams(getRawParams()); + } + function updateState(params) { + const unusedKeys = new Set(Object.keys(state)); + for (const key of params.keys()) { + const paramsForKey = params.getAll(key); + state[key] = paramsForKey.length > 1 ? paramsForKey : params.get(key) || ""; + unusedKeys.delete(key); + } + Array.from(unusedKeys).forEach((key) => delete state[key]); + } + const { pause, resume } = watchPausable( + state, + () => { + const params = new URLSearchParams(""); + Object.keys(state).forEach((key) => { + const mapEntry = state[key]; + if (Array.isArray(mapEntry)) + mapEntry.forEach((value) => params.append(key, value)); + else if (removeNullishValues && mapEntry == null) + params.delete(key); + else if (removeFalsyValues && !mapEntry) + params.delete(key); + else + params.set(key, mapEntry); + }); + write(params, false); + }, + { deep: true } + ); + function write(params, shouldUpdate) { + pause(); + if (shouldUpdate) + updateState(params); + if (writeMode === "replace") { + window2.history.replaceState( + window2.history.state, + window2.document.title, + window2.location.pathname + constructQuery(params) + ); + } else { + window2.history.pushState( + window2.history.state, + window2.document.title, + window2.location.pathname + constructQuery(params) + ); + } + resume(); + } + function onChanged() { + if (!enableWrite) + return; + write(read(), true); + } + const listenerOptions = { passive: true }; + useEventListener(window2, "popstate", onChanged, listenerOptions); + if (mode !== "history") + useEventListener(window2, "hashchange", onChanged, listenerOptions); + const initial = read(); + if (initial.keys().next().value) + updateState(initial); + else + Object.assign(state, initialValue); + return state; +} +function useUserMedia(options = {}) { + var _a, _b; + const enabled = shallowRef((_a = options.enabled) != null ? _a : false); + const autoSwitch = shallowRef((_b = options.autoSwitch) != null ? _b : true); + const constraints = ref(options.constraints); + const { navigator: navigator2 = defaultNavigator } = options; + const isSupported = useSupported(() => { + var _a2; + return (_a2 = navigator2 == null ? void 0 : navigator2.mediaDevices) == null ? void 0 : _a2.getUserMedia; + }); + const stream = shallowRef(); + function getDeviceOptions(type) { + switch (type) { + case "video": { + if (constraints.value) + return constraints.value.video || false; + break; + } + case "audio": { + if (constraints.value) + return constraints.value.audio || false; + break; + } + } + } + async function _start() { + if (!isSupported.value || stream.value) + return; + stream.value = await navigator2.mediaDevices.getUserMedia({ + video: getDeviceOptions("video"), + audio: getDeviceOptions("audio") + }); + return stream.value; + } + function _stop() { + var _a2; + (_a2 = stream.value) == null ? void 0 : _a2.getTracks().forEach((t) => t.stop()); + stream.value = void 0; + } + function stop() { + _stop(); + enabled.value = false; + } + async function start() { + await _start(); + if (stream.value) + enabled.value = true; + return stream.value; + } + async function restart() { + _stop(); + return await start(); + } + watch( + enabled, + (v) => { + if (v) + _start(); + else _stop(); + }, + { immediate: true } + ); + watch( + constraints, + () => { + if (autoSwitch.value && stream.value) + restart(); + }, + { immediate: true } + ); + tryOnScopeDispose(() => { + stop(); + }); + return { + isSupported, + stream, + start, + stop, + restart, + constraints, + enabled, + autoSwitch + }; +} +function useVModel(props, key, emit, options = {}) { + var _a, _b, _c; + const { + clone = false, + passive = false, + eventName, + deep = false, + defaultValue, + shouldEmit + } = options; + const vm = getCurrentInstance(); + const _emit = emit || (vm == null ? void 0 : vm.emit) || ((_a = vm == null ? void 0 : vm.$emit) == null ? void 0 : _a.bind(vm)) || ((_c = (_b = vm == null ? void 0 : vm.proxy) == null ? void 0 : _b.$emit) == null ? void 0 : _c.bind(vm == null ? void 0 : vm.proxy)); + let event = eventName; + if (!key) { + key = "modelValue"; + } + event = event || `update:${key.toString()}`; + const cloneFn = (val) => !clone ? val : typeof clone === "function" ? clone(val) : cloneFnJSON(val); + const getValue2 = () => isDef(props[key]) ? cloneFn(props[key]) : defaultValue; + const triggerEmit = (value) => { + if (shouldEmit) { + if (shouldEmit(value)) + _emit(event, value); + } else { + _emit(event, value); + } + }; + if (passive) { + const initialValue = getValue2(); + const proxy = ref(initialValue); + let isUpdating = false; + watch( + () => props[key], + (v) => { + if (!isUpdating) { + isUpdating = true; + proxy.value = cloneFn(v); + nextTick(() => isUpdating = false); + } + } + ); + watch( + proxy, + (v) => { + if (!isUpdating && (v !== props[key] || deep)) + triggerEmit(v); + }, + { deep } + ); + return proxy; + } else { + return computed({ + get() { + return getValue2(); + }, + set(value) { + triggerEmit(value); + } + }); + } +} +function useVModels(props, emit, options = {}) { + const ret = {}; + for (const key in props) { + ret[key] = useVModel( + props, + key, + emit, + options + ); + } + return ret; +} +function useVibrate(options) { + const { + pattern = [], + interval = 0, + navigator: navigator2 = defaultNavigator + } = options || {}; + const isSupported = useSupported(() => typeof navigator2 !== "undefined" && "vibrate" in navigator2); + const patternRef = toRef2(pattern); + let intervalControls; + const vibrate = (pattern2 = patternRef.value) => { + if (isSupported.value) + navigator2.vibrate(pattern2); + }; + const stop = () => { + if (isSupported.value) + navigator2.vibrate(0); + intervalControls == null ? void 0 : intervalControls.pause(); + }; + if (interval > 0) { + intervalControls = useIntervalFn( + vibrate, + interval, + { + immediate: false, + immediateCallback: false + } + ); + } + return { + isSupported, + pattern, + intervalControls, + vibrate, + stop + }; +} +function useVirtualList(list, options) { + const { containerStyle, wrapperProps, scrollTo, calculateRange, currentList, containerRef } = "itemHeight" in options ? useVerticalVirtualList(options, list) : useHorizontalVirtualList(options, list); + return { + list: currentList, + scrollTo, + containerProps: { + ref: containerRef, + onScroll: () => { + calculateRange(); + }, + style: containerStyle + }, + wrapperProps + }; +} +function useVirtualListResources(list) { + const containerRef = shallowRef(null); + const size = useElementSize(containerRef); + const currentList = ref([]); + const source = shallowRef(list); + const state = ref({ start: 0, end: 10 }); + return { state, source, currentList, size, containerRef }; +} +function createGetViewCapacity(state, source, itemSize) { + return (containerSize) => { + if (typeof itemSize === "number") + return Math.ceil(containerSize / itemSize); + const { start = 0 } = state.value; + let sum = 0; + let capacity = 0; + for (let i = start; i < source.value.length; i++) { + const size = itemSize(i); + sum += size; + capacity = i; + if (sum > containerSize) + break; + } + return capacity - start; + }; +} +function createGetOffset(source, itemSize) { + return (scrollDirection) => { + if (typeof itemSize === "number") + return Math.floor(scrollDirection / itemSize) + 1; + let sum = 0; + let offset = 0; + for (let i = 0; i < source.value.length; i++) { + const size = itemSize(i); + sum += size; + if (sum >= scrollDirection) { + offset = i; + break; + } + } + return offset + 1; + }; +} +function createCalculateRange(type, overscan, getOffset, getViewCapacity, { containerRef, state, currentList, source }) { + return () => { + const element = containerRef.value; + if (element) { + const offset = getOffset(type === "vertical" ? element.scrollTop : element.scrollLeft); + const viewCapacity = getViewCapacity(type === "vertical" ? element.clientHeight : element.clientWidth); + const from = offset - overscan; + const to = offset + viewCapacity + overscan; + state.value = { + start: from < 0 ? 0 : from, + end: to > source.value.length ? source.value.length : to + }; + currentList.value = source.value.slice(state.value.start, state.value.end).map((ele, index) => ({ + data: ele, + index: index + state.value.start + })); + } + }; +} +function createGetDistance(itemSize, source) { + return (index) => { + if (typeof itemSize === "number") { + const size2 = index * itemSize; + return size2; + } + const size = source.value.slice(0, index).reduce((sum, _, i) => sum + itemSize(i), 0); + return size; + }; +} +function useWatchForSizes(size, list, containerRef, calculateRange) { + watch([size.width, size.height, list, containerRef], () => { + calculateRange(); + }); +} +function createComputedTotalSize(itemSize, source) { + return computed(() => { + if (typeof itemSize === "number") + return source.value.length * itemSize; + return source.value.reduce((sum, _, index) => sum + itemSize(index), 0); + }); +} +var scrollToDictionaryForElementScrollKey = { + horizontal: "scrollLeft", + vertical: "scrollTop" +}; +function createScrollTo(type, calculateRange, getDistance, containerRef) { + return (index) => { + if (containerRef.value) { + containerRef.value[scrollToDictionaryForElementScrollKey[type]] = getDistance(index); + calculateRange(); + } + }; +} +function useHorizontalVirtualList(options, list) { + const resources = useVirtualListResources(list); + const { state, source, currentList, size, containerRef } = resources; + const containerStyle = { overflowX: "auto" }; + const { itemWidth, overscan = 5 } = options; + const getViewCapacity = createGetViewCapacity(state, source, itemWidth); + const getOffset = createGetOffset(source, itemWidth); + const calculateRange = createCalculateRange("horizontal", overscan, getOffset, getViewCapacity, resources); + const getDistanceLeft = createGetDistance(itemWidth, source); + const offsetLeft = computed(() => getDistanceLeft(state.value.start)); + const totalWidth = createComputedTotalSize(itemWidth, source); + useWatchForSizes(size, list, containerRef, calculateRange); + const scrollTo = createScrollTo("horizontal", calculateRange, getDistanceLeft, containerRef); + const wrapperProps = computed(() => { + return { + style: { + height: "100%", + width: `${totalWidth.value - offsetLeft.value}px`, + marginLeft: `${offsetLeft.value}px`, + display: "flex" + } + }; + }); + return { + scrollTo, + calculateRange, + wrapperProps, + containerStyle, + currentList, + containerRef + }; +} +function useVerticalVirtualList(options, list) { + const resources = useVirtualListResources(list); + const { state, source, currentList, size, containerRef } = resources; + const containerStyle = { overflowY: "auto" }; + const { itemHeight, overscan = 5 } = options; + const getViewCapacity = createGetViewCapacity(state, source, itemHeight); + const getOffset = createGetOffset(source, itemHeight); + const calculateRange = createCalculateRange("vertical", overscan, getOffset, getViewCapacity, resources); + const getDistanceTop = createGetDistance(itemHeight, source); + const offsetTop = computed(() => getDistanceTop(state.value.start)); + const totalHeight = createComputedTotalSize(itemHeight, source); + useWatchForSizes(size, list, containerRef, calculateRange); + const scrollTo = createScrollTo("vertical", calculateRange, getDistanceTop, containerRef); + const wrapperProps = computed(() => { + return { + style: { + width: "100%", + height: `${totalHeight.value - offsetTop.value}px`, + marginTop: `${offsetTop.value}px` + } + }; + }); + return { + calculateRange, + scrollTo, + containerStyle, + wrapperProps, + currentList, + containerRef + }; +} +function useWakeLock(options = {}) { + const { + navigator: navigator2 = defaultNavigator, + document: document2 = defaultDocument + } = options; + const requestedType = shallowRef(false); + const sentinel = shallowRef(null); + const documentVisibility = useDocumentVisibility({ document: document2 }); + const isSupported = useSupported(() => navigator2 && "wakeLock" in navigator2); + const isActive = computed(() => !!sentinel.value && documentVisibility.value === "visible"); + if (isSupported.value) { + useEventListener(sentinel, "release", () => { + var _a, _b; + requestedType.value = (_b = (_a = sentinel.value) == null ? void 0 : _a.type) != null ? _b : false; + }, { passive: true }); + whenever( + () => documentVisibility.value === "visible" && (document2 == null ? void 0 : document2.visibilityState) === "visible" && requestedType.value, + (type) => { + requestedType.value = false; + forceRequest(type); + } + ); + } + async function forceRequest(type) { + var _a; + await ((_a = sentinel.value) == null ? void 0 : _a.release()); + sentinel.value = isSupported.value ? await navigator2.wakeLock.request(type) : null; + } + async function request(type) { + if (documentVisibility.value === "visible") + await forceRequest(type); + else + requestedType.value = type; + } + async function release() { + requestedType.value = false; + const s = sentinel.value; + sentinel.value = null; + await (s == null ? void 0 : s.release()); + } + return { + sentinel, + isSupported, + isActive, + request, + forceRequest, + release + }; +} +function useWebNotification(options = {}) { + const { + window: window2 = defaultWindow, + requestPermissions: _requestForPermissions = true + } = options; + const defaultWebNotificationOptions = options; + const isSupported = useSupported(() => { + if (!window2 || !("Notification" in window2)) + return false; + if (Notification.permission === "granted") + return true; + try { + const notification2 = new Notification(""); + notification2.onshow = () => { + notification2.close(); + }; + } catch (e) { + if (e.name === "TypeError") + return false; + } + return true; + }); + const permissionGranted = shallowRef(isSupported.value && "permission" in Notification && Notification.permission === "granted"); + const notification = ref(null); + const ensurePermissions = async () => { + if (!isSupported.value) + return; + if (!permissionGranted.value && Notification.permission !== "denied") { + const result = await Notification.requestPermission(); + if (result === "granted") + permissionGranted.value = true; + } + return permissionGranted.value; + }; + const { on: onClick, trigger: clickTrigger } = createEventHook(); + const { on: onShow, trigger: showTrigger } = createEventHook(); + const { on: onError, trigger: errorTrigger } = createEventHook(); + const { on: onClose, trigger: closeTrigger } = createEventHook(); + const show = async (overrides) => { + if (!isSupported.value || !permissionGranted.value) + return; + const options2 = Object.assign({}, defaultWebNotificationOptions, overrides); + notification.value = new Notification(options2.title || "", options2); + notification.value.onclick = clickTrigger; + notification.value.onshow = showTrigger; + notification.value.onerror = errorTrigger; + notification.value.onclose = closeTrigger; + return notification.value; + }; + const close = () => { + if (notification.value) + notification.value.close(); + notification.value = null; + }; + if (_requestForPermissions) + tryOnMounted(ensurePermissions); + tryOnScopeDispose(close); + if (isSupported.value && window2) { + const document2 = window2.document; + useEventListener(document2, "visibilitychange", (e) => { + e.preventDefault(); + if (document2.visibilityState === "visible") { + close(); + } + }); + } + return { + isSupported, + notification, + ensurePermissions, + permissionGranted, + show, + close, + onClick, + onShow, + onError, + onClose + }; +} +var DEFAULT_PING_MESSAGE = "ping"; +function resolveNestedOptions(options) { + if (options === true) + return {}; + return options; +} +function useWebSocket(url, options = {}) { + const { + onConnected, + onDisconnected, + onError, + onMessage, + immediate = true, + autoConnect = true, + autoClose = true, + protocols = [] + } = options; + const data = ref(null); + const status = shallowRef("CLOSED"); + const wsRef = ref(); + const urlRef = toRef2(url); + let heartbeatPause; + let heartbeatResume; + let explicitlyClosed = false; + let retried = 0; + let bufferedData = []; + let retryTimeout; + let pongTimeoutWait; + const _sendBuffer = () => { + if (bufferedData.length && wsRef.value && status.value === "OPEN") { + for (const buffer of bufferedData) + wsRef.value.send(buffer); + bufferedData = []; + } + }; + const resetRetry = () => { + if (retryTimeout != null) { + clearTimeout(retryTimeout); + retryTimeout = void 0; + } + }; + const resetHeartbeat = () => { + clearTimeout(pongTimeoutWait); + pongTimeoutWait = void 0; + }; + const close = (code = 1e3, reason) => { + resetRetry(); + if (!isClient && !isWorker || !wsRef.value) + return; + explicitlyClosed = true; + resetHeartbeat(); + heartbeatPause == null ? void 0 : heartbeatPause(); + wsRef.value.close(code, reason); + wsRef.value = void 0; + }; + const send = (data2, useBuffer = true) => { + if (!wsRef.value || status.value !== "OPEN") { + if (useBuffer) + bufferedData.push(data2); + return false; + } + _sendBuffer(); + wsRef.value.send(data2); + return true; + }; + const _init = () => { + if (explicitlyClosed || typeof urlRef.value === "undefined") + return; + const ws = new WebSocket(urlRef.value, protocols); + wsRef.value = ws; + status.value = "CONNECTING"; + ws.onopen = () => { + status.value = "OPEN"; + retried = 0; + onConnected == null ? void 0 : onConnected(ws); + heartbeatResume == null ? void 0 : heartbeatResume(); + _sendBuffer(); + }; + ws.onclose = (ev) => { + status.value = "CLOSED"; + resetHeartbeat(); + heartbeatPause == null ? void 0 : heartbeatPause(); + onDisconnected == null ? void 0 : onDisconnected(ws, ev); + if (!explicitlyClosed && options.autoReconnect && (wsRef.value == null || ws === wsRef.value)) { + const { + retries = -1, + delay = 1e3, + onFailed + } = resolveNestedOptions(options.autoReconnect); + const checkRetires = typeof retries === "function" ? retries : () => typeof retries === "number" && (retries < 0 || retried < retries); + if (checkRetires(retried)) { + retried += 1; + retryTimeout = setTimeout(_init, delay); + } else { + onFailed == null ? void 0 : onFailed(); + } + } + }; + ws.onerror = (e) => { + onError == null ? void 0 : onError(ws, e); + }; + ws.onmessage = (e) => { + if (options.heartbeat) { + resetHeartbeat(); + const { + message = DEFAULT_PING_MESSAGE, + responseMessage = message + } = resolveNestedOptions(options.heartbeat); + if (e.data === toValue(responseMessage)) + return; + } + data.value = e.data; + onMessage == null ? void 0 : onMessage(ws, e); + }; + }; + if (options.heartbeat) { + const { + message = DEFAULT_PING_MESSAGE, + interval = 1e3, + pongTimeout = 1e3 + } = resolveNestedOptions(options.heartbeat); + const { pause, resume } = useIntervalFn( + () => { + send(toValue(message), false); + if (pongTimeoutWait != null) + return; + pongTimeoutWait = setTimeout(() => { + close(); + explicitlyClosed = false; + }, pongTimeout); + }, + interval, + { immediate: false } + ); + heartbeatPause = pause; + heartbeatResume = resume; + } + if (autoClose) { + if (isClient) + useEventListener("beforeunload", () => close(), { passive: true }); + tryOnScopeDispose(close); + } + const open = () => { + if (!isClient && !isWorker) + return; + close(); + explicitlyClosed = false; + retried = 0; + _init(); + }; + if (immediate) + open(); + if (autoConnect) + watch(urlRef, open); + return { + data, + status, + close, + send, + open, + ws: wsRef + }; +} +function useWebWorker(arg0, workerOptions, options) { + const { + window: window2 = defaultWindow + } = options != null ? options : {}; + const data = ref(null); + const worker = shallowRef(); + const post = (...args) => { + if (!worker.value) + return; + worker.value.postMessage(...args); + }; + const terminate = function terminate2() { + if (!worker.value) + return; + worker.value.terminate(); + }; + if (window2) { + if (typeof arg0 === "string") + worker.value = new Worker(arg0, workerOptions); + else if (typeof arg0 === "function") + worker.value = arg0(); + else + worker.value = arg0; + worker.value.onmessage = (e) => { + data.value = e.data; + }; + tryOnScopeDispose(() => { + if (worker.value) + worker.value.terminate(); + }); + } + return { + data, + post, + terminate, + worker + }; +} +function depsParser(deps, localDeps) { + if (deps.length === 0 && localDeps.length === 0) + return ""; + const depsString = deps.map((dep) => `'${dep}'`).toString(); + const depsFunctionString = localDeps.filter((dep) => typeof dep === "function").map((fn) => { + const str = fn.toString(); + if (str.trim().startsWith("function")) { + return str; + } else { + const name = fn.name; + return `const ${name} = ${str}`; + } + }).join(";"); + const importString = `importScripts(${depsString});`; + return `${depsString.trim() === "" ? "" : importString} ${depsFunctionString}`; +} +function jobRunner(userFunc) { + return (e) => { + const userFuncArgs = e.data[0]; + return Promise.resolve(userFunc.apply(void 0, userFuncArgs)).then((result) => { + postMessage(["SUCCESS", result]); + }).catch((error) => { + postMessage(["ERROR", error]); + }); + }; +} +function createWorkerBlobUrl(fn, deps, localDeps) { + const blobCode = `${depsParser(deps, localDeps)}; onmessage=(${jobRunner})(${fn})`; + const blob = new Blob([blobCode], { type: "text/javascript" }); + const url = URL.createObjectURL(blob); + return url; +} +function useWebWorkerFn(fn, options = {}) { + const { + dependencies = [], + localDependencies = [], + timeout, + window: window2 = defaultWindow + } = options; + const worker = ref(); + const workerStatus = shallowRef("PENDING"); + const promise = ref({}); + const timeoutId = shallowRef(); + const workerTerminate = (status = "PENDING") => { + if (worker.value && worker.value._url && window2) { + worker.value.terminate(); + URL.revokeObjectURL(worker.value._url); + promise.value = {}; + worker.value = void 0; + window2.clearTimeout(timeoutId.value); + workerStatus.value = status; + } + }; + workerTerminate(); + tryOnScopeDispose(workerTerminate); + const generateWorker = () => { + const blobUrl = createWorkerBlobUrl(fn, dependencies, localDependencies); + const newWorker = new Worker(blobUrl); + newWorker._url = blobUrl; + newWorker.onmessage = (e) => { + const { resolve = () => { + }, reject = () => { + } } = promise.value; + const [status, result] = e.data; + switch (status) { + case "SUCCESS": + resolve(result); + workerTerminate(status); + break; + default: + reject(result); + workerTerminate("ERROR"); + break; + } + }; + newWorker.onerror = (e) => { + const { reject = () => { + } } = promise.value; + e.preventDefault(); + reject(e); + workerTerminate("ERROR"); + }; + if (timeout) { + timeoutId.value = setTimeout( + () => workerTerminate("TIMEOUT_EXPIRED"), + timeout + ); + } + return newWorker; + }; + const callWorker = (...fnArgs) => new Promise((resolve, reject) => { + var _a; + promise.value = { + resolve, + reject + }; + (_a = worker.value) == null ? void 0 : _a.postMessage([[...fnArgs]]); + workerStatus.value = "RUNNING"; + }); + const workerFn = (...fnArgs) => { + if (workerStatus.value === "RUNNING") { + console.error( + "[useWebWorkerFn] You can only run one instance of the worker at a time." + ); + return Promise.reject(); + } + worker.value = generateWorker(); + return callWorker(...fnArgs); + }; + return { + workerFn, + workerStatus, + workerTerminate + }; +} +function useWindowFocus(options = {}) { + const { window: window2 = defaultWindow } = options; + if (!window2) + return shallowRef(false); + const focused = shallowRef(window2.document.hasFocus()); + const listenerOptions = { passive: true }; + useEventListener(window2, "blur", () => { + focused.value = false; + }, listenerOptions); + useEventListener(window2, "focus", () => { + focused.value = true; + }, listenerOptions); + return focused; +} +function useWindowScroll(options = {}) { + const { window: window2 = defaultWindow, ...rest } = options; + return useScroll(window2, rest); +} +function useWindowSize(options = {}) { + const { + window: window2 = defaultWindow, + initialWidth = Number.POSITIVE_INFINITY, + initialHeight = Number.POSITIVE_INFINITY, + listenOrientation = true, + includeScrollbar = true, + type = "inner" + } = options; + const width = shallowRef(initialWidth); + const height = shallowRef(initialHeight); + const update = () => { + if (window2) { + if (type === "outer") { + width.value = window2.outerWidth; + height.value = window2.outerHeight; + } else if (type === "visual" && window2.visualViewport) { + const { width: visualViewportWidth, height: visualViewportHeight, scale } = window2.visualViewport; + width.value = Math.round(visualViewportWidth * scale); + height.value = Math.round(visualViewportHeight * scale); + } else if (includeScrollbar) { + width.value = window2.innerWidth; + height.value = window2.innerHeight; + } else { + width.value = window2.document.documentElement.clientWidth; + height.value = window2.document.documentElement.clientHeight; + } + } + }; + update(); + tryOnMounted(update); + const listenerOptions = { passive: true }; + useEventListener("resize", update, listenerOptions); + if (window2 && type === "visual" && window2.visualViewport) { + useEventListener(window2.visualViewport, "resize", update, listenerOptions); + } + if (listenOrientation) { + const matches = useMediaQuery("(orientation: portrait)"); + watch(matches, () => update()); + } + return { width, height }; +} + +export { + computedEager, + computedWithControl, + tryOnScopeDispose, + createEventHook, + createGlobalState, + injectLocal, + provideLocal, + createInjectionState, + createRef, + createSharedComposable, + extendRef, + get, + isDefined, + makeDestructurable, + reactify, + reactifyObject, + toReactive, + reactiveComputed, + reactiveOmit, + isClient, + isWorker, + isDef, + notNullish, + assert, + isObject, + now, + timestamp, + clamp, + noop, + rand, + hasOwn, + isIOS, + createFilterWrapper, + bypassFilter, + debounceFilter, + throttleFilter, + pausableFilter, + hyphenate, + camelize, + promiseTimeout, + identity, + createSingletonPromise, + invoke, + containsProp, + increaseWithUnit, + pxValue, + objectPick, + objectOmit, + objectEntries, + getLifeCycleTarget, + toArray, + toRef2 as toRef, + resolveRef, + reactivePick, + refAutoReset, + useDebounceFn, + refDebounced, + refDefault, + useThrottleFn, + refThrottled, + refWithControl, + controlledRef, + set, + watchWithFilter, + watchPausable, + syncRef, + syncRefs, + toRefs2 as toRefs, + toValue2 as toValue, + resolveUnref, + tryOnBeforeMount, + tryOnBeforeUnmount, + tryOnMounted, + tryOnUnmounted, + until, + useArrayDifference, + useArrayEvery, + useArrayFilter, + useArrayFind, + useArrayFindIndex, + useArrayFindLast, + useArrayIncludes, + useArrayJoin, + useArrayMap, + useArrayReduce, + useArraySome, + useArrayUnique, + useCounter, + formatDate, + normalizeDate, + useDateFormat, + useIntervalFn, + useInterval, + useLastChanged, + useTimeoutFn, + useTimeout, + useToNumber, + useToString, + useToggle, + watchArray, + watchAtMost, + watchDebounced, + watchDeep, + watchIgnorable, + watchImmediate, + watchOnce, + watchThrottled, + watchTriggerable, + whenever, + computedAsync, + computedInject, + createReusableTemplate, + createTemplatePromise, + createUnrefFn, + defaultWindow, + defaultDocument, + defaultNavigator, + defaultLocation, + unrefElement, + useEventListener, + onClickOutside, + useMounted, + useSupported, + useMutationObserver, + onElementRemoval, + onKeyStroke, + onKeyDown, + onKeyPressed, + onKeyUp, + onLongPress, + onStartTyping, + templateRef, + useActiveElement, + useRafFn, + useAnimate, + useAsyncQueue, + useAsyncState, + useBase64, + useBattery, + useBluetooth, + useSSRWidth, + provideSSRWidth, + useMediaQuery, + breakpointsTailwind, + breakpointsBootstrapV5, + breakpointsVuetifyV2, + breakpointsVuetifyV3, + breakpointsVuetify, + breakpointsAntDesign, + breakpointsQuasar, + breakpointsSematic, + breakpointsMasterCss, + breakpointsPrimeFlex, + breakpointsElement, + useBreakpoints, + useBroadcastChannel, + useBrowserLocation, + useCached, + usePermission, + useClipboard, + useClipboardItems, + cloneFnJSON, + useCloned, + getSSRHandler, + setSSRHandler, + usePreferredDark, + StorageSerializers, + customStorageEventName, + useStorage, + useColorMode, + useConfirmDialog, + useCountdown, + useCssVar, + useCurrentElement, + useCycleList, + useDark, + useManualRefHistory, + useRefHistory, + useDebouncedRefHistory, + useDeviceMotion, + useDeviceOrientation, + useDevicePixelRatio, + useDevicesList, + useDisplayMedia, + useDocumentVisibility, + useDraggable, + useDropZone, + useResizeObserver, + useElementBounding, + useElementByPoint, + useElementHover, + useElementSize, + useIntersectionObserver, + useElementVisibility, + useEventBus, + useEventSource, + useEyeDropper, + useFavicon, + createFetch, + useFetch, + useFileDialog, + useFileSystemAccess, + useFocus, + useFocusWithin, + useFps, + useFullscreen, + mapGamepadToXbox360Controller, + useGamepad, + useGeolocation, + useIdle, + useImage, + useScroll, + useInfiniteScroll, + useKeyModifier, + useLocalStorage, + DefaultMagicKeysAliasMap, + useMagicKeys, + useMediaControls, + useMemoize, + useMemory, + useMouse, + useMouseInElement, + useMousePressed, + useNavigatorLanguage, + useNetwork, + useNow, + useObjectUrl, + useOffsetPagination, + useOnline, + usePageLeave, + useScreenOrientation, + useParallax, + useParentElement, + usePerformanceObserver, + usePointer, + usePointerLock, + usePointerSwipe, + usePreferredColorScheme, + usePreferredContrast, + usePreferredLanguages, + usePreferredReducedMotion, + usePreferredReducedTransparency, + usePrevious, + useScreenSafeArea, + useScriptTag, + useScrollLock, + useSessionStorage, + useShare, + useSorted, + useSpeechRecognition, + useSpeechSynthesis, + useStepper, + useStorageAsync, + useStyleTag, + useSwipe, + useTemplateRefsList, + useTextDirection, + useTextSelection, + useTextareaAutosize, + useThrottledRefHistory, + useTimeAgo, + formatTimeAgo, + useTimeoutPoll, + useTimestamp, + useTitle, + TransitionPresets, + executeTransition, + useTransition, + useUrlSearchParams, + useUserMedia, + useVModel, + useVModels, + useVibrate, + useVirtualList, + useWakeLock, + useWebNotification, + useWebSocket, + useWebWorker, + useWebWorkerFn, + useWindowFocus, + useWindowScroll, + useWindowSize +}; +//# sourceMappingURL=chunk-2CLQ7TTZ.js.map diff --git a/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js.map b/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js.map new file mode 100644 index 0000000..64aea30 --- /dev/null +++ b/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../../../../node_modules/@vueuse/shared/index.mjs", "../../../../node_modules/@vueuse/core/index.mjs"], + "sourcesContent": ["import { shallowRef, watchEffect, readonly, watch, customRef, getCurrentScope, onScopeDispose, effectScope, getCurrentInstance, hasInjectionContext, inject, provide, ref, isRef, unref, toValue as toValue$1, computed, reactive, toRefs as toRefs$1, toRef as toRef$1, onBeforeMount, nextTick, onBeforeUnmount, onMounted, onUnmounted, isReactive } from 'vue';\n\nfunction computedEager(fn, options) {\n var _a;\n const result = shallowRef();\n watchEffect(() => {\n result.value = fn();\n }, {\n ...options,\n flush: (_a = options == null ? void 0 : options.flush) != null ? _a : \"sync\"\n });\n return readonly(result);\n}\n\nfunction computedWithControl(source, fn) {\n let v = void 0;\n let track;\n let trigger;\n const dirty = shallowRef(true);\n const update = () => {\n dirty.value = true;\n trigger();\n };\n watch(source, update, { flush: \"sync\" });\n const get = typeof fn === \"function\" ? fn : fn.get;\n const set = typeof fn === \"function\" ? void 0 : fn.set;\n const result = customRef((_track, _trigger) => {\n track = _track;\n trigger = _trigger;\n return {\n get() {\n if (dirty.value) {\n v = get(v);\n dirty.value = false;\n }\n track();\n return v;\n },\n set(v2) {\n set == null ? void 0 : set(v2);\n }\n };\n });\n if (Object.isExtensible(result))\n result.trigger = update;\n return result;\n}\n\nfunction tryOnScopeDispose(fn) {\n if (getCurrentScope()) {\n onScopeDispose(fn);\n return true;\n }\n return false;\n}\n\nfunction createEventHook() {\n const fns = /* @__PURE__ */ new Set();\n const off = (fn) => {\n fns.delete(fn);\n };\n const clear = () => {\n fns.clear();\n };\n const on = (fn) => {\n fns.add(fn);\n const offFn = () => off(fn);\n tryOnScopeDispose(offFn);\n return {\n off: offFn\n };\n };\n const trigger = (...args) => {\n return Promise.all(Array.from(fns).map((fn) => fn(...args)));\n };\n return {\n on,\n off,\n trigger,\n clear\n };\n}\n\nfunction createGlobalState(stateFactory) {\n let initialized = false;\n let state;\n const scope = effectScope(true);\n return (...args) => {\n if (!initialized) {\n state = scope.run(() => stateFactory(...args));\n initialized = true;\n }\n return state;\n };\n}\n\nconst localProvidedStateMap = /* @__PURE__ */ new WeakMap();\n\nconst injectLocal = (...args) => {\n var _a;\n const key = args[0];\n const instance = (_a = getCurrentInstance()) == null ? void 0 : _a.proxy;\n if (instance == null && !hasInjectionContext())\n throw new Error(\"injectLocal must be called in setup\");\n if (instance && localProvidedStateMap.has(instance) && key in localProvidedStateMap.get(instance))\n return localProvidedStateMap.get(instance)[key];\n return inject(...args);\n};\n\nconst provideLocal = (key, value) => {\n var _a;\n const instance = (_a = getCurrentInstance()) == null ? void 0 : _a.proxy;\n if (instance == null)\n throw new Error(\"provideLocal must be called in setup\");\n if (!localProvidedStateMap.has(instance))\n localProvidedStateMap.set(instance, /* @__PURE__ */ Object.create(null));\n const localProvidedState = localProvidedStateMap.get(instance);\n localProvidedState[key] = value;\n provide(key, value);\n};\n\nfunction createInjectionState(composable, options) {\n const key = (options == null ? void 0 : options.injectionKey) || Symbol(composable.name || \"InjectionState\");\n const defaultValue = options == null ? void 0 : options.defaultValue;\n const useProvidingState = (...args) => {\n const state = composable(...args);\n provideLocal(key, state);\n return state;\n };\n const useInjectedState = () => injectLocal(key, defaultValue);\n return [useProvidingState, useInjectedState];\n}\n\nfunction createRef(value, deep) {\n if (deep === true) {\n return ref(value);\n } else {\n return shallowRef(value);\n }\n}\n\nfunction createSharedComposable(composable) {\n let subscribers = 0;\n let state;\n let scope;\n const dispose = () => {\n subscribers -= 1;\n if (scope && subscribers <= 0) {\n scope.stop();\n state = void 0;\n scope = void 0;\n }\n };\n return (...args) => {\n subscribers += 1;\n if (!scope) {\n scope = effectScope(true);\n state = scope.run(() => composable(...args));\n }\n tryOnScopeDispose(dispose);\n return state;\n };\n}\n\nfunction extendRef(ref, extend, { enumerable = false, unwrap = true } = {}) {\n for (const [key, value] of Object.entries(extend)) {\n if (key === \"value\")\n continue;\n if (isRef(value) && unwrap) {\n Object.defineProperty(ref, key, {\n get() {\n return value.value;\n },\n set(v) {\n value.value = v;\n },\n enumerable\n });\n } else {\n Object.defineProperty(ref, key, { value, enumerable });\n }\n }\n return ref;\n}\n\nfunction get(obj, key) {\n if (key == null)\n return unref(obj);\n return unref(obj)[key];\n}\n\nfunction isDefined(v) {\n return unref(v) != null;\n}\n\nfunction makeDestructurable(obj, arr) {\n if (typeof Symbol !== \"undefined\") {\n const clone = { ...obj };\n Object.defineProperty(clone, Symbol.iterator, {\n enumerable: false,\n value() {\n let index = 0;\n return {\n next: () => ({\n value: arr[index++],\n done: index > arr.length\n })\n };\n }\n });\n return clone;\n } else {\n return Object.assign([...arr], obj);\n }\n}\n\nfunction reactify(fn, options) {\n const unrefFn = (options == null ? void 0 : options.computedGetter) === false ? unref : toValue$1;\n return function(...args) {\n return computed(() => fn.apply(this, args.map((i) => unrefFn(i))));\n };\n}\n\nfunction reactifyObject(obj, optionsOrKeys = {}) {\n let keys = [];\n let options;\n if (Array.isArray(optionsOrKeys)) {\n keys = optionsOrKeys;\n } else {\n options = optionsOrKeys;\n const { includeOwnProperties = true } = optionsOrKeys;\n keys.push(...Object.keys(obj));\n if (includeOwnProperties)\n keys.push(...Object.getOwnPropertyNames(obj));\n }\n return Object.fromEntries(\n keys.map((key) => {\n const value = obj[key];\n return [\n key,\n typeof value === \"function\" ? reactify(value.bind(obj), options) : value\n ];\n })\n );\n}\n\nfunction toReactive(objectRef) {\n if (!isRef(objectRef))\n return reactive(objectRef);\n const proxy = new Proxy({}, {\n get(_, p, receiver) {\n return unref(Reflect.get(objectRef.value, p, receiver));\n },\n set(_, p, value) {\n if (isRef(objectRef.value[p]) && !isRef(value))\n objectRef.value[p].value = value;\n else\n objectRef.value[p] = value;\n return true;\n },\n deleteProperty(_, p) {\n return Reflect.deleteProperty(objectRef.value, p);\n },\n has(_, p) {\n return Reflect.has(objectRef.value, p);\n },\n ownKeys() {\n return Object.keys(objectRef.value);\n },\n getOwnPropertyDescriptor() {\n return {\n enumerable: true,\n configurable: true\n };\n }\n });\n return reactive(proxy);\n}\n\nfunction reactiveComputed(fn) {\n return toReactive(computed(fn));\n}\n\nfunction reactiveOmit(obj, ...keys) {\n const flatKeys = keys.flat();\n const predicate = flatKeys[0];\n return reactiveComputed(() => typeof predicate === \"function\" ? Object.fromEntries(Object.entries(toRefs$1(obj)).filter(([k, v]) => !predicate(toValue$1(v), k))) : Object.fromEntries(Object.entries(toRefs$1(obj)).filter((e) => !flatKeys.includes(e[0]))));\n}\n\nconst isClient = typeof window !== \"undefined\" && typeof document !== \"undefined\";\nconst isWorker = typeof WorkerGlobalScope !== \"undefined\" && globalThis instanceof WorkerGlobalScope;\nconst isDef = (val) => typeof val !== \"undefined\";\nconst notNullish = (val) => val != null;\nconst assert = (condition, ...infos) => {\n if (!condition)\n console.warn(...infos);\n};\nconst toString = Object.prototype.toString;\nconst isObject = (val) => toString.call(val) === \"[object Object]\";\nconst now = () => Date.now();\nconst timestamp = () => +Date.now();\nconst clamp = (n, min, max) => Math.min(max, Math.max(min, n));\nconst noop = () => {\n};\nconst rand = (min, max) => {\n min = Math.ceil(min);\n max = Math.floor(max);\n return Math.floor(Math.random() * (max - min + 1)) + min;\n};\nconst hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);\nconst isIOS = /* @__PURE__ */ getIsIOS();\nfunction getIsIOS() {\n var _a, _b;\n return isClient && ((_a = window == null ? void 0 : window.navigator) == null ? void 0 : _a.userAgent) && (/iP(?:ad|hone|od)/.test(window.navigator.userAgent) || ((_b = window == null ? void 0 : window.navigator) == null ? void 0 : _b.maxTouchPoints) > 2 && /iPad|Macintosh/.test(window == null ? void 0 : window.navigator.userAgent));\n}\n\nfunction createFilterWrapper(filter, fn) {\n function wrapper(...args) {\n return new Promise((resolve, reject) => {\n Promise.resolve(filter(() => fn.apply(this, args), { fn, thisArg: this, args })).then(resolve).catch(reject);\n });\n }\n return wrapper;\n}\nconst bypassFilter = (invoke) => {\n return invoke();\n};\nfunction debounceFilter(ms, options = {}) {\n let timer;\n let maxTimer;\n let lastRejector = noop;\n const _clearTimeout = (timer2) => {\n clearTimeout(timer2);\n lastRejector();\n lastRejector = noop;\n };\n let lastInvoker;\n const filter = (invoke) => {\n const duration = toValue$1(ms);\n const maxDuration = toValue$1(options.maxWait);\n if (timer)\n _clearTimeout(timer);\n if (duration <= 0 || maxDuration !== void 0 && maxDuration <= 0) {\n if (maxTimer) {\n _clearTimeout(maxTimer);\n maxTimer = null;\n }\n return Promise.resolve(invoke());\n }\n return new Promise((resolve, reject) => {\n lastRejector = options.rejectOnCancel ? reject : resolve;\n lastInvoker = invoke;\n if (maxDuration && !maxTimer) {\n maxTimer = setTimeout(() => {\n if (timer)\n _clearTimeout(timer);\n maxTimer = null;\n resolve(lastInvoker());\n }, maxDuration);\n }\n timer = setTimeout(() => {\n if (maxTimer)\n _clearTimeout(maxTimer);\n maxTimer = null;\n resolve(invoke());\n }, duration);\n });\n };\n return filter;\n}\nfunction throttleFilter(...args) {\n let lastExec = 0;\n let timer;\n let isLeading = true;\n let lastRejector = noop;\n let lastValue;\n let ms;\n let trailing;\n let leading;\n let rejectOnCancel;\n if (!isRef(args[0]) && typeof args[0] === \"object\")\n ({ delay: ms, trailing = true, leading = true, rejectOnCancel = false } = args[0]);\n else\n [ms, trailing = true, leading = true, rejectOnCancel = false] = args;\n const clear = () => {\n if (timer) {\n clearTimeout(timer);\n timer = void 0;\n lastRejector();\n lastRejector = noop;\n }\n };\n const filter = (_invoke) => {\n const duration = toValue$1(ms);\n const elapsed = Date.now() - lastExec;\n const invoke = () => {\n return lastValue = _invoke();\n };\n clear();\n if (duration <= 0) {\n lastExec = Date.now();\n return invoke();\n }\n if (elapsed > duration && (leading || !isLeading)) {\n lastExec = Date.now();\n invoke();\n } else if (trailing) {\n lastValue = new Promise((resolve, reject) => {\n lastRejector = rejectOnCancel ? reject : resolve;\n timer = setTimeout(() => {\n lastExec = Date.now();\n isLeading = true;\n resolve(invoke());\n clear();\n }, Math.max(0, duration - elapsed));\n });\n }\n if (!leading && !timer)\n timer = setTimeout(() => isLeading = true, duration);\n isLeading = false;\n return lastValue;\n };\n return filter;\n}\nfunction pausableFilter(extendFilter = bypassFilter, options = {}) {\n const {\n initialState = \"active\"\n } = options;\n const isActive = toRef(initialState === \"active\");\n function pause() {\n isActive.value = false;\n }\n function resume() {\n isActive.value = true;\n }\n const eventFilter = (...args) => {\n if (isActive.value)\n extendFilter(...args);\n };\n return { isActive: readonly(isActive), pause, resume, eventFilter };\n}\n\nfunction cacheStringFunction(fn) {\n const cache = /* @__PURE__ */ Object.create(null);\n return (str) => {\n const hit = cache[str];\n return hit || (cache[str] = fn(str));\n };\n}\nconst hyphenateRE = /\\B([A-Z])/g;\nconst hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, \"-$1\").toLowerCase());\nconst camelizeRE = /-(\\w)/g;\nconst camelize = cacheStringFunction((str) => {\n return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : \"\");\n});\n\nfunction promiseTimeout(ms, throwOnTimeout = false, reason = \"Timeout\") {\n return new Promise((resolve, reject) => {\n if (throwOnTimeout)\n setTimeout(() => reject(reason), ms);\n else\n setTimeout(resolve, ms);\n });\n}\nfunction identity(arg) {\n return arg;\n}\nfunction createSingletonPromise(fn) {\n let _promise;\n function wrapper() {\n if (!_promise)\n _promise = fn();\n return _promise;\n }\n wrapper.reset = async () => {\n const _prev = _promise;\n _promise = void 0;\n if (_prev)\n await _prev;\n };\n return wrapper;\n}\nfunction invoke(fn) {\n return fn();\n}\nfunction containsProp(obj, ...props) {\n return props.some((k) => k in obj);\n}\nfunction increaseWithUnit(target, delta) {\n var _a;\n if (typeof target === \"number\")\n return target + delta;\n const value = ((_a = target.match(/^-?\\d+\\.?\\d*/)) == null ? void 0 : _a[0]) || \"\";\n const unit = target.slice(value.length);\n const result = Number.parseFloat(value) + delta;\n if (Number.isNaN(result))\n return target;\n return result + unit;\n}\nfunction pxValue(px) {\n return px.endsWith(\"rem\") ? Number.parseFloat(px) * 16 : Number.parseFloat(px);\n}\nfunction objectPick(obj, keys, omitUndefined = false) {\n return keys.reduce((n, k) => {\n if (k in obj) {\n if (!omitUndefined || obj[k] !== void 0)\n n[k] = obj[k];\n }\n return n;\n }, {});\n}\nfunction objectOmit(obj, keys, omitUndefined = false) {\n return Object.fromEntries(Object.entries(obj).filter(([key, value]) => {\n return (!omitUndefined || value !== void 0) && !keys.includes(key);\n }));\n}\nfunction objectEntries(obj) {\n return Object.entries(obj);\n}\nfunction getLifeCycleTarget(target) {\n return target || getCurrentInstance();\n}\nfunction toArray(value) {\n return Array.isArray(value) ? value : [value];\n}\n\nfunction toRef(...args) {\n if (args.length !== 1)\n return toRef$1(...args);\n const r = args[0];\n return typeof r === \"function\" ? readonly(customRef(() => ({ get: r, set: noop }))) : ref(r);\n}\nconst resolveRef = toRef;\n\nfunction reactivePick(obj, ...keys) {\n const flatKeys = keys.flat();\n const predicate = flatKeys[0];\n return reactiveComputed(() => typeof predicate === \"function\" ? Object.fromEntries(Object.entries(toRefs$1(obj)).filter(([k, v]) => predicate(toValue$1(v), k))) : Object.fromEntries(flatKeys.map((k) => [k, toRef(obj, k)])));\n}\n\nfunction refAutoReset(defaultValue, afterMs = 1e4) {\n return customRef((track, trigger) => {\n let value = toValue$1(defaultValue);\n let timer;\n const resetAfter = () => setTimeout(() => {\n value = toValue$1(defaultValue);\n trigger();\n }, toValue$1(afterMs));\n tryOnScopeDispose(() => {\n clearTimeout(timer);\n });\n return {\n get() {\n track();\n return value;\n },\n set(newValue) {\n value = newValue;\n trigger();\n clearTimeout(timer);\n timer = resetAfter();\n }\n };\n });\n}\n\nfunction useDebounceFn(fn, ms = 200, options = {}) {\n return createFilterWrapper(\n debounceFilter(ms, options),\n fn\n );\n}\n\nfunction refDebounced(value, ms = 200, options = {}) {\n const debounced = ref(value.value);\n const updater = useDebounceFn(() => {\n debounced.value = value.value;\n }, ms, options);\n watch(value, () => updater());\n return debounced;\n}\n\nfunction refDefault(source, defaultValue) {\n return computed({\n get() {\n var _a;\n return (_a = source.value) != null ? _a : defaultValue;\n },\n set(value) {\n source.value = value;\n }\n });\n}\n\nfunction useThrottleFn(fn, ms = 200, trailing = false, leading = true, rejectOnCancel = false) {\n return createFilterWrapper(\n throttleFilter(ms, trailing, leading, rejectOnCancel),\n fn\n );\n}\n\nfunction refThrottled(value, delay = 200, trailing = true, leading = true) {\n if (delay <= 0)\n return value;\n const throttled = ref(value.value);\n const updater = useThrottleFn(() => {\n throttled.value = value.value;\n }, delay, trailing, leading);\n watch(value, () => updater());\n return throttled;\n}\n\nfunction refWithControl(initial, options = {}) {\n let source = initial;\n let track;\n let trigger;\n const ref = customRef((_track, _trigger) => {\n track = _track;\n trigger = _trigger;\n return {\n get() {\n return get();\n },\n set(v) {\n set(v);\n }\n };\n });\n function get(tracking = true) {\n if (tracking)\n track();\n return source;\n }\n function set(value, triggering = true) {\n var _a, _b;\n if (value === source)\n return;\n const old = source;\n if (((_a = options.onBeforeChange) == null ? void 0 : _a.call(options, value, old)) === false)\n return;\n source = value;\n (_b = options.onChanged) == null ? void 0 : _b.call(options, value, old);\n if (triggering)\n trigger();\n }\n const untrackedGet = () => get(false);\n const silentSet = (v) => set(v, false);\n const peek = () => get(false);\n const lay = (v) => set(v, false);\n return extendRef(\n ref,\n {\n get,\n set,\n untrackedGet,\n silentSet,\n peek,\n lay\n },\n { enumerable: true }\n );\n}\nconst controlledRef = refWithControl;\n\nfunction set(...args) {\n if (args.length === 2) {\n const [ref, value] = args;\n ref.value = value;\n }\n if (args.length === 3) {\n const [target, key, value] = args;\n target[key] = value;\n }\n}\n\nfunction watchWithFilter(source, cb, options = {}) {\n const {\n eventFilter = bypassFilter,\n ...watchOptions\n } = options;\n return watch(\n source,\n createFilterWrapper(\n eventFilter,\n cb\n ),\n watchOptions\n );\n}\n\nfunction watchPausable(source, cb, options = {}) {\n const {\n eventFilter: filter,\n initialState = \"active\",\n ...watchOptions\n } = options;\n const { eventFilter, pause, resume, isActive } = pausableFilter(filter, { initialState });\n const stop = watchWithFilter(\n source,\n cb,\n {\n ...watchOptions,\n eventFilter\n }\n );\n return { stop, pause, resume, isActive };\n}\n\nfunction syncRef(left, right, ...[options]) {\n const {\n flush = \"sync\",\n deep = false,\n immediate = true,\n direction = \"both\",\n transform = {}\n } = options || {};\n const watchers = [];\n const transformLTR = \"ltr\" in transform && transform.ltr || ((v) => v);\n const transformRTL = \"rtl\" in transform && transform.rtl || ((v) => v);\n if (direction === \"both\" || direction === \"ltr\") {\n watchers.push(watchPausable(\n left,\n (newValue) => {\n watchers.forEach((w) => w.pause());\n right.value = transformLTR(newValue);\n watchers.forEach((w) => w.resume());\n },\n { flush, deep, immediate }\n ));\n }\n if (direction === \"both\" || direction === \"rtl\") {\n watchers.push(watchPausable(\n right,\n (newValue) => {\n watchers.forEach((w) => w.pause());\n left.value = transformRTL(newValue);\n watchers.forEach((w) => w.resume());\n },\n { flush, deep, immediate }\n ));\n }\n const stop = () => {\n watchers.forEach((w) => w.stop());\n };\n return stop;\n}\n\nfunction syncRefs(source, targets, options = {}) {\n const {\n flush = \"sync\",\n deep = false,\n immediate = true\n } = options;\n const targetsArray = toArray(targets);\n return watch(\n source,\n (newValue) => targetsArray.forEach((target) => target.value = newValue),\n { flush, deep, immediate }\n );\n}\n\nfunction toRefs(objectRef, options = {}) {\n if (!isRef(objectRef))\n return toRefs$1(objectRef);\n const result = Array.isArray(objectRef.value) ? Array.from({ length: objectRef.value.length }) : {};\n for (const key in objectRef.value) {\n result[key] = customRef(() => ({\n get() {\n return objectRef.value[key];\n },\n set(v) {\n var _a;\n const replaceRef = (_a = toValue$1(options.replaceRef)) != null ? _a : true;\n if (replaceRef) {\n if (Array.isArray(objectRef.value)) {\n const copy = [...objectRef.value];\n copy[key] = v;\n objectRef.value = copy;\n } else {\n const newObject = { ...objectRef.value, [key]: v };\n Object.setPrototypeOf(newObject, Object.getPrototypeOf(objectRef.value));\n objectRef.value = newObject;\n }\n } else {\n objectRef.value[key] = v;\n }\n }\n }));\n }\n return result;\n}\n\nconst toValue = toValue$1;\nconst resolveUnref = toValue$1;\n\nfunction tryOnBeforeMount(fn, sync = true, target) {\n const instance = getLifeCycleTarget(target);\n if (instance)\n onBeforeMount(fn, target);\n else if (sync)\n fn();\n else\n nextTick(fn);\n}\n\nfunction tryOnBeforeUnmount(fn, target) {\n const instance = getLifeCycleTarget(target);\n if (instance)\n onBeforeUnmount(fn, target);\n}\n\nfunction tryOnMounted(fn, sync = true, target) {\n const instance = getLifeCycleTarget();\n if (instance)\n onMounted(fn, target);\n else if (sync)\n fn();\n else\n nextTick(fn);\n}\n\nfunction tryOnUnmounted(fn, target) {\n const instance = getLifeCycleTarget(target);\n if (instance)\n onUnmounted(fn, target);\n}\n\nfunction createUntil(r, isNot = false) {\n function toMatch(condition, { flush = \"sync\", deep = false, timeout, throwOnTimeout } = {}) {\n let stop = null;\n const watcher = new Promise((resolve) => {\n stop = watch(\n r,\n (v) => {\n if (condition(v) !== isNot) {\n if (stop)\n stop();\n else\n nextTick(() => stop == null ? void 0 : stop());\n resolve(v);\n }\n },\n {\n flush,\n deep,\n immediate: true\n }\n );\n });\n const promises = [watcher];\n if (timeout != null) {\n promises.push(\n promiseTimeout(timeout, throwOnTimeout).then(() => toValue$1(r)).finally(() => stop == null ? void 0 : stop())\n );\n }\n return Promise.race(promises);\n }\n function toBe(value, options) {\n if (!isRef(value))\n return toMatch((v) => v === value, options);\n const { flush = \"sync\", deep = false, timeout, throwOnTimeout } = options != null ? options : {};\n let stop = null;\n const watcher = new Promise((resolve) => {\n stop = watch(\n [r, value],\n ([v1, v2]) => {\n if (isNot !== (v1 === v2)) {\n if (stop)\n stop();\n else\n nextTick(() => stop == null ? void 0 : stop());\n resolve(v1);\n }\n },\n {\n flush,\n deep,\n immediate: true\n }\n );\n });\n const promises = [watcher];\n if (timeout != null) {\n promises.push(\n promiseTimeout(timeout, throwOnTimeout).then(() => toValue$1(r)).finally(() => {\n stop == null ? void 0 : stop();\n return toValue$1(r);\n })\n );\n }\n return Promise.race(promises);\n }\n function toBeTruthy(options) {\n return toMatch((v) => Boolean(v), options);\n }\n function toBeNull(options) {\n return toBe(null, options);\n }\n function toBeUndefined(options) {\n return toBe(void 0, options);\n }\n function toBeNaN(options) {\n return toMatch(Number.isNaN, options);\n }\n function toContains(value, options) {\n return toMatch((v) => {\n const array = Array.from(v);\n return array.includes(value) || array.includes(toValue$1(value));\n }, options);\n }\n function changed(options) {\n return changedTimes(1, options);\n }\n function changedTimes(n = 1, options) {\n let count = -1;\n return toMatch(() => {\n count += 1;\n return count >= n;\n }, options);\n }\n if (Array.isArray(toValue$1(r))) {\n const instance = {\n toMatch,\n toContains,\n changed,\n changedTimes,\n get not() {\n return createUntil(r, !isNot);\n }\n };\n return instance;\n } else {\n const instance = {\n toMatch,\n toBe,\n toBeTruthy,\n toBeNull,\n toBeNaN,\n toBeUndefined,\n changed,\n changedTimes,\n get not() {\n return createUntil(r, !isNot);\n }\n };\n return instance;\n }\n}\nfunction until(r) {\n return createUntil(r);\n}\n\nfunction defaultComparator(value, othVal) {\n return value === othVal;\n}\nfunction useArrayDifference(...args) {\n var _a, _b;\n const list = args[0];\n const values = args[1];\n let compareFn = (_a = args[2]) != null ? _a : defaultComparator;\n const {\n symmetric = false\n } = (_b = args[3]) != null ? _b : {};\n if (typeof compareFn === \"string\") {\n const key = compareFn;\n compareFn = (value, othVal) => value[key] === othVal[key];\n }\n const diff1 = computed(() => toValue$1(list).filter((x) => toValue$1(values).findIndex((y) => compareFn(x, y)) === -1));\n if (symmetric) {\n const diff2 = computed(() => toValue$1(values).filter((x) => toValue$1(list).findIndex((y) => compareFn(x, y)) === -1));\n return computed(() => symmetric ? [...toValue$1(diff1), ...toValue$1(diff2)] : toValue$1(diff1));\n } else {\n return diff1;\n }\n}\n\nfunction useArrayEvery(list, fn) {\n return computed(() => toValue$1(list).every((element, index, array) => fn(toValue$1(element), index, array)));\n}\n\nfunction useArrayFilter(list, fn) {\n return computed(() => toValue$1(list).map((i) => toValue$1(i)).filter(fn));\n}\n\nfunction useArrayFind(list, fn) {\n return computed(() => toValue$1(\n toValue$1(list).find((element, index, array) => fn(toValue$1(element), index, array))\n ));\n}\n\nfunction useArrayFindIndex(list, fn) {\n return computed(() => toValue$1(list).findIndex((element, index, array) => fn(toValue$1(element), index, array)));\n}\n\nfunction findLast(arr, cb) {\n let index = arr.length;\n while (index-- > 0) {\n if (cb(arr[index], index, arr))\n return arr[index];\n }\n return void 0;\n}\nfunction useArrayFindLast(list, fn) {\n return computed(() => toValue$1(\n !Array.prototype.findLast ? findLast(toValue$1(list), (element, index, array) => fn(toValue$1(element), index, array)) : toValue$1(list).findLast((element, index, array) => fn(toValue$1(element), index, array))\n ));\n}\n\nfunction isArrayIncludesOptions(obj) {\n return isObject(obj) && containsProp(obj, \"formIndex\", \"comparator\");\n}\nfunction useArrayIncludes(...args) {\n var _a;\n const list = args[0];\n const value = args[1];\n let comparator = args[2];\n let formIndex = 0;\n if (isArrayIncludesOptions(comparator)) {\n formIndex = (_a = comparator.fromIndex) != null ? _a : 0;\n comparator = comparator.comparator;\n }\n if (typeof comparator === \"string\") {\n const key = comparator;\n comparator = (element, value2) => element[key] === toValue$1(value2);\n }\n comparator = comparator != null ? comparator : (element, value2) => element === toValue$1(value2);\n return computed(() => toValue$1(list).slice(formIndex).some((element, index, array) => comparator(\n toValue$1(element),\n toValue$1(value),\n index,\n toValue$1(array)\n )));\n}\n\nfunction useArrayJoin(list, separator) {\n return computed(() => toValue$1(list).map((i) => toValue$1(i)).join(toValue$1(separator)));\n}\n\nfunction useArrayMap(list, fn) {\n return computed(() => toValue$1(list).map((i) => toValue$1(i)).map(fn));\n}\n\nfunction useArrayReduce(list, reducer, ...args) {\n const reduceCallback = (sum, value, index) => reducer(toValue$1(sum), toValue$1(value), index);\n return computed(() => {\n const resolved = toValue$1(list);\n return args.length ? resolved.reduce(reduceCallback, typeof args[0] === \"function\" ? toValue$1(args[0]()) : toValue$1(args[0])) : resolved.reduce(reduceCallback);\n });\n}\n\nfunction useArraySome(list, fn) {\n return computed(() => toValue$1(list).some((element, index, array) => fn(toValue$1(element), index, array)));\n}\n\nfunction uniq(array) {\n return Array.from(new Set(array));\n}\nfunction uniqueElementsBy(array, fn) {\n return array.reduce((acc, v) => {\n if (!acc.some((x) => fn(v, x, array)))\n acc.push(v);\n return acc;\n }, []);\n}\nfunction useArrayUnique(list, compareFn) {\n return computed(() => {\n const resolvedList = toValue$1(list).map((element) => toValue$1(element));\n return compareFn ? uniqueElementsBy(resolvedList, compareFn) : uniq(resolvedList);\n });\n}\n\nfunction useCounter(initialValue = 0, options = {}) {\n let _initialValue = unref(initialValue);\n const count = shallowRef(initialValue);\n const {\n max = Number.POSITIVE_INFINITY,\n min = Number.NEGATIVE_INFINITY\n } = options;\n const inc = (delta = 1) => count.value = Math.max(Math.min(max, count.value + delta), min);\n const dec = (delta = 1) => count.value = Math.min(Math.max(min, count.value - delta), max);\n const get = () => count.value;\n const set = (val) => count.value = Math.max(min, Math.min(max, val));\n const reset = (val = _initialValue) => {\n _initialValue = val;\n return set(val);\n };\n return { count, inc, dec, get, set, reset };\n}\n\nconst REGEX_PARSE = /^(\\d{4})[-/]?(\\d{1,2})?[-/]?(\\d{0,2})[T\\s]*(\\d{1,2})?:?(\\d{1,2})?:?(\\d{1,2})?[.:]?(\\d+)?$/i;\nconst REGEX_FORMAT = /[YMDHhms]o|\\[([^\\]]+)\\]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a{1,2}|A{1,2}|m{1,2}|s{1,2}|Z{1,2}|z{1,4}|SSS/g;\nfunction defaultMeridiem(hours, minutes, isLowercase, hasPeriod) {\n let m = hours < 12 ? \"AM\" : \"PM\";\n if (hasPeriod)\n m = m.split(\"\").reduce((acc, curr) => acc += `${curr}.`, \"\");\n return isLowercase ? m.toLowerCase() : m;\n}\nfunction formatOrdinal(num) {\n const suffixes = [\"th\", \"st\", \"nd\", \"rd\"];\n const v = num % 100;\n return num + (suffixes[(v - 20) % 10] || suffixes[v] || suffixes[0]);\n}\nfunction formatDate(date, formatStr, options = {}) {\n var _a;\n const years = date.getFullYear();\n const month = date.getMonth();\n const days = date.getDate();\n const hours = date.getHours();\n const minutes = date.getMinutes();\n const seconds = date.getSeconds();\n const milliseconds = date.getMilliseconds();\n const day = date.getDay();\n const meridiem = (_a = options.customMeridiem) != null ? _a : defaultMeridiem;\n const stripTimeZone = (dateString) => {\n var _a2;\n return (_a2 = dateString.split(\" \")[1]) != null ? _a2 : \"\";\n };\n const matches = {\n Yo: () => formatOrdinal(years),\n YY: () => String(years).slice(-2),\n YYYY: () => years,\n M: () => month + 1,\n Mo: () => formatOrdinal(month + 1),\n MM: () => `${month + 1}`.padStart(2, \"0\"),\n MMM: () => date.toLocaleDateString(toValue$1(options.locales), { month: \"short\" }),\n MMMM: () => date.toLocaleDateString(toValue$1(options.locales), { month: \"long\" }),\n D: () => String(days),\n Do: () => formatOrdinal(days),\n DD: () => `${days}`.padStart(2, \"0\"),\n H: () => String(hours),\n Ho: () => formatOrdinal(hours),\n HH: () => `${hours}`.padStart(2, \"0\"),\n h: () => `${hours % 12 || 12}`.padStart(1, \"0\"),\n ho: () => formatOrdinal(hours % 12 || 12),\n hh: () => `${hours % 12 || 12}`.padStart(2, \"0\"),\n m: () => String(minutes),\n mo: () => formatOrdinal(minutes),\n mm: () => `${minutes}`.padStart(2, \"0\"),\n s: () => String(seconds),\n so: () => formatOrdinal(seconds),\n ss: () => `${seconds}`.padStart(2, \"0\"),\n SSS: () => `${milliseconds}`.padStart(3, \"0\"),\n d: () => day,\n dd: () => date.toLocaleDateString(toValue$1(options.locales), { weekday: \"narrow\" }),\n ddd: () => date.toLocaleDateString(toValue$1(options.locales), { weekday: \"short\" }),\n dddd: () => date.toLocaleDateString(toValue$1(options.locales), { weekday: \"long\" }),\n A: () => meridiem(hours, minutes),\n AA: () => meridiem(hours, minutes, false, true),\n a: () => meridiem(hours, minutes, true),\n aa: () => meridiem(hours, minutes, true, true),\n z: () => stripTimeZone(date.toLocaleDateString(toValue$1(options.locales), { timeZoneName: \"shortOffset\" })),\n zz: () => stripTimeZone(date.toLocaleDateString(toValue$1(options.locales), { timeZoneName: \"shortOffset\" })),\n zzz: () => stripTimeZone(date.toLocaleDateString(toValue$1(options.locales), { timeZoneName: \"shortOffset\" })),\n zzzz: () => stripTimeZone(date.toLocaleDateString(toValue$1(options.locales), { timeZoneName: \"longOffset\" }))\n };\n return formatStr.replace(REGEX_FORMAT, (match, $1) => {\n var _a2, _b;\n return (_b = $1 != null ? $1 : (_a2 = matches[match]) == null ? void 0 : _a2.call(matches)) != null ? _b : match;\n });\n}\nfunction normalizeDate(date) {\n if (date === null)\n return new Date(Number.NaN);\n if (date === void 0)\n return /* @__PURE__ */ new Date();\n if (date instanceof Date)\n return new Date(date);\n if (typeof date === \"string\" && !/Z$/i.test(date)) {\n const d = date.match(REGEX_PARSE);\n if (d) {\n const m = d[2] - 1 || 0;\n const ms = (d[7] || \"0\").substring(0, 3);\n return new Date(d[1], m, d[3] || 1, d[4] || 0, d[5] || 0, d[6] || 0, ms);\n }\n }\n return new Date(date);\n}\nfunction useDateFormat(date, formatStr = \"HH:mm:ss\", options = {}) {\n return computed(() => formatDate(normalizeDate(toValue$1(date)), toValue$1(formatStr), options));\n}\n\nfunction useIntervalFn(cb, interval = 1e3, options = {}) {\n const {\n immediate = true,\n immediateCallback = false\n } = options;\n let timer = null;\n const isActive = shallowRef(false);\n function clean() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n }\n function pause() {\n isActive.value = false;\n clean();\n }\n function resume() {\n const intervalValue = toValue$1(interval);\n if (intervalValue <= 0)\n return;\n isActive.value = true;\n if (immediateCallback)\n cb();\n clean();\n if (isActive.value)\n timer = setInterval(cb, intervalValue);\n }\n if (immediate && isClient)\n resume();\n if (isRef(interval) || typeof interval === \"function\") {\n const stopWatch = watch(interval, () => {\n if (isActive.value && isClient)\n resume();\n });\n tryOnScopeDispose(stopWatch);\n }\n tryOnScopeDispose(pause);\n return {\n isActive,\n pause,\n resume\n };\n}\n\nfunction useInterval(interval = 1e3, options = {}) {\n const {\n controls: exposeControls = false,\n immediate = true,\n callback\n } = options;\n const counter = shallowRef(0);\n const update = () => counter.value += 1;\n const reset = () => {\n counter.value = 0;\n };\n const controls = useIntervalFn(\n callback ? () => {\n update();\n callback(counter.value);\n } : update,\n interval,\n { immediate }\n );\n if (exposeControls) {\n return {\n counter,\n reset,\n ...controls\n };\n } else {\n return counter;\n }\n}\n\nfunction useLastChanged(source, options = {}) {\n var _a;\n const ms = shallowRef((_a = options.initialValue) != null ? _a : null);\n watch(\n source,\n () => ms.value = timestamp(),\n options\n );\n return ms;\n}\n\nfunction useTimeoutFn(cb, interval, options = {}) {\n const {\n immediate = true,\n immediateCallback = false\n } = options;\n const isPending = shallowRef(false);\n let timer = null;\n function clear() {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n }\n function stop() {\n isPending.value = false;\n clear();\n }\n function start(...args) {\n if (immediateCallback)\n cb();\n clear();\n isPending.value = true;\n timer = setTimeout(() => {\n isPending.value = false;\n timer = null;\n cb(...args);\n }, toValue$1(interval));\n }\n if (immediate) {\n isPending.value = true;\n if (isClient)\n start();\n }\n tryOnScopeDispose(stop);\n return {\n isPending: readonly(isPending),\n start,\n stop\n };\n}\n\nfunction useTimeout(interval = 1e3, options = {}) {\n const {\n controls: exposeControls = false,\n callback\n } = options;\n const controls = useTimeoutFn(\n callback != null ? callback : noop,\n interval,\n options\n );\n const ready = computed(() => !controls.isPending.value);\n if (exposeControls) {\n return {\n ready,\n ...controls\n };\n } else {\n return ready;\n }\n}\n\nfunction useToNumber(value, options = {}) {\n const {\n method = \"parseFloat\",\n radix,\n nanToZero\n } = options;\n return computed(() => {\n let resolved = toValue$1(value);\n if (typeof method === \"function\")\n resolved = method(resolved);\n else if (typeof resolved === \"string\")\n resolved = Number[method](resolved, radix);\n if (nanToZero && Number.isNaN(resolved))\n resolved = 0;\n return resolved;\n });\n}\n\nfunction useToString(value) {\n return computed(() => `${toValue$1(value)}`);\n}\n\nfunction useToggle(initialValue = false, options = {}) {\n const {\n truthyValue = true,\n falsyValue = false\n } = options;\n const valueIsRef = isRef(initialValue);\n const _value = shallowRef(initialValue);\n function toggle(value) {\n if (arguments.length) {\n _value.value = value;\n return _value.value;\n } else {\n const truthy = toValue$1(truthyValue);\n _value.value = _value.value === truthy ? toValue$1(falsyValue) : truthy;\n return _value.value;\n }\n }\n if (valueIsRef)\n return toggle;\n else\n return [_value, toggle];\n}\n\nfunction watchArray(source, cb, options) {\n let oldList = (options == null ? void 0 : options.immediate) ? [] : [...typeof source === \"function\" ? source() : Array.isArray(source) ? source : toValue$1(source)];\n return watch(source, (newList, _, onCleanup) => {\n const oldListRemains = Array.from({ length: oldList.length });\n const added = [];\n for (const obj of newList) {\n let found = false;\n for (let i = 0; i < oldList.length; i++) {\n if (!oldListRemains[i] && obj === oldList[i]) {\n oldListRemains[i] = true;\n found = true;\n break;\n }\n }\n if (!found)\n added.push(obj);\n }\n const removed = oldList.filter((_2, i) => !oldListRemains[i]);\n cb(newList, oldList, added, removed, onCleanup);\n oldList = [...newList];\n }, options);\n}\n\nfunction watchAtMost(source, cb, options) {\n const {\n count,\n ...watchOptions\n } = options;\n const current = shallowRef(0);\n const stop = watchWithFilter(\n source,\n (...args) => {\n current.value += 1;\n if (current.value >= toValue$1(count))\n nextTick(() => stop());\n cb(...args);\n },\n watchOptions\n );\n return { count: current, stop };\n}\n\nfunction watchDebounced(source, cb, options = {}) {\n const {\n debounce = 0,\n maxWait = void 0,\n ...watchOptions\n } = options;\n return watchWithFilter(\n source,\n cb,\n {\n ...watchOptions,\n eventFilter: debounceFilter(debounce, { maxWait })\n }\n );\n}\n\nfunction watchDeep(source, cb, options) {\n return watch(\n source,\n cb,\n {\n ...options,\n deep: true\n }\n );\n}\n\nfunction watchIgnorable(source, cb, options = {}) {\n const {\n eventFilter = bypassFilter,\n ...watchOptions\n } = options;\n const filteredCb = createFilterWrapper(\n eventFilter,\n cb\n );\n let ignoreUpdates;\n let ignorePrevAsyncUpdates;\n let stop;\n if (watchOptions.flush === \"sync\") {\n const ignore = shallowRef(false);\n ignorePrevAsyncUpdates = () => {\n };\n ignoreUpdates = (updater) => {\n ignore.value = true;\n updater();\n ignore.value = false;\n };\n stop = watch(\n source,\n (...args) => {\n if (!ignore.value)\n filteredCb(...args);\n },\n watchOptions\n );\n } else {\n const disposables = [];\n const ignoreCounter = shallowRef(0);\n const syncCounter = shallowRef(0);\n ignorePrevAsyncUpdates = () => {\n ignoreCounter.value = syncCounter.value;\n };\n disposables.push(\n watch(\n source,\n () => {\n syncCounter.value++;\n },\n { ...watchOptions, flush: \"sync\" }\n )\n );\n ignoreUpdates = (updater) => {\n const syncCounterPrev = syncCounter.value;\n updater();\n ignoreCounter.value += syncCounter.value - syncCounterPrev;\n };\n disposables.push(\n watch(\n source,\n (...args) => {\n const ignore = ignoreCounter.value > 0 && ignoreCounter.value === syncCounter.value;\n ignoreCounter.value = 0;\n syncCounter.value = 0;\n if (ignore)\n return;\n filteredCb(...args);\n },\n watchOptions\n )\n );\n stop = () => {\n disposables.forEach((fn) => fn());\n };\n }\n return { stop, ignoreUpdates, ignorePrevAsyncUpdates };\n}\n\nfunction watchImmediate(source, cb, options) {\n return watch(\n source,\n cb,\n {\n ...options,\n immediate: true\n }\n );\n}\n\nfunction watchOnce(source, cb, options) {\n const stop = watch(source, (...args) => {\n nextTick(() => stop());\n return cb(...args);\n }, options);\n return stop;\n}\n\nfunction watchThrottled(source, cb, options = {}) {\n const {\n throttle = 0,\n trailing = true,\n leading = true,\n ...watchOptions\n } = options;\n return watchWithFilter(\n source,\n cb,\n {\n ...watchOptions,\n eventFilter: throttleFilter(throttle, trailing, leading)\n }\n );\n}\n\nfunction watchTriggerable(source, cb, options = {}) {\n let cleanupFn;\n function onEffect() {\n if (!cleanupFn)\n return;\n const fn = cleanupFn;\n cleanupFn = void 0;\n fn();\n }\n function onCleanup(callback) {\n cleanupFn = callback;\n }\n const _cb = (value, oldValue) => {\n onEffect();\n return cb(value, oldValue, onCleanup);\n };\n const res = watchIgnorable(source, _cb, options);\n const { ignoreUpdates } = res;\n const trigger = () => {\n let res2;\n ignoreUpdates(() => {\n res2 = _cb(getWatchSources(source), getOldValue(source));\n });\n return res2;\n };\n return {\n ...res,\n trigger\n };\n}\nfunction getWatchSources(sources) {\n if (isReactive(sources))\n return sources;\n if (Array.isArray(sources))\n return sources.map((item) => toValue$1(item));\n return toValue$1(sources);\n}\nfunction getOldValue(source) {\n return Array.isArray(source) ? source.map(() => void 0) : void 0;\n}\n\nfunction whenever(source, cb, options) {\n const stop = watch(\n source,\n (v, ov, onInvalidate) => {\n if (v) {\n if (options == null ? void 0 : options.once)\n nextTick(() => stop());\n cb(v, ov, onInvalidate);\n }\n },\n {\n ...options,\n once: false\n }\n );\n return stop;\n}\n\nexport { assert, refAutoReset as autoResetRef, bypassFilter, camelize, clamp, computedEager, computedWithControl, containsProp, computedWithControl as controlledComputed, controlledRef, createEventHook, createFilterWrapper, createGlobalState, createInjectionState, reactify as createReactiveFn, createRef, createSharedComposable, createSingletonPromise, debounceFilter, refDebounced as debouncedRef, watchDebounced as debouncedWatch, computedEager as eagerComputed, extendRef, formatDate, get, getLifeCycleTarget, hasOwn, hyphenate, identity, watchIgnorable as ignorableWatch, increaseWithUnit, injectLocal, invoke, isClient, isDef, isDefined, isIOS, isObject, isWorker, makeDestructurable, noop, normalizeDate, notNullish, now, objectEntries, objectOmit, objectPick, pausableFilter, watchPausable as pausableWatch, promiseTimeout, provideLocal, pxValue, rand, reactify, reactifyObject, reactiveComputed, reactiveOmit, reactivePick, refAutoReset, refDebounced, refDefault, refThrottled, refWithControl, resolveRef, resolveUnref, set, syncRef, syncRefs, throttleFilter, refThrottled as throttledRef, watchThrottled as throttledWatch, timestamp, toArray, toReactive, toRef, toRefs, toValue, tryOnBeforeMount, tryOnBeforeUnmount, tryOnMounted, tryOnScopeDispose, tryOnUnmounted, until, useArrayDifference, useArrayEvery, useArrayFilter, useArrayFind, useArrayFindIndex, useArrayFindLast, useArrayIncludes, useArrayJoin, useArrayMap, useArrayReduce, useArraySome, useArrayUnique, useCounter, useDateFormat, refDebounced as useDebounce, useDebounceFn, useInterval, useIntervalFn, useLastChanged, refThrottled as useThrottle, useThrottleFn, useTimeout, useTimeoutFn, useToNumber, useToString, useToggle, watchArray, watchAtMost, watchDebounced, watchDeep, watchIgnorable, watchImmediate, watchOnce, watchPausable, watchThrottled, watchTriggerable, watchWithFilter, whenever };\n", "import { noop, makeDestructurable, camelize, isClient, toArray, watchImmediate, isObject, tryOnScopeDispose, isIOS, notNullish, tryOnMounted, objectOmit, promiseTimeout, until, injectLocal, provideLocal, pxValue, increaseWithUnit, objectEntries, createRef, createSingletonPromise, useTimeoutFn, pausableWatch, toRef, createEventHook, useIntervalFn, computedWithControl, timestamp, pausableFilter, watchIgnorable, debounceFilter, bypassFilter, createFilterWrapper, toRefs, watchOnce, containsProp, hasOwn, throttleFilter, useDebounceFn, useThrottleFn, tryOnUnmounted, clamp, syncRef, objectPick, watchWithFilter, identity, isDef, whenever, isWorker } from '@vueuse/shared';\nexport * from '@vueuse/shared';\nimport { isRef, shallowRef, ref, watchEffect, computed, inject, defineComponent, h, TransitionGroup, shallowReactive, Fragment, toValue, unref, getCurrentInstance, onMounted, watch, customRef, onUpdated, readonly, reactive, hasInjectionContext, toRaw, nextTick, markRaw, getCurrentScope, isReadonly, onBeforeUpdate } from 'vue';\n\nfunction computedAsync(evaluationCallback, initialState, optionsOrRef) {\n let options;\n if (isRef(optionsOrRef)) {\n options = {\n evaluating: optionsOrRef\n };\n } else {\n options = optionsOrRef || {};\n }\n const {\n lazy = false,\n evaluating = void 0,\n shallow = true,\n onError = noop\n } = options;\n const started = shallowRef(!lazy);\n const current = shallow ? shallowRef(initialState) : ref(initialState);\n let counter = 0;\n watchEffect(async (onInvalidate) => {\n if (!started.value)\n return;\n counter++;\n const counterAtBeginning = counter;\n let hasFinished = false;\n if (evaluating) {\n Promise.resolve().then(() => {\n evaluating.value = true;\n });\n }\n try {\n const result = await evaluationCallback((cancelCallback) => {\n onInvalidate(() => {\n if (evaluating)\n evaluating.value = false;\n if (!hasFinished)\n cancelCallback();\n });\n });\n if (counterAtBeginning === counter)\n current.value = result;\n } catch (e) {\n onError(e);\n } finally {\n if (evaluating && counterAtBeginning === counter)\n evaluating.value = false;\n hasFinished = true;\n }\n });\n if (lazy) {\n return computed(() => {\n started.value = true;\n return current.value;\n });\n } else {\n return current;\n }\n}\n\nfunction computedInject(key, options, defaultSource, treatDefaultAsFactory) {\n let source = inject(key);\n if (defaultSource)\n source = inject(key, defaultSource);\n if (treatDefaultAsFactory)\n source = inject(key, defaultSource, treatDefaultAsFactory);\n if (typeof options === \"function\") {\n return computed((ctx) => options(source, ctx));\n } else {\n return computed({\n get: (ctx) => options.get(source, ctx),\n set: options.set\n });\n }\n}\n\nfunction createReusableTemplate(options = {}) {\n const {\n inheritAttrs = true\n } = options;\n const render = shallowRef();\n const define = /*@__PURE__*/ defineComponent({\n setup(_, { slots }) {\n return () => {\n render.value = slots.default;\n };\n }\n });\n const reuse = /*@__PURE__*/ defineComponent({\n inheritAttrs,\n props: options.props,\n setup(props, { attrs, slots }) {\n return () => {\n var _a;\n if (!render.value && process.env.NODE_ENV !== \"production\")\n throw new Error(\"[VueUse] Failed to find the definition of reusable template\");\n const vnode = (_a = render.value) == null ? void 0 : _a.call(render, {\n ...options.props == null ? keysToCamelKebabCase(attrs) : props,\n $slots: slots\n });\n return inheritAttrs && (vnode == null ? void 0 : vnode.length) === 1 ? vnode[0] : vnode;\n };\n }\n });\n return makeDestructurable(\n { define, reuse },\n [define, reuse]\n );\n}\nfunction keysToCamelKebabCase(obj) {\n const newObj = {};\n for (const key in obj)\n newObj[camelize(key)] = obj[key];\n return newObj;\n}\n\nfunction createTemplatePromise(options = {}) {\n let index = 0;\n const instances = ref([]);\n function create(...args) {\n const props = shallowReactive({\n key: index++,\n args,\n promise: void 0,\n resolve: () => {\n },\n reject: () => {\n },\n isResolving: false,\n options\n });\n instances.value.push(props);\n props.promise = new Promise((_resolve, _reject) => {\n props.resolve = (v) => {\n props.isResolving = true;\n return _resolve(v);\n };\n props.reject = _reject;\n }).finally(() => {\n props.promise = void 0;\n const index2 = instances.value.indexOf(props);\n if (index2 !== -1)\n instances.value.splice(index2, 1);\n });\n return props.promise;\n }\n function start(...args) {\n if (options.singleton && instances.value.length > 0)\n return instances.value[0].promise;\n return create(...args);\n }\n const component = /*@__PURE__*/ defineComponent((_, { slots }) => {\n const renderList = () => instances.value.map((props) => {\n var _a;\n return h(Fragment, { key: props.key }, (_a = slots.default) == null ? void 0 : _a.call(slots, props));\n });\n if (options.transition)\n return () => h(TransitionGroup, options.transition, renderList);\n return renderList;\n });\n component.start = start;\n return component;\n}\n\nfunction createUnrefFn(fn) {\n return function(...args) {\n return fn.apply(this, args.map((i) => toValue(i)));\n };\n}\n\nconst defaultWindow = isClient ? window : void 0;\nconst defaultDocument = isClient ? window.document : void 0;\nconst defaultNavigator = isClient ? window.navigator : void 0;\nconst defaultLocation = isClient ? window.location : void 0;\n\nfunction unrefElement(elRef) {\n var _a;\n const plain = toValue(elRef);\n return (_a = plain == null ? void 0 : plain.$el) != null ? _a : plain;\n}\n\nfunction useEventListener(...args) {\n const cleanups = [];\n const cleanup = () => {\n cleanups.forEach((fn) => fn());\n cleanups.length = 0;\n };\n const register = (el, event, listener, options) => {\n el.addEventListener(event, listener, options);\n return () => el.removeEventListener(event, listener, options);\n };\n const firstParamTargets = computed(() => {\n const test = toArray(toValue(args[0])).filter((e) => e != null);\n return test.every((e) => typeof e !== \"string\") ? test : void 0;\n });\n const stopWatch = watchImmediate(\n () => {\n var _a, _b;\n return [\n (_b = (_a = firstParamTargets.value) == null ? void 0 : _a.map((e) => unrefElement(e))) != null ? _b : [defaultWindow].filter((e) => e != null),\n toArray(toValue(firstParamTargets.value ? args[1] : args[0])),\n toArray(unref(firstParamTargets.value ? args[2] : args[1])),\n // @ts-expect-error - TypeScript gets the correct types, but somehow still complains\n toValue(firstParamTargets.value ? args[3] : args[2])\n ];\n },\n ([raw_targets, raw_events, raw_listeners, raw_options]) => {\n cleanup();\n if (!(raw_targets == null ? void 0 : raw_targets.length) || !(raw_events == null ? void 0 : raw_events.length) || !(raw_listeners == null ? void 0 : raw_listeners.length))\n return;\n const optionsClone = isObject(raw_options) ? { ...raw_options } : raw_options;\n cleanups.push(\n ...raw_targets.flatMap(\n (el) => raw_events.flatMap(\n (event) => raw_listeners.map((listener) => register(el, event, listener, optionsClone))\n )\n )\n );\n },\n { flush: \"post\" }\n );\n const stop = () => {\n stopWatch();\n cleanup();\n };\n tryOnScopeDispose(cleanup);\n return stop;\n}\n\nlet _iOSWorkaround = false;\nfunction onClickOutside(target, handler, options = {}) {\n const { window = defaultWindow, ignore = [], capture = true, detectIframe = false, controls = false } = options;\n if (!window) {\n return controls ? { stop: noop, cancel: noop, trigger: noop } : noop;\n }\n if (isIOS && !_iOSWorkaround) {\n _iOSWorkaround = true;\n const listenerOptions = { passive: true };\n Array.from(window.document.body.children).forEach((el) => useEventListener(el, \"click\", noop, listenerOptions));\n useEventListener(window.document.documentElement, \"click\", noop, listenerOptions);\n }\n let shouldListen = true;\n const shouldIgnore = (event) => {\n return toValue(ignore).some((target2) => {\n if (typeof target2 === \"string\") {\n return Array.from(window.document.querySelectorAll(target2)).some((el) => el === event.target || event.composedPath().includes(el));\n } else {\n const el = unrefElement(target2);\n return el && (event.target === el || event.composedPath().includes(el));\n }\n });\n };\n function hasMultipleRoots(target2) {\n const vm = toValue(target2);\n return vm && vm.$.subTree.shapeFlag === 16;\n }\n function checkMultipleRoots(target2, event) {\n const vm = toValue(target2);\n const children = vm.$.subTree && vm.$.subTree.children;\n if (children == null || !Array.isArray(children))\n return false;\n return children.some((child) => child.el === event.target || event.composedPath().includes(child.el));\n }\n const listener = (event) => {\n const el = unrefElement(target);\n if (event.target == null)\n return;\n if (!(el instanceof Element) && hasMultipleRoots(target) && checkMultipleRoots(target, event))\n return;\n if (!el || el === event.target || event.composedPath().includes(el))\n return;\n if (\"detail\" in event && event.detail === 0)\n shouldListen = !shouldIgnore(event);\n if (!shouldListen) {\n shouldListen = true;\n return;\n }\n handler(event);\n };\n let isProcessingClick = false;\n const cleanup = [\n useEventListener(window, \"click\", (event) => {\n if (!isProcessingClick) {\n isProcessingClick = true;\n setTimeout(() => {\n isProcessingClick = false;\n }, 0);\n listener(event);\n }\n }, { passive: true, capture }),\n useEventListener(window, \"pointerdown\", (e) => {\n const el = unrefElement(target);\n shouldListen = !shouldIgnore(e) && !!(el && !e.composedPath().includes(el));\n }, { passive: true }),\n detectIframe && useEventListener(window, \"blur\", (event) => {\n setTimeout(() => {\n var _a;\n const el = unrefElement(target);\n if (((_a = window.document.activeElement) == null ? void 0 : _a.tagName) === \"IFRAME\" && !(el == null ? void 0 : el.contains(window.document.activeElement))) {\n handler(event);\n }\n }, 0);\n }, { passive: true })\n ].filter(Boolean);\n const stop = () => cleanup.forEach((fn) => fn());\n if (controls) {\n return {\n stop,\n cancel: () => {\n shouldListen = false;\n },\n trigger: (event) => {\n shouldListen = true;\n listener(event);\n shouldListen = false;\n }\n };\n }\n return stop;\n}\n\nfunction useMounted() {\n const isMounted = shallowRef(false);\n const instance = getCurrentInstance();\n if (instance) {\n onMounted(() => {\n isMounted.value = true;\n }, instance);\n }\n return isMounted;\n}\n\nfunction useSupported(callback) {\n const isMounted = useMounted();\n return computed(() => {\n isMounted.value;\n return Boolean(callback());\n });\n}\n\nfunction useMutationObserver(target, callback, options = {}) {\n const { window = defaultWindow, ...mutationOptions } = options;\n let observer;\n const isSupported = useSupported(() => window && \"MutationObserver\" in window);\n const cleanup = () => {\n if (observer) {\n observer.disconnect();\n observer = void 0;\n }\n };\n const targets = computed(() => {\n const value = toValue(target);\n const items = toArray(value).map(unrefElement).filter(notNullish);\n return new Set(items);\n });\n const stopWatch = watch(\n () => targets.value,\n (targets2) => {\n cleanup();\n if (isSupported.value && targets2.size) {\n observer = new MutationObserver(callback);\n targets2.forEach((el) => observer.observe(el, mutationOptions));\n }\n },\n { immediate: true, flush: \"post\" }\n );\n const takeRecords = () => {\n return observer == null ? void 0 : observer.takeRecords();\n };\n const stop = () => {\n stopWatch();\n cleanup();\n };\n tryOnScopeDispose(stop);\n return {\n isSupported,\n stop,\n takeRecords\n };\n}\n\nfunction onElementRemoval(target, callback, options = {}) {\n const {\n window = defaultWindow,\n document = window == null ? void 0 : window.document,\n flush = \"sync\"\n } = options;\n if (!window || !document)\n return noop;\n let stopFn;\n const cleanupAndUpdate = (fn) => {\n stopFn == null ? void 0 : stopFn();\n stopFn = fn;\n };\n const stopWatch = watchEffect(() => {\n const el = unrefElement(target);\n if (el) {\n const { stop } = useMutationObserver(\n document,\n (mutationsList) => {\n const targetRemoved = mutationsList.map((mutation) => [...mutation.removedNodes]).flat().some((node) => node === el || node.contains(el));\n if (targetRemoved) {\n callback(mutationsList);\n }\n },\n {\n window,\n childList: true,\n subtree: true\n }\n );\n cleanupAndUpdate(stop);\n }\n }, { flush });\n const stopHandle = () => {\n stopWatch();\n cleanupAndUpdate();\n };\n tryOnScopeDispose(stopHandle);\n return stopHandle;\n}\n\nfunction createKeyPredicate(keyFilter) {\n if (typeof keyFilter === \"function\")\n return keyFilter;\n else if (typeof keyFilter === \"string\")\n return (event) => event.key === keyFilter;\n else if (Array.isArray(keyFilter))\n return (event) => keyFilter.includes(event.key);\n return () => true;\n}\nfunction onKeyStroke(...args) {\n let key;\n let handler;\n let options = {};\n if (args.length === 3) {\n key = args[0];\n handler = args[1];\n options = args[2];\n } else if (args.length === 2) {\n if (typeof args[1] === \"object\") {\n key = true;\n handler = args[0];\n options = args[1];\n } else {\n key = args[0];\n handler = args[1];\n }\n } else {\n key = true;\n handler = args[0];\n }\n const {\n target = defaultWindow,\n eventName = \"keydown\",\n passive = false,\n dedupe = false\n } = options;\n const predicate = createKeyPredicate(key);\n const listener = (e) => {\n if (e.repeat && toValue(dedupe))\n return;\n if (predicate(e))\n handler(e);\n };\n return useEventListener(target, eventName, listener, passive);\n}\nfunction onKeyDown(key, handler, options = {}) {\n return onKeyStroke(key, handler, { ...options, eventName: \"keydown\" });\n}\nfunction onKeyPressed(key, handler, options = {}) {\n return onKeyStroke(key, handler, { ...options, eventName: \"keypress\" });\n}\nfunction onKeyUp(key, handler, options = {}) {\n return onKeyStroke(key, handler, { ...options, eventName: \"keyup\" });\n}\n\nconst DEFAULT_DELAY = 500;\nconst DEFAULT_THRESHOLD = 10;\nfunction onLongPress(target, handler, options) {\n var _a, _b;\n const elementRef = computed(() => unrefElement(target));\n let timeout;\n let posStart;\n let startTimestamp;\n let hasLongPressed = false;\n function clear() {\n if (timeout) {\n clearTimeout(timeout);\n timeout = void 0;\n }\n posStart = void 0;\n startTimestamp = void 0;\n hasLongPressed = false;\n }\n function onRelease(ev) {\n var _a2, _b2, _c;\n const [_startTimestamp, _posStart, _hasLongPressed] = [startTimestamp, posStart, hasLongPressed];\n clear();\n if (!(options == null ? void 0 : options.onMouseUp) || !_posStart || !_startTimestamp)\n return;\n if (((_a2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _a2.self) && ev.target !== elementRef.value)\n return;\n if ((_b2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _b2.prevent)\n ev.preventDefault();\n if ((_c = options == null ? void 0 : options.modifiers) == null ? void 0 : _c.stop)\n ev.stopPropagation();\n const dx = ev.x - _posStart.x;\n const dy = ev.y - _posStart.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n options.onMouseUp(ev.timeStamp - _startTimestamp, distance, _hasLongPressed);\n }\n function onDown(ev) {\n var _a2, _b2, _c, _d;\n if (((_a2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _a2.self) && ev.target !== elementRef.value)\n return;\n clear();\n if ((_b2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _b2.prevent)\n ev.preventDefault();\n if ((_c = options == null ? void 0 : options.modifiers) == null ? void 0 : _c.stop)\n ev.stopPropagation();\n posStart = {\n x: ev.x,\n y: ev.y\n };\n startTimestamp = ev.timeStamp;\n timeout = setTimeout(\n () => {\n hasLongPressed = true;\n handler(ev);\n },\n (_d = options == null ? void 0 : options.delay) != null ? _d : DEFAULT_DELAY\n );\n }\n function onMove(ev) {\n var _a2, _b2, _c, _d;\n if (((_a2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _a2.self) && ev.target !== elementRef.value)\n return;\n if (!posStart || (options == null ? void 0 : options.distanceThreshold) === false)\n return;\n if ((_b2 = options == null ? void 0 : options.modifiers) == null ? void 0 : _b2.prevent)\n ev.preventDefault();\n if ((_c = options == null ? void 0 : options.modifiers) == null ? void 0 : _c.stop)\n ev.stopPropagation();\n const dx = ev.x - posStart.x;\n const dy = ev.y - posStart.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n if (distance >= ((_d = options == null ? void 0 : options.distanceThreshold) != null ? _d : DEFAULT_THRESHOLD))\n clear();\n }\n const listenerOptions = {\n capture: (_a = options == null ? void 0 : options.modifiers) == null ? void 0 : _a.capture,\n once: (_b = options == null ? void 0 : options.modifiers) == null ? void 0 : _b.once\n };\n const cleanup = [\n useEventListener(elementRef, \"pointerdown\", onDown, listenerOptions),\n useEventListener(elementRef, \"pointermove\", onMove, listenerOptions),\n useEventListener(elementRef, [\"pointerup\", \"pointerleave\"], onRelease, listenerOptions)\n ];\n const stop = () => cleanup.forEach((fn) => fn());\n return stop;\n}\n\nfunction isFocusedElementEditable() {\n const { activeElement, body } = document;\n if (!activeElement)\n return false;\n if (activeElement === body)\n return false;\n switch (activeElement.tagName) {\n case \"INPUT\":\n case \"TEXTAREA\":\n return true;\n }\n return activeElement.hasAttribute(\"contenteditable\");\n}\nfunction isTypedCharValid({\n keyCode,\n metaKey,\n ctrlKey,\n altKey\n}) {\n if (metaKey || ctrlKey || altKey)\n return false;\n if (keyCode >= 48 && keyCode <= 57 || keyCode >= 96 && keyCode <= 105)\n return true;\n if (keyCode >= 65 && keyCode <= 90)\n return true;\n return false;\n}\nfunction onStartTyping(callback, options = {}) {\n const { document: document2 = defaultDocument } = options;\n const keydown = (event) => {\n if (!isFocusedElementEditable() && isTypedCharValid(event)) {\n callback(event);\n }\n };\n if (document2)\n useEventListener(document2, \"keydown\", keydown, { passive: true });\n}\n\nfunction templateRef(key, initialValue = null) {\n const instance = getCurrentInstance();\n let _trigger = () => {\n };\n const element = customRef((track, trigger) => {\n _trigger = trigger;\n return {\n get() {\n var _a, _b;\n track();\n return (_b = (_a = instance == null ? void 0 : instance.proxy) == null ? void 0 : _a.$refs[key]) != null ? _b : initialValue;\n },\n set() {\n }\n };\n });\n tryOnMounted(_trigger);\n onUpdated(_trigger);\n return element;\n}\n\nfunction useActiveElement(options = {}) {\n var _a;\n const {\n window = defaultWindow,\n deep = true,\n triggerOnRemoval = false\n } = options;\n const document = (_a = options.document) != null ? _a : window == null ? void 0 : window.document;\n const getDeepActiveElement = () => {\n var _a2;\n let element = document == null ? void 0 : document.activeElement;\n if (deep) {\n while (element == null ? void 0 : element.shadowRoot)\n element = (_a2 = element == null ? void 0 : element.shadowRoot) == null ? void 0 : _a2.activeElement;\n }\n return element;\n };\n const activeElement = shallowRef();\n const trigger = () => {\n activeElement.value = getDeepActiveElement();\n };\n if (window) {\n const listenerOptions = {\n capture: true,\n passive: true\n };\n useEventListener(\n window,\n \"blur\",\n (event) => {\n if (event.relatedTarget !== null)\n return;\n trigger();\n },\n listenerOptions\n );\n useEventListener(\n window,\n \"focus\",\n trigger,\n listenerOptions\n );\n }\n if (triggerOnRemoval) {\n onElementRemoval(activeElement, trigger, { document });\n }\n trigger();\n return activeElement;\n}\n\nfunction useRafFn(fn, options = {}) {\n const {\n immediate = true,\n fpsLimit = void 0,\n window = defaultWindow,\n once = false\n } = options;\n const isActive = shallowRef(false);\n const intervalLimit = computed(() => {\n return fpsLimit ? 1e3 / toValue(fpsLimit) : null;\n });\n let previousFrameTimestamp = 0;\n let rafId = null;\n function loop(timestamp) {\n if (!isActive.value || !window)\n return;\n if (!previousFrameTimestamp)\n previousFrameTimestamp = timestamp;\n const delta = timestamp - previousFrameTimestamp;\n if (intervalLimit.value && delta < intervalLimit.value) {\n rafId = window.requestAnimationFrame(loop);\n return;\n }\n previousFrameTimestamp = timestamp;\n fn({ delta, timestamp });\n if (once) {\n isActive.value = false;\n rafId = null;\n return;\n }\n rafId = window.requestAnimationFrame(loop);\n }\n function resume() {\n if (!isActive.value && window) {\n isActive.value = true;\n previousFrameTimestamp = 0;\n rafId = window.requestAnimationFrame(loop);\n }\n }\n function pause() {\n isActive.value = false;\n if (rafId != null && window) {\n window.cancelAnimationFrame(rafId);\n rafId = null;\n }\n }\n if (immediate)\n resume();\n tryOnScopeDispose(pause);\n return {\n isActive: readonly(isActive),\n pause,\n resume\n };\n}\n\nfunction useAnimate(target, keyframes, options) {\n let config;\n let animateOptions;\n if (isObject(options)) {\n config = options;\n animateOptions = objectOmit(options, [\"window\", \"immediate\", \"commitStyles\", \"persist\", \"onReady\", \"onError\"]);\n } else {\n config = { duration: options };\n animateOptions = options;\n }\n const {\n window = defaultWindow,\n immediate = true,\n commitStyles,\n persist,\n playbackRate: _playbackRate = 1,\n onReady,\n onError = (e) => {\n console.error(e);\n }\n } = config;\n const isSupported = useSupported(() => window && HTMLElement && \"animate\" in HTMLElement.prototype);\n const animate = shallowRef(void 0);\n const store = shallowReactive({\n startTime: null,\n currentTime: null,\n timeline: null,\n playbackRate: _playbackRate,\n pending: false,\n playState: immediate ? \"idle\" : \"paused\",\n replaceState: \"active\"\n });\n const pending = computed(() => store.pending);\n const playState = computed(() => store.playState);\n const replaceState = computed(() => store.replaceState);\n const startTime = computed({\n get() {\n return store.startTime;\n },\n set(value) {\n store.startTime = value;\n if (animate.value)\n animate.value.startTime = value;\n }\n });\n const currentTime = computed({\n get() {\n return store.currentTime;\n },\n set(value) {\n store.currentTime = value;\n if (animate.value) {\n animate.value.currentTime = value;\n syncResume();\n }\n }\n });\n const timeline = computed({\n get() {\n return store.timeline;\n },\n set(value) {\n store.timeline = value;\n if (animate.value)\n animate.value.timeline = value;\n }\n });\n const playbackRate = computed({\n get() {\n return store.playbackRate;\n },\n set(value) {\n store.playbackRate = value;\n if (animate.value)\n animate.value.playbackRate = value;\n }\n });\n const play = () => {\n if (animate.value) {\n try {\n animate.value.play();\n syncResume();\n } catch (e) {\n syncPause();\n onError(e);\n }\n } else {\n update();\n }\n };\n const pause = () => {\n var _a;\n try {\n (_a = animate.value) == null ? void 0 : _a.pause();\n syncPause();\n } catch (e) {\n onError(e);\n }\n };\n const reverse = () => {\n var _a;\n if (!animate.value)\n update();\n try {\n (_a = animate.value) == null ? void 0 : _a.reverse();\n syncResume();\n } catch (e) {\n syncPause();\n onError(e);\n }\n };\n const finish = () => {\n var _a;\n try {\n (_a = animate.value) == null ? void 0 : _a.finish();\n syncPause();\n } catch (e) {\n onError(e);\n }\n };\n const cancel = () => {\n var _a;\n try {\n (_a = animate.value) == null ? void 0 : _a.cancel();\n syncPause();\n } catch (e) {\n onError(e);\n }\n };\n watch(() => unrefElement(target), (el) => {\n if (el) {\n update();\n } else {\n animate.value = void 0;\n }\n });\n watch(() => keyframes, (value) => {\n if (animate.value) {\n update();\n const targetEl = unrefElement(target);\n if (targetEl) {\n animate.value.effect = new KeyframeEffect(\n targetEl,\n toValue(value),\n animateOptions\n );\n }\n }\n }, { deep: true });\n tryOnMounted(() => update(true), false);\n tryOnScopeDispose(cancel);\n function update(init) {\n const el = unrefElement(target);\n if (!isSupported.value || !el)\n return;\n if (!animate.value)\n animate.value = el.animate(toValue(keyframes), animateOptions);\n if (persist)\n animate.value.persist();\n if (_playbackRate !== 1)\n animate.value.playbackRate = _playbackRate;\n if (init && !immediate)\n animate.value.pause();\n else\n syncResume();\n onReady == null ? void 0 : onReady(animate.value);\n }\n const listenerOptions = { passive: true };\n useEventListener(animate, [\"cancel\", \"finish\", \"remove\"], syncPause, listenerOptions);\n useEventListener(animate, \"finish\", () => {\n var _a;\n if (commitStyles)\n (_a = animate.value) == null ? void 0 : _a.commitStyles();\n }, listenerOptions);\n const { resume: resumeRef, pause: pauseRef } = useRafFn(() => {\n if (!animate.value)\n return;\n store.pending = animate.value.pending;\n store.playState = animate.value.playState;\n store.replaceState = animate.value.replaceState;\n store.startTime = animate.value.startTime;\n store.currentTime = animate.value.currentTime;\n store.timeline = animate.value.timeline;\n store.playbackRate = animate.value.playbackRate;\n }, { immediate: false });\n function syncResume() {\n if (isSupported.value)\n resumeRef();\n }\n function syncPause() {\n if (isSupported.value && window)\n window.requestAnimationFrame(pauseRef);\n }\n return {\n isSupported,\n animate,\n // actions\n play,\n pause,\n reverse,\n finish,\n cancel,\n // state\n pending,\n playState,\n replaceState,\n startTime,\n currentTime,\n timeline,\n playbackRate\n };\n}\n\nfunction useAsyncQueue(tasks, options) {\n const {\n interrupt = true,\n onError = noop,\n onFinished = noop,\n signal\n } = options || {};\n const promiseState = {\n aborted: \"aborted\",\n fulfilled: \"fulfilled\",\n pending: \"pending\",\n rejected: \"rejected\"\n };\n const initialResult = Array.from(Array.from({ length: tasks.length }), () => ({ state: promiseState.pending, data: null }));\n const result = reactive(initialResult);\n const activeIndex = shallowRef(-1);\n if (!tasks || tasks.length === 0) {\n onFinished();\n return {\n activeIndex,\n result\n };\n }\n function updateResult(state, res) {\n activeIndex.value++;\n result[activeIndex.value].data = res;\n result[activeIndex.value].state = state;\n }\n tasks.reduce((prev, curr) => {\n return prev.then((prevRes) => {\n var _a;\n if (signal == null ? void 0 : signal.aborted) {\n updateResult(promiseState.aborted, new Error(\"aborted\"));\n return;\n }\n if (((_a = result[activeIndex.value]) == null ? void 0 : _a.state) === promiseState.rejected && interrupt) {\n onFinished();\n return;\n }\n const done = curr(prevRes).then((currentRes) => {\n updateResult(promiseState.fulfilled, currentRes);\n if (activeIndex.value === tasks.length - 1)\n onFinished();\n return currentRes;\n });\n if (!signal)\n return done;\n return Promise.race([done, whenAborted(signal)]);\n }).catch((e) => {\n if (signal == null ? void 0 : signal.aborted) {\n updateResult(promiseState.aborted, e);\n return e;\n }\n updateResult(promiseState.rejected, e);\n onError();\n return e;\n });\n }, Promise.resolve());\n return {\n activeIndex,\n result\n };\n}\nfunction whenAborted(signal) {\n return new Promise((resolve, reject) => {\n const error = new Error(\"aborted\");\n if (signal.aborted)\n reject(error);\n else\n signal.addEventListener(\"abort\", () => reject(error), { once: true });\n });\n}\n\nfunction useAsyncState(promise, initialState, options) {\n const {\n immediate = true,\n delay = 0,\n onError = noop,\n onSuccess = noop,\n resetOnExecute = true,\n shallow = true,\n throwError\n } = options != null ? options : {};\n const state = shallow ? shallowRef(initialState) : ref(initialState);\n const isReady = shallowRef(false);\n const isLoading = shallowRef(false);\n const error = shallowRef(void 0);\n async function execute(delay2 = 0, ...args) {\n if (resetOnExecute)\n state.value = initialState;\n error.value = void 0;\n isReady.value = false;\n isLoading.value = true;\n if (delay2 > 0)\n await promiseTimeout(delay2);\n const _promise = typeof promise === \"function\" ? promise(...args) : promise;\n try {\n const data = await _promise;\n state.value = data;\n isReady.value = true;\n onSuccess(data);\n } catch (e) {\n error.value = e;\n onError(e);\n if (throwError)\n throw e;\n } finally {\n isLoading.value = false;\n }\n return state.value;\n }\n if (immediate) {\n execute(delay);\n }\n const shell = {\n state,\n isReady,\n isLoading,\n error,\n execute\n };\n function waitUntilIsLoaded() {\n return new Promise((resolve, reject) => {\n until(isLoading).toBe(false).then(() => resolve(shell)).catch(reject);\n });\n }\n return {\n ...shell,\n then(onFulfilled, onRejected) {\n return waitUntilIsLoaded().then(onFulfilled, onRejected);\n }\n };\n}\n\nconst defaults = {\n array: (v) => JSON.stringify(v),\n object: (v) => JSON.stringify(v),\n set: (v) => JSON.stringify(Array.from(v)),\n map: (v) => JSON.stringify(Object.fromEntries(v)),\n null: () => \"\"\n};\nfunction getDefaultSerialization(target) {\n if (!target)\n return defaults.null;\n if (target instanceof Map)\n return defaults.map;\n else if (target instanceof Set)\n return defaults.set;\n else if (Array.isArray(target))\n return defaults.array;\n else\n return defaults.object;\n}\n\nfunction useBase64(target, options) {\n const base64 = shallowRef(\"\");\n const promise = shallowRef();\n function execute() {\n if (!isClient)\n return;\n promise.value = new Promise((resolve, reject) => {\n try {\n const _target = toValue(target);\n if (_target == null) {\n resolve(\"\");\n } else if (typeof _target === \"string\") {\n resolve(blobToBase64(new Blob([_target], { type: \"text/plain\" })));\n } else if (_target instanceof Blob) {\n resolve(blobToBase64(_target));\n } else if (_target instanceof ArrayBuffer) {\n resolve(window.btoa(String.fromCharCode(...new Uint8Array(_target))));\n } else if (_target instanceof HTMLCanvasElement) {\n resolve(_target.toDataURL(options == null ? void 0 : options.type, options == null ? void 0 : options.quality));\n } else if (_target instanceof HTMLImageElement) {\n const img = _target.cloneNode(false);\n img.crossOrigin = \"Anonymous\";\n imgLoaded(img).then(() => {\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n canvas.width = img.width;\n canvas.height = img.height;\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height);\n resolve(canvas.toDataURL(options == null ? void 0 : options.type, options == null ? void 0 : options.quality));\n }).catch(reject);\n } else if (typeof _target === \"object\") {\n const _serializeFn = (options == null ? void 0 : options.serializer) || getDefaultSerialization(_target);\n const serialized = _serializeFn(_target);\n return resolve(blobToBase64(new Blob([serialized], { type: \"application/json\" })));\n } else {\n reject(new Error(\"target is unsupported types\"));\n }\n } catch (error) {\n reject(error);\n }\n });\n promise.value.then((res) => {\n base64.value = (options == null ? void 0 : options.dataUrl) === false ? res.replace(/^data:.*?;base64,/, \"\") : res;\n });\n return promise.value;\n }\n if (isRef(target) || typeof target === \"function\")\n watch(target, execute, { immediate: true });\n else\n execute();\n return {\n base64,\n promise,\n execute\n };\n}\nfunction imgLoaded(img) {\n return new Promise((resolve, reject) => {\n if (!img.complete) {\n img.onload = () => {\n resolve();\n };\n img.onerror = reject;\n } else {\n resolve();\n }\n });\n}\nfunction blobToBase64(blob) {\n return new Promise((resolve, reject) => {\n const fr = new FileReader();\n fr.onload = (e) => {\n resolve(e.target.result);\n };\n fr.onerror = reject;\n fr.readAsDataURL(blob);\n });\n}\n\nfunction useBattery(options = {}) {\n const { navigator = defaultNavigator } = options;\n const events = [\"chargingchange\", \"chargingtimechange\", \"dischargingtimechange\", \"levelchange\"];\n const isSupported = useSupported(() => navigator && \"getBattery\" in navigator && typeof navigator.getBattery === \"function\");\n const charging = shallowRef(false);\n const chargingTime = shallowRef(0);\n const dischargingTime = shallowRef(0);\n const level = shallowRef(1);\n let battery;\n function updateBatteryInfo() {\n charging.value = this.charging;\n chargingTime.value = this.chargingTime || 0;\n dischargingTime.value = this.dischargingTime || 0;\n level.value = this.level;\n }\n if (isSupported.value) {\n navigator.getBattery().then((_battery) => {\n battery = _battery;\n updateBatteryInfo.call(battery);\n useEventListener(battery, events, updateBatteryInfo, { passive: true });\n });\n }\n return {\n isSupported,\n charging,\n chargingTime,\n dischargingTime,\n level\n };\n}\n\nfunction useBluetooth(options) {\n let {\n acceptAllDevices = false\n } = options || {};\n const {\n filters = void 0,\n optionalServices = void 0,\n navigator = defaultNavigator\n } = options || {};\n const isSupported = useSupported(() => navigator && \"bluetooth\" in navigator);\n const device = shallowRef();\n const error = shallowRef(null);\n watch(device, () => {\n connectToBluetoothGATTServer();\n });\n async function requestDevice() {\n if (!isSupported.value)\n return;\n error.value = null;\n if (filters && filters.length > 0)\n acceptAllDevices = false;\n try {\n device.value = await (navigator == null ? void 0 : navigator.bluetooth.requestDevice({\n acceptAllDevices,\n filters,\n optionalServices\n }));\n } catch (err) {\n error.value = err;\n }\n }\n const server = shallowRef();\n const isConnected = shallowRef(false);\n function reset() {\n isConnected.value = false;\n device.value = void 0;\n server.value = void 0;\n }\n async function connectToBluetoothGATTServer() {\n error.value = null;\n if (device.value && device.value.gatt) {\n useEventListener(device, \"gattserverdisconnected\", reset, { passive: true });\n try {\n server.value = await device.value.gatt.connect();\n isConnected.value = server.value.connected;\n } catch (err) {\n error.value = err;\n }\n }\n }\n tryOnMounted(() => {\n var _a;\n if (device.value)\n (_a = device.value.gatt) == null ? void 0 : _a.connect();\n });\n tryOnScopeDispose(() => {\n var _a;\n if (device.value)\n (_a = device.value.gatt) == null ? void 0 : _a.disconnect();\n });\n return {\n isSupported,\n isConnected: readonly(isConnected),\n // Device:\n device,\n requestDevice,\n // Server:\n server,\n // Errors:\n error\n };\n}\n\nconst ssrWidthSymbol = Symbol(\"vueuse-ssr-width\");\nfunction useSSRWidth() {\n const ssrWidth = hasInjectionContext() ? injectLocal(ssrWidthSymbol, null) : null;\n return typeof ssrWidth === \"number\" ? ssrWidth : void 0;\n}\nfunction provideSSRWidth(width, app) {\n if (app !== void 0) {\n app.provide(ssrWidthSymbol, width);\n } else {\n provideLocal(ssrWidthSymbol, width);\n }\n}\n\nfunction useMediaQuery(query, options = {}) {\n const { window = defaultWindow, ssrWidth = useSSRWidth() } = options;\n const isSupported = useSupported(() => window && \"matchMedia\" in window && typeof window.matchMedia === \"function\");\n const ssrSupport = shallowRef(typeof ssrWidth === \"number\");\n const mediaQuery = shallowRef();\n const matches = shallowRef(false);\n const handler = (event) => {\n matches.value = event.matches;\n };\n watchEffect(() => {\n if (ssrSupport.value) {\n ssrSupport.value = !isSupported.value;\n const queryStrings = toValue(query).split(\",\");\n matches.value = queryStrings.some((queryString) => {\n const not = queryString.includes(\"not all\");\n const minWidth = queryString.match(/\\(\\s*min-width:\\s*(-?\\d+(?:\\.\\d*)?[a-z]+\\s*)\\)/);\n const maxWidth = queryString.match(/\\(\\s*max-width:\\s*(-?\\d+(?:\\.\\d*)?[a-z]+\\s*)\\)/);\n let res = Boolean(minWidth || maxWidth);\n if (minWidth && res) {\n res = ssrWidth >= pxValue(minWidth[1]);\n }\n if (maxWidth && res) {\n res = ssrWidth <= pxValue(maxWidth[1]);\n }\n return not ? !res : res;\n });\n return;\n }\n if (!isSupported.value)\n return;\n mediaQuery.value = window.matchMedia(toValue(query));\n matches.value = mediaQuery.value.matches;\n });\n useEventListener(mediaQuery, \"change\", handler, { passive: true });\n return computed(() => matches.value);\n}\n\nconst breakpointsTailwind = {\n \"sm\": 640,\n \"md\": 768,\n \"lg\": 1024,\n \"xl\": 1280,\n \"2xl\": 1536\n};\nconst breakpointsBootstrapV5 = {\n xs: 0,\n sm: 576,\n md: 768,\n lg: 992,\n xl: 1200,\n xxl: 1400\n};\nconst breakpointsVuetifyV2 = {\n xs: 0,\n sm: 600,\n md: 960,\n lg: 1264,\n xl: 1904\n};\nconst breakpointsVuetifyV3 = {\n xs: 0,\n sm: 600,\n md: 960,\n lg: 1280,\n xl: 1920,\n xxl: 2560\n};\nconst breakpointsVuetify = breakpointsVuetifyV2;\nconst breakpointsAntDesign = {\n xs: 480,\n sm: 576,\n md: 768,\n lg: 992,\n xl: 1200,\n xxl: 1600\n};\nconst breakpointsQuasar = {\n xs: 0,\n sm: 600,\n md: 1024,\n lg: 1440,\n xl: 1920\n};\nconst breakpointsSematic = {\n mobileS: 320,\n mobileM: 375,\n mobileL: 425,\n tablet: 768,\n laptop: 1024,\n laptopL: 1440,\n desktop4K: 2560\n};\nconst breakpointsMasterCss = {\n \"3xs\": 360,\n \"2xs\": 480,\n \"xs\": 600,\n \"sm\": 768,\n \"md\": 1024,\n \"lg\": 1280,\n \"xl\": 1440,\n \"2xl\": 1600,\n \"3xl\": 1920,\n \"4xl\": 2560\n};\nconst breakpointsPrimeFlex = {\n sm: 576,\n md: 768,\n lg: 992,\n xl: 1200\n};\nconst breakpointsElement = {\n xs: 0,\n sm: 768,\n md: 992,\n lg: 1200,\n xl: 1920\n};\n\nfunction useBreakpoints(breakpoints, options = {}) {\n function getValue(k, delta) {\n let v = toValue(breakpoints[toValue(k)]);\n if (delta != null)\n v = increaseWithUnit(v, delta);\n if (typeof v === \"number\")\n v = `${v}px`;\n return v;\n }\n const { window = defaultWindow, strategy = \"min-width\", ssrWidth = useSSRWidth() } = options;\n const ssrSupport = typeof ssrWidth === \"number\";\n const mounted = ssrSupport ? shallowRef(false) : { value: true };\n if (ssrSupport) {\n tryOnMounted(() => mounted.value = !!window);\n }\n function match(query, size) {\n if (!mounted.value && ssrSupport) {\n return query === \"min\" ? ssrWidth >= pxValue(size) : ssrWidth <= pxValue(size);\n }\n if (!window)\n return false;\n return window.matchMedia(`(${query}-width: ${size})`).matches;\n }\n const greaterOrEqual = (k) => {\n return useMediaQuery(() => `(min-width: ${getValue(k)})`, options);\n };\n const smallerOrEqual = (k) => {\n return useMediaQuery(() => `(max-width: ${getValue(k)})`, options);\n };\n const shortcutMethods = Object.keys(breakpoints).reduce((shortcuts, k) => {\n Object.defineProperty(shortcuts, k, {\n get: () => strategy === \"min-width\" ? greaterOrEqual(k) : smallerOrEqual(k),\n enumerable: true,\n configurable: true\n });\n return shortcuts;\n }, {});\n function current() {\n const points = Object.keys(breakpoints).map((k) => [k, shortcutMethods[k], pxValue(getValue(k))]).sort((a, b) => a[2] - b[2]);\n return computed(() => points.filter(([, v]) => v.value).map(([k]) => k));\n }\n return Object.assign(shortcutMethods, {\n greaterOrEqual,\n smallerOrEqual,\n greater(k) {\n return useMediaQuery(() => `(min-width: ${getValue(k, 0.1)})`, options);\n },\n smaller(k) {\n return useMediaQuery(() => `(max-width: ${getValue(k, -0.1)})`, options);\n },\n between(a, b) {\n return useMediaQuery(() => `(min-width: ${getValue(a)}) and (max-width: ${getValue(b, -0.1)})`, options);\n },\n isGreater(k) {\n return match(\"min\", getValue(k, 0.1));\n },\n isGreaterOrEqual(k) {\n return match(\"min\", getValue(k));\n },\n isSmaller(k) {\n return match(\"max\", getValue(k, -0.1));\n },\n isSmallerOrEqual(k) {\n return match(\"max\", getValue(k));\n },\n isInBetween(a, b) {\n return match(\"min\", getValue(a)) && match(\"max\", getValue(b, -0.1));\n },\n current,\n active() {\n const bps = current();\n return computed(() => bps.value.length === 0 ? \"\" : bps.value.at(strategy === \"min-width\" ? -1 : 0));\n }\n });\n}\n\nfunction useBroadcastChannel(options) {\n const {\n name,\n window = defaultWindow\n } = options;\n const isSupported = useSupported(() => window && \"BroadcastChannel\" in window);\n const isClosed = shallowRef(false);\n const channel = ref();\n const data = ref();\n const error = shallowRef(null);\n const post = (data2) => {\n if (channel.value)\n channel.value.postMessage(data2);\n };\n const close = () => {\n if (channel.value)\n channel.value.close();\n isClosed.value = true;\n };\n if (isSupported.value) {\n tryOnMounted(() => {\n error.value = null;\n channel.value = new BroadcastChannel(name);\n const listenerOptions = {\n passive: true\n };\n useEventListener(channel, \"message\", (e) => {\n data.value = e.data;\n }, listenerOptions);\n useEventListener(channel, \"messageerror\", (e) => {\n error.value = e;\n }, listenerOptions);\n useEventListener(channel, \"close\", () => {\n isClosed.value = true;\n }, listenerOptions);\n });\n }\n tryOnScopeDispose(() => {\n close();\n });\n return {\n isSupported,\n channel,\n data,\n post,\n close,\n error,\n isClosed\n };\n}\n\nconst WRITABLE_PROPERTIES = [\n \"hash\",\n \"host\",\n \"hostname\",\n \"href\",\n \"pathname\",\n \"port\",\n \"protocol\",\n \"search\"\n];\nfunction useBrowserLocation(options = {}) {\n const { window = defaultWindow } = options;\n const refs = Object.fromEntries(\n WRITABLE_PROPERTIES.map((key) => [key, ref()])\n );\n for (const [key, ref] of objectEntries(refs)) {\n watch(ref, (value) => {\n if (!(window == null ? void 0 : window.location) || window.location[key] === value)\n return;\n window.location[key] = value;\n });\n }\n const buildState = (trigger) => {\n var _a;\n const { state: state2, length } = (window == null ? void 0 : window.history) || {};\n const { origin } = (window == null ? void 0 : window.location) || {};\n for (const key of WRITABLE_PROPERTIES)\n refs[key].value = (_a = window == null ? void 0 : window.location) == null ? void 0 : _a[key];\n return reactive({\n trigger,\n state: state2,\n length,\n origin,\n ...refs\n });\n };\n const state = ref(buildState(\"load\"));\n if (window) {\n const listenerOptions = { passive: true };\n useEventListener(window, \"popstate\", () => state.value = buildState(\"popstate\"), listenerOptions);\n useEventListener(window, \"hashchange\", () => state.value = buildState(\"hashchange\"), listenerOptions);\n }\n return state;\n}\n\nfunction useCached(refValue, comparator = (a, b) => a === b, options) {\n const { deepRefs = true, ...watchOptions } = options || {};\n const cachedValue = createRef(refValue.value, deepRefs);\n watch(() => refValue.value, (value) => {\n if (!comparator(value, cachedValue.value))\n cachedValue.value = value;\n }, watchOptions);\n return cachedValue;\n}\n\nfunction usePermission(permissionDesc, options = {}) {\n const {\n controls = false,\n navigator = defaultNavigator\n } = options;\n const isSupported = useSupported(() => navigator && \"permissions\" in navigator);\n const permissionStatus = shallowRef();\n const desc = typeof permissionDesc === \"string\" ? { name: permissionDesc } : permissionDesc;\n const state = shallowRef();\n const update = () => {\n var _a, _b;\n state.value = (_b = (_a = permissionStatus.value) == null ? void 0 : _a.state) != null ? _b : \"prompt\";\n };\n useEventListener(permissionStatus, \"change\", update, { passive: true });\n const query = createSingletonPromise(async () => {\n if (!isSupported.value)\n return;\n if (!permissionStatus.value) {\n try {\n permissionStatus.value = await navigator.permissions.query(desc);\n } catch (e) {\n permissionStatus.value = void 0;\n } finally {\n update();\n }\n }\n if (controls)\n return toRaw(permissionStatus.value);\n });\n query();\n if (controls) {\n return {\n state,\n isSupported,\n query\n };\n } else {\n return state;\n }\n}\n\nfunction useClipboard(options = {}) {\n const {\n navigator = defaultNavigator,\n read = false,\n source,\n copiedDuring = 1500,\n legacy = false\n } = options;\n const isClipboardApiSupported = useSupported(() => navigator && \"clipboard\" in navigator);\n const permissionRead = usePermission(\"clipboard-read\");\n const permissionWrite = usePermission(\"clipboard-write\");\n const isSupported = computed(() => isClipboardApiSupported.value || legacy);\n const text = shallowRef(\"\");\n const copied = shallowRef(false);\n const timeout = useTimeoutFn(() => copied.value = false, copiedDuring, { immediate: false });\n async function updateText() {\n let useLegacy = !(isClipboardApiSupported.value && isAllowed(permissionRead.value));\n if (!useLegacy) {\n try {\n text.value = await navigator.clipboard.readText();\n } catch (e) {\n useLegacy = true;\n }\n }\n if (useLegacy) {\n text.value = legacyRead();\n }\n }\n if (isSupported.value && read)\n useEventListener([\"copy\", \"cut\"], updateText, { passive: true });\n async function copy(value = toValue(source)) {\n if (isSupported.value && value != null) {\n let useLegacy = !(isClipboardApiSupported.value && isAllowed(permissionWrite.value));\n if (!useLegacy) {\n try {\n await navigator.clipboard.writeText(value);\n } catch (e) {\n useLegacy = true;\n }\n }\n if (useLegacy)\n legacyCopy(value);\n text.value = value;\n copied.value = true;\n timeout.start();\n }\n }\n function legacyCopy(value) {\n const ta = document.createElement(\"textarea\");\n ta.value = value != null ? value : \"\";\n ta.style.position = \"absolute\";\n ta.style.opacity = \"0\";\n document.body.appendChild(ta);\n ta.select();\n document.execCommand(\"copy\");\n ta.remove();\n }\n function legacyRead() {\n var _a, _b, _c;\n return (_c = (_b = (_a = document == null ? void 0 : document.getSelection) == null ? void 0 : _a.call(document)) == null ? void 0 : _b.toString()) != null ? _c : \"\";\n }\n function isAllowed(status) {\n return status === \"granted\" || status === \"prompt\";\n }\n return {\n isSupported,\n text,\n copied,\n copy\n };\n}\n\nfunction useClipboardItems(options = {}) {\n const {\n navigator = defaultNavigator,\n read = false,\n source,\n copiedDuring = 1500\n } = options;\n const isSupported = useSupported(() => navigator && \"clipboard\" in navigator);\n const content = ref([]);\n const copied = shallowRef(false);\n const timeout = useTimeoutFn(() => copied.value = false, copiedDuring, { immediate: false });\n function updateContent() {\n if (isSupported.value) {\n navigator.clipboard.read().then((items) => {\n content.value = items;\n });\n }\n }\n if (isSupported.value && read)\n useEventListener([\"copy\", \"cut\"], updateContent, { passive: true });\n async function copy(value = toValue(source)) {\n if (isSupported.value && value != null) {\n await navigator.clipboard.write(value);\n content.value = value;\n copied.value = true;\n timeout.start();\n }\n }\n return {\n isSupported,\n content,\n copied,\n copy\n };\n}\n\nfunction cloneFnJSON(source) {\n return JSON.parse(JSON.stringify(source));\n}\nfunction useCloned(source, options = {}) {\n const cloned = ref({});\n const isModified = shallowRef(false);\n let _lastSync = false;\n const {\n manual,\n clone = cloneFnJSON,\n // watch options\n deep = true,\n immediate = true\n } = options;\n watch(cloned, () => {\n if (_lastSync) {\n _lastSync = false;\n return;\n }\n isModified.value = true;\n }, {\n deep: true,\n flush: \"sync\"\n });\n function sync() {\n _lastSync = true;\n isModified.value = false;\n cloned.value = clone(toValue(source));\n }\n if (!manual && (isRef(source) || typeof source === \"function\")) {\n watch(source, sync, {\n ...options,\n deep,\n immediate\n });\n } else {\n sync();\n }\n return { cloned, isModified, sync };\n}\n\nconst _global = typeof globalThis !== \"undefined\" ? globalThis : typeof window !== \"undefined\" ? window : typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : {};\nconst globalKey = \"__vueuse_ssr_handlers__\";\nconst handlers = /* @__PURE__ */ getHandlers();\nfunction getHandlers() {\n if (!(globalKey in _global))\n _global[globalKey] = _global[globalKey] || {};\n return _global[globalKey];\n}\nfunction getSSRHandler(key, fallback) {\n return handlers[key] || fallback;\n}\nfunction setSSRHandler(key, fn) {\n handlers[key] = fn;\n}\n\nfunction usePreferredDark(options) {\n return useMediaQuery(\"(prefers-color-scheme: dark)\", options);\n}\n\nfunction guessSerializerType(rawInit) {\n return rawInit == null ? \"any\" : rawInit instanceof Set ? \"set\" : rawInit instanceof Map ? \"map\" : rawInit instanceof Date ? \"date\" : typeof rawInit === \"boolean\" ? \"boolean\" : typeof rawInit === \"string\" ? \"string\" : typeof rawInit === \"object\" ? \"object\" : !Number.isNaN(rawInit) ? \"number\" : \"any\";\n}\n\nconst StorageSerializers = {\n boolean: {\n read: (v) => v === \"true\",\n write: (v) => String(v)\n },\n object: {\n read: (v) => JSON.parse(v),\n write: (v) => JSON.stringify(v)\n },\n number: {\n read: (v) => Number.parseFloat(v),\n write: (v) => String(v)\n },\n any: {\n read: (v) => v,\n write: (v) => String(v)\n },\n string: {\n read: (v) => v,\n write: (v) => String(v)\n },\n map: {\n read: (v) => new Map(JSON.parse(v)),\n write: (v) => JSON.stringify(Array.from(v.entries()))\n },\n set: {\n read: (v) => new Set(JSON.parse(v)),\n write: (v) => JSON.stringify(Array.from(v))\n },\n date: {\n read: (v) => new Date(v),\n write: (v) => v.toISOString()\n }\n};\nconst customStorageEventName = \"vueuse-storage\";\nfunction useStorage(key, defaults, storage, options = {}) {\n var _a;\n const {\n flush = \"pre\",\n deep = true,\n listenToStorageChanges = true,\n writeDefaults = true,\n mergeDefaults = false,\n shallow,\n window = defaultWindow,\n eventFilter,\n onError = (e) => {\n console.error(e);\n },\n initOnMounted\n } = options;\n const data = (shallow ? shallowRef : ref)(typeof defaults === \"function\" ? defaults() : defaults);\n const keyComputed = computed(() => toValue(key));\n if (!storage) {\n try {\n storage = getSSRHandler(\"getDefaultStorage\", () => {\n var _a2;\n return (_a2 = defaultWindow) == null ? void 0 : _a2.localStorage;\n })();\n } catch (e) {\n onError(e);\n }\n }\n if (!storage)\n return data;\n const rawInit = toValue(defaults);\n const type = guessSerializerType(rawInit);\n const serializer = (_a = options.serializer) != null ? _a : StorageSerializers[type];\n const { pause: pauseWatch, resume: resumeWatch } = pausableWatch(\n data,\n () => write(data.value),\n { flush, deep, eventFilter }\n );\n watch(keyComputed, () => update(), { flush });\n if (window && listenToStorageChanges) {\n tryOnMounted(() => {\n if (storage instanceof Storage)\n useEventListener(window, \"storage\", update, { passive: true });\n else\n useEventListener(window, customStorageEventName, updateFromCustomEvent);\n if (initOnMounted)\n update();\n });\n }\n if (!initOnMounted)\n update();\n function dispatchWriteEvent(oldValue, newValue) {\n if (window) {\n const payload = {\n key: keyComputed.value,\n oldValue,\n newValue,\n storageArea: storage\n };\n window.dispatchEvent(storage instanceof Storage ? new StorageEvent(\"storage\", payload) : new CustomEvent(customStorageEventName, {\n detail: payload\n }));\n }\n }\n function write(v) {\n try {\n const oldValue = storage.getItem(keyComputed.value);\n if (v == null) {\n dispatchWriteEvent(oldValue, null);\n storage.removeItem(keyComputed.value);\n } else {\n const serialized = serializer.write(v);\n if (oldValue !== serialized) {\n storage.setItem(keyComputed.value, serialized);\n dispatchWriteEvent(oldValue, serialized);\n }\n }\n } catch (e) {\n onError(e);\n }\n }\n function read(event) {\n const rawValue = event ? event.newValue : storage.getItem(keyComputed.value);\n if (rawValue == null) {\n if (writeDefaults && rawInit != null)\n storage.setItem(keyComputed.value, serializer.write(rawInit));\n return rawInit;\n } else if (!event && mergeDefaults) {\n const value = serializer.read(rawValue);\n if (typeof mergeDefaults === \"function\")\n return mergeDefaults(value, rawInit);\n else if (type === \"object\" && !Array.isArray(value))\n return { ...rawInit, ...value };\n return value;\n } else if (typeof rawValue !== \"string\") {\n return rawValue;\n } else {\n return serializer.read(rawValue);\n }\n }\n function update(event) {\n if (event && event.storageArea !== storage)\n return;\n if (event && event.key == null) {\n data.value = rawInit;\n return;\n }\n if (event && event.key !== keyComputed.value)\n return;\n pauseWatch();\n try {\n if ((event == null ? void 0 : event.newValue) !== serializer.write(data.value))\n data.value = read(event);\n } catch (e) {\n onError(e);\n } finally {\n if (event)\n nextTick(resumeWatch);\n else\n resumeWatch();\n }\n }\n function updateFromCustomEvent(event) {\n update(event.detail);\n }\n return data;\n}\n\nconst CSS_DISABLE_TRANS = \"*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}\";\nfunction useColorMode(options = {}) {\n const {\n selector = \"html\",\n attribute = \"class\",\n initialValue = \"auto\",\n window = defaultWindow,\n storage,\n storageKey = \"vueuse-color-scheme\",\n listenToStorageChanges = true,\n storageRef,\n emitAuto,\n disableTransition = true\n } = options;\n const modes = {\n auto: \"\",\n light: \"light\",\n dark: \"dark\",\n ...options.modes || {}\n };\n const preferredDark = usePreferredDark({ window });\n const system = computed(() => preferredDark.value ? \"dark\" : \"light\");\n const store = storageRef || (storageKey == null ? toRef(initialValue) : useStorage(storageKey, initialValue, storage, { window, listenToStorageChanges }));\n const state = computed(() => store.value === \"auto\" ? system.value : store.value);\n const updateHTMLAttrs = getSSRHandler(\n \"updateHTMLAttrs\",\n (selector2, attribute2, value) => {\n const el = typeof selector2 === \"string\" ? window == null ? void 0 : window.document.querySelector(selector2) : unrefElement(selector2);\n if (!el)\n return;\n const classesToAdd = /* @__PURE__ */ new Set();\n const classesToRemove = /* @__PURE__ */ new Set();\n let attributeToChange = null;\n if (attribute2 === \"class\") {\n const current = value.split(/\\s/g);\n Object.values(modes).flatMap((i) => (i || \"\").split(/\\s/g)).filter(Boolean).forEach((v) => {\n if (current.includes(v))\n classesToAdd.add(v);\n else\n classesToRemove.add(v);\n });\n } else {\n attributeToChange = { key: attribute2, value };\n }\n if (classesToAdd.size === 0 && classesToRemove.size === 0 && attributeToChange === null)\n return;\n let style;\n if (disableTransition) {\n style = window.document.createElement(\"style\");\n style.appendChild(document.createTextNode(CSS_DISABLE_TRANS));\n window.document.head.appendChild(style);\n }\n for (const c of classesToAdd) {\n el.classList.add(c);\n }\n for (const c of classesToRemove) {\n el.classList.remove(c);\n }\n if (attributeToChange) {\n el.setAttribute(attributeToChange.key, attributeToChange.value);\n }\n if (disableTransition) {\n window.getComputedStyle(style).opacity;\n document.head.removeChild(style);\n }\n }\n );\n function defaultOnChanged(mode) {\n var _a;\n updateHTMLAttrs(selector, attribute, (_a = modes[mode]) != null ? _a : mode);\n }\n function onChanged(mode) {\n if (options.onChanged)\n options.onChanged(mode, defaultOnChanged);\n else\n defaultOnChanged(mode);\n }\n watch(state, onChanged, { flush: \"post\", immediate: true });\n tryOnMounted(() => onChanged(state.value));\n const auto = computed({\n get() {\n return emitAuto ? store.value : state.value;\n },\n set(v) {\n store.value = v;\n }\n });\n return Object.assign(auto, { store, system, state });\n}\n\nfunction useConfirmDialog(revealed = shallowRef(false)) {\n const confirmHook = createEventHook();\n const cancelHook = createEventHook();\n const revealHook = createEventHook();\n let _resolve = noop;\n const reveal = (data) => {\n revealHook.trigger(data);\n revealed.value = true;\n return new Promise((resolve) => {\n _resolve = resolve;\n });\n };\n const confirm = (data) => {\n revealed.value = false;\n confirmHook.trigger(data);\n _resolve({ data, isCanceled: false });\n };\n const cancel = (data) => {\n revealed.value = false;\n cancelHook.trigger(data);\n _resolve({ data, isCanceled: true });\n };\n return {\n isRevealed: computed(() => revealed.value),\n reveal,\n confirm,\n cancel,\n onReveal: revealHook.on,\n onConfirm: confirmHook.on,\n onCancel: cancelHook.on\n };\n}\n\nfunction useCountdown(initialCountdown, options) {\n var _a, _b;\n const remaining = shallowRef(toValue(initialCountdown));\n const intervalController = useIntervalFn(() => {\n var _a2, _b2;\n const value = remaining.value - 1;\n remaining.value = value < 0 ? 0 : value;\n (_a2 = options == null ? void 0 : options.onTick) == null ? void 0 : _a2.call(options);\n if (remaining.value <= 0) {\n intervalController.pause();\n (_b2 = options == null ? void 0 : options.onComplete) == null ? void 0 : _b2.call(options);\n }\n }, (_a = options == null ? void 0 : options.interval) != null ? _a : 1e3, { immediate: (_b = options == null ? void 0 : options.immediate) != null ? _b : false });\n const reset = (countdown) => {\n var _a2;\n remaining.value = (_a2 = toValue(countdown)) != null ? _a2 : toValue(initialCountdown);\n };\n const stop = () => {\n intervalController.pause();\n reset();\n };\n const resume = () => {\n if (!intervalController.isActive.value) {\n if (remaining.value > 0) {\n intervalController.resume();\n }\n }\n };\n const start = (countdown) => {\n reset(countdown);\n intervalController.resume();\n };\n return {\n remaining,\n reset,\n stop,\n start,\n pause: intervalController.pause,\n resume,\n isActive: intervalController.isActive\n };\n}\n\nfunction useCssVar(prop, target, options = {}) {\n const { window = defaultWindow, initialValue, observe = false } = options;\n const variable = shallowRef(initialValue);\n const elRef = computed(() => {\n var _a;\n return unrefElement(target) || ((_a = window == null ? void 0 : window.document) == null ? void 0 : _a.documentElement);\n });\n function updateCssVar() {\n var _a;\n const key = toValue(prop);\n const el = toValue(elRef);\n if (el && window && key) {\n const value = (_a = window.getComputedStyle(el).getPropertyValue(key)) == null ? void 0 : _a.trim();\n variable.value = value || variable.value || initialValue;\n }\n }\n if (observe) {\n useMutationObserver(elRef, updateCssVar, {\n attributeFilter: [\"style\", \"class\"],\n window\n });\n }\n watch(\n [elRef, () => toValue(prop)],\n (_, old) => {\n if (old[0] && old[1])\n old[0].style.removeProperty(old[1]);\n updateCssVar();\n },\n { immediate: true }\n );\n watch(\n [variable, elRef],\n ([val, el]) => {\n const raw_prop = toValue(prop);\n if ((el == null ? void 0 : el.style) && raw_prop) {\n if (val == null)\n el.style.removeProperty(raw_prop);\n else\n el.style.setProperty(raw_prop, val);\n }\n },\n { immediate: true }\n );\n return variable;\n}\n\nfunction useCurrentElement(rootComponent) {\n const vm = getCurrentInstance();\n const currentElement = computedWithControl(\n () => null,\n () => rootComponent ? unrefElement(rootComponent) : vm.proxy.$el\n );\n onUpdated(currentElement.trigger);\n onMounted(currentElement.trigger);\n return currentElement;\n}\n\nfunction useCycleList(list, options) {\n const state = shallowRef(getInitialValue());\n const listRef = toRef(list);\n const index = computed({\n get() {\n var _a;\n const targetList = listRef.value;\n let index2 = (options == null ? void 0 : options.getIndexOf) ? options.getIndexOf(state.value, targetList) : targetList.indexOf(state.value);\n if (index2 < 0)\n index2 = (_a = options == null ? void 0 : options.fallbackIndex) != null ? _a : 0;\n return index2;\n },\n set(v) {\n set(v);\n }\n });\n function set(i) {\n const targetList = listRef.value;\n const length = targetList.length;\n const index2 = (i % length + length) % length;\n const value = targetList[index2];\n state.value = value;\n return value;\n }\n function shift(delta = 1) {\n return set(index.value + delta);\n }\n function next(n = 1) {\n return shift(n);\n }\n function prev(n = 1) {\n return shift(-n);\n }\n function getInitialValue() {\n var _a, _b;\n return (_b = toValue((_a = options == null ? void 0 : options.initialValue) != null ? _a : toValue(list)[0])) != null ? _b : void 0;\n }\n watch(listRef, () => set(index.value));\n return {\n state,\n index,\n next,\n prev,\n go: set\n };\n}\n\nfunction useDark(options = {}) {\n const {\n valueDark = \"dark\",\n valueLight = \"\"\n } = options;\n const mode = useColorMode({\n ...options,\n onChanged: (mode2, defaultHandler) => {\n var _a;\n if (options.onChanged)\n (_a = options.onChanged) == null ? void 0 : _a.call(options, mode2 === \"dark\", defaultHandler, mode2);\n else\n defaultHandler(mode2);\n },\n modes: {\n dark: valueDark,\n light: valueLight\n }\n });\n const system = computed(() => mode.system.value);\n const isDark = computed({\n get() {\n return mode.value === \"dark\";\n },\n set(v) {\n const modeVal = v ? \"dark\" : \"light\";\n if (system.value === modeVal)\n mode.value = \"auto\";\n else\n mode.value = modeVal;\n }\n });\n return isDark;\n}\n\nfunction fnBypass(v) {\n return v;\n}\nfunction fnSetSource(source, value) {\n return source.value = value;\n}\nfunction defaultDump(clone) {\n return clone ? typeof clone === \"function\" ? clone : cloneFnJSON : fnBypass;\n}\nfunction defaultParse(clone) {\n return clone ? typeof clone === \"function\" ? clone : cloneFnJSON : fnBypass;\n}\nfunction useManualRefHistory(source, options = {}) {\n const {\n clone = false,\n dump = defaultDump(clone),\n parse = defaultParse(clone),\n setSource = fnSetSource\n } = options;\n function _createHistoryRecord() {\n return markRaw({\n snapshot: dump(source.value),\n timestamp: timestamp()\n });\n }\n const last = ref(_createHistoryRecord());\n const undoStack = ref([]);\n const redoStack = ref([]);\n const _setSource = (record) => {\n setSource(source, parse(record.snapshot));\n last.value = record;\n };\n const commit = () => {\n undoStack.value.unshift(last.value);\n last.value = _createHistoryRecord();\n if (options.capacity && undoStack.value.length > options.capacity)\n undoStack.value.splice(options.capacity, Number.POSITIVE_INFINITY);\n if (redoStack.value.length)\n redoStack.value.splice(0, redoStack.value.length);\n };\n const clear = () => {\n undoStack.value.splice(0, undoStack.value.length);\n redoStack.value.splice(0, redoStack.value.length);\n };\n const undo = () => {\n const state = undoStack.value.shift();\n if (state) {\n redoStack.value.unshift(last.value);\n _setSource(state);\n }\n };\n const redo = () => {\n const state = redoStack.value.shift();\n if (state) {\n undoStack.value.unshift(last.value);\n _setSource(state);\n }\n };\n const reset = () => {\n _setSource(last.value);\n };\n const history = computed(() => [last.value, ...undoStack.value]);\n const canUndo = computed(() => undoStack.value.length > 0);\n const canRedo = computed(() => redoStack.value.length > 0);\n return {\n source,\n undoStack,\n redoStack,\n last,\n history,\n canUndo,\n canRedo,\n clear,\n commit,\n reset,\n undo,\n redo\n };\n}\n\nfunction useRefHistory(source, options = {}) {\n const {\n deep = false,\n flush = \"pre\",\n eventFilter\n } = options;\n const {\n eventFilter: composedFilter,\n pause,\n resume: resumeTracking,\n isActive: isTracking\n } = pausableFilter(eventFilter);\n const {\n ignoreUpdates,\n ignorePrevAsyncUpdates,\n stop\n } = watchIgnorable(\n source,\n commit,\n { deep, flush, eventFilter: composedFilter }\n );\n function setSource(source2, value) {\n ignorePrevAsyncUpdates();\n ignoreUpdates(() => {\n source2.value = value;\n });\n }\n const manualHistory = useManualRefHistory(source, { ...options, clone: options.clone || deep, setSource });\n const { clear, commit: manualCommit } = manualHistory;\n function commit() {\n ignorePrevAsyncUpdates();\n manualCommit();\n }\n function resume(commitNow) {\n resumeTracking();\n if (commitNow)\n commit();\n }\n function batch(fn) {\n let canceled = false;\n const cancel = () => canceled = true;\n ignoreUpdates(() => {\n fn(cancel);\n });\n if (!canceled)\n commit();\n }\n function dispose() {\n stop();\n clear();\n }\n return {\n ...manualHistory,\n isTracking,\n pause,\n resume,\n commit,\n batch,\n dispose\n };\n}\n\nfunction useDebouncedRefHistory(source, options = {}) {\n const filter = options.debounce ? debounceFilter(options.debounce) : void 0;\n const history = useRefHistory(source, { ...options, eventFilter: filter });\n return {\n ...history\n };\n}\n\nfunction useDeviceMotion(options = {}) {\n const {\n window = defaultWindow,\n requestPermissions = false,\n eventFilter = bypassFilter\n } = options;\n const isSupported = useSupported(() => typeof DeviceMotionEvent !== \"undefined\");\n const requirePermissions = useSupported(() => isSupported.value && \"requestPermission\" in DeviceMotionEvent && typeof DeviceMotionEvent.requestPermission === \"function\");\n const permissionGranted = shallowRef(false);\n const acceleration = ref({ x: null, y: null, z: null });\n const rotationRate = ref({ alpha: null, beta: null, gamma: null });\n const interval = shallowRef(0);\n const accelerationIncludingGravity = ref({\n x: null,\n y: null,\n z: null\n });\n function init() {\n if (window) {\n const onDeviceMotion = createFilterWrapper(\n eventFilter,\n (event) => {\n var _a, _b, _c, _d, _e, _f, _g, _h, _i;\n acceleration.value = {\n x: ((_a = event.acceleration) == null ? void 0 : _a.x) || null,\n y: ((_b = event.acceleration) == null ? void 0 : _b.y) || null,\n z: ((_c = event.acceleration) == null ? void 0 : _c.z) || null\n };\n accelerationIncludingGravity.value = {\n x: ((_d = event.accelerationIncludingGravity) == null ? void 0 : _d.x) || null,\n y: ((_e = event.accelerationIncludingGravity) == null ? void 0 : _e.y) || null,\n z: ((_f = event.accelerationIncludingGravity) == null ? void 0 : _f.z) || null\n };\n rotationRate.value = {\n alpha: ((_g = event.rotationRate) == null ? void 0 : _g.alpha) || null,\n beta: ((_h = event.rotationRate) == null ? void 0 : _h.beta) || null,\n gamma: ((_i = event.rotationRate) == null ? void 0 : _i.gamma) || null\n };\n interval.value = event.interval;\n }\n );\n useEventListener(window, \"devicemotion\", onDeviceMotion, { passive: true });\n }\n }\n const ensurePermissions = async () => {\n if (!requirePermissions.value)\n permissionGranted.value = true;\n if (permissionGranted.value)\n return;\n if (requirePermissions.value) {\n const requestPermission = DeviceMotionEvent.requestPermission;\n try {\n const response = await requestPermission();\n if (response === \"granted\") {\n permissionGranted.value = true;\n init();\n }\n } catch (error) {\n console.error(error);\n }\n }\n };\n if (isSupported.value) {\n if (requestPermissions && requirePermissions.value) {\n ensurePermissions().then(() => init());\n } else {\n init();\n }\n }\n return {\n acceleration,\n accelerationIncludingGravity,\n rotationRate,\n interval,\n isSupported,\n requirePermissions,\n ensurePermissions,\n permissionGranted\n };\n}\n\nfunction useDeviceOrientation(options = {}) {\n const { window = defaultWindow } = options;\n const isSupported = useSupported(() => window && \"DeviceOrientationEvent\" in window);\n const isAbsolute = shallowRef(false);\n const alpha = shallowRef(null);\n const beta = shallowRef(null);\n const gamma = shallowRef(null);\n if (window && isSupported.value) {\n useEventListener(window, \"deviceorientation\", (event) => {\n isAbsolute.value = event.absolute;\n alpha.value = event.alpha;\n beta.value = event.beta;\n gamma.value = event.gamma;\n }, { passive: true });\n }\n return {\n isSupported,\n isAbsolute,\n alpha,\n beta,\n gamma\n };\n}\n\nfunction useDevicePixelRatio(options = {}) {\n const {\n window = defaultWindow\n } = options;\n const pixelRatio = shallowRef(1);\n const query = useMediaQuery(() => `(resolution: ${pixelRatio.value}dppx)`, options);\n let stop = noop;\n if (window) {\n stop = watchImmediate(query, () => pixelRatio.value = window.devicePixelRatio);\n }\n return {\n pixelRatio: readonly(pixelRatio),\n stop\n };\n}\n\nfunction useDevicesList(options = {}) {\n const {\n navigator = defaultNavigator,\n requestPermissions = false,\n constraints = { audio: true, video: true },\n onUpdated\n } = options;\n const devices = ref([]);\n const videoInputs = computed(() => devices.value.filter((i) => i.kind === \"videoinput\"));\n const audioInputs = computed(() => devices.value.filter((i) => i.kind === \"audioinput\"));\n const audioOutputs = computed(() => devices.value.filter((i) => i.kind === \"audiooutput\"));\n const isSupported = useSupported(() => navigator && navigator.mediaDevices && navigator.mediaDevices.enumerateDevices);\n const permissionGranted = shallowRef(false);\n let stream;\n async function update() {\n if (!isSupported.value)\n return;\n devices.value = await navigator.mediaDevices.enumerateDevices();\n onUpdated == null ? void 0 : onUpdated(devices.value);\n if (stream) {\n stream.getTracks().forEach((t) => t.stop());\n stream = null;\n }\n }\n async function ensurePermissions() {\n const deviceName = constraints.video ? \"camera\" : \"microphone\";\n if (!isSupported.value)\n return false;\n if (permissionGranted.value)\n return true;\n const { state, query } = usePermission(deviceName, { controls: true });\n await query();\n if (state.value !== \"granted\") {\n let granted = true;\n try {\n stream = await navigator.mediaDevices.getUserMedia(constraints);\n } catch (e) {\n stream = null;\n granted = false;\n }\n update();\n permissionGranted.value = granted;\n } else {\n permissionGranted.value = true;\n }\n return permissionGranted.value;\n }\n if (isSupported.value) {\n if (requestPermissions)\n ensurePermissions();\n useEventListener(navigator.mediaDevices, \"devicechange\", update, { passive: true });\n update();\n }\n return {\n devices,\n ensurePermissions,\n permissionGranted,\n videoInputs,\n audioInputs,\n audioOutputs,\n isSupported\n };\n}\n\nfunction useDisplayMedia(options = {}) {\n var _a;\n const enabled = shallowRef((_a = options.enabled) != null ? _a : false);\n const video = options.video;\n const audio = options.audio;\n const { navigator = defaultNavigator } = options;\n const isSupported = useSupported(() => {\n var _a2;\n return (_a2 = navigator == null ? void 0 : navigator.mediaDevices) == null ? void 0 : _a2.getDisplayMedia;\n });\n const constraint = { audio, video };\n const stream = shallowRef();\n async function _start() {\n var _a2;\n if (!isSupported.value || stream.value)\n return;\n stream.value = await navigator.mediaDevices.getDisplayMedia(constraint);\n (_a2 = stream.value) == null ? void 0 : _a2.getTracks().forEach((t) => useEventListener(t, \"ended\", stop, { passive: true }));\n return stream.value;\n }\n async function _stop() {\n var _a2;\n (_a2 = stream.value) == null ? void 0 : _a2.getTracks().forEach((t) => t.stop());\n stream.value = void 0;\n }\n function stop() {\n _stop();\n enabled.value = false;\n }\n async function start() {\n await _start();\n if (stream.value)\n enabled.value = true;\n return stream.value;\n }\n watch(\n enabled,\n (v) => {\n if (v)\n _start();\n else\n _stop();\n },\n { immediate: true }\n );\n return {\n isSupported,\n stream,\n start,\n stop,\n enabled\n };\n}\n\nfunction useDocumentVisibility(options = {}) {\n const { document = defaultDocument } = options;\n if (!document)\n return shallowRef(\"visible\");\n const visibility = shallowRef(document.visibilityState);\n useEventListener(document, \"visibilitychange\", () => {\n visibility.value = document.visibilityState;\n }, { passive: true });\n return visibility;\n}\n\nfunction useDraggable(target, options = {}) {\n var _a;\n const {\n pointerTypes,\n preventDefault,\n stopPropagation,\n exact,\n onMove,\n onEnd,\n onStart,\n initialValue,\n axis = \"both\",\n draggingElement = defaultWindow,\n containerElement,\n handle: draggingHandle = target,\n buttons = [0]\n } = options;\n const position = ref(\n (_a = toValue(initialValue)) != null ? _a : { x: 0, y: 0 }\n );\n const pressedDelta = ref();\n const filterEvent = (e) => {\n if (pointerTypes)\n return pointerTypes.includes(e.pointerType);\n return true;\n };\n const handleEvent = (e) => {\n if (toValue(preventDefault))\n e.preventDefault();\n if (toValue(stopPropagation))\n e.stopPropagation();\n };\n const start = (e) => {\n var _a2;\n if (!toValue(buttons).includes(e.button))\n return;\n if (toValue(options.disabled) || !filterEvent(e))\n return;\n if (toValue(exact) && e.target !== toValue(target))\n return;\n const container = toValue(containerElement);\n const containerRect = (_a2 = container == null ? void 0 : container.getBoundingClientRect) == null ? void 0 : _a2.call(container);\n const targetRect = toValue(target).getBoundingClientRect();\n const pos = {\n x: e.clientX - (container ? targetRect.left - containerRect.left + container.scrollLeft : targetRect.left),\n y: e.clientY - (container ? targetRect.top - containerRect.top + container.scrollTop : targetRect.top)\n };\n if ((onStart == null ? void 0 : onStart(pos, e)) === false)\n return;\n pressedDelta.value = pos;\n handleEvent(e);\n };\n const move = (e) => {\n if (toValue(options.disabled) || !filterEvent(e))\n return;\n if (!pressedDelta.value)\n return;\n const container = toValue(containerElement);\n const targetRect = toValue(target).getBoundingClientRect();\n let { x, y } = position.value;\n if (axis === \"x\" || axis === \"both\") {\n x = e.clientX - pressedDelta.value.x;\n if (container)\n x = Math.min(Math.max(0, x), container.scrollWidth - targetRect.width);\n }\n if (axis === \"y\" || axis === \"both\") {\n y = e.clientY - pressedDelta.value.y;\n if (container)\n y = Math.min(Math.max(0, y), container.scrollHeight - targetRect.height);\n }\n position.value = {\n x,\n y\n };\n onMove == null ? void 0 : onMove(position.value, e);\n handleEvent(e);\n };\n const end = (e) => {\n if (toValue(options.disabled) || !filterEvent(e))\n return;\n if (!pressedDelta.value)\n return;\n pressedDelta.value = void 0;\n onEnd == null ? void 0 : onEnd(position.value, e);\n handleEvent(e);\n };\n if (isClient) {\n const config = () => {\n var _a2;\n return {\n capture: (_a2 = options.capture) != null ? _a2 : true,\n passive: !toValue(preventDefault)\n };\n };\n useEventListener(draggingHandle, \"pointerdown\", start, config);\n useEventListener(draggingElement, \"pointermove\", move, config);\n useEventListener(draggingElement, \"pointerup\", end, config);\n }\n return {\n ...toRefs(position),\n position,\n isDragging: computed(() => !!pressedDelta.value),\n style: computed(\n () => `left:${position.value.x}px;top:${position.value.y}px;`\n )\n };\n}\n\nfunction useDropZone(target, options = {}) {\n var _a, _b;\n const isOverDropZone = shallowRef(false);\n const files = shallowRef(null);\n let counter = 0;\n let isValid = true;\n if (isClient) {\n const _options = typeof options === \"function\" ? { onDrop: options } : options;\n const multiple = (_a = _options.multiple) != null ? _a : true;\n const preventDefaultForUnhandled = (_b = _options.preventDefaultForUnhandled) != null ? _b : false;\n const getFiles = (event) => {\n var _a2, _b2;\n const list = Array.from((_b2 = (_a2 = event.dataTransfer) == null ? void 0 : _a2.files) != null ? _b2 : []);\n return list.length === 0 ? null : multiple ? list : [list[0]];\n };\n const checkDataTypes = (types) => {\n const dataTypes = unref(_options.dataTypes);\n if (typeof dataTypes === \"function\")\n return dataTypes(types);\n if (!(dataTypes == null ? void 0 : dataTypes.length))\n return true;\n if (types.length === 0)\n return false;\n return types.every(\n (type) => dataTypes.some((allowedType) => type.includes(allowedType))\n );\n };\n const checkValidity = (items) => {\n const types = Array.from(items != null ? items : []).map((item) => item.type);\n const dataTypesValid = checkDataTypes(types);\n const multipleFilesValid = multiple || items.length <= 1;\n return dataTypesValid && multipleFilesValid;\n };\n const isSafari = () => /^(?:(?!chrome|android).)*safari/i.test(navigator.userAgent) && !(\"chrome\" in window);\n const handleDragEvent = (event, eventType) => {\n var _a2, _b2, _c, _d, _e, _f;\n const dataTransferItemList = (_a2 = event.dataTransfer) == null ? void 0 : _a2.items;\n isValid = (_b2 = dataTransferItemList && checkValidity(dataTransferItemList)) != null ? _b2 : false;\n if (preventDefaultForUnhandled) {\n event.preventDefault();\n }\n if (!isSafari() && !isValid) {\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = \"none\";\n }\n return;\n }\n event.preventDefault();\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = \"copy\";\n }\n const currentFiles = getFiles(event);\n switch (eventType) {\n case \"enter\":\n counter += 1;\n isOverDropZone.value = true;\n (_c = _options.onEnter) == null ? void 0 : _c.call(_options, null, event);\n break;\n case \"over\":\n (_d = _options.onOver) == null ? void 0 : _d.call(_options, null, event);\n break;\n case \"leave\":\n counter -= 1;\n if (counter === 0)\n isOverDropZone.value = false;\n (_e = _options.onLeave) == null ? void 0 : _e.call(_options, null, event);\n break;\n case \"drop\":\n counter = 0;\n isOverDropZone.value = false;\n if (isValid) {\n files.value = currentFiles;\n (_f = _options.onDrop) == null ? void 0 : _f.call(_options, currentFiles, event);\n }\n break;\n }\n };\n useEventListener(target, \"dragenter\", (event) => handleDragEvent(event, \"enter\"));\n useEventListener(target, \"dragover\", (event) => handleDragEvent(event, \"over\"));\n useEventListener(target, \"dragleave\", (event) => handleDragEvent(event, \"leave\"));\n useEventListener(target, \"drop\", (event) => handleDragEvent(event, \"drop\"));\n }\n return {\n files,\n isOverDropZone\n };\n}\n\nfunction useResizeObserver(target, callback, options = {}) {\n const { window = defaultWindow, ...observerOptions } = options;\n let observer;\n const isSupported = useSupported(() => window && \"ResizeObserver\" in window);\n const cleanup = () => {\n if (observer) {\n observer.disconnect();\n observer = void 0;\n }\n };\n const targets = computed(() => {\n const _targets = toValue(target);\n return Array.isArray(_targets) ? _targets.map((el) => unrefElement(el)) : [unrefElement(_targets)];\n });\n const stopWatch = watch(\n targets,\n (els) => {\n cleanup();\n if (isSupported.value && window) {\n observer = new ResizeObserver(callback);\n for (const _el of els) {\n if (_el)\n observer.observe(_el, observerOptions);\n }\n }\n },\n { immediate: true, flush: \"post\" }\n );\n const stop = () => {\n cleanup();\n stopWatch();\n };\n tryOnScopeDispose(stop);\n return {\n isSupported,\n stop\n };\n}\n\nfunction useElementBounding(target, options = {}) {\n const {\n reset = true,\n windowResize = true,\n windowScroll = true,\n immediate = true,\n updateTiming = \"sync\"\n } = options;\n const height = shallowRef(0);\n const bottom = shallowRef(0);\n const left = shallowRef(0);\n const right = shallowRef(0);\n const top = shallowRef(0);\n const width = shallowRef(0);\n const x = shallowRef(0);\n const y = shallowRef(0);\n function recalculate() {\n const el = unrefElement(target);\n if (!el) {\n if (reset) {\n height.value = 0;\n bottom.value = 0;\n left.value = 0;\n right.value = 0;\n top.value = 0;\n width.value = 0;\n x.value = 0;\n y.value = 0;\n }\n return;\n }\n const rect = el.getBoundingClientRect();\n height.value = rect.height;\n bottom.value = rect.bottom;\n left.value = rect.left;\n right.value = rect.right;\n top.value = rect.top;\n width.value = rect.width;\n x.value = rect.x;\n y.value = rect.y;\n }\n function update() {\n if (updateTiming === \"sync\")\n recalculate();\n else if (updateTiming === \"next-frame\")\n requestAnimationFrame(() => recalculate());\n }\n useResizeObserver(target, update);\n watch(() => unrefElement(target), (ele) => !ele && update());\n useMutationObserver(target, update, {\n attributeFilter: [\"style\", \"class\"]\n });\n if (windowScroll)\n useEventListener(\"scroll\", update, { capture: true, passive: true });\n if (windowResize)\n useEventListener(\"resize\", update, { passive: true });\n tryOnMounted(() => {\n if (immediate)\n update();\n });\n return {\n height,\n bottom,\n left,\n right,\n top,\n width,\n x,\n y,\n update\n };\n}\n\nfunction useElementByPoint(options) {\n const {\n x,\n y,\n document = defaultDocument,\n multiple,\n interval = \"requestAnimationFrame\",\n immediate = true\n } = options;\n const isSupported = useSupported(() => {\n if (toValue(multiple))\n return document && \"elementsFromPoint\" in document;\n return document && \"elementFromPoint\" in document;\n });\n const element = shallowRef(null);\n const cb = () => {\n var _a, _b;\n element.value = toValue(multiple) ? (_a = document == null ? void 0 : document.elementsFromPoint(toValue(x), toValue(y))) != null ? _a : [] : (_b = document == null ? void 0 : document.elementFromPoint(toValue(x), toValue(y))) != null ? _b : null;\n };\n const controls = interval === \"requestAnimationFrame\" ? useRafFn(cb, { immediate }) : useIntervalFn(cb, interval, { immediate });\n return {\n isSupported,\n element,\n ...controls\n };\n}\n\nfunction useElementHover(el, options = {}) {\n const {\n delayEnter = 0,\n delayLeave = 0,\n triggerOnRemoval = false,\n window = defaultWindow\n } = options;\n const isHovered = shallowRef(false);\n let timer;\n const toggle = (entering) => {\n const delay = entering ? delayEnter : delayLeave;\n if (timer) {\n clearTimeout(timer);\n timer = void 0;\n }\n if (delay)\n timer = setTimeout(() => isHovered.value = entering, delay);\n else\n isHovered.value = entering;\n };\n if (!window)\n return isHovered;\n useEventListener(el, \"mouseenter\", () => toggle(true), { passive: true });\n useEventListener(el, \"mouseleave\", () => toggle(false), { passive: true });\n if (triggerOnRemoval) {\n onElementRemoval(\n computed(() => unrefElement(el)),\n () => toggle(false)\n );\n }\n return isHovered;\n}\n\nfunction useElementSize(target, initialSize = { width: 0, height: 0 }, options = {}) {\n const { window = defaultWindow, box = \"content-box\" } = options;\n const isSVG = computed(() => {\n var _a, _b;\n return (_b = (_a = unrefElement(target)) == null ? void 0 : _a.namespaceURI) == null ? void 0 : _b.includes(\"svg\");\n });\n const width = shallowRef(initialSize.width);\n const height = shallowRef(initialSize.height);\n const { stop: stop1 } = useResizeObserver(\n target,\n ([entry]) => {\n const boxSize = box === \"border-box\" ? entry.borderBoxSize : box === \"content-box\" ? entry.contentBoxSize : entry.devicePixelContentBoxSize;\n if (window && isSVG.value) {\n const $elem = unrefElement(target);\n if ($elem) {\n const rect = $elem.getBoundingClientRect();\n width.value = rect.width;\n height.value = rect.height;\n }\n } else {\n if (boxSize) {\n const formatBoxSize = toArray(boxSize);\n width.value = formatBoxSize.reduce((acc, { inlineSize }) => acc + inlineSize, 0);\n height.value = formatBoxSize.reduce((acc, { blockSize }) => acc + blockSize, 0);\n } else {\n width.value = entry.contentRect.width;\n height.value = entry.contentRect.height;\n }\n }\n },\n options\n );\n tryOnMounted(() => {\n const ele = unrefElement(target);\n if (ele) {\n width.value = \"offsetWidth\" in ele ? ele.offsetWidth : initialSize.width;\n height.value = \"offsetHeight\" in ele ? ele.offsetHeight : initialSize.height;\n }\n });\n const stop2 = watch(\n () => unrefElement(target),\n (ele) => {\n width.value = ele ? initialSize.width : 0;\n height.value = ele ? initialSize.height : 0;\n }\n );\n function stop() {\n stop1();\n stop2();\n }\n return {\n width,\n height,\n stop\n };\n}\n\nfunction useIntersectionObserver(target, callback, options = {}) {\n const {\n root,\n rootMargin = \"0px\",\n threshold = 0,\n window = defaultWindow,\n immediate = true\n } = options;\n const isSupported = useSupported(() => window && \"IntersectionObserver\" in window);\n const targets = computed(() => {\n const _target = toValue(target);\n return toArray(_target).map(unrefElement).filter(notNullish);\n });\n let cleanup = noop;\n const isActive = shallowRef(immediate);\n const stopWatch = isSupported.value ? watch(\n () => [targets.value, unrefElement(root), isActive.value],\n ([targets2, root2]) => {\n cleanup();\n if (!isActive.value)\n return;\n if (!targets2.length)\n return;\n const observer = new IntersectionObserver(\n callback,\n {\n root: unrefElement(root2),\n rootMargin,\n threshold\n }\n );\n targets2.forEach((el) => el && observer.observe(el));\n cleanup = () => {\n observer.disconnect();\n cleanup = noop;\n };\n },\n { immediate, flush: \"post\" }\n ) : noop;\n const stop = () => {\n cleanup();\n stopWatch();\n isActive.value = false;\n };\n tryOnScopeDispose(stop);\n return {\n isSupported,\n isActive,\n pause() {\n cleanup();\n isActive.value = false;\n },\n resume() {\n isActive.value = true;\n },\n stop\n };\n}\n\nfunction useElementVisibility(element, options = {}) {\n const {\n window = defaultWindow,\n scrollTarget,\n threshold = 0,\n rootMargin,\n once = false\n } = options;\n const elementIsVisible = shallowRef(false);\n const { stop } = useIntersectionObserver(\n element,\n (intersectionObserverEntries) => {\n let isIntersecting = elementIsVisible.value;\n let latestTime = 0;\n for (const entry of intersectionObserverEntries) {\n if (entry.time >= latestTime) {\n latestTime = entry.time;\n isIntersecting = entry.isIntersecting;\n }\n }\n elementIsVisible.value = isIntersecting;\n if (once) {\n watchOnce(elementIsVisible, () => {\n stop();\n });\n }\n },\n {\n root: scrollTarget,\n window,\n threshold,\n rootMargin: toValue(rootMargin)\n }\n );\n return elementIsVisible;\n}\n\nconst events = /* @__PURE__ */ new Map();\n\nfunction useEventBus(key) {\n const scope = getCurrentScope();\n function on(listener) {\n var _a;\n const listeners = events.get(key) || /* @__PURE__ */ new Set();\n listeners.add(listener);\n events.set(key, listeners);\n const _off = () => off(listener);\n (_a = scope == null ? void 0 : scope.cleanups) == null ? void 0 : _a.push(_off);\n return _off;\n }\n function once(listener) {\n function _listener(...args) {\n off(_listener);\n listener(...args);\n }\n return on(_listener);\n }\n function off(listener) {\n const listeners = events.get(key);\n if (!listeners)\n return;\n listeners.delete(listener);\n if (!listeners.size)\n reset();\n }\n function reset() {\n events.delete(key);\n }\n function emit(event, payload) {\n var _a;\n (_a = events.get(key)) == null ? void 0 : _a.forEach((v) => v(event, payload));\n }\n return { on, once, off, emit, reset };\n}\n\nfunction resolveNestedOptions$1(options) {\n if (options === true)\n return {};\n return options;\n}\nfunction useEventSource(url, events = [], options = {}) {\n const event = shallowRef(null);\n const data = shallowRef(null);\n const status = shallowRef(\"CONNECTING\");\n const eventSource = ref(null);\n const error = shallowRef(null);\n const urlRef = toRef(url);\n const lastEventId = shallowRef(null);\n let explicitlyClosed = false;\n let retried = 0;\n const {\n withCredentials = false,\n immediate = true,\n autoConnect = true,\n autoReconnect\n } = options;\n const close = () => {\n if (isClient && eventSource.value) {\n eventSource.value.close();\n eventSource.value = null;\n status.value = \"CLOSED\";\n explicitlyClosed = true;\n }\n };\n const _init = () => {\n if (explicitlyClosed || typeof urlRef.value === \"undefined\")\n return;\n const es = new EventSource(urlRef.value, { withCredentials });\n status.value = \"CONNECTING\";\n eventSource.value = es;\n es.onopen = () => {\n status.value = \"OPEN\";\n error.value = null;\n };\n es.onerror = (e) => {\n status.value = \"CLOSED\";\n error.value = e;\n if (es.readyState === 2 && !explicitlyClosed && autoReconnect) {\n es.close();\n const {\n retries = -1,\n delay = 1e3,\n onFailed\n } = resolveNestedOptions$1(autoReconnect);\n retried += 1;\n if (typeof retries === \"number\" && (retries < 0 || retried < retries))\n setTimeout(_init, delay);\n else if (typeof retries === \"function\" && retries())\n setTimeout(_init, delay);\n else\n onFailed == null ? void 0 : onFailed();\n }\n };\n es.onmessage = (e) => {\n event.value = null;\n data.value = e.data;\n lastEventId.value = e.lastEventId;\n };\n for (const event_name of events) {\n useEventListener(es, event_name, (e) => {\n event.value = event_name;\n data.value = e.data || null;\n }, { passive: true });\n }\n };\n const open = () => {\n if (!isClient)\n return;\n close();\n explicitlyClosed = false;\n retried = 0;\n _init();\n };\n if (immediate)\n open();\n if (autoConnect)\n watch(urlRef, open);\n tryOnScopeDispose(close);\n return {\n eventSource,\n event,\n data,\n status,\n error,\n open,\n close,\n lastEventId\n };\n}\n\nfunction useEyeDropper(options = {}) {\n const { initialValue = \"\" } = options;\n const isSupported = useSupported(() => typeof window !== \"undefined\" && \"EyeDropper\" in window);\n const sRGBHex = shallowRef(initialValue);\n async function open(openOptions) {\n if (!isSupported.value)\n return;\n const eyeDropper = new window.EyeDropper();\n const result = await eyeDropper.open(openOptions);\n sRGBHex.value = result.sRGBHex;\n return result;\n }\n return { isSupported, sRGBHex, open };\n}\n\nfunction useFavicon(newIcon = null, options = {}) {\n const {\n baseUrl = \"\",\n rel = \"icon\",\n document = defaultDocument\n } = options;\n const favicon = toRef(newIcon);\n const applyIcon = (icon) => {\n const elements = document == null ? void 0 : document.head.querySelectorAll(`link[rel*=\"${rel}\"]`);\n if (!elements || elements.length === 0) {\n const link = document == null ? void 0 : document.createElement(\"link\");\n if (link) {\n link.rel = rel;\n link.href = `${baseUrl}${icon}`;\n link.type = `image/${icon.split(\".\").pop()}`;\n document == null ? void 0 : document.head.append(link);\n }\n return;\n }\n elements == null ? void 0 : elements.forEach((el) => el.href = `${baseUrl}${icon}`);\n };\n watch(\n favicon,\n (i, o) => {\n if (typeof i === \"string\" && i !== o)\n applyIcon(i);\n },\n { immediate: true }\n );\n return favicon;\n}\n\nconst payloadMapping = {\n json: \"application/json\",\n text: \"text/plain\"\n};\nfunction isFetchOptions(obj) {\n return obj && containsProp(obj, \"immediate\", \"refetch\", \"initialData\", \"timeout\", \"beforeFetch\", \"afterFetch\", \"onFetchError\", \"fetch\", \"updateDataOnError\");\n}\nconst reAbsolute = /^(?:[a-z][a-z\\d+\\-.]*:)?\\/\\//i;\nfunction isAbsoluteURL(url) {\n return reAbsolute.test(url);\n}\nfunction headersToObject(headers) {\n if (typeof Headers !== \"undefined\" && headers instanceof Headers)\n return Object.fromEntries(headers.entries());\n return headers;\n}\nfunction combineCallbacks(combination, ...callbacks) {\n if (combination === \"overwrite\") {\n return async (ctx) => {\n let callback;\n for (let i = callbacks.length - 1; i >= 0; i--) {\n if (callbacks[i] != null) {\n callback = callbacks[i];\n break;\n }\n }\n if (callback)\n return { ...ctx, ...await callback(ctx) };\n return ctx;\n };\n } else {\n return async (ctx) => {\n for (const callback of callbacks) {\n if (callback)\n ctx = { ...ctx, ...await callback(ctx) };\n }\n return ctx;\n };\n }\n}\nfunction createFetch(config = {}) {\n const _combination = config.combination || \"chain\";\n const _options = config.options || {};\n const _fetchOptions = config.fetchOptions || {};\n function useFactoryFetch(url, ...args) {\n const computedUrl = computed(() => {\n const baseUrl = toValue(config.baseUrl);\n const targetUrl = toValue(url);\n return baseUrl && !isAbsoluteURL(targetUrl) ? joinPaths(baseUrl, targetUrl) : targetUrl;\n });\n let options = _options;\n let fetchOptions = _fetchOptions;\n if (args.length > 0) {\n if (isFetchOptions(args[0])) {\n options = {\n ...options,\n ...args[0],\n beforeFetch: combineCallbacks(_combination, _options.beforeFetch, args[0].beforeFetch),\n afterFetch: combineCallbacks(_combination, _options.afterFetch, args[0].afterFetch),\n onFetchError: combineCallbacks(_combination, _options.onFetchError, args[0].onFetchError)\n };\n } else {\n fetchOptions = {\n ...fetchOptions,\n ...args[0],\n headers: {\n ...headersToObject(fetchOptions.headers) || {},\n ...headersToObject(args[0].headers) || {}\n }\n };\n }\n }\n if (args.length > 1 && isFetchOptions(args[1])) {\n options = {\n ...options,\n ...args[1],\n beforeFetch: combineCallbacks(_combination, _options.beforeFetch, args[1].beforeFetch),\n afterFetch: combineCallbacks(_combination, _options.afterFetch, args[1].afterFetch),\n onFetchError: combineCallbacks(_combination, _options.onFetchError, args[1].onFetchError)\n };\n }\n return useFetch(computedUrl, fetchOptions, options);\n }\n return useFactoryFetch;\n}\nfunction useFetch(url, ...args) {\n var _a;\n const supportsAbort = typeof AbortController === \"function\";\n let fetchOptions = {};\n let options = {\n immediate: true,\n refetch: false,\n timeout: 0,\n updateDataOnError: false\n };\n const config = {\n method: \"GET\",\n type: \"text\",\n payload: void 0\n };\n if (args.length > 0) {\n if (isFetchOptions(args[0]))\n options = { ...options, ...args[0] };\n else\n fetchOptions = args[0];\n }\n if (args.length > 1) {\n if (isFetchOptions(args[1]))\n options = { ...options, ...args[1] };\n }\n const {\n fetch = (_a = defaultWindow) == null ? void 0 : _a.fetch,\n initialData,\n timeout\n } = options;\n const responseEvent = createEventHook();\n const errorEvent = createEventHook();\n const finallyEvent = createEventHook();\n const isFinished = shallowRef(false);\n const isFetching = shallowRef(false);\n const aborted = shallowRef(false);\n const statusCode = shallowRef(null);\n const response = shallowRef(null);\n const error = shallowRef(null);\n const data = shallowRef(initialData || null);\n const canAbort = computed(() => supportsAbort && isFetching.value);\n let controller;\n let timer;\n const abort = () => {\n if (supportsAbort) {\n controller == null ? void 0 : controller.abort();\n controller = new AbortController();\n controller.signal.onabort = () => aborted.value = true;\n fetchOptions = {\n ...fetchOptions,\n signal: controller.signal\n };\n }\n };\n const loading = (isLoading) => {\n isFetching.value = isLoading;\n isFinished.value = !isLoading;\n };\n if (timeout)\n timer = useTimeoutFn(abort, timeout, { immediate: false });\n let executeCounter = 0;\n const execute = async (throwOnFailed = false) => {\n var _a2, _b;\n abort();\n loading(true);\n error.value = null;\n statusCode.value = null;\n aborted.value = false;\n executeCounter += 1;\n const currentExecuteCounter = executeCounter;\n const defaultFetchOptions = {\n method: config.method,\n headers: {}\n };\n const payload = toValue(config.payload);\n if (payload) {\n const headers = headersToObject(defaultFetchOptions.headers);\n const proto = Object.getPrototypeOf(payload);\n if (!config.payloadType && payload && (proto === Object.prototype || Array.isArray(proto)) && !(payload instanceof FormData))\n config.payloadType = \"json\";\n if (config.payloadType)\n headers[\"Content-Type\"] = (_a2 = payloadMapping[config.payloadType]) != null ? _a2 : config.payloadType;\n defaultFetchOptions.body = config.payloadType === \"json\" ? JSON.stringify(payload) : payload;\n }\n let isCanceled = false;\n const context = {\n url: toValue(url),\n options: {\n ...defaultFetchOptions,\n ...fetchOptions\n },\n cancel: () => {\n isCanceled = true;\n }\n };\n if (options.beforeFetch)\n Object.assign(context, await options.beforeFetch(context));\n if (isCanceled || !fetch) {\n loading(false);\n return Promise.resolve(null);\n }\n let responseData = null;\n if (timer)\n timer.start();\n return fetch(\n context.url,\n {\n ...defaultFetchOptions,\n ...context.options,\n headers: {\n ...headersToObject(defaultFetchOptions.headers),\n ...headersToObject((_b = context.options) == null ? void 0 : _b.headers)\n }\n }\n ).then(async (fetchResponse) => {\n response.value = fetchResponse;\n statusCode.value = fetchResponse.status;\n responseData = await fetchResponse.clone()[config.type]();\n if (!fetchResponse.ok) {\n data.value = initialData || null;\n throw new Error(fetchResponse.statusText);\n }\n if (options.afterFetch) {\n ({ data: responseData } = await options.afterFetch({\n data: responseData,\n response: fetchResponse,\n context,\n execute\n }));\n }\n data.value = responseData;\n responseEvent.trigger(fetchResponse);\n return fetchResponse;\n }).catch(async (fetchError) => {\n let errorData = fetchError.message || fetchError.name;\n if (options.onFetchError) {\n ({ error: errorData, data: responseData } = await options.onFetchError({\n data: responseData,\n error: fetchError,\n response: response.value,\n context,\n execute\n }));\n }\n error.value = errorData;\n if (options.updateDataOnError)\n data.value = responseData;\n errorEvent.trigger(fetchError);\n if (throwOnFailed)\n throw fetchError;\n return null;\n }).finally(() => {\n if (currentExecuteCounter === executeCounter)\n loading(false);\n if (timer)\n timer.stop();\n finallyEvent.trigger(null);\n });\n };\n const refetch = toRef(options.refetch);\n watch(\n [\n refetch,\n toRef(url)\n ],\n ([refetch2]) => refetch2 && execute(),\n { deep: true }\n );\n const shell = {\n isFinished: readonly(isFinished),\n isFetching: readonly(isFetching),\n statusCode,\n response,\n error,\n data,\n canAbort,\n aborted,\n abort,\n execute,\n onFetchResponse: responseEvent.on,\n onFetchError: errorEvent.on,\n onFetchFinally: finallyEvent.on,\n // method\n get: setMethod(\"GET\"),\n put: setMethod(\"PUT\"),\n post: setMethod(\"POST\"),\n delete: setMethod(\"DELETE\"),\n patch: setMethod(\"PATCH\"),\n head: setMethod(\"HEAD\"),\n options: setMethod(\"OPTIONS\"),\n // type\n json: setType(\"json\"),\n text: setType(\"text\"),\n blob: setType(\"blob\"),\n arrayBuffer: setType(\"arrayBuffer\"),\n formData: setType(\"formData\")\n };\n function setMethod(method) {\n return (payload, payloadType) => {\n if (!isFetching.value) {\n config.method = method;\n config.payload = payload;\n config.payloadType = payloadType;\n if (isRef(config.payload)) {\n watch(\n [\n refetch,\n toRef(config.payload)\n ],\n ([refetch2]) => refetch2 && execute(),\n { deep: true }\n );\n }\n return {\n ...shell,\n then(onFulfilled, onRejected) {\n return waitUntilFinished().then(onFulfilled, onRejected);\n }\n };\n }\n return void 0;\n };\n }\n function waitUntilFinished() {\n return new Promise((resolve, reject) => {\n until(isFinished).toBe(true).then(() => resolve(shell)).catch(reject);\n });\n }\n function setType(type) {\n return () => {\n if (!isFetching.value) {\n config.type = type;\n return {\n ...shell,\n then(onFulfilled, onRejected) {\n return waitUntilFinished().then(onFulfilled, onRejected);\n }\n };\n }\n return void 0;\n };\n }\n if (options.immediate)\n Promise.resolve().then(() => execute());\n return {\n ...shell,\n then(onFulfilled, onRejected) {\n return waitUntilFinished().then(onFulfilled, onRejected);\n }\n };\n}\nfunction joinPaths(start, end) {\n if (!start.endsWith(\"/\") && !end.startsWith(\"/\")) {\n return `${start}/${end}`;\n }\n if (start.endsWith(\"/\") && end.startsWith(\"/\")) {\n return `${start.slice(0, -1)}${end}`;\n }\n return `${start}${end}`;\n}\n\nconst DEFAULT_OPTIONS = {\n multiple: true,\n accept: \"*\",\n reset: false,\n directory: false\n};\nfunction prepareInitialFiles(files) {\n if (!files)\n return null;\n if (files instanceof FileList)\n return files;\n const dt = new DataTransfer();\n for (const file of files) {\n dt.items.add(file);\n }\n return dt.files;\n}\nfunction useFileDialog(options = {}) {\n const {\n document = defaultDocument\n } = options;\n const files = ref(prepareInitialFiles(options.initialFiles));\n const { on: onChange, trigger: changeTrigger } = createEventHook();\n const { on: onCancel, trigger: cancelTrigger } = createEventHook();\n let input;\n if (document) {\n input = document.createElement(\"input\");\n input.type = \"file\";\n input.onchange = (event) => {\n const result = event.target;\n files.value = result.files;\n changeTrigger(files.value);\n };\n input.oncancel = () => {\n cancelTrigger();\n };\n }\n const reset = () => {\n files.value = null;\n if (input && input.value) {\n input.value = \"\";\n changeTrigger(null);\n }\n };\n const open = (localOptions) => {\n if (!input)\n return;\n const _options = {\n ...DEFAULT_OPTIONS,\n ...options,\n ...localOptions\n };\n input.multiple = _options.multiple;\n input.accept = _options.accept;\n input.webkitdirectory = _options.directory;\n if (hasOwn(_options, \"capture\"))\n input.capture = _options.capture;\n if (_options.reset)\n reset();\n input.click();\n };\n return {\n files: readonly(files),\n open,\n reset,\n onCancel,\n onChange\n };\n}\n\nfunction useFileSystemAccess(options = {}) {\n const {\n window: _window = defaultWindow,\n dataType = \"Text\"\n } = options;\n const window = _window;\n const isSupported = useSupported(() => window && \"showSaveFilePicker\" in window && \"showOpenFilePicker\" in window);\n const fileHandle = shallowRef();\n const data = shallowRef();\n const file = shallowRef();\n const fileName = computed(() => {\n var _a, _b;\n return (_b = (_a = file.value) == null ? void 0 : _a.name) != null ? _b : \"\";\n });\n const fileMIME = computed(() => {\n var _a, _b;\n return (_b = (_a = file.value) == null ? void 0 : _a.type) != null ? _b : \"\";\n });\n const fileSize = computed(() => {\n var _a, _b;\n return (_b = (_a = file.value) == null ? void 0 : _a.size) != null ? _b : 0;\n });\n const fileLastModified = computed(() => {\n var _a, _b;\n return (_b = (_a = file.value) == null ? void 0 : _a.lastModified) != null ? _b : 0;\n });\n async function open(_options = {}) {\n if (!isSupported.value)\n return;\n const [handle] = await window.showOpenFilePicker({ ...toValue(options), ..._options });\n fileHandle.value = handle;\n await updateData();\n }\n async function create(_options = {}) {\n if (!isSupported.value)\n return;\n fileHandle.value = await window.showSaveFilePicker({ ...options, ..._options });\n data.value = void 0;\n await updateData();\n }\n async function save(_options = {}) {\n if (!isSupported.value)\n return;\n if (!fileHandle.value)\n return saveAs(_options);\n if (data.value) {\n const writableStream = await fileHandle.value.createWritable();\n await writableStream.write(data.value);\n await writableStream.close();\n }\n await updateFile();\n }\n async function saveAs(_options = {}) {\n if (!isSupported.value)\n return;\n fileHandle.value = await window.showSaveFilePicker({ ...options, ..._options });\n if (data.value) {\n const writableStream = await fileHandle.value.createWritable();\n await writableStream.write(data.value);\n await writableStream.close();\n }\n await updateFile();\n }\n async function updateFile() {\n var _a;\n file.value = await ((_a = fileHandle.value) == null ? void 0 : _a.getFile());\n }\n async function updateData() {\n var _a, _b;\n await updateFile();\n const type = toValue(dataType);\n if (type === \"Text\")\n data.value = await ((_a = file.value) == null ? void 0 : _a.text());\n else if (type === \"ArrayBuffer\")\n data.value = await ((_b = file.value) == null ? void 0 : _b.arrayBuffer());\n else if (type === \"Blob\")\n data.value = file.value;\n }\n watch(() => toValue(dataType), updateData);\n return {\n isSupported,\n data,\n file,\n fileName,\n fileMIME,\n fileSize,\n fileLastModified,\n open,\n create,\n save,\n saveAs,\n updateData\n };\n}\n\nfunction useFocus(target, options = {}) {\n const { initialValue = false, focusVisible = false, preventScroll = false } = options;\n const innerFocused = shallowRef(false);\n const targetElement = computed(() => unrefElement(target));\n const listenerOptions = { passive: true };\n useEventListener(targetElement, \"focus\", (event) => {\n var _a, _b;\n if (!focusVisible || ((_b = (_a = event.target).matches) == null ? void 0 : _b.call(_a, \":focus-visible\")))\n innerFocused.value = true;\n }, listenerOptions);\n useEventListener(targetElement, \"blur\", () => innerFocused.value = false, listenerOptions);\n const focused = computed({\n get: () => innerFocused.value,\n set(value) {\n var _a, _b;\n if (!value && innerFocused.value)\n (_a = targetElement.value) == null ? void 0 : _a.blur();\n else if (value && !innerFocused.value)\n (_b = targetElement.value) == null ? void 0 : _b.focus({ preventScroll });\n }\n });\n watch(\n targetElement,\n () => {\n focused.value = initialValue;\n },\n { immediate: true, flush: \"post\" }\n );\n return { focused };\n}\n\nconst EVENT_FOCUS_IN = \"focusin\";\nconst EVENT_FOCUS_OUT = \"focusout\";\nconst PSEUDO_CLASS_FOCUS_WITHIN = \":focus-within\";\nfunction useFocusWithin(target, options = {}) {\n const { window = defaultWindow } = options;\n const targetElement = computed(() => unrefElement(target));\n const _focused = shallowRef(false);\n const focused = computed(() => _focused.value);\n const activeElement = useActiveElement(options);\n if (!window || !activeElement.value) {\n return { focused };\n }\n const listenerOptions = { passive: true };\n useEventListener(targetElement, EVENT_FOCUS_IN, () => _focused.value = true, listenerOptions);\n useEventListener(targetElement, EVENT_FOCUS_OUT, () => {\n var _a, _b, _c;\n return _focused.value = (_c = (_b = (_a = targetElement.value) == null ? void 0 : _a.matches) == null ? void 0 : _b.call(_a, PSEUDO_CLASS_FOCUS_WITHIN)) != null ? _c : false;\n }, listenerOptions);\n return { focused };\n}\n\nfunction useFps(options) {\n var _a;\n const fps = shallowRef(0);\n if (typeof performance === \"undefined\")\n return fps;\n const every = (_a = options == null ? void 0 : options.every) != null ? _a : 10;\n let last = performance.now();\n let ticks = 0;\n useRafFn(() => {\n ticks += 1;\n if (ticks >= every) {\n const now = performance.now();\n const diff = now - last;\n fps.value = Math.round(1e3 / (diff / ticks));\n last = now;\n ticks = 0;\n }\n });\n return fps;\n}\n\nconst eventHandlers = [\n \"fullscreenchange\",\n \"webkitfullscreenchange\",\n \"webkitendfullscreen\",\n \"mozfullscreenchange\",\n \"MSFullscreenChange\"\n];\nfunction useFullscreen(target, options = {}) {\n const {\n document = defaultDocument,\n autoExit = false\n } = options;\n const targetRef = computed(() => {\n var _a;\n return (_a = unrefElement(target)) != null ? _a : document == null ? void 0 : document.documentElement;\n });\n const isFullscreen = shallowRef(false);\n const requestMethod = computed(() => {\n return [\n \"requestFullscreen\",\n \"webkitRequestFullscreen\",\n \"webkitEnterFullscreen\",\n \"webkitEnterFullScreen\",\n \"webkitRequestFullScreen\",\n \"mozRequestFullScreen\",\n \"msRequestFullscreen\"\n ].find((m) => document && m in document || targetRef.value && m in targetRef.value);\n });\n const exitMethod = computed(() => {\n return [\n \"exitFullscreen\",\n \"webkitExitFullscreen\",\n \"webkitExitFullScreen\",\n \"webkitCancelFullScreen\",\n \"mozCancelFullScreen\",\n \"msExitFullscreen\"\n ].find((m) => document && m in document || targetRef.value && m in targetRef.value);\n });\n const fullscreenEnabled = computed(() => {\n return [\n \"fullScreen\",\n \"webkitIsFullScreen\",\n \"webkitDisplayingFullscreen\",\n \"mozFullScreen\",\n \"msFullscreenElement\"\n ].find((m) => document && m in document || targetRef.value && m in targetRef.value);\n });\n const fullscreenElementMethod = [\n \"fullscreenElement\",\n \"webkitFullscreenElement\",\n \"mozFullScreenElement\",\n \"msFullscreenElement\"\n ].find((m) => document && m in document);\n const isSupported = useSupported(() => targetRef.value && document && requestMethod.value !== void 0 && exitMethod.value !== void 0 && fullscreenEnabled.value !== void 0);\n const isCurrentElementFullScreen = () => {\n if (fullscreenElementMethod)\n return (document == null ? void 0 : document[fullscreenElementMethod]) === targetRef.value;\n return false;\n };\n const isElementFullScreen = () => {\n if (fullscreenEnabled.value) {\n if (document && document[fullscreenEnabled.value] != null) {\n return document[fullscreenEnabled.value];\n } else {\n const target2 = targetRef.value;\n if ((target2 == null ? void 0 : target2[fullscreenEnabled.value]) != null) {\n return Boolean(target2[fullscreenEnabled.value]);\n }\n }\n }\n return false;\n };\n async function exit() {\n if (!isSupported.value || !isFullscreen.value)\n return;\n if (exitMethod.value) {\n if ((document == null ? void 0 : document[exitMethod.value]) != null) {\n await document[exitMethod.value]();\n } else {\n const target2 = targetRef.value;\n if ((target2 == null ? void 0 : target2[exitMethod.value]) != null)\n await target2[exitMethod.value]();\n }\n }\n isFullscreen.value = false;\n }\n async function enter() {\n if (!isSupported.value || isFullscreen.value)\n return;\n if (isElementFullScreen())\n await exit();\n const target2 = targetRef.value;\n if (requestMethod.value && (target2 == null ? void 0 : target2[requestMethod.value]) != null) {\n await target2[requestMethod.value]();\n isFullscreen.value = true;\n }\n }\n async function toggle() {\n await (isFullscreen.value ? exit() : enter());\n }\n const handlerCallback = () => {\n const isElementFullScreenValue = isElementFullScreen();\n if (!isElementFullScreenValue || isElementFullScreenValue && isCurrentElementFullScreen())\n isFullscreen.value = isElementFullScreenValue;\n };\n const listenerOptions = { capture: false, passive: true };\n useEventListener(document, eventHandlers, handlerCallback, listenerOptions);\n useEventListener(() => unrefElement(targetRef), eventHandlers, handlerCallback, listenerOptions);\n if (autoExit)\n tryOnScopeDispose(exit);\n return {\n isSupported,\n isFullscreen,\n enter,\n exit,\n toggle\n };\n}\n\nfunction mapGamepadToXbox360Controller(gamepad) {\n return computed(() => {\n if (gamepad.value) {\n return {\n buttons: {\n a: gamepad.value.buttons[0],\n b: gamepad.value.buttons[1],\n x: gamepad.value.buttons[2],\n y: gamepad.value.buttons[3]\n },\n bumper: {\n left: gamepad.value.buttons[4],\n right: gamepad.value.buttons[5]\n },\n triggers: {\n left: gamepad.value.buttons[6],\n right: gamepad.value.buttons[7]\n },\n stick: {\n left: {\n horizontal: gamepad.value.axes[0],\n vertical: gamepad.value.axes[1],\n button: gamepad.value.buttons[10]\n },\n right: {\n horizontal: gamepad.value.axes[2],\n vertical: gamepad.value.axes[3],\n button: gamepad.value.buttons[11]\n }\n },\n dpad: {\n up: gamepad.value.buttons[12],\n down: gamepad.value.buttons[13],\n left: gamepad.value.buttons[14],\n right: gamepad.value.buttons[15]\n },\n back: gamepad.value.buttons[8],\n start: gamepad.value.buttons[9]\n };\n }\n return null;\n });\n}\nfunction useGamepad(options = {}) {\n const {\n navigator = defaultNavigator\n } = options;\n const isSupported = useSupported(() => navigator && \"getGamepads\" in navigator);\n const gamepads = ref([]);\n const onConnectedHook = createEventHook();\n const onDisconnectedHook = createEventHook();\n const stateFromGamepad = (gamepad) => {\n const hapticActuators = [];\n const vibrationActuator = \"vibrationActuator\" in gamepad ? gamepad.vibrationActuator : null;\n if (vibrationActuator)\n hapticActuators.push(vibrationActuator);\n if (gamepad.hapticActuators)\n hapticActuators.push(...gamepad.hapticActuators);\n return {\n id: gamepad.id,\n index: gamepad.index,\n connected: gamepad.connected,\n mapping: gamepad.mapping,\n timestamp: gamepad.timestamp,\n vibrationActuator: gamepad.vibrationActuator,\n hapticActuators,\n axes: gamepad.axes.map((axes) => axes),\n buttons: gamepad.buttons.map((button) => ({ pressed: button.pressed, touched: button.touched, value: button.value }))\n };\n };\n const updateGamepadState = () => {\n const _gamepads = (navigator == null ? void 0 : navigator.getGamepads()) || [];\n for (const gamepad of _gamepads) {\n if (gamepad && gamepads.value[gamepad.index])\n gamepads.value[gamepad.index] = stateFromGamepad(gamepad);\n }\n };\n const { isActive, pause, resume } = useRafFn(updateGamepadState);\n const onGamepadConnected = (gamepad) => {\n if (!gamepads.value.some(({ index }) => index === gamepad.index)) {\n gamepads.value.push(stateFromGamepad(gamepad));\n onConnectedHook.trigger(gamepad.index);\n }\n resume();\n };\n const onGamepadDisconnected = (gamepad) => {\n gamepads.value = gamepads.value.filter((x) => x.index !== gamepad.index);\n onDisconnectedHook.trigger(gamepad.index);\n };\n const listenerOptions = { passive: true };\n useEventListener(\"gamepadconnected\", (e) => onGamepadConnected(e.gamepad), listenerOptions);\n useEventListener(\"gamepaddisconnected\", (e) => onGamepadDisconnected(e.gamepad), listenerOptions);\n tryOnMounted(() => {\n const _gamepads = (navigator == null ? void 0 : navigator.getGamepads()) || [];\n for (const gamepad of _gamepads) {\n if (gamepad && gamepads.value[gamepad.index])\n onGamepadConnected(gamepad);\n }\n });\n pause();\n return {\n isSupported,\n onConnected: onConnectedHook.on,\n onDisconnected: onDisconnectedHook.on,\n gamepads,\n pause,\n resume,\n isActive\n };\n}\n\nfunction useGeolocation(options = {}) {\n const {\n enableHighAccuracy = true,\n maximumAge = 3e4,\n timeout = 27e3,\n navigator = defaultNavigator,\n immediate = true\n } = options;\n const isSupported = useSupported(() => navigator && \"geolocation\" in navigator);\n const locatedAt = shallowRef(null);\n const error = shallowRef(null);\n const coords = ref({\n accuracy: 0,\n latitude: Number.POSITIVE_INFINITY,\n longitude: Number.POSITIVE_INFINITY,\n altitude: null,\n altitudeAccuracy: null,\n heading: null,\n speed: null\n });\n function updatePosition(position) {\n locatedAt.value = position.timestamp;\n coords.value = position.coords;\n error.value = null;\n }\n let watcher;\n function resume() {\n if (isSupported.value) {\n watcher = navigator.geolocation.watchPosition(\n updatePosition,\n (err) => error.value = err,\n {\n enableHighAccuracy,\n maximumAge,\n timeout\n }\n );\n }\n }\n if (immediate)\n resume();\n function pause() {\n if (watcher && navigator)\n navigator.geolocation.clearWatch(watcher);\n }\n tryOnScopeDispose(() => {\n pause();\n });\n return {\n isSupported,\n coords,\n locatedAt,\n error,\n resume,\n pause\n };\n}\n\nconst defaultEvents$1 = [\"mousemove\", \"mousedown\", \"resize\", \"keydown\", \"touchstart\", \"wheel\"];\nconst oneMinute = 6e4;\nfunction useIdle(timeout = oneMinute, options = {}) {\n const {\n initialState = false,\n listenForVisibilityChange = true,\n events = defaultEvents$1,\n window = defaultWindow,\n eventFilter = throttleFilter(50)\n } = options;\n const idle = shallowRef(initialState);\n const lastActive = shallowRef(timestamp());\n let timer;\n const reset = () => {\n idle.value = false;\n clearTimeout(timer);\n timer = setTimeout(() => idle.value = true, timeout);\n };\n const onEvent = createFilterWrapper(\n eventFilter,\n () => {\n lastActive.value = timestamp();\n reset();\n }\n );\n if (window) {\n const document = window.document;\n const listenerOptions = { passive: true };\n for (const event of events)\n useEventListener(window, event, onEvent, listenerOptions);\n if (listenForVisibilityChange) {\n useEventListener(document, \"visibilitychange\", () => {\n if (!document.hidden)\n onEvent();\n }, listenerOptions);\n }\n reset();\n }\n return {\n idle,\n lastActive,\n reset\n };\n}\n\nasync function loadImage(options) {\n return new Promise((resolve, reject) => {\n const img = new Image();\n const { src, srcset, sizes, class: clazz, loading, crossorigin, referrerPolicy, width, height, decoding, fetchPriority, ismap, usemap } = options;\n img.src = src;\n if (srcset != null)\n img.srcset = srcset;\n if (sizes != null)\n img.sizes = sizes;\n if (clazz != null)\n img.className = clazz;\n if (loading != null)\n img.loading = loading;\n if (crossorigin != null)\n img.crossOrigin = crossorigin;\n if (referrerPolicy != null)\n img.referrerPolicy = referrerPolicy;\n if (width != null)\n img.width = width;\n if (height != null)\n img.height = height;\n if (decoding != null)\n img.decoding = decoding;\n if (fetchPriority != null)\n img.fetchPriority = fetchPriority;\n if (ismap != null)\n img.isMap = ismap;\n if (usemap != null)\n img.useMap = usemap;\n img.onload = () => resolve(img);\n img.onerror = reject;\n });\n}\nfunction useImage(options, asyncStateOptions = {}) {\n const state = useAsyncState(\n () => loadImage(toValue(options)),\n void 0,\n {\n resetOnExecute: true,\n ...asyncStateOptions\n }\n );\n watch(\n () => toValue(options),\n () => state.execute(asyncStateOptions.delay),\n { deep: true }\n );\n return state;\n}\n\nfunction resolveElement(el) {\n if (typeof Window !== \"undefined\" && el instanceof Window)\n return el.document.documentElement;\n if (typeof Document !== \"undefined\" && el instanceof Document)\n return el.documentElement;\n return el;\n}\n\nconst ARRIVED_STATE_THRESHOLD_PIXELS = 1;\nfunction useScroll(element, options = {}) {\n const {\n throttle = 0,\n idle = 200,\n onStop = noop,\n onScroll = noop,\n offset = {\n left: 0,\n right: 0,\n top: 0,\n bottom: 0\n },\n eventListenerOptions = {\n capture: false,\n passive: true\n },\n behavior = \"auto\",\n window = defaultWindow,\n onError = (e) => {\n console.error(e);\n }\n } = options;\n const internalX = shallowRef(0);\n const internalY = shallowRef(0);\n const x = computed({\n get() {\n return internalX.value;\n },\n set(x2) {\n scrollTo(x2, void 0);\n }\n });\n const y = computed({\n get() {\n return internalY.value;\n },\n set(y2) {\n scrollTo(void 0, y2);\n }\n });\n function scrollTo(_x, _y) {\n var _a, _b, _c, _d;\n if (!window)\n return;\n const _element = toValue(element);\n if (!_element)\n return;\n (_c = _element instanceof Document ? window.document.body : _element) == null ? void 0 : _c.scrollTo({\n top: (_a = toValue(_y)) != null ? _a : y.value,\n left: (_b = toValue(_x)) != null ? _b : x.value,\n behavior: toValue(behavior)\n });\n const scrollContainer = ((_d = _element == null ? void 0 : _element.document) == null ? void 0 : _d.documentElement) || (_element == null ? void 0 : _element.documentElement) || _element;\n if (x != null)\n internalX.value = scrollContainer.scrollLeft;\n if (y != null)\n internalY.value = scrollContainer.scrollTop;\n }\n const isScrolling = shallowRef(false);\n const arrivedState = reactive({\n left: true,\n right: false,\n top: true,\n bottom: false\n });\n const directions = reactive({\n left: false,\n right: false,\n top: false,\n bottom: false\n });\n const onScrollEnd = (e) => {\n if (!isScrolling.value)\n return;\n isScrolling.value = false;\n directions.left = false;\n directions.right = false;\n directions.top = false;\n directions.bottom = false;\n onStop(e);\n };\n const onScrollEndDebounced = useDebounceFn(onScrollEnd, throttle + idle);\n const setArrivedState = (target) => {\n var _a;\n if (!window)\n return;\n const el = ((_a = target == null ? void 0 : target.document) == null ? void 0 : _a.documentElement) || (target == null ? void 0 : target.documentElement) || unrefElement(target);\n const { display, flexDirection, direction } = getComputedStyle(el);\n const directionMultipler = direction === \"rtl\" ? -1 : 1;\n const scrollLeft = el.scrollLeft;\n directions.left = scrollLeft < internalX.value;\n directions.right = scrollLeft > internalX.value;\n const left = Math.abs(scrollLeft * directionMultipler) <= (offset.left || 0);\n const right = Math.abs(scrollLeft * directionMultipler) + el.clientWidth >= el.scrollWidth - (offset.right || 0) - ARRIVED_STATE_THRESHOLD_PIXELS;\n if (display === \"flex\" && flexDirection === \"row-reverse\") {\n arrivedState.left = right;\n arrivedState.right = left;\n } else {\n arrivedState.left = left;\n arrivedState.right = right;\n }\n internalX.value = scrollLeft;\n let scrollTop = el.scrollTop;\n if (target === window.document && !scrollTop)\n scrollTop = window.document.body.scrollTop;\n directions.top = scrollTop < internalY.value;\n directions.bottom = scrollTop > internalY.value;\n const top = Math.abs(scrollTop) <= (offset.top || 0);\n const bottom = Math.abs(scrollTop) + el.clientHeight >= el.scrollHeight - (offset.bottom || 0) - ARRIVED_STATE_THRESHOLD_PIXELS;\n if (display === \"flex\" && flexDirection === \"column-reverse\") {\n arrivedState.top = bottom;\n arrivedState.bottom = top;\n } else {\n arrivedState.top = top;\n arrivedState.bottom = bottom;\n }\n internalY.value = scrollTop;\n };\n const onScrollHandler = (e) => {\n var _a;\n if (!window)\n return;\n const eventTarget = (_a = e.target.documentElement) != null ? _a : e.target;\n setArrivedState(eventTarget);\n isScrolling.value = true;\n onScrollEndDebounced(e);\n onScroll(e);\n };\n useEventListener(\n element,\n \"scroll\",\n throttle ? useThrottleFn(onScrollHandler, throttle, true, false) : onScrollHandler,\n eventListenerOptions\n );\n tryOnMounted(() => {\n try {\n const _element = toValue(element);\n if (!_element)\n return;\n setArrivedState(_element);\n } catch (e) {\n onError(e);\n }\n });\n useEventListener(\n element,\n \"scrollend\",\n onScrollEnd,\n eventListenerOptions\n );\n return {\n x,\n y,\n isScrolling,\n arrivedState,\n directions,\n measure() {\n const _element = toValue(element);\n if (window && _element)\n setArrivedState(_element);\n }\n };\n}\n\nfunction useInfiniteScroll(element, onLoadMore, options = {}) {\n var _a;\n const {\n direction = \"bottom\",\n interval = 100,\n canLoadMore = () => true\n } = options;\n const state = reactive(useScroll(\n element,\n {\n ...options,\n offset: {\n [direction]: (_a = options.distance) != null ? _a : 0,\n ...options.offset\n }\n }\n ));\n const promise = ref();\n const isLoading = computed(() => !!promise.value);\n const observedElement = computed(() => {\n return resolveElement(toValue(element));\n });\n const isElementVisible = useElementVisibility(observedElement);\n function checkAndLoad() {\n state.measure();\n if (!observedElement.value || !isElementVisible.value || !canLoadMore(observedElement.value))\n return;\n const { scrollHeight, clientHeight, scrollWidth, clientWidth } = observedElement.value;\n const isNarrower = direction === \"bottom\" || direction === \"top\" ? scrollHeight <= clientHeight : scrollWidth <= clientWidth;\n if (state.arrivedState[direction] || isNarrower) {\n if (!promise.value) {\n promise.value = Promise.all([\n onLoadMore(state),\n new Promise((resolve) => setTimeout(resolve, interval))\n ]).finally(() => {\n promise.value = null;\n nextTick(() => checkAndLoad());\n });\n }\n }\n }\n const stop = watch(\n () => [state.arrivedState[direction], isElementVisible.value],\n checkAndLoad,\n { immediate: true }\n );\n tryOnUnmounted(stop);\n return {\n isLoading,\n reset() {\n nextTick(() => checkAndLoad());\n }\n };\n}\n\nconst defaultEvents = [\"mousedown\", \"mouseup\", \"keydown\", \"keyup\"];\nfunction useKeyModifier(modifier, options = {}) {\n const {\n events = defaultEvents,\n document = defaultDocument,\n initial = null\n } = options;\n const state = shallowRef(initial);\n if (document) {\n events.forEach((listenerEvent) => {\n useEventListener(document, listenerEvent, (evt) => {\n if (typeof evt.getModifierState === \"function\")\n state.value = evt.getModifierState(modifier);\n }, { passive: true });\n });\n }\n return state;\n}\n\nfunction useLocalStorage(key, initialValue, options = {}) {\n const { window = defaultWindow } = options;\n return useStorage(key, initialValue, window == null ? void 0 : window.localStorage, options);\n}\n\nconst DefaultMagicKeysAliasMap = {\n ctrl: \"control\",\n command: \"meta\",\n cmd: \"meta\",\n option: \"alt\",\n up: \"arrowup\",\n down: \"arrowdown\",\n left: \"arrowleft\",\n right: \"arrowright\"\n};\n\nfunction useMagicKeys(options = {}) {\n const {\n reactive: useReactive = false,\n target = defaultWindow,\n aliasMap = DefaultMagicKeysAliasMap,\n passive = true,\n onEventFired = noop\n } = options;\n const current = reactive(/* @__PURE__ */ new Set());\n const obj = {\n toJSON() {\n return {};\n },\n current\n };\n const refs = useReactive ? reactive(obj) : obj;\n const metaDeps = /* @__PURE__ */ new Set();\n const usedKeys = /* @__PURE__ */ new Set();\n function setRefs(key, value) {\n if (key in refs) {\n if (useReactive)\n refs[key] = value;\n else\n refs[key].value = value;\n }\n }\n function reset() {\n current.clear();\n for (const key of usedKeys)\n setRefs(key, false);\n }\n function updateRefs(e, value) {\n var _a, _b;\n const key = (_a = e.key) == null ? void 0 : _a.toLowerCase();\n const code = (_b = e.code) == null ? void 0 : _b.toLowerCase();\n const values = [code, key].filter(Boolean);\n if (key) {\n if (value)\n current.add(key);\n else\n current.delete(key);\n }\n for (const key2 of values) {\n usedKeys.add(key2);\n setRefs(key2, value);\n }\n if (key === \"meta\" && !value) {\n metaDeps.forEach((key2) => {\n current.delete(key2);\n setRefs(key2, false);\n });\n metaDeps.clear();\n } else if (typeof e.getModifierState === \"function\" && e.getModifierState(\"Meta\") && value) {\n [...current, ...values].forEach((key2) => metaDeps.add(key2));\n }\n }\n useEventListener(target, \"keydown\", (e) => {\n updateRefs(e, true);\n return onEventFired(e);\n }, { passive });\n useEventListener(target, \"keyup\", (e) => {\n updateRefs(e, false);\n return onEventFired(e);\n }, { passive });\n useEventListener(\"blur\", reset, { passive });\n useEventListener(\"focus\", reset, { passive });\n const proxy = new Proxy(\n refs,\n {\n get(target2, prop, rec) {\n if (typeof prop !== \"string\")\n return Reflect.get(target2, prop, rec);\n prop = prop.toLowerCase();\n if (prop in aliasMap)\n prop = aliasMap[prop];\n if (!(prop in refs)) {\n if (/[+_-]/.test(prop)) {\n const keys = prop.split(/[+_-]/g).map((i) => i.trim());\n refs[prop] = computed(() => keys.map((key) => toValue(proxy[key])).every(Boolean));\n } else {\n refs[prop] = shallowRef(false);\n }\n }\n const r = Reflect.get(target2, prop, rec);\n return useReactive ? toValue(r) : r;\n }\n }\n );\n return proxy;\n}\n\nfunction usingElRef(source, cb) {\n if (toValue(source))\n cb(toValue(source));\n}\nfunction timeRangeToArray(timeRanges) {\n let ranges = [];\n for (let i = 0; i < timeRanges.length; ++i)\n ranges = [...ranges, [timeRanges.start(i), timeRanges.end(i)]];\n return ranges;\n}\nfunction tracksToArray(tracks) {\n return Array.from(tracks).map(({ label, kind, language, mode, activeCues, cues, inBandMetadataTrackDispatchType }, id) => ({ id, label, kind, language, mode, activeCues, cues, inBandMetadataTrackDispatchType }));\n}\nconst defaultOptions = {\n src: \"\",\n tracks: []\n};\nfunction useMediaControls(target, options = {}) {\n target = toRef(target);\n options = {\n ...defaultOptions,\n ...options\n };\n const {\n document = defaultDocument\n } = options;\n const listenerOptions = { passive: true };\n const currentTime = shallowRef(0);\n const duration = shallowRef(0);\n const seeking = shallowRef(false);\n const volume = shallowRef(1);\n const waiting = shallowRef(false);\n const ended = shallowRef(false);\n const playing = shallowRef(false);\n const rate = shallowRef(1);\n const stalled = shallowRef(false);\n const buffered = ref([]);\n const tracks = ref([]);\n const selectedTrack = shallowRef(-1);\n const isPictureInPicture = shallowRef(false);\n const muted = shallowRef(false);\n const supportsPictureInPicture = document && \"pictureInPictureEnabled\" in document;\n const sourceErrorEvent = createEventHook();\n const playbackErrorEvent = createEventHook();\n const disableTrack = (track) => {\n usingElRef(target, (el) => {\n if (track) {\n const id = typeof track === \"number\" ? track : track.id;\n el.textTracks[id].mode = \"disabled\";\n } else {\n for (let i = 0; i < el.textTracks.length; ++i)\n el.textTracks[i].mode = \"disabled\";\n }\n selectedTrack.value = -1;\n });\n };\n const enableTrack = (track, disableTracks = true) => {\n usingElRef(target, (el) => {\n const id = typeof track === \"number\" ? track : track.id;\n if (disableTracks)\n disableTrack();\n el.textTracks[id].mode = \"showing\";\n selectedTrack.value = id;\n });\n };\n const togglePictureInPicture = () => {\n return new Promise((resolve, reject) => {\n usingElRef(target, async (el) => {\n if (supportsPictureInPicture) {\n if (!isPictureInPicture.value) {\n el.requestPictureInPicture().then(resolve).catch(reject);\n } else {\n document.exitPictureInPicture().then(resolve).catch(reject);\n }\n }\n });\n });\n };\n watchEffect(() => {\n if (!document)\n return;\n const el = toValue(target);\n if (!el)\n return;\n const src = toValue(options.src);\n let sources = [];\n if (!src)\n return;\n if (typeof src === \"string\")\n sources = [{ src }];\n else if (Array.isArray(src))\n sources = src;\n else if (isObject(src))\n sources = [src];\n el.querySelectorAll(\"source\").forEach((e) => {\n e.remove();\n });\n sources.forEach(({ src: src2, type, media }) => {\n const source = document.createElement(\"source\");\n source.setAttribute(\"src\", src2);\n source.setAttribute(\"type\", type || \"\");\n source.setAttribute(\"media\", media || \"\");\n useEventListener(source, \"error\", sourceErrorEvent.trigger, listenerOptions);\n el.appendChild(source);\n });\n el.load();\n });\n watch([target, volume], () => {\n const el = toValue(target);\n if (!el)\n return;\n el.volume = volume.value;\n });\n watch([target, muted], () => {\n const el = toValue(target);\n if (!el)\n return;\n el.muted = muted.value;\n });\n watch([target, rate], () => {\n const el = toValue(target);\n if (!el)\n return;\n el.playbackRate = rate.value;\n });\n watchEffect(() => {\n if (!document)\n return;\n const textTracks = toValue(options.tracks);\n const el = toValue(target);\n if (!textTracks || !textTracks.length || !el)\n return;\n el.querySelectorAll(\"track\").forEach((e) => e.remove());\n textTracks.forEach(({ default: isDefault, kind, label, src, srcLang }, i) => {\n const track = document.createElement(\"track\");\n track.default = isDefault || false;\n track.kind = kind;\n track.label = label;\n track.src = src;\n track.srclang = srcLang;\n if (track.default)\n selectedTrack.value = i;\n el.appendChild(track);\n });\n });\n const { ignoreUpdates: ignoreCurrentTimeUpdates } = watchIgnorable(currentTime, (time) => {\n const el = toValue(target);\n if (!el)\n return;\n el.currentTime = time;\n });\n const { ignoreUpdates: ignorePlayingUpdates } = watchIgnorable(playing, (isPlaying) => {\n const el = toValue(target);\n if (!el)\n return;\n if (isPlaying) {\n el.play().catch((e) => {\n playbackErrorEvent.trigger(e);\n throw e;\n });\n } else {\n el.pause();\n }\n });\n useEventListener(\n target,\n \"timeupdate\",\n () => ignoreCurrentTimeUpdates(() => currentTime.value = toValue(target).currentTime),\n listenerOptions\n );\n useEventListener(\n target,\n \"durationchange\",\n () => duration.value = toValue(target).duration,\n listenerOptions\n );\n useEventListener(\n target,\n \"progress\",\n () => buffered.value = timeRangeToArray(toValue(target).buffered),\n listenerOptions\n );\n useEventListener(\n target,\n \"seeking\",\n () => seeking.value = true,\n listenerOptions\n );\n useEventListener(\n target,\n \"seeked\",\n () => seeking.value = false,\n listenerOptions\n );\n useEventListener(\n target,\n [\"waiting\", \"loadstart\"],\n () => {\n waiting.value = true;\n ignorePlayingUpdates(() => playing.value = false);\n },\n listenerOptions\n );\n useEventListener(\n target,\n \"loadeddata\",\n () => waiting.value = false,\n listenerOptions\n );\n useEventListener(\n target,\n \"playing\",\n () => {\n waiting.value = false;\n ended.value = false;\n ignorePlayingUpdates(() => playing.value = true);\n },\n listenerOptions\n );\n useEventListener(\n target,\n \"ratechange\",\n () => rate.value = toValue(target).playbackRate,\n listenerOptions\n );\n useEventListener(\n target,\n \"stalled\",\n () => stalled.value = true,\n listenerOptions\n );\n useEventListener(\n target,\n \"ended\",\n () => ended.value = true,\n listenerOptions\n );\n useEventListener(\n target,\n \"pause\",\n () => ignorePlayingUpdates(() => playing.value = false),\n listenerOptions\n );\n useEventListener(\n target,\n \"play\",\n () => ignorePlayingUpdates(() => playing.value = true),\n listenerOptions\n );\n useEventListener(\n target,\n \"enterpictureinpicture\",\n () => isPictureInPicture.value = true,\n listenerOptions\n );\n useEventListener(\n target,\n \"leavepictureinpicture\",\n () => isPictureInPicture.value = false,\n listenerOptions\n );\n useEventListener(\n target,\n \"volumechange\",\n () => {\n const el = toValue(target);\n if (!el)\n return;\n volume.value = el.volume;\n muted.value = el.muted;\n },\n listenerOptions\n );\n const listeners = [];\n const stop = watch([target], () => {\n const el = toValue(target);\n if (!el)\n return;\n stop();\n listeners[0] = useEventListener(el.textTracks, \"addtrack\", () => tracks.value = tracksToArray(el.textTracks), listenerOptions);\n listeners[1] = useEventListener(el.textTracks, \"removetrack\", () => tracks.value = tracksToArray(el.textTracks), listenerOptions);\n listeners[2] = useEventListener(el.textTracks, \"change\", () => tracks.value = tracksToArray(el.textTracks), listenerOptions);\n });\n tryOnScopeDispose(() => listeners.forEach((listener) => listener()));\n return {\n currentTime,\n duration,\n waiting,\n seeking,\n ended,\n stalled,\n buffered,\n playing,\n rate,\n // Volume\n volume,\n muted,\n // Tracks\n tracks,\n selectedTrack,\n enableTrack,\n disableTrack,\n // Picture in Picture\n supportsPictureInPicture,\n togglePictureInPicture,\n isPictureInPicture,\n // Events\n onSourceError: sourceErrorEvent.on,\n onPlaybackError: playbackErrorEvent.on\n };\n}\n\nfunction useMemoize(resolver, options) {\n const initCache = () => {\n if (options == null ? void 0 : options.cache)\n return shallowReactive(options.cache);\n return shallowReactive(/* @__PURE__ */ new Map());\n };\n const cache = initCache();\n const generateKey = (...args) => (options == null ? void 0 : options.getKey) ? options.getKey(...args) : JSON.stringify(args);\n const _loadData = (key, ...args) => {\n cache.set(key, resolver(...args));\n return cache.get(key);\n };\n const loadData = (...args) => _loadData(generateKey(...args), ...args);\n const deleteData = (...args) => {\n cache.delete(generateKey(...args));\n };\n const clearData = () => {\n cache.clear();\n };\n const memoized = (...args) => {\n const key = generateKey(...args);\n if (cache.has(key))\n return cache.get(key);\n return _loadData(key, ...args);\n };\n memoized.load = loadData;\n memoized.delete = deleteData;\n memoized.clear = clearData;\n memoized.generateKey = generateKey;\n memoized.cache = cache;\n return memoized;\n}\n\nfunction useMemory(options = {}) {\n const memory = ref();\n const isSupported = useSupported(() => typeof performance !== \"undefined\" && \"memory\" in performance);\n if (isSupported.value) {\n const { interval = 1e3 } = options;\n useIntervalFn(() => {\n memory.value = performance.memory;\n }, interval, { immediate: options.immediate, immediateCallback: options.immediateCallback });\n }\n return { isSupported, memory };\n}\n\nconst UseMouseBuiltinExtractors = {\n page: (event) => [event.pageX, event.pageY],\n client: (event) => [event.clientX, event.clientY],\n screen: (event) => [event.screenX, event.screenY],\n movement: (event) => event instanceof MouseEvent ? [event.movementX, event.movementY] : null\n};\nfunction useMouse(options = {}) {\n const {\n type = \"page\",\n touch = true,\n resetOnTouchEnds = false,\n initialValue = { x: 0, y: 0 },\n window = defaultWindow,\n target = window,\n scroll = true,\n eventFilter\n } = options;\n let _prevMouseEvent = null;\n let _prevScrollX = 0;\n let _prevScrollY = 0;\n const x = shallowRef(initialValue.x);\n const y = shallowRef(initialValue.y);\n const sourceType = shallowRef(null);\n const extractor = typeof type === \"function\" ? type : UseMouseBuiltinExtractors[type];\n const mouseHandler = (event) => {\n const result = extractor(event);\n _prevMouseEvent = event;\n if (result) {\n [x.value, y.value] = result;\n sourceType.value = \"mouse\";\n }\n if (window) {\n _prevScrollX = window.scrollX;\n _prevScrollY = window.scrollY;\n }\n };\n const touchHandler = (event) => {\n if (event.touches.length > 0) {\n const result = extractor(event.touches[0]);\n if (result) {\n [x.value, y.value] = result;\n sourceType.value = \"touch\";\n }\n }\n };\n const scrollHandler = () => {\n if (!_prevMouseEvent || !window)\n return;\n const pos = extractor(_prevMouseEvent);\n if (_prevMouseEvent instanceof MouseEvent && pos) {\n x.value = pos[0] + window.scrollX - _prevScrollX;\n y.value = pos[1] + window.scrollY - _prevScrollY;\n }\n };\n const reset = () => {\n x.value = initialValue.x;\n y.value = initialValue.y;\n };\n const mouseHandlerWrapper = eventFilter ? (event) => eventFilter(() => mouseHandler(event), {}) : (event) => mouseHandler(event);\n const touchHandlerWrapper = eventFilter ? (event) => eventFilter(() => touchHandler(event), {}) : (event) => touchHandler(event);\n const scrollHandlerWrapper = eventFilter ? () => eventFilter(() => scrollHandler(), {}) : () => scrollHandler();\n if (target) {\n const listenerOptions = { passive: true };\n useEventListener(target, [\"mousemove\", \"dragover\"], mouseHandlerWrapper, listenerOptions);\n if (touch && type !== \"movement\") {\n useEventListener(target, [\"touchstart\", \"touchmove\"], touchHandlerWrapper, listenerOptions);\n if (resetOnTouchEnds)\n useEventListener(target, \"touchend\", reset, listenerOptions);\n }\n if (scroll && type === \"page\")\n useEventListener(window, \"scroll\", scrollHandlerWrapper, listenerOptions);\n }\n return {\n x,\n y,\n sourceType\n };\n}\n\nfunction useMouseInElement(target, options = {}) {\n const {\n handleOutside = true,\n window = defaultWindow\n } = options;\n const type = options.type || \"page\";\n const { x, y, sourceType } = useMouse(options);\n const targetRef = shallowRef(target != null ? target : window == null ? void 0 : window.document.body);\n const elementX = shallowRef(0);\n const elementY = shallowRef(0);\n const elementPositionX = shallowRef(0);\n const elementPositionY = shallowRef(0);\n const elementHeight = shallowRef(0);\n const elementWidth = shallowRef(0);\n const isOutside = shallowRef(true);\n let stop = () => {\n };\n if (window) {\n stop = watch(\n [targetRef, x, y],\n () => {\n const el = unrefElement(targetRef);\n if (!el || !(el instanceof Element))\n return;\n const {\n left,\n top,\n width,\n height\n } = el.getBoundingClientRect();\n elementPositionX.value = left + (type === \"page\" ? window.pageXOffset : 0);\n elementPositionY.value = top + (type === \"page\" ? window.pageYOffset : 0);\n elementHeight.value = height;\n elementWidth.value = width;\n const elX = x.value - elementPositionX.value;\n const elY = y.value - elementPositionY.value;\n isOutside.value = width === 0 || height === 0 || elX < 0 || elY < 0 || elX > width || elY > height;\n if (handleOutside || !isOutside.value) {\n elementX.value = elX;\n elementY.value = elY;\n }\n },\n { immediate: true }\n );\n useEventListener(\n document,\n \"mouseleave\",\n () => isOutside.value = true,\n { passive: true }\n );\n }\n return {\n x,\n y,\n sourceType,\n elementX,\n elementY,\n elementPositionX,\n elementPositionY,\n elementHeight,\n elementWidth,\n isOutside,\n stop\n };\n}\n\nfunction useMousePressed(options = {}) {\n const {\n touch = true,\n drag = true,\n capture = false,\n initialValue = false,\n window = defaultWindow\n } = options;\n const pressed = shallowRef(initialValue);\n const sourceType = shallowRef(null);\n if (!window) {\n return {\n pressed,\n sourceType\n };\n }\n const onPressed = (srcType) => (event) => {\n var _a;\n pressed.value = true;\n sourceType.value = srcType;\n (_a = options.onPressed) == null ? void 0 : _a.call(options, event);\n };\n const onReleased = (event) => {\n var _a;\n pressed.value = false;\n sourceType.value = null;\n (_a = options.onReleased) == null ? void 0 : _a.call(options, event);\n };\n const target = computed(() => unrefElement(options.target) || window);\n const listenerOptions = { passive: true, capture };\n useEventListener(target, \"mousedown\", onPressed(\"mouse\"), listenerOptions);\n useEventListener(window, \"mouseleave\", onReleased, listenerOptions);\n useEventListener(window, \"mouseup\", onReleased, listenerOptions);\n if (drag) {\n useEventListener(target, \"dragstart\", onPressed(\"mouse\"), listenerOptions);\n useEventListener(window, \"drop\", onReleased, listenerOptions);\n useEventListener(window, \"dragend\", onReleased, listenerOptions);\n }\n if (touch) {\n useEventListener(target, \"touchstart\", onPressed(\"touch\"), listenerOptions);\n useEventListener(window, \"touchend\", onReleased, listenerOptions);\n useEventListener(window, \"touchcancel\", onReleased, listenerOptions);\n }\n return {\n pressed,\n sourceType\n };\n}\n\nfunction useNavigatorLanguage(options = {}) {\n const { window = defaultWindow } = options;\n const navigator = window == null ? void 0 : window.navigator;\n const isSupported = useSupported(() => navigator && \"language\" in navigator);\n const language = shallowRef(navigator == null ? void 0 : navigator.language);\n useEventListener(window, \"languagechange\", () => {\n if (navigator)\n language.value = navigator.language;\n }, { passive: true });\n return {\n isSupported,\n language\n };\n}\n\nfunction useNetwork(options = {}) {\n const { window = defaultWindow } = options;\n const navigator = window == null ? void 0 : window.navigator;\n const isSupported = useSupported(() => navigator && \"connection\" in navigator);\n const isOnline = shallowRef(true);\n const saveData = shallowRef(false);\n const offlineAt = shallowRef(void 0);\n const onlineAt = shallowRef(void 0);\n const downlink = shallowRef(void 0);\n const downlinkMax = shallowRef(void 0);\n const rtt = shallowRef(void 0);\n const effectiveType = shallowRef(void 0);\n const type = shallowRef(\"unknown\");\n const connection = isSupported.value && navigator.connection;\n function updateNetworkInformation() {\n if (!navigator)\n return;\n isOnline.value = navigator.onLine;\n offlineAt.value = isOnline.value ? void 0 : Date.now();\n onlineAt.value = isOnline.value ? Date.now() : void 0;\n if (connection) {\n downlink.value = connection.downlink;\n downlinkMax.value = connection.downlinkMax;\n effectiveType.value = connection.effectiveType;\n rtt.value = connection.rtt;\n saveData.value = connection.saveData;\n type.value = connection.type;\n }\n }\n const listenerOptions = { passive: true };\n if (window) {\n useEventListener(window, \"offline\", () => {\n isOnline.value = false;\n offlineAt.value = Date.now();\n }, listenerOptions);\n useEventListener(window, \"online\", () => {\n isOnline.value = true;\n onlineAt.value = Date.now();\n }, listenerOptions);\n }\n if (connection)\n useEventListener(connection, \"change\", updateNetworkInformation, listenerOptions);\n updateNetworkInformation();\n return {\n isSupported,\n isOnline: readonly(isOnline),\n saveData: readonly(saveData),\n offlineAt: readonly(offlineAt),\n onlineAt: readonly(onlineAt),\n downlink: readonly(downlink),\n downlinkMax: readonly(downlinkMax),\n effectiveType: readonly(effectiveType),\n rtt: readonly(rtt),\n type: readonly(type)\n };\n}\n\nfunction useNow(options = {}) {\n const {\n controls: exposeControls = false,\n interval = \"requestAnimationFrame\"\n } = options;\n const now = ref(/* @__PURE__ */ new Date());\n const update = () => now.value = /* @__PURE__ */ new Date();\n const controls = interval === \"requestAnimationFrame\" ? useRafFn(update, { immediate: true }) : useIntervalFn(update, interval, { immediate: true });\n if (exposeControls) {\n return {\n now,\n ...controls\n };\n } else {\n return now;\n }\n}\n\nfunction useObjectUrl(object) {\n const url = shallowRef();\n const release = () => {\n if (url.value)\n URL.revokeObjectURL(url.value);\n url.value = void 0;\n };\n watch(\n () => toValue(object),\n (newObject) => {\n release();\n if (newObject)\n url.value = URL.createObjectURL(newObject);\n },\n { immediate: true }\n );\n tryOnScopeDispose(release);\n return readonly(url);\n}\n\nfunction useClamp(value, min, max) {\n if (typeof value === \"function\" || isReadonly(value))\n return computed(() => clamp(toValue(value), toValue(min), toValue(max)));\n const _value = ref(value);\n return computed({\n get() {\n return _value.value = clamp(_value.value, toValue(min), toValue(max));\n },\n set(value2) {\n _value.value = clamp(value2, toValue(min), toValue(max));\n }\n });\n}\n\nfunction useOffsetPagination(options) {\n const {\n total = Number.POSITIVE_INFINITY,\n pageSize = 10,\n page = 1,\n onPageChange = noop,\n onPageSizeChange = noop,\n onPageCountChange = noop\n } = options;\n const currentPageSize = useClamp(pageSize, 1, Number.POSITIVE_INFINITY);\n const pageCount = computed(() => Math.max(\n 1,\n Math.ceil(toValue(total) / toValue(currentPageSize))\n ));\n const currentPage = useClamp(page, 1, pageCount);\n const isFirstPage = computed(() => currentPage.value === 1);\n const isLastPage = computed(() => currentPage.value === pageCount.value);\n if (isRef(page)) {\n syncRef(page, currentPage, {\n direction: isReadonly(page) ? \"ltr\" : \"both\"\n });\n }\n if (isRef(pageSize)) {\n syncRef(pageSize, currentPageSize, {\n direction: isReadonly(pageSize) ? \"ltr\" : \"both\"\n });\n }\n function prev() {\n currentPage.value--;\n }\n function next() {\n currentPage.value++;\n }\n const returnValue = {\n currentPage,\n currentPageSize,\n pageCount,\n isFirstPage,\n isLastPage,\n prev,\n next\n };\n watch(currentPage, () => {\n onPageChange(reactive(returnValue));\n });\n watch(currentPageSize, () => {\n onPageSizeChange(reactive(returnValue));\n });\n watch(pageCount, () => {\n onPageCountChange(reactive(returnValue));\n });\n return returnValue;\n}\n\nfunction useOnline(options = {}) {\n const { isOnline } = useNetwork(options);\n return isOnline;\n}\n\nfunction usePageLeave(options = {}) {\n const { window = defaultWindow } = options;\n const isLeft = shallowRef(false);\n const handler = (event) => {\n if (!window)\n return;\n event = event || window.event;\n const from = event.relatedTarget || event.toElement;\n isLeft.value = !from;\n };\n if (window) {\n const listenerOptions = { passive: true };\n useEventListener(window, \"mouseout\", handler, listenerOptions);\n useEventListener(window.document, \"mouseleave\", handler, listenerOptions);\n useEventListener(window.document, \"mouseenter\", handler, listenerOptions);\n }\n return isLeft;\n}\n\nfunction useScreenOrientation(options = {}) {\n const {\n window = defaultWindow\n } = options;\n const isSupported = useSupported(() => window && \"screen\" in window && \"orientation\" in window.screen);\n const screenOrientation = isSupported.value ? window.screen.orientation : {};\n const orientation = ref(screenOrientation.type);\n const angle = shallowRef(screenOrientation.angle || 0);\n if (isSupported.value) {\n useEventListener(window, \"orientationchange\", () => {\n orientation.value = screenOrientation.type;\n angle.value = screenOrientation.angle;\n }, { passive: true });\n }\n const lockOrientation = (type) => {\n if (isSupported.value && typeof screenOrientation.lock === \"function\")\n return screenOrientation.lock(type);\n return Promise.reject(new Error(\"Not supported\"));\n };\n const unlockOrientation = () => {\n if (isSupported.value && typeof screenOrientation.unlock === \"function\")\n screenOrientation.unlock();\n };\n return {\n isSupported,\n orientation,\n angle,\n lockOrientation,\n unlockOrientation\n };\n}\n\nfunction useParallax(target, options = {}) {\n const {\n deviceOrientationTiltAdjust = (i) => i,\n deviceOrientationRollAdjust = (i) => i,\n mouseTiltAdjust = (i) => i,\n mouseRollAdjust = (i) => i,\n window = defaultWindow\n } = options;\n const orientation = reactive(useDeviceOrientation({ window }));\n const screenOrientation = reactive(useScreenOrientation({ window }));\n const {\n elementX: x,\n elementY: y,\n elementWidth: width,\n elementHeight: height\n } = useMouseInElement(target, { handleOutside: false, window });\n const source = computed(() => {\n if (orientation.isSupported && (orientation.alpha != null && orientation.alpha !== 0 || orientation.gamma != null && orientation.gamma !== 0)) {\n return \"deviceOrientation\";\n }\n return \"mouse\";\n });\n const roll = computed(() => {\n if (source.value === \"deviceOrientation\") {\n let value;\n switch (screenOrientation.orientation) {\n case \"landscape-primary\":\n value = orientation.gamma / 90;\n break;\n case \"landscape-secondary\":\n value = -orientation.gamma / 90;\n break;\n case \"portrait-primary\":\n value = -orientation.beta / 90;\n break;\n case \"portrait-secondary\":\n value = orientation.beta / 90;\n break;\n default:\n value = -orientation.beta / 90;\n }\n return deviceOrientationRollAdjust(value);\n } else {\n const value = -(y.value - height.value / 2) / height.value;\n return mouseRollAdjust(value);\n }\n });\n const tilt = computed(() => {\n if (source.value === \"deviceOrientation\") {\n let value;\n switch (screenOrientation.orientation) {\n case \"landscape-primary\":\n value = orientation.beta / 90;\n break;\n case \"landscape-secondary\":\n value = -orientation.beta / 90;\n break;\n case \"portrait-primary\":\n value = orientation.gamma / 90;\n break;\n case \"portrait-secondary\":\n value = -orientation.gamma / 90;\n break;\n default:\n value = orientation.gamma / 90;\n }\n return deviceOrientationTiltAdjust(value);\n } else {\n const value = (x.value - width.value / 2) / width.value;\n return mouseTiltAdjust(value);\n }\n });\n return { roll, tilt, source };\n}\n\nfunction useParentElement(element = useCurrentElement()) {\n const parentElement = shallowRef();\n const update = () => {\n const el = unrefElement(element);\n if (el)\n parentElement.value = el.parentElement;\n };\n tryOnMounted(update);\n watch(() => toValue(element), update);\n return parentElement;\n}\n\nfunction usePerformanceObserver(options, callback) {\n const {\n window = defaultWindow,\n immediate = true,\n ...performanceOptions\n } = options;\n const isSupported = useSupported(() => window && \"PerformanceObserver\" in window);\n let observer;\n const stop = () => {\n observer == null ? void 0 : observer.disconnect();\n };\n const start = () => {\n if (isSupported.value) {\n stop();\n observer = new PerformanceObserver(callback);\n observer.observe(performanceOptions);\n }\n };\n tryOnScopeDispose(stop);\n if (immediate)\n start();\n return {\n isSupported,\n start,\n stop\n };\n}\n\nconst defaultState = {\n x: 0,\n y: 0,\n pointerId: 0,\n pressure: 0,\n tiltX: 0,\n tiltY: 0,\n width: 0,\n height: 0,\n twist: 0,\n pointerType: null\n};\nconst keys = /* @__PURE__ */ Object.keys(defaultState);\nfunction usePointer(options = {}) {\n const {\n target = defaultWindow\n } = options;\n const isInside = shallowRef(false);\n const state = ref(options.initialValue || {});\n Object.assign(state.value, defaultState, state.value);\n const handler = (event) => {\n isInside.value = true;\n if (options.pointerTypes && !options.pointerTypes.includes(event.pointerType))\n return;\n state.value = objectPick(event, keys, false);\n };\n if (target) {\n const listenerOptions = { passive: true };\n useEventListener(target, [\"pointerdown\", \"pointermove\", \"pointerup\"], handler, listenerOptions);\n useEventListener(target, \"pointerleave\", () => isInside.value = false, listenerOptions);\n }\n return {\n ...toRefs(state),\n isInside\n };\n}\n\nfunction usePointerLock(target, options = {}) {\n const { document = defaultDocument } = options;\n const isSupported = useSupported(() => document && \"pointerLockElement\" in document);\n const element = shallowRef();\n const triggerElement = shallowRef();\n let targetElement;\n if (isSupported.value) {\n const listenerOptions = { passive: true };\n useEventListener(document, \"pointerlockchange\", () => {\n var _a;\n const currentElement = (_a = document.pointerLockElement) != null ? _a : element.value;\n if (targetElement && currentElement === targetElement) {\n element.value = document.pointerLockElement;\n if (!element.value)\n targetElement = triggerElement.value = null;\n }\n }, listenerOptions);\n useEventListener(document, \"pointerlockerror\", () => {\n var _a;\n const currentElement = (_a = document.pointerLockElement) != null ? _a : element.value;\n if (targetElement && currentElement === targetElement) {\n const action = document.pointerLockElement ? \"release\" : \"acquire\";\n throw new Error(`Failed to ${action} pointer lock.`);\n }\n }, listenerOptions);\n }\n async function lock(e) {\n var _a;\n if (!isSupported.value)\n throw new Error(\"Pointer Lock API is not supported by your browser.\");\n triggerElement.value = e instanceof Event ? e.currentTarget : null;\n targetElement = e instanceof Event ? (_a = unrefElement(target)) != null ? _a : triggerElement.value : unrefElement(e);\n if (!targetElement)\n throw new Error(\"Target element undefined.\");\n targetElement.requestPointerLock();\n return await until(element).toBe(targetElement);\n }\n async function unlock() {\n if (!element.value)\n return false;\n document.exitPointerLock();\n await until(element).toBeNull();\n return true;\n }\n return {\n isSupported,\n element,\n triggerElement,\n lock,\n unlock\n };\n}\n\nfunction usePointerSwipe(target, options = {}) {\n const targetRef = toRef(target);\n const {\n threshold = 50,\n onSwipe,\n onSwipeEnd,\n onSwipeStart,\n disableTextSelect = false\n } = options;\n const posStart = reactive({ x: 0, y: 0 });\n const updatePosStart = (x, y) => {\n posStart.x = x;\n posStart.y = y;\n };\n const posEnd = reactive({ x: 0, y: 0 });\n const updatePosEnd = (x, y) => {\n posEnd.x = x;\n posEnd.y = y;\n };\n const distanceX = computed(() => posStart.x - posEnd.x);\n const distanceY = computed(() => posStart.y - posEnd.y);\n const { max, abs } = Math;\n const isThresholdExceeded = computed(() => max(abs(distanceX.value), abs(distanceY.value)) >= threshold);\n const isSwiping = shallowRef(false);\n const isPointerDown = shallowRef(false);\n const direction = computed(() => {\n if (!isThresholdExceeded.value)\n return \"none\";\n if (abs(distanceX.value) > abs(distanceY.value)) {\n return distanceX.value > 0 ? \"left\" : \"right\";\n } else {\n return distanceY.value > 0 ? \"up\" : \"down\";\n }\n });\n const eventIsAllowed = (e) => {\n var _a, _b, _c;\n const isReleasingButton = e.buttons === 0;\n const isPrimaryButton = e.buttons === 1;\n return (_c = (_b = (_a = options.pointerTypes) == null ? void 0 : _a.includes(e.pointerType)) != null ? _b : isReleasingButton || isPrimaryButton) != null ? _c : true;\n };\n const listenerOptions = { passive: true };\n const stops = [\n useEventListener(target, \"pointerdown\", (e) => {\n if (!eventIsAllowed(e))\n return;\n isPointerDown.value = true;\n const eventTarget = e.target;\n eventTarget == null ? void 0 : eventTarget.setPointerCapture(e.pointerId);\n const { clientX: x, clientY: y } = e;\n updatePosStart(x, y);\n updatePosEnd(x, y);\n onSwipeStart == null ? void 0 : onSwipeStart(e);\n }, listenerOptions),\n useEventListener(target, \"pointermove\", (e) => {\n if (!eventIsAllowed(e))\n return;\n if (!isPointerDown.value)\n return;\n const { clientX: x, clientY: y } = e;\n updatePosEnd(x, y);\n if (!isSwiping.value && isThresholdExceeded.value)\n isSwiping.value = true;\n if (isSwiping.value)\n onSwipe == null ? void 0 : onSwipe(e);\n }, listenerOptions),\n useEventListener(target, \"pointerup\", (e) => {\n if (!eventIsAllowed(e))\n return;\n if (isSwiping.value)\n onSwipeEnd == null ? void 0 : onSwipeEnd(e, direction.value);\n isPointerDown.value = false;\n isSwiping.value = false;\n }, listenerOptions)\n ];\n tryOnMounted(() => {\n var _a, _b, _c, _d, _e, _f, _g, _h;\n (_b = (_a = targetRef.value) == null ? void 0 : _a.style) == null ? void 0 : _b.setProperty(\"touch-action\", \"none\");\n if (disableTextSelect) {\n (_d = (_c = targetRef.value) == null ? void 0 : _c.style) == null ? void 0 : _d.setProperty(\"-webkit-user-select\", \"none\");\n (_f = (_e = targetRef.value) == null ? void 0 : _e.style) == null ? void 0 : _f.setProperty(\"-ms-user-select\", \"none\");\n (_h = (_g = targetRef.value) == null ? void 0 : _g.style) == null ? void 0 : _h.setProperty(\"user-select\", \"none\");\n }\n });\n const stop = () => stops.forEach((s) => s());\n return {\n isSwiping: readonly(isSwiping),\n direction: readonly(direction),\n posStart: readonly(posStart),\n posEnd: readonly(posEnd),\n distanceX,\n distanceY,\n stop\n };\n}\n\nfunction usePreferredColorScheme(options) {\n const isLight = useMediaQuery(\"(prefers-color-scheme: light)\", options);\n const isDark = useMediaQuery(\"(prefers-color-scheme: dark)\", options);\n return computed(() => {\n if (isDark.value)\n return \"dark\";\n if (isLight.value)\n return \"light\";\n return \"no-preference\";\n });\n}\n\nfunction usePreferredContrast(options) {\n const isMore = useMediaQuery(\"(prefers-contrast: more)\", options);\n const isLess = useMediaQuery(\"(prefers-contrast: less)\", options);\n const isCustom = useMediaQuery(\"(prefers-contrast: custom)\", options);\n return computed(() => {\n if (isMore.value)\n return \"more\";\n if (isLess.value)\n return \"less\";\n if (isCustom.value)\n return \"custom\";\n return \"no-preference\";\n });\n}\n\nfunction usePreferredLanguages(options = {}) {\n const { window = defaultWindow } = options;\n if (!window)\n return ref([\"en\"]);\n const navigator = window.navigator;\n const value = ref(navigator.languages);\n useEventListener(window, \"languagechange\", () => {\n value.value = navigator.languages;\n }, { passive: true });\n return value;\n}\n\nfunction usePreferredReducedMotion(options) {\n const isReduced = useMediaQuery(\"(prefers-reduced-motion: reduce)\", options);\n return computed(() => {\n if (isReduced.value)\n return \"reduce\";\n return \"no-preference\";\n });\n}\n\nfunction usePreferredReducedTransparency(options) {\n const isReduced = useMediaQuery(\"(prefers-reduced-transparency: reduce)\", options);\n return computed(() => {\n if (isReduced.value)\n return \"reduce\";\n return \"no-preference\";\n });\n}\n\nfunction usePrevious(value, initialValue) {\n const previous = shallowRef(initialValue);\n watch(\n toRef(value),\n (_, oldValue) => {\n previous.value = oldValue;\n },\n { flush: \"sync\" }\n );\n return readonly(previous);\n}\n\nconst topVarName = \"--vueuse-safe-area-top\";\nconst rightVarName = \"--vueuse-safe-area-right\";\nconst bottomVarName = \"--vueuse-safe-area-bottom\";\nconst leftVarName = \"--vueuse-safe-area-left\";\nfunction useScreenSafeArea() {\n const top = shallowRef(\"\");\n const right = shallowRef(\"\");\n const bottom = shallowRef(\"\");\n const left = shallowRef(\"\");\n if (isClient) {\n const topCssVar = useCssVar(topVarName);\n const rightCssVar = useCssVar(rightVarName);\n const bottomCssVar = useCssVar(bottomVarName);\n const leftCssVar = useCssVar(leftVarName);\n topCssVar.value = \"env(safe-area-inset-top, 0px)\";\n rightCssVar.value = \"env(safe-area-inset-right, 0px)\";\n bottomCssVar.value = \"env(safe-area-inset-bottom, 0px)\";\n leftCssVar.value = \"env(safe-area-inset-left, 0px)\";\n update();\n useEventListener(\"resize\", useDebounceFn(update), { passive: true });\n }\n function update() {\n top.value = getValue(topVarName);\n right.value = getValue(rightVarName);\n bottom.value = getValue(bottomVarName);\n left.value = getValue(leftVarName);\n }\n return {\n top,\n right,\n bottom,\n left,\n update\n };\n}\nfunction getValue(position) {\n return getComputedStyle(document.documentElement).getPropertyValue(position);\n}\n\nfunction useScriptTag(src, onLoaded = noop, options = {}) {\n const {\n immediate = true,\n manual = false,\n type = \"text/javascript\",\n async = true,\n crossOrigin,\n referrerPolicy,\n noModule,\n defer,\n document = defaultDocument,\n attrs = {}\n } = options;\n const scriptTag = shallowRef(null);\n let _promise = null;\n const loadScript = (waitForScriptLoad) => new Promise((resolve, reject) => {\n const resolveWithElement = (el2) => {\n scriptTag.value = el2;\n resolve(el2);\n return el2;\n };\n if (!document) {\n resolve(false);\n return;\n }\n let shouldAppend = false;\n let el = document.querySelector(`script[src=\"${toValue(src)}\"]`);\n if (!el) {\n el = document.createElement(\"script\");\n el.type = type;\n el.async = async;\n el.src = toValue(src);\n if (defer)\n el.defer = defer;\n if (crossOrigin)\n el.crossOrigin = crossOrigin;\n if (noModule)\n el.noModule = noModule;\n if (referrerPolicy)\n el.referrerPolicy = referrerPolicy;\n Object.entries(attrs).forEach(([name, value]) => el == null ? void 0 : el.setAttribute(name, value));\n shouldAppend = true;\n } else if (el.hasAttribute(\"data-loaded\")) {\n resolveWithElement(el);\n }\n const listenerOptions = {\n passive: true\n };\n useEventListener(el, \"error\", (event) => reject(event), listenerOptions);\n useEventListener(el, \"abort\", (event) => reject(event), listenerOptions);\n useEventListener(el, \"load\", () => {\n el.setAttribute(\"data-loaded\", \"true\");\n onLoaded(el);\n resolveWithElement(el);\n }, listenerOptions);\n if (shouldAppend)\n el = document.head.appendChild(el);\n if (!waitForScriptLoad)\n resolveWithElement(el);\n });\n const load = (waitForScriptLoad = true) => {\n if (!_promise)\n _promise = loadScript(waitForScriptLoad);\n return _promise;\n };\n const unload = () => {\n if (!document)\n return;\n _promise = null;\n if (scriptTag.value)\n scriptTag.value = null;\n const el = document.querySelector(`script[src=\"${toValue(src)}\"]`);\n if (el)\n document.head.removeChild(el);\n };\n if (immediate && !manual)\n tryOnMounted(load);\n if (!manual)\n tryOnUnmounted(unload);\n return { scriptTag, load, unload };\n}\n\nfunction checkOverflowScroll(ele) {\n const style = window.getComputedStyle(ele);\n if (style.overflowX === \"scroll\" || style.overflowY === \"scroll\" || style.overflowX === \"auto\" && ele.clientWidth < ele.scrollWidth || style.overflowY === \"auto\" && ele.clientHeight < ele.scrollHeight) {\n return true;\n } else {\n const parent = ele.parentNode;\n if (!parent || parent.tagName === \"BODY\")\n return false;\n return checkOverflowScroll(parent);\n }\n}\nfunction preventDefault(rawEvent) {\n const e = rawEvent || window.event;\n const _target = e.target;\n if (checkOverflowScroll(_target))\n return false;\n if (e.touches.length > 1)\n return true;\n if (e.preventDefault)\n e.preventDefault();\n return false;\n}\nconst elInitialOverflow = /* @__PURE__ */ new WeakMap();\nfunction useScrollLock(element, initialState = false) {\n const isLocked = shallowRef(initialState);\n let stopTouchMoveListener = null;\n let initialOverflow = \"\";\n watch(toRef(element), (el) => {\n const target = resolveElement(toValue(el));\n if (target) {\n const ele = target;\n if (!elInitialOverflow.get(ele))\n elInitialOverflow.set(ele, ele.style.overflow);\n if (ele.style.overflow !== \"hidden\")\n initialOverflow = ele.style.overflow;\n if (ele.style.overflow === \"hidden\")\n return isLocked.value = true;\n if (isLocked.value)\n return ele.style.overflow = \"hidden\";\n }\n }, {\n immediate: true\n });\n const lock = () => {\n const el = resolveElement(toValue(element));\n if (!el || isLocked.value)\n return;\n if (isIOS) {\n stopTouchMoveListener = useEventListener(\n el,\n \"touchmove\",\n (e) => {\n preventDefault(e);\n },\n { passive: false }\n );\n }\n el.style.overflow = \"hidden\";\n isLocked.value = true;\n };\n const unlock = () => {\n const el = resolveElement(toValue(element));\n if (!el || !isLocked.value)\n return;\n if (isIOS)\n stopTouchMoveListener == null ? void 0 : stopTouchMoveListener();\n el.style.overflow = initialOverflow;\n elInitialOverflow.delete(el);\n isLocked.value = false;\n };\n tryOnScopeDispose(unlock);\n return computed({\n get() {\n return isLocked.value;\n },\n set(v) {\n if (v)\n lock();\n else unlock();\n }\n });\n}\n\nfunction useSessionStorage(key, initialValue, options = {}) {\n const { window = defaultWindow } = options;\n return useStorage(key, initialValue, window == null ? void 0 : window.sessionStorage, options);\n}\n\nfunction useShare(shareOptions = {}, options = {}) {\n const { navigator = defaultNavigator } = options;\n const _navigator = navigator;\n const isSupported = useSupported(() => _navigator && \"canShare\" in _navigator);\n const share = async (overrideOptions = {}) => {\n if (isSupported.value) {\n const data = {\n ...toValue(shareOptions),\n ...toValue(overrideOptions)\n };\n let granted = true;\n if (data.files && _navigator.canShare)\n granted = _navigator.canShare({ files: data.files });\n if (granted)\n return _navigator.share(data);\n }\n };\n return {\n isSupported,\n share\n };\n}\n\nconst defaultSortFn = (source, compareFn) => source.sort(compareFn);\nconst defaultCompare = (a, b) => a - b;\nfunction useSorted(...args) {\n var _a, _b, _c, _d;\n const [source] = args;\n let compareFn = defaultCompare;\n let options = {};\n if (args.length === 2) {\n if (typeof args[1] === \"object\") {\n options = args[1];\n compareFn = (_a = options.compareFn) != null ? _a : defaultCompare;\n } else {\n compareFn = (_b = args[1]) != null ? _b : defaultCompare;\n }\n } else if (args.length > 2) {\n compareFn = (_c = args[1]) != null ? _c : defaultCompare;\n options = (_d = args[2]) != null ? _d : {};\n }\n const {\n dirty = false,\n sortFn = defaultSortFn\n } = options;\n if (!dirty)\n return computed(() => sortFn([...toValue(source)], compareFn));\n watchEffect(() => {\n const result = sortFn(toValue(source), compareFn);\n if (isRef(source))\n source.value = result;\n else\n source.splice(0, source.length, ...result);\n });\n return source;\n}\n\nfunction useSpeechRecognition(options = {}) {\n const {\n interimResults = true,\n continuous = true,\n maxAlternatives = 1,\n window = defaultWindow\n } = options;\n const lang = toRef(options.lang || \"en-US\");\n const isListening = shallowRef(false);\n const isFinal = shallowRef(false);\n const result = shallowRef(\"\");\n const error = shallowRef(void 0);\n let recognition;\n const start = () => {\n isListening.value = true;\n };\n const stop = () => {\n isListening.value = false;\n };\n const toggle = (value = !isListening.value) => {\n if (value) {\n start();\n } else {\n stop();\n }\n };\n const SpeechRecognition = window && (window.SpeechRecognition || window.webkitSpeechRecognition);\n const isSupported = useSupported(() => SpeechRecognition);\n if (isSupported.value) {\n recognition = new SpeechRecognition();\n recognition.continuous = continuous;\n recognition.interimResults = interimResults;\n recognition.lang = toValue(lang);\n recognition.maxAlternatives = maxAlternatives;\n recognition.onstart = () => {\n isListening.value = true;\n isFinal.value = false;\n };\n watch(lang, (lang2) => {\n if (recognition && !isListening.value)\n recognition.lang = lang2;\n });\n recognition.onresult = (event) => {\n const currentResult = event.results[event.resultIndex];\n const { transcript } = currentResult[0];\n isFinal.value = currentResult.isFinal;\n result.value = transcript;\n error.value = void 0;\n };\n recognition.onerror = (event) => {\n error.value = event;\n };\n recognition.onend = () => {\n isListening.value = false;\n recognition.lang = toValue(lang);\n };\n watch(isListening, (newValue, oldValue) => {\n if (newValue === oldValue)\n return;\n if (newValue)\n recognition.start();\n else\n recognition.stop();\n });\n }\n tryOnScopeDispose(() => {\n stop();\n });\n return {\n isSupported,\n isListening,\n isFinal,\n recognition,\n result,\n error,\n toggle,\n start,\n stop\n };\n}\n\nfunction useSpeechSynthesis(text, options = {}) {\n const {\n pitch = 1,\n rate = 1,\n volume = 1,\n window = defaultWindow\n } = options;\n const synth = window && window.speechSynthesis;\n const isSupported = useSupported(() => synth);\n const isPlaying = shallowRef(false);\n const status = shallowRef(\"init\");\n const spokenText = toRef(text || \"\");\n const lang = toRef(options.lang || \"en-US\");\n const error = shallowRef(void 0);\n const toggle = (value = !isPlaying.value) => {\n isPlaying.value = value;\n };\n const bindEventsForUtterance = (utterance2) => {\n utterance2.lang = toValue(lang);\n utterance2.voice = toValue(options.voice) || null;\n utterance2.pitch = toValue(pitch);\n utterance2.rate = toValue(rate);\n utterance2.volume = volume;\n utterance2.onstart = () => {\n isPlaying.value = true;\n status.value = \"play\";\n };\n utterance2.onpause = () => {\n isPlaying.value = false;\n status.value = \"pause\";\n };\n utterance2.onresume = () => {\n isPlaying.value = true;\n status.value = \"play\";\n };\n utterance2.onend = () => {\n isPlaying.value = false;\n status.value = \"end\";\n };\n utterance2.onerror = (event) => {\n error.value = event;\n };\n };\n const utterance = computed(() => {\n isPlaying.value = false;\n status.value = \"init\";\n const newUtterance = new SpeechSynthesisUtterance(spokenText.value);\n bindEventsForUtterance(newUtterance);\n return newUtterance;\n });\n const speak = () => {\n synth.cancel();\n if (utterance)\n synth.speak(utterance.value);\n };\n const stop = () => {\n synth.cancel();\n isPlaying.value = false;\n };\n if (isSupported.value) {\n bindEventsForUtterance(utterance.value);\n watch(lang, (lang2) => {\n if (utterance.value && !isPlaying.value)\n utterance.value.lang = lang2;\n });\n if (options.voice) {\n watch(options.voice, () => {\n synth.cancel();\n });\n }\n watch(isPlaying, () => {\n if (isPlaying.value)\n synth.resume();\n else\n synth.pause();\n });\n }\n tryOnScopeDispose(() => {\n isPlaying.value = false;\n });\n return {\n isSupported,\n isPlaying,\n status,\n utterance,\n error,\n stop,\n toggle,\n speak\n };\n}\n\nfunction useStepper(steps, initialStep) {\n const stepsRef = ref(steps);\n const stepNames = computed(() => Array.isArray(stepsRef.value) ? stepsRef.value : Object.keys(stepsRef.value));\n const index = ref(stepNames.value.indexOf(initialStep != null ? initialStep : stepNames.value[0]));\n const current = computed(() => at(index.value));\n const isFirst = computed(() => index.value === 0);\n const isLast = computed(() => index.value === stepNames.value.length - 1);\n const next = computed(() => stepNames.value[index.value + 1]);\n const previous = computed(() => stepNames.value[index.value - 1]);\n function at(index2) {\n if (Array.isArray(stepsRef.value))\n return stepsRef.value[index2];\n return stepsRef.value[stepNames.value[index2]];\n }\n function get(step) {\n if (!stepNames.value.includes(step))\n return;\n return at(stepNames.value.indexOf(step));\n }\n function goTo(step) {\n if (stepNames.value.includes(step))\n index.value = stepNames.value.indexOf(step);\n }\n function goToNext() {\n if (isLast.value)\n return;\n index.value++;\n }\n function goToPrevious() {\n if (isFirst.value)\n return;\n index.value--;\n }\n function goBackTo(step) {\n if (isAfter(step))\n goTo(step);\n }\n function isNext(step) {\n return stepNames.value.indexOf(step) === index.value + 1;\n }\n function isPrevious(step) {\n return stepNames.value.indexOf(step) === index.value - 1;\n }\n function isCurrent(step) {\n return stepNames.value.indexOf(step) === index.value;\n }\n function isBefore(step) {\n return index.value < stepNames.value.indexOf(step);\n }\n function isAfter(step) {\n return index.value > stepNames.value.indexOf(step);\n }\n return {\n steps: stepsRef,\n stepNames,\n index,\n current,\n next,\n previous,\n isFirst,\n isLast,\n at,\n get,\n goTo,\n goToNext,\n goToPrevious,\n goBackTo,\n isNext,\n isPrevious,\n isCurrent,\n isBefore,\n isAfter\n };\n}\n\nfunction useStorageAsync(key, initialValue, storage, options = {}) {\n var _a;\n const {\n flush = \"pre\",\n deep = true,\n listenToStorageChanges = true,\n writeDefaults = true,\n mergeDefaults = false,\n shallow,\n window = defaultWindow,\n eventFilter,\n onError = (e) => {\n console.error(e);\n }\n } = options;\n const rawInit = toValue(initialValue);\n const type = guessSerializerType(rawInit);\n const data = (shallow ? shallowRef : ref)(toValue(initialValue));\n const serializer = (_a = options.serializer) != null ? _a : StorageSerializers[type];\n if (!storage) {\n try {\n storage = getSSRHandler(\"getDefaultStorageAsync\", () => {\n var _a2;\n return (_a2 = defaultWindow) == null ? void 0 : _a2.localStorage;\n })();\n } catch (e) {\n onError(e);\n }\n }\n async function read(event) {\n if (!storage || event && event.key !== key)\n return;\n try {\n const rawValue = event ? event.newValue : await storage.getItem(key);\n if (rawValue == null) {\n data.value = rawInit;\n if (writeDefaults && rawInit !== null)\n await storage.setItem(key, await serializer.write(rawInit));\n } else if (mergeDefaults) {\n const value = await serializer.read(rawValue);\n if (typeof mergeDefaults === \"function\")\n data.value = mergeDefaults(value, rawInit);\n else if (type === \"object\" && !Array.isArray(value))\n data.value = { ...rawInit, ...value };\n else data.value = value;\n } else {\n data.value = await serializer.read(rawValue);\n }\n } catch (e) {\n onError(e);\n }\n }\n read();\n if (window && listenToStorageChanges)\n useEventListener(window, \"storage\", (e) => Promise.resolve().then(() => read(e)), { passive: true });\n if (storage) {\n watchWithFilter(\n data,\n async () => {\n try {\n if (data.value == null)\n await storage.removeItem(key);\n else\n await storage.setItem(key, await serializer.write(data.value));\n } catch (e) {\n onError(e);\n }\n },\n {\n flush,\n deep,\n eventFilter\n }\n );\n }\n return data;\n}\n\nlet _id = 0;\nfunction useStyleTag(css, options = {}) {\n const isLoaded = shallowRef(false);\n const {\n document = defaultDocument,\n immediate = true,\n manual = false,\n id = `vueuse_styletag_${++_id}`\n } = options;\n const cssRef = shallowRef(css);\n let stop = () => {\n };\n const load = () => {\n if (!document)\n return;\n const el = document.getElementById(id) || document.createElement(\"style\");\n if (!el.isConnected) {\n el.id = id;\n if (options.media)\n el.media = options.media;\n document.head.appendChild(el);\n }\n if (isLoaded.value)\n return;\n stop = watch(\n cssRef,\n (value) => {\n el.textContent = value;\n },\n { immediate: true }\n );\n isLoaded.value = true;\n };\n const unload = () => {\n if (!document || !isLoaded.value)\n return;\n stop();\n document.head.removeChild(document.getElementById(id));\n isLoaded.value = false;\n };\n if (immediate && !manual)\n tryOnMounted(load);\n if (!manual)\n tryOnScopeDispose(unload);\n return {\n id,\n css: cssRef,\n unload,\n load,\n isLoaded: readonly(isLoaded)\n };\n}\n\nfunction useSwipe(target, options = {}) {\n const {\n threshold = 50,\n onSwipe,\n onSwipeEnd,\n onSwipeStart,\n passive = true\n } = options;\n const coordsStart = reactive({ x: 0, y: 0 });\n const coordsEnd = reactive({ x: 0, y: 0 });\n const diffX = computed(() => coordsStart.x - coordsEnd.x);\n const diffY = computed(() => coordsStart.y - coordsEnd.y);\n const { max, abs } = Math;\n const isThresholdExceeded = computed(() => max(abs(diffX.value), abs(diffY.value)) >= threshold);\n const isSwiping = shallowRef(false);\n const direction = computed(() => {\n if (!isThresholdExceeded.value)\n return \"none\";\n if (abs(diffX.value) > abs(diffY.value)) {\n return diffX.value > 0 ? \"left\" : \"right\";\n } else {\n return diffY.value > 0 ? \"up\" : \"down\";\n }\n });\n const getTouchEventCoords = (e) => [e.touches[0].clientX, e.touches[0].clientY];\n const updateCoordsStart = (x, y) => {\n coordsStart.x = x;\n coordsStart.y = y;\n };\n const updateCoordsEnd = (x, y) => {\n coordsEnd.x = x;\n coordsEnd.y = y;\n };\n const listenerOptions = { passive, capture: !passive };\n const onTouchEnd = (e) => {\n if (isSwiping.value)\n onSwipeEnd == null ? void 0 : onSwipeEnd(e, direction.value);\n isSwiping.value = false;\n };\n const stops = [\n useEventListener(target, \"touchstart\", (e) => {\n if (e.touches.length !== 1)\n return;\n const [x, y] = getTouchEventCoords(e);\n updateCoordsStart(x, y);\n updateCoordsEnd(x, y);\n onSwipeStart == null ? void 0 : onSwipeStart(e);\n }, listenerOptions),\n useEventListener(target, \"touchmove\", (e) => {\n if (e.touches.length !== 1)\n return;\n const [x, y] = getTouchEventCoords(e);\n updateCoordsEnd(x, y);\n if (listenerOptions.capture && !listenerOptions.passive && Math.abs(diffX.value) > Math.abs(diffY.value))\n e.preventDefault();\n if (!isSwiping.value && isThresholdExceeded.value)\n isSwiping.value = true;\n if (isSwiping.value)\n onSwipe == null ? void 0 : onSwipe(e);\n }, listenerOptions),\n useEventListener(target, [\"touchend\", \"touchcancel\"], onTouchEnd, listenerOptions)\n ];\n const stop = () => stops.forEach((s) => s());\n return {\n isSwiping,\n direction,\n coordsStart,\n coordsEnd,\n lengthX: diffX,\n lengthY: diffY,\n stop,\n // TODO: Remove in the next major version\n isPassiveEventSupported: true\n };\n}\n\nfunction useTemplateRefsList() {\n const refs = ref([]);\n refs.value.set = (el) => {\n if (el)\n refs.value.push(el);\n };\n onBeforeUpdate(() => {\n refs.value.length = 0;\n });\n return refs;\n}\n\nfunction useTextDirection(options = {}) {\n const {\n document = defaultDocument,\n selector = \"html\",\n observe = false,\n initialValue = \"ltr\"\n } = options;\n function getValue() {\n var _a, _b;\n return (_b = (_a = document == null ? void 0 : document.querySelector(selector)) == null ? void 0 : _a.getAttribute(\"dir\")) != null ? _b : initialValue;\n }\n const dir = ref(getValue());\n tryOnMounted(() => dir.value = getValue());\n if (observe && document) {\n useMutationObserver(\n document.querySelector(selector),\n () => dir.value = getValue(),\n { attributes: true }\n );\n }\n return computed({\n get() {\n return dir.value;\n },\n set(v) {\n var _a, _b;\n dir.value = v;\n if (!document)\n return;\n if (dir.value)\n (_a = document.querySelector(selector)) == null ? void 0 : _a.setAttribute(\"dir\", dir.value);\n else\n (_b = document.querySelector(selector)) == null ? void 0 : _b.removeAttribute(\"dir\");\n }\n });\n}\n\nfunction getRangesFromSelection(selection) {\n var _a;\n const rangeCount = (_a = selection.rangeCount) != null ? _a : 0;\n return Array.from({ length: rangeCount }, (_, i) => selection.getRangeAt(i));\n}\nfunction useTextSelection(options = {}) {\n const {\n window = defaultWindow\n } = options;\n const selection = ref(null);\n const text = computed(() => {\n var _a, _b;\n return (_b = (_a = selection.value) == null ? void 0 : _a.toString()) != null ? _b : \"\";\n });\n const ranges = computed(() => selection.value ? getRangesFromSelection(selection.value) : []);\n const rects = computed(() => ranges.value.map((range) => range.getBoundingClientRect()));\n function onSelectionChange() {\n selection.value = null;\n if (window)\n selection.value = window.getSelection();\n }\n if (window)\n useEventListener(window.document, \"selectionchange\", onSelectionChange, { passive: true });\n return {\n text,\n rects,\n ranges,\n selection\n };\n}\n\nfunction tryRequestAnimationFrame(window = defaultWindow, fn) {\n if (window && typeof window.requestAnimationFrame === \"function\") {\n window.requestAnimationFrame(fn);\n } else {\n fn();\n }\n}\nfunction useTextareaAutosize(options = {}) {\n var _a, _b;\n const { window = defaultWindow } = options;\n const textarea = toRef(options == null ? void 0 : options.element);\n const input = toRef((_a = options == null ? void 0 : options.input) != null ? _a : \"\");\n const styleProp = (_b = options == null ? void 0 : options.styleProp) != null ? _b : \"height\";\n const textareaScrollHeight = shallowRef(1);\n const textareaOldWidth = shallowRef(0);\n function triggerResize() {\n var _a2;\n if (!textarea.value)\n return;\n let height = \"\";\n textarea.value.style[styleProp] = \"1px\";\n textareaScrollHeight.value = (_a2 = textarea.value) == null ? void 0 : _a2.scrollHeight;\n const _styleTarget = toValue(options == null ? void 0 : options.styleTarget);\n if (_styleTarget)\n _styleTarget.style[styleProp] = `${textareaScrollHeight.value}px`;\n else\n height = `${textareaScrollHeight.value}px`;\n textarea.value.style[styleProp] = height;\n }\n watch([input, textarea], () => nextTick(triggerResize), { immediate: true });\n watch(textareaScrollHeight, () => {\n var _a2;\n return (_a2 = options == null ? void 0 : options.onResize) == null ? void 0 : _a2.call(options);\n });\n useResizeObserver(textarea, ([{ contentRect }]) => {\n if (textareaOldWidth.value === contentRect.width)\n return;\n tryRequestAnimationFrame(window, () => {\n textareaOldWidth.value = contentRect.width;\n triggerResize();\n });\n });\n if (options == null ? void 0 : options.watch)\n watch(options.watch, triggerResize, { immediate: true, deep: true });\n return {\n textarea,\n input,\n triggerResize\n };\n}\n\nfunction useThrottledRefHistory(source, options = {}) {\n const { throttle = 200, trailing = true } = options;\n const filter = throttleFilter(throttle, trailing);\n const history = useRefHistory(source, { ...options, eventFilter: filter });\n return {\n ...history\n };\n}\n\nconst DEFAULT_UNITS = [\n { max: 6e4, value: 1e3, name: \"second\" },\n { max: 276e4, value: 6e4, name: \"minute\" },\n { max: 72e6, value: 36e5, name: \"hour\" },\n { max: 5184e5, value: 864e5, name: \"day\" },\n { max: 24192e5, value: 6048e5, name: \"week\" },\n { max: 28512e6, value: 2592e6, name: \"month\" },\n { max: Number.POSITIVE_INFINITY, value: 31536e6, name: \"year\" }\n];\nconst DEFAULT_MESSAGES = {\n justNow: \"just now\",\n past: (n) => n.match(/\\d/) ? `${n} ago` : n,\n future: (n) => n.match(/\\d/) ? `in ${n}` : n,\n month: (n, past) => n === 1 ? past ? \"last month\" : \"next month\" : `${n} month${n > 1 ? \"s\" : \"\"}`,\n year: (n, past) => n === 1 ? past ? \"last year\" : \"next year\" : `${n} year${n > 1 ? \"s\" : \"\"}`,\n day: (n, past) => n === 1 ? past ? \"yesterday\" : \"tomorrow\" : `${n} day${n > 1 ? \"s\" : \"\"}`,\n week: (n, past) => n === 1 ? past ? \"last week\" : \"next week\" : `${n} week${n > 1 ? \"s\" : \"\"}`,\n hour: (n) => `${n} hour${n > 1 ? \"s\" : \"\"}`,\n minute: (n) => `${n} minute${n > 1 ? \"s\" : \"\"}`,\n second: (n) => `${n} second${n > 1 ? \"s\" : \"\"}`,\n invalid: \"\"\n};\nfunction DEFAULT_FORMATTER(date) {\n return date.toISOString().slice(0, 10);\n}\nfunction useTimeAgo(time, options = {}) {\n const {\n controls: exposeControls = false,\n updateInterval = 3e4\n } = options;\n const { now, ...controls } = useNow({ interval: updateInterval, controls: true });\n const timeAgo = computed(() => formatTimeAgo(new Date(toValue(time)), options, toValue(now)));\n if (exposeControls) {\n return {\n timeAgo,\n ...controls\n };\n } else {\n return timeAgo;\n }\n}\nfunction formatTimeAgo(from, options = {}, now = Date.now()) {\n var _a;\n const {\n max,\n messages = DEFAULT_MESSAGES,\n fullDateFormatter = DEFAULT_FORMATTER,\n units = DEFAULT_UNITS,\n showSecond = false,\n rounding = \"round\"\n } = options;\n const roundFn = typeof rounding === \"number\" ? (n) => +n.toFixed(rounding) : Math[rounding];\n const diff = +now - +from;\n const absDiff = Math.abs(diff);\n function getValue(diff2, unit) {\n return roundFn(Math.abs(diff2) / unit.value);\n }\n function format(diff2, unit) {\n const val = getValue(diff2, unit);\n const past = diff2 > 0;\n const str = applyFormat(unit.name, val, past);\n return applyFormat(past ? \"past\" : \"future\", str, past);\n }\n function applyFormat(name, val, isPast) {\n const formatter = messages[name];\n if (typeof formatter === \"function\")\n return formatter(val, isPast);\n return formatter.replace(\"{0}\", val.toString());\n }\n if (absDiff < 6e4 && !showSecond)\n return messages.justNow;\n if (typeof max === \"number\" && absDiff > max)\n return fullDateFormatter(new Date(from));\n if (typeof max === \"string\") {\n const unitMax = (_a = units.find((i) => i.name === max)) == null ? void 0 : _a.max;\n if (unitMax && absDiff > unitMax)\n return fullDateFormatter(new Date(from));\n }\n for (const [idx, unit] of units.entries()) {\n const val = getValue(diff, unit);\n if (val <= 0 && units[idx - 1])\n return format(diff, units[idx - 1]);\n if (absDiff < unit.max)\n return format(diff, unit);\n }\n return messages.invalid;\n}\n\nfunction useTimeoutPoll(fn, interval, options = {}) {\n const {\n immediate = true,\n immediateCallback = false\n } = options;\n const { start } = useTimeoutFn(loop, interval, { immediate });\n const isActive = shallowRef(false);\n async function loop() {\n if (!isActive.value)\n return;\n await fn();\n start();\n }\n function resume() {\n if (!isActive.value) {\n isActive.value = true;\n if (immediateCallback)\n fn();\n start();\n }\n }\n function pause() {\n isActive.value = false;\n }\n if (immediate && isClient)\n resume();\n tryOnScopeDispose(pause);\n return {\n isActive,\n pause,\n resume\n };\n}\n\nfunction useTimestamp(options = {}) {\n const {\n controls: exposeControls = false,\n offset = 0,\n immediate = true,\n interval = \"requestAnimationFrame\",\n callback\n } = options;\n const ts = shallowRef(timestamp() + offset);\n const update = () => ts.value = timestamp() + offset;\n const cb = callback ? () => {\n update();\n callback(ts.value);\n } : update;\n const controls = interval === \"requestAnimationFrame\" ? useRafFn(cb, { immediate }) : useIntervalFn(cb, interval, { immediate });\n if (exposeControls) {\n return {\n timestamp: ts,\n ...controls\n };\n } else {\n return ts;\n }\n}\n\nfunction useTitle(newTitle = null, options = {}) {\n var _a, _b, _c;\n const {\n document = defaultDocument,\n restoreOnUnmount = (t) => t\n } = options;\n const originalTitle = (_a = document == null ? void 0 : document.title) != null ? _a : \"\";\n const title = toRef((_b = newTitle != null ? newTitle : document == null ? void 0 : document.title) != null ? _b : null);\n const isReadonly = !!(newTitle && typeof newTitle === \"function\");\n function format(t) {\n if (!(\"titleTemplate\" in options))\n return t;\n const template = options.titleTemplate || \"%s\";\n return typeof template === \"function\" ? template(t) : toValue(template).replace(/%s/g, t);\n }\n watch(\n title,\n (newValue, oldValue) => {\n if (newValue !== oldValue && document)\n document.title = format(newValue != null ? newValue : \"\");\n },\n { immediate: true }\n );\n if (options.observe && !options.titleTemplate && document && !isReadonly) {\n useMutationObserver(\n (_c = document.head) == null ? void 0 : _c.querySelector(\"title\"),\n () => {\n if (document && document.title !== title.value)\n title.value = format(document.title);\n },\n { childList: true }\n );\n }\n tryOnScopeDispose(() => {\n if (restoreOnUnmount) {\n const restoredTitle = restoreOnUnmount(originalTitle, title.value || \"\");\n if (restoredTitle != null && document)\n document.title = restoredTitle;\n }\n });\n return title;\n}\n\nconst _TransitionPresets = {\n easeInSine: [0.12, 0, 0.39, 0],\n easeOutSine: [0.61, 1, 0.88, 1],\n easeInOutSine: [0.37, 0, 0.63, 1],\n easeInQuad: [0.11, 0, 0.5, 0],\n easeOutQuad: [0.5, 1, 0.89, 1],\n easeInOutQuad: [0.45, 0, 0.55, 1],\n easeInCubic: [0.32, 0, 0.67, 0],\n easeOutCubic: [0.33, 1, 0.68, 1],\n easeInOutCubic: [0.65, 0, 0.35, 1],\n easeInQuart: [0.5, 0, 0.75, 0],\n easeOutQuart: [0.25, 1, 0.5, 1],\n easeInOutQuart: [0.76, 0, 0.24, 1],\n easeInQuint: [0.64, 0, 0.78, 0],\n easeOutQuint: [0.22, 1, 0.36, 1],\n easeInOutQuint: [0.83, 0, 0.17, 1],\n easeInExpo: [0.7, 0, 0.84, 0],\n easeOutExpo: [0.16, 1, 0.3, 1],\n easeInOutExpo: [0.87, 0, 0.13, 1],\n easeInCirc: [0.55, 0, 1, 0.45],\n easeOutCirc: [0, 0.55, 0.45, 1],\n easeInOutCirc: [0.85, 0, 0.15, 1],\n easeInBack: [0.36, 0, 0.66, -0.56],\n easeOutBack: [0.34, 1.56, 0.64, 1],\n easeInOutBack: [0.68, -0.6, 0.32, 1.6]\n};\nconst TransitionPresets = /* @__PURE__ */ Object.assign({}, { linear: identity }, _TransitionPresets);\nfunction createEasingFunction([p0, p1, p2, p3]) {\n const a = (a1, a2) => 1 - 3 * a2 + 3 * a1;\n const b = (a1, a2) => 3 * a2 - 6 * a1;\n const c = (a1) => 3 * a1;\n const calcBezier = (t, a1, a2) => ((a(a1, a2) * t + b(a1, a2)) * t + c(a1)) * t;\n const getSlope = (t, a1, a2) => 3 * a(a1, a2) * t * t + 2 * b(a1, a2) * t + c(a1);\n const getTforX = (x) => {\n let aGuessT = x;\n for (let i = 0; i < 4; ++i) {\n const currentSlope = getSlope(aGuessT, p0, p2);\n if (currentSlope === 0)\n return aGuessT;\n const currentX = calcBezier(aGuessT, p0, p2) - x;\n aGuessT -= currentX / currentSlope;\n }\n return aGuessT;\n };\n return (x) => p0 === p1 && p2 === p3 ? x : calcBezier(getTforX(x), p1, p3);\n}\nfunction lerp(a, b, alpha) {\n return a + alpha * (b - a);\n}\nfunction toVec(t) {\n return (typeof t === \"number\" ? [t] : t) || [];\n}\nfunction executeTransition(source, from, to, options = {}) {\n var _a, _b;\n const fromVal = toValue(from);\n const toVal = toValue(to);\n const v1 = toVec(fromVal);\n const v2 = toVec(toVal);\n const duration = (_a = toValue(options.duration)) != null ? _a : 1e3;\n const startedAt = Date.now();\n const endAt = Date.now() + duration;\n const trans = typeof options.transition === \"function\" ? options.transition : (_b = toValue(options.transition)) != null ? _b : identity;\n const ease = typeof trans === \"function\" ? trans : createEasingFunction(trans);\n return new Promise((resolve) => {\n source.value = fromVal;\n const tick = () => {\n var _a2;\n if ((_a2 = options.abort) == null ? void 0 : _a2.call(options)) {\n resolve();\n return;\n }\n const now = Date.now();\n const alpha = ease((now - startedAt) / duration);\n const arr = toVec(source.value).map((n, i) => lerp(v1[i], v2[i], alpha));\n if (Array.isArray(source.value))\n source.value = arr.map((n, i) => {\n var _a3, _b2;\n return lerp((_a3 = v1[i]) != null ? _a3 : 0, (_b2 = v2[i]) != null ? _b2 : 0, alpha);\n });\n else if (typeof source.value === \"number\")\n source.value = arr[0];\n if (now < endAt) {\n requestAnimationFrame(tick);\n } else {\n source.value = toVal;\n resolve();\n }\n };\n tick();\n });\n}\nfunction useTransition(source, options = {}) {\n let currentId = 0;\n const sourceVal = () => {\n const v = toValue(source);\n return typeof v === \"number\" ? v : v.map(toValue);\n };\n const outputRef = ref(sourceVal());\n watch(sourceVal, async (to) => {\n var _a, _b;\n if (toValue(options.disabled))\n return;\n const id = ++currentId;\n if (options.delay)\n await promiseTimeout(toValue(options.delay));\n if (id !== currentId)\n return;\n const toVal = Array.isArray(to) ? to.map(toValue) : toValue(to);\n (_a = options.onStarted) == null ? void 0 : _a.call(options);\n await executeTransition(outputRef, outputRef.value, toVal, {\n ...options,\n abort: () => {\n var _a2;\n return id !== currentId || ((_a2 = options.abort) == null ? void 0 : _a2.call(options));\n }\n });\n (_b = options.onFinished) == null ? void 0 : _b.call(options);\n }, { deep: true });\n watch(() => toValue(options.disabled), (disabled) => {\n if (disabled) {\n currentId++;\n outputRef.value = sourceVal();\n }\n });\n tryOnScopeDispose(() => {\n currentId++;\n });\n return computed(() => toValue(options.disabled) ? sourceVal() : outputRef.value);\n}\n\nfunction useUrlSearchParams(mode = \"history\", options = {}) {\n const {\n initialValue = {},\n removeNullishValues = true,\n removeFalsyValues = false,\n write: enableWrite = true,\n writeMode = \"replace\",\n window = defaultWindow\n } = options;\n if (!window)\n return reactive(initialValue);\n const state = reactive({});\n function getRawParams() {\n if (mode === \"history\") {\n return window.location.search || \"\";\n } else if (mode === \"hash\") {\n const hash = window.location.hash || \"\";\n const index = hash.indexOf(\"?\");\n return index > 0 ? hash.slice(index) : \"\";\n } else {\n return (window.location.hash || \"\").replace(/^#/, \"\");\n }\n }\n function constructQuery(params) {\n const stringified = params.toString();\n if (mode === \"history\")\n return `${stringified ? `?${stringified}` : \"\"}${window.location.hash || \"\"}`;\n if (mode === \"hash-params\")\n return `${window.location.search || \"\"}${stringified ? `#${stringified}` : \"\"}`;\n const hash = window.location.hash || \"#\";\n const index = hash.indexOf(\"?\");\n if (index > 0)\n return `${window.location.search || \"\"}${hash.slice(0, index)}${stringified ? `?${stringified}` : \"\"}`;\n return `${window.location.search || \"\"}${hash}${stringified ? `?${stringified}` : \"\"}`;\n }\n function read() {\n return new URLSearchParams(getRawParams());\n }\n function updateState(params) {\n const unusedKeys = new Set(Object.keys(state));\n for (const key of params.keys()) {\n const paramsForKey = params.getAll(key);\n state[key] = paramsForKey.length > 1 ? paramsForKey : params.get(key) || \"\";\n unusedKeys.delete(key);\n }\n Array.from(unusedKeys).forEach((key) => delete state[key]);\n }\n const { pause, resume } = pausableWatch(\n state,\n () => {\n const params = new URLSearchParams(\"\");\n Object.keys(state).forEach((key) => {\n const mapEntry = state[key];\n if (Array.isArray(mapEntry))\n mapEntry.forEach((value) => params.append(key, value));\n else if (removeNullishValues && mapEntry == null)\n params.delete(key);\n else if (removeFalsyValues && !mapEntry)\n params.delete(key);\n else\n params.set(key, mapEntry);\n });\n write(params, false);\n },\n { deep: true }\n );\n function write(params, shouldUpdate) {\n pause();\n if (shouldUpdate)\n updateState(params);\n if (writeMode === \"replace\") {\n window.history.replaceState(\n window.history.state,\n window.document.title,\n window.location.pathname + constructQuery(params)\n );\n } else {\n window.history.pushState(\n window.history.state,\n window.document.title,\n window.location.pathname + constructQuery(params)\n );\n }\n resume();\n }\n function onChanged() {\n if (!enableWrite)\n return;\n write(read(), true);\n }\n const listenerOptions = { passive: true };\n useEventListener(window, \"popstate\", onChanged, listenerOptions);\n if (mode !== \"history\")\n useEventListener(window, \"hashchange\", onChanged, listenerOptions);\n const initial = read();\n if (initial.keys().next().value)\n updateState(initial);\n else\n Object.assign(state, initialValue);\n return state;\n}\n\nfunction useUserMedia(options = {}) {\n var _a, _b;\n const enabled = shallowRef((_a = options.enabled) != null ? _a : false);\n const autoSwitch = shallowRef((_b = options.autoSwitch) != null ? _b : true);\n const constraints = ref(options.constraints);\n const { navigator = defaultNavigator } = options;\n const isSupported = useSupported(() => {\n var _a2;\n return (_a2 = navigator == null ? void 0 : navigator.mediaDevices) == null ? void 0 : _a2.getUserMedia;\n });\n const stream = shallowRef();\n function getDeviceOptions(type) {\n switch (type) {\n case \"video\": {\n if (constraints.value)\n return constraints.value.video || false;\n break;\n }\n case \"audio\": {\n if (constraints.value)\n return constraints.value.audio || false;\n break;\n }\n }\n }\n async function _start() {\n if (!isSupported.value || stream.value)\n return;\n stream.value = await navigator.mediaDevices.getUserMedia({\n video: getDeviceOptions(\"video\"),\n audio: getDeviceOptions(\"audio\")\n });\n return stream.value;\n }\n function _stop() {\n var _a2;\n (_a2 = stream.value) == null ? void 0 : _a2.getTracks().forEach((t) => t.stop());\n stream.value = void 0;\n }\n function stop() {\n _stop();\n enabled.value = false;\n }\n async function start() {\n await _start();\n if (stream.value)\n enabled.value = true;\n return stream.value;\n }\n async function restart() {\n _stop();\n return await start();\n }\n watch(\n enabled,\n (v) => {\n if (v)\n _start();\n else _stop();\n },\n { immediate: true }\n );\n watch(\n constraints,\n () => {\n if (autoSwitch.value && stream.value)\n restart();\n },\n { immediate: true }\n );\n tryOnScopeDispose(() => {\n stop();\n });\n return {\n isSupported,\n stream,\n start,\n stop,\n restart,\n constraints,\n enabled,\n autoSwitch\n };\n}\n\nfunction useVModel(props, key, emit, options = {}) {\n var _a, _b, _c;\n const {\n clone = false,\n passive = false,\n eventName,\n deep = false,\n defaultValue,\n shouldEmit\n } = options;\n const vm = getCurrentInstance();\n const _emit = emit || (vm == null ? void 0 : vm.emit) || ((_a = vm == null ? void 0 : vm.$emit) == null ? void 0 : _a.bind(vm)) || ((_c = (_b = vm == null ? void 0 : vm.proxy) == null ? void 0 : _b.$emit) == null ? void 0 : _c.bind(vm == null ? void 0 : vm.proxy));\n let event = eventName;\n if (!key) {\n key = \"modelValue\";\n }\n event = event || `update:${key.toString()}`;\n const cloneFn = (val) => !clone ? val : typeof clone === \"function\" ? clone(val) : cloneFnJSON(val);\n const getValue = () => isDef(props[key]) ? cloneFn(props[key]) : defaultValue;\n const triggerEmit = (value) => {\n if (shouldEmit) {\n if (shouldEmit(value))\n _emit(event, value);\n } else {\n _emit(event, value);\n }\n };\n if (passive) {\n const initialValue = getValue();\n const proxy = ref(initialValue);\n let isUpdating = false;\n watch(\n () => props[key],\n (v) => {\n if (!isUpdating) {\n isUpdating = true;\n proxy.value = cloneFn(v);\n nextTick(() => isUpdating = false);\n }\n }\n );\n watch(\n proxy,\n (v) => {\n if (!isUpdating && (v !== props[key] || deep))\n triggerEmit(v);\n },\n { deep }\n );\n return proxy;\n } else {\n return computed({\n get() {\n return getValue();\n },\n set(value) {\n triggerEmit(value);\n }\n });\n }\n}\n\nfunction useVModels(props, emit, options = {}) {\n const ret = {};\n for (const key in props) {\n ret[key] = useVModel(\n props,\n key,\n emit,\n options\n );\n }\n return ret;\n}\n\nfunction useVibrate(options) {\n const {\n pattern = [],\n interval = 0,\n navigator = defaultNavigator\n } = options || {};\n const isSupported = useSupported(() => typeof navigator !== \"undefined\" && \"vibrate\" in navigator);\n const patternRef = toRef(pattern);\n let intervalControls;\n const vibrate = (pattern2 = patternRef.value) => {\n if (isSupported.value)\n navigator.vibrate(pattern2);\n };\n const stop = () => {\n if (isSupported.value)\n navigator.vibrate(0);\n intervalControls == null ? void 0 : intervalControls.pause();\n };\n if (interval > 0) {\n intervalControls = useIntervalFn(\n vibrate,\n interval,\n {\n immediate: false,\n immediateCallback: false\n }\n );\n }\n return {\n isSupported,\n pattern,\n intervalControls,\n vibrate,\n stop\n };\n}\n\nfunction useVirtualList(list, options) {\n const { containerStyle, wrapperProps, scrollTo, calculateRange, currentList, containerRef } = \"itemHeight\" in options ? useVerticalVirtualList(options, list) : useHorizontalVirtualList(options, list);\n return {\n list: currentList,\n scrollTo,\n containerProps: {\n ref: containerRef,\n onScroll: () => {\n calculateRange();\n },\n style: containerStyle\n },\n wrapperProps\n };\n}\nfunction useVirtualListResources(list) {\n const containerRef = shallowRef(null);\n const size = useElementSize(containerRef);\n const currentList = ref([]);\n const source = shallowRef(list);\n const state = ref({ start: 0, end: 10 });\n return { state, source, currentList, size, containerRef };\n}\nfunction createGetViewCapacity(state, source, itemSize) {\n return (containerSize) => {\n if (typeof itemSize === \"number\")\n return Math.ceil(containerSize / itemSize);\n const { start = 0 } = state.value;\n let sum = 0;\n let capacity = 0;\n for (let i = start; i < source.value.length; i++) {\n const size = itemSize(i);\n sum += size;\n capacity = i;\n if (sum > containerSize)\n break;\n }\n return capacity - start;\n };\n}\nfunction createGetOffset(source, itemSize) {\n return (scrollDirection) => {\n if (typeof itemSize === \"number\")\n return Math.floor(scrollDirection / itemSize) + 1;\n let sum = 0;\n let offset = 0;\n for (let i = 0; i < source.value.length; i++) {\n const size = itemSize(i);\n sum += size;\n if (sum >= scrollDirection) {\n offset = i;\n break;\n }\n }\n return offset + 1;\n };\n}\nfunction createCalculateRange(type, overscan, getOffset, getViewCapacity, { containerRef, state, currentList, source }) {\n return () => {\n const element = containerRef.value;\n if (element) {\n const offset = getOffset(type === \"vertical\" ? element.scrollTop : element.scrollLeft);\n const viewCapacity = getViewCapacity(type === \"vertical\" ? element.clientHeight : element.clientWidth);\n const from = offset - overscan;\n const to = offset + viewCapacity + overscan;\n state.value = {\n start: from < 0 ? 0 : from,\n end: to > source.value.length ? source.value.length : to\n };\n currentList.value = source.value.slice(state.value.start, state.value.end).map((ele, index) => ({\n data: ele,\n index: index + state.value.start\n }));\n }\n };\n}\nfunction createGetDistance(itemSize, source) {\n return (index) => {\n if (typeof itemSize === \"number\") {\n const size2 = index * itemSize;\n return size2;\n }\n const size = source.value.slice(0, index).reduce((sum, _, i) => sum + itemSize(i), 0);\n return size;\n };\n}\nfunction useWatchForSizes(size, list, containerRef, calculateRange) {\n watch([size.width, size.height, list, containerRef], () => {\n calculateRange();\n });\n}\nfunction createComputedTotalSize(itemSize, source) {\n return computed(() => {\n if (typeof itemSize === \"number\")\n return source.value.length * itemSize;\n return source.value.reduce((sum, _, index) => sum + itemSize(index), 0);\n });\n}\nconst scrollToDictionaryForElementScrollKey = {\n horizontal: \"scrollLeft\",\n vertical: \"scrollTop\"\n};\nfunction createScrollTo(type, calculateRange, getDistance, containerRef) {\n return (index) => {\n if (containerRef.value) {\n containerRef.value[scrollToDictionaryForElementScrollKey[type]] = getDistance(index);\n calculateRange();\n }\n };\n}\nfunction useHorizontalVirtualList(options, list) {\n const resources = useVirtualListResources(list);\n const { state, source, currentList, size, containerRef } = resources;\n const containerStyle = { overflowX: \"auto\" };\n const { itemWidth, overscan = 5 } = options;\n const getViewCapacity = createGetViewCapacity(state, source, itemWidth);\n const getOffset = createGetOffset(source, itemWidth);\n const calculateRange = createCalculateRange(\"horizontal\", overscan, getOffset, getViewCapacity, resources);\n const getDistanceLeft = createGetDistance(itemWidth, source);\n const offsetLeft = computed(() => getDistanceLeft(state.value.start));\n const totalWidth = createComputedTotalSize(itemWidth, source);\n useWatchForSizes(size, list, containerRef, calculateRange);\n const scrollTo = createScrollTo(\"horizontal\", calculateRange, getDistanceLeft, containerRef);\n const wrapperProps = computed(() => {\n return {\n style: {\n height: \"100%\",\n width: `${totalWidth.value - offsetLeft.value}px`,\n marginLeft: `${offsetLeft.value}px`,\n display: \"flex\"\n }\n };\n });\n return {\n scrollTo,\n calculateRange,\n wrapperProps,\n containerStyle,\n currentList,\n containerRef\n };\n}\nfunction useVerticalVirtualList(options, list) {\n const resources = useVirtualListResources(list);\n const { state, source, currentList, size, containerRef } = resources;\n const containerStyle = { overflowY: \"auto\" };\n const { itemHeight, overscan = 5 } = options;\n const getViewCapacity = createGetViewCapacity(state, source, itemHeight);\n const getOffset = createGetOffset(source, itemHeight);\n const calculateRange = createCalculateRange(\"vertical\", overscan, getOffset, getViewCapacity, resources);\n const getDistanceTop = createGetDistance(itemHeight, source);\n const offsetTop = computed(() => getDistanceTop(state.value.start));\n const totalHeight = createComputedTotalSize(itemHeight, source);\n useWatchForSizes(size, list, containerRef, calculateRange);\n const scrollTo = createScrollTo(\"vertical\", calculateRange, getDistanceTop, containerRef);\n const wrapperProps = computed(() => {\n return {\n style: {\n width: \"100%\",\n height: `${totalHeight.value - offsetTop.value}px`,\n marginTop: `${offsetTop.value}px`\n }\n };\n });\n return {\n calculateRange,\n scrollTo,\n containerStyle,\n wrapperProps,\n currentList,\n containerRef\n };\n}\n\nfunction useWakeLock(options = {}) {\n const {\n navigator = defaultNavigator,\n document = defaultDocument\n } = options;\n const requestedType = shallowRef(false);\n const sentinel = shallowRef(null);\n const documentVisibility = useDocumentVisibility({ document });\n const isSupported = useSupported(() => navigator && \"wakeLock\" in navigator);\n const isActive = computed(() => !!sentinel.value && documentVisibility.value === \"visible\");\n if (isSupported.value) {\n useEventListener(sentinel, \"release\", () => {\n var _a, _b;\n requestedType.value = (_b = (_a = sentinel.value) == null ? void 0 : _a.type) != null ? _b : false;\n }, { passive: true });\n whenever(\n () => documentVisibility.value === \"visible\" && (document == null ? void 0 : document.visibilityState) === \"visible\" && requestedType.value,\n (type) => {\n requestedType.value = false;\n forceRequest(type);\n }\n );\n }\n async function forceRequest(type) {\n var _a;\n await ((_a = sentinel.value) == null ? void 0 : _a.release());\n sentinel.value = isSupported.value ? await navigator.wakeLock.request(type) : null;\n }\n async function request(type) {\n if (documentVisibility.value === \"visible\")\n await forceRequest(type);\n else\n requestedType.value = type;\n }\n async function release() {\n requestedType.value = false;\n const s = sentinel.value;\n sentinel.value = null;\n await (s == null ? void 0 : s.release());\n }\n return {\n sentinel,\n isSupported,\n isActive,\n request,\n forceRequest,\n release\n };\n}\n\nfunction useWebNotification(options = {}) {\n const {\n window = defaultWindow,\n requestPermissions: _requestForPermissions = true\n } = options;\n const defaultWebNotificationOptions = options;\n const isSupported = useSupported(() => {\n if (!window || !(\"Notification\" in window))\n return false;\n if (Notification.permission === \"granted\")\n return true;\n try {\n const notification2 = new Notification(\"\");\n notification2.onshow = () => {\n notification2.close();\n };\n } catch (e) {\n if (e.name === \"TypeError\")\n return false;\n }\n return true;\n });\n const permissionGranted = shallowRef(isSupported.value && \"permission\" in Notification && Notification.permission === \"granted\");\n const notification = ref(null);\n const ensurePermissions = async () => {\n if (!isSupported.value)\n return;\n if (!permissionGranted.value && Notification.permission !== \"denied\") {\n const result = await Notification.requestPermission();\n if (result === \"granted\")\n permissionGranted.value = true;\n }\n return permissionGranted.value;\n };\n const { on: onClick, trigger: clickTrigger } = createEventHook();\n const { on: onShow, trigger: showTrigger } = createEventHook();\n const { on: onError, trigger: errorTrigger } = createEventHook();\n const { on: onClose, trigger: closeTrigger } = createEventHook();\n const show = async (overrides) => {\n if (!isSupported.value || !permissionGranted.value)\n return;\n const options2 = Object.assign({}, defaultWebNotificationOptions, overrides);\n notification.value = new Notification(options2.title || \"\", options2);\n notification.value.onclick = clickTrigger;\n notification.value.onshow = showTrigger;\n notification.value.onerror = errorTrigger;\n notification.value.onclose = closeTrigger;\n return notification.value;\n };\n const close = () => {\n if (notification.value)\n notification.value.close();\n notification.value = null;\n };\n if (_requestForPermissions)\n tryOnMounted(ensurePermissions);\n tryOnScopeDispose(close);\n if (isSupported.value && window) {\n const document = window.document;\n useEventListener(document, \"visibilitychange\", (e) => {\n e.preventDefault();\n if (document.visibilityState === \"visible\") {\n close();\n }\n });\n }\n return {\n isSupported,\n notification,\n ensurePermissions,\n permissionGranted,\n show,\n close,\n onClick,\n onShow,\n onError,\n onClose\n };\n}\n\nconst DEFAULT_PING_MESSAGE = \"ping\";\nfunction resolveNestedOptions(options) {\n if (options === true)\n return {};\n return options;\n}\nfunction useWebSocket(url, options = {}) {\n const {\n onConnected,\n onDisconnected,\n onError,\n onMessage,\n immediate = true,\n autoConnect = true,\n autoClose = true,\n protocols = []\n } = options;\n const data = ref(null);\n const status = shallowRef(\"CLOSED\");\n const wsRef = ref();\n const urlRef = toRef(url);\n let heartbeatPause;\n let heartbeatResume;\n let explicitlyClosed = false;\n let retried = 0;\n let bufferedData = [];\n let retryTimeout;\n let pongTimeoutWait;\n const _sendBuffer = () => {\n if (bufferedData.length && wsRef.value && status.value === \"OPEN\") {\n for (const buffer of bufferedData)\n wsRef.value.send(buffer);\n bufferedData = [];\n }\n };\n const resetRetry = () => {\n if (retryTimeout != null) {\n clearTimeout(retryTimeout);\n retryTimeout = void 0;\n }\n };\n const resetHeartbeat = () => {\n clearTimeout(pongTimeoutWait);\n pongTimeoutWait = void 0;\n };\n const close = (code = 1e3, reason) => {\n resetRetry();\n if (!isClient && !isWorker || !wsRef.value)\n return;\n explicitlyClosed = true;\n resetHeartbeat();\n heartbeatPause == null ? void 0 : heartbeatPause();\n wsRef.value.close(code, reason);\n wsRef.value = void 0;\n };\n const send = (data2, useBuffer = true) => {\n if (!wsRef.value || status.value !== \"OPEN\") {\n if (useBuffer)\n bufferedData.push(data2);\n return false;\n }\n _sendBuffer();\n wsRef.value.send(data2);\n return true;\n };\n const _init = () => {\n if (explicitlyClosed || typeof urlRef.value === \"undefined\")\n return;\n const ws = new WebSocket(urlRef.value, protocols);\n wsRef.value = ws;\n status.value = \"CONNECTING\";\n ws.onopen = () => {\n status.value = \"OPEN\";\n retried = 0;\n onConnected == null ? void 0 : onConnected(ws);\n heartbeatResume == null ? void 0 : heartbeatResume();\n _sendBuffer();\n };\n ws.onclose = (ev) => {\n status.value = \"CLOSED\";\n resetHeartbeat();\n heartbeatPause == null ? void 0 : heartbeatPause();\n onDisconnected == null ? void 0 : onDisconnected(ws, ev);\n if (!explicitlyClosed && options.autoReconnect && (wsRef.value == null || ws === wsRef.value)) {\n const {\n retries = -1,\n delay = 1e3,\n onFailed\n } = resolveNestedOptions(options.autoReconnect);\n const checkRetires = typeof retries === \"function\" ? retries : () => typeof retries === \"number\" && (retries < 0 || retried < retries);\n if (checkRetires(retried)) {\n retried += 1;\n retryTimeout = setTimeout(_init, delay);\n } else {\n onFailed == null ? void 0 : onFailed();\n }\n }\n };\n ws.onerror = (e) => {\n onError == null ? void 0 : onError(ws, e);\n };\n ws.onmessage = (e) => {\n if (options.heartbeat) {\n resetHeartbeat();\n const {\n message = DEFAULT_PING_MESSAGE,\n responseMessage = message\n } = resolveNestedOptions(options.heartbeat);\n if (e.data === toValue(responseMessage))\n return;\n }\n data.value = e.data;\n onMessage == null ? void 0 : onMessage(ws, e);\n };\n };\n if (options.heartbeat) {\n const {\n message = DEFAULT_PING_MESSAGE,\n interval = 1e3,\n pongTimeout = 1e3\n } = resolveNestedOptions(options.heartbeat);\n const { pause, resume } = useIntervalFn(\n () => {\n send(toValue(message), false);\n if (pongTimeoutWait != null)\n return;\n pongTimeoutWait = setTimeout(() => {\n close();\n explicitlyClosed = false;\n }, pongTimeout);\n },\n interval,\n { immediate: false }\n );\n heartbeatPause = pause;\n heartbeatResume = resume;\n }\n if (autoClose) {\n if (isClient)\n useEventListener(\"beforeunload\", () => close(), { passive: true });\n tryOnScopeDispose(close);\n }\n const open = () => {\n if (!isClient && !isWorker)\n return;\n close();\n explicitlyClosed = false;\n retried = 0;\n _init();\n };\n if (immediate)\n open();\n if (autoConnect)\n watch(urlRef, open);\n return {\n data,\n status,\n close,\n send,\n open,\n ws: wsRef\n };\n}\n\nfunction useWebWorker(arg0, workerOptions, options) {\n const {\n window = defaultWindow\n } = options != null ? options : {};\n const data = ref(null);\n const worker = shallowRef();\n const post = (...args) => {\n if (!worker.value)\n return;\n worker.value.postMessage(...args);\n };\n const terminate = function terminate2() {\n if (!worker.value)\n return;\n worker.value.terminate();\n };\n if (window) {\n if (typeof arg0 === \"string\")\n worker.value = new Worker(arg0, workerOptions);\n else if (typeof arg0 === \"function\")\n worker.value = arg0();\n else\n worker.value = arg0;\n worker.value.onmessage = (e) => {\n data.value = e.data;\n };\n tryOnScopeDispose(() => {\n if (worker.value)\n worker.value.terminate();\n });\n }\n return {\n data,\n post,\n terminate,\n worker\n };\n}\n\nfunction depsParser(deps, localDeps) {\n if (deps.length === 0 && localDeps.length === 0)\n return \"\";\n const depsString = deps.map((dep) => `'${dep}'`).toString();\n const depsFunctionString = localDeps.filter((dep) => typeof dep === \"function\").map((fn) => {\n const str = fn.toString();\n if (str.trim().startsWith(\"function\")) {\n return str;\n } else {\n const name = fn.name;\n return `const ${name} = ${str}`;\n }\n }).join(\";\");\n const importString = `importScripts(${depsString});`;\n return `${depsString.trim() === \"\" ? \"\" : importString} ${depsFunctionString}`;\n}\n\nfunction jobRunner(userFunc) {\n return (e) => {\n const userFuncArgs = e.data[0];\n return Promise.resolve(userFunc.apply(void 0, userFuncArgs)).then((result) => {\n postMessage([\"SUCCESS\", result]);\n }).catch((error) => {\n postMessage([\"ERROR\", error]);\n });\n };\n}\n\nfunction createWorkerBlobUrl(fn, deps, localDeps) {\n const blobCode = `${depsParser(deps, localDeps)}; onmessage=(${jobRunner})(${fn})`;\n const blob = new Blob([blobCode], { type: \"text/javascript\" });\n const url = URL.createObjectURL(blob);\n return url;\n}\n\nfunction useWebWorkerFn(fn, options = {}) {\n const {\n dependencies = [],\n localDependencies = [],\n timeout,\n window = defaultWindow\n } = options;\n const worker = ref();\n const workerStatus = shallowRef(\"PENDING\");\n const promise = ref({});\n const timeoutId = shallowRef();\n const workerTerminate = (status = \"PENDING\") => {\n if (worker.value && worker.value._url && window) {\n worker.value.terminate();\n URL.revokeObjectURL(worker.value._url);\n promise.value = {};\n worker.value = void 0;\n window.clearTimeout(timeoutId.value);\n workerStatus.value = status;\n }\n };\n workerTerminate();\n tryOnScopeDispose(workerTerminate);\n const generateWorker = () => {\n const blobUrl = createWorkerBlobUrl(fn, dependencies, localDependencies);\n const newWorker = new Worker(blobUrl);\n newWorker._url = blobUrl;\n newWorker.onmessage = (e) => {\n const { resolve = () => {\n }, reject = () => {\n } } = promise.value;\n const [status, result] = e.data;\n switch (status) {\n case \"SUCCESS\":\n resolve(result);\n workerTerminate(status);\n break;\n default:\n reject(result);\n workerTerminate(\"ERROR\");\n break;\n }\n };\n newWorker.onerror = (e) => {\n const { reject = () => {\n } } = promise.value;\n e.preventDefault();\n reject(e);\n workerTerminate(\"ERROR\");\n };\n if (timeout) {\n timeoutId.value = setTimeout(\n () => workerTerminate(\"TIMEOUT_EXPIRED\"),\n timeout\n );\n }\n return newWorker;\n };\n const callWorker = (...fnArgs) => new Promise((resolve, reject) => {\n var _a;\n promise.value = {\n resolve,\n reject\n };\n (_a = worker.value) == null ? void 0 : _a.postMessage([[...fnArgs]]);\n workerStatus.value = \"RUNNING\";\n });\n const workerFn = (...fnArgs) => {\n if (workerStatus.value === \"RUNNING\") {\n console.error(\n \"[useWebWorkerFn] You can only run one instance of the worker at a time.\"\n );\n return Promise.reject();\n }\n worker.value = generateWorker();\n return callWorker(...fnArgs);\n };\n return {\n workerFn,\n workerStatus,\n workerTerminate\n };\n}\n\nfunction useWindowFocus(options = {}) {\n const { window = defaultWindow } = options;\n if (!window)\n return shallowRef(false);\n const focused = shallowRef(window.document.hasFocus());\n const listenerOptions = { passive: true };\n useEventListener(window, \"blur\", () => {\n focused.value = false;\n }, listenerOptions);\n useEventListener(window, \"focus\", () => {\n focused.value = true;\n }, listenerOptions);\n return focused;\n}\n\nfunction useWindowScroll(options = {}) {\n const { window = defaultWindow, ...rest } = options;\n return useScroll(window, rest);\n}\n\nfunction useWindowSize(options = {}) {\n const {\n window = defaultWindow,\n initialWidth = Number.POSITIVE_INFINITY,\n initialHeight = Number.POSITIVE_INFINITY,\n listenOrientation = true,\n includeScrollbar = true,\n type = \"inner\"\n } = options;\n const width = shallowRef(initialWidth);\n const height = shallowRef(initialHeight);\n const update = () => {\n if (window) {\n if (type === \"outer\") {\n width.value = window.outerWidth;\n height.value = window.outerHeight;\n } else if (type === \"visual\" && window.visualViewport) {\n const { width: visualViewportWidth, height: visualViewportHeight, scale } = window.visualViewport;\n width.value = Math.round(visualViewportWidth * scale);\n height.value = Math.round(visualViewportHeight * scale);\n } else if (includeScrollbar) {\n width.value = window.innerWidth;\n height.value = window.innerHeight;\n } else {\n width.value = window.document.documentElement.clientWidth;\n height.value = window.document.documentElement.clientHeight;\n }\n }\n };\n update();\n tryOnMounted(update);\n const listenerOptions = { passive: true };\n useEventListener(\"resize\", update, listenerOptions);\n if (window && type === \"visual\" && window.visualViewport) {\n useEventListener(window.visualViewport, \"resize\", update, listenerOptions);\n }\n if (listenOrientation) {\n const matches = useMediaQuery(\"(orientation: portrait)\");\n watch(matches, () => update());\n }\n return { width, height };\n}\n\nexport { DefaultMagicKeysAliasMap, StorageSerializers, TransitionPresets, computedAsync as asyncComputed, breakpointsAntDesign, breakpointsBootstrapV5, breakpointsElement, breakpointsMasterCss, breakpointsPrimeFlex, breakpointsQuasar, breakpointsSematic, breakpointsTailwind, breakpointsVuetify, breakpointsVuetifyV2, breakpointsVuetifyV3, cloneFnJSON, computedAsync, computedInject, createFetch, createReusableTemplate, createTemplatePromise, createUnrefFn, customStorageEventName, defaultDocument, defaultLocation, defaultNavigator, defaultWindow, executeTransition, formatTimeAgo, getSSRHandler, mapGamepadToXbox360Controller, onClickOutside, onElementRemoval, onKeyDown, onKeyPressed, onKeyStroke, onKeyUp, onLongPress, onStartTyping, provideSSRWidth, setSSRHandler, templateRef, unrefElement, useActiveElement, useAnimate, useAsyncQueue, useAsyncState, useBase64, useBattery, useBluetooth, useBreakpoints, useBroadcastChannel, useBrowserLocation, useCached, useClipboard, useClipboardItems, useCloned, useColorMode, useConfirmDialog, useCountdown, useCssVar, useCurrentElement, useCycleList, useDark, useDebouncedRefHistory, useDeviceMotion, useDeviceOrientation, useDevicePixelRatio, useDevicesList, useDisplayMedia, useDocumentVisibility, useDraggable, useDropZone, useElementBounding, useElementByPoint, useElementHover, useElementSize, useElementVisibility, useEventBus, useEventListener, useEventSource, useEyeDropper, useFavicon, useFetch, useFileDialog, useFileSystemAccess, useFocus, useFocusWithin, useFps, useFullscreen, useGamepad, useGeolocation, useIdle, useImage, useInfiniteScroll, useIntersectionObserver, useKeyModifier, useLocalStorage, useMagicKeys, useManualRefHistory, useMediaControls, useMediaQuery, useMemoize, useMemory, useMounted, useMouse, useMouseInElement, useMousePressed, useMutationObserver, useNavigatorLanguage, useNetwork, useNow, useObjectUrl, useOffsetPagination, useOnline, usePageLeave, useParallax, useParentElement, usePerformanceObserver, usePermission, usePointer, usePointerLock, usePointerSwipe, usePreferredColorScheme, usePreferredContrast, usePreferredDark, usePreferredLanguages, usePreferredReducedMotion, usePreferredReducedTransparency, usePrevious, useRafFn, useRefHistory, useResizeObserver, useSSRWidth, useScreenOrientation, useScreenSafeArea, useScriptTag, useScroll, useScrollLock, useSessionStorage, useShare, useSorted, useSpeechRecognition, useSpeechSynthesis, useStepper, useStorage, useStorageAsync, useStyleTag, useSupported, useSwipe, useTemplateRefsList, useTextDirection, useTextSelection, useTextareaAutosize, useThrottledRefHistory, useTimeAgo, useTimeoutPoll, useTimestamp, useTitle, useTransition, useUrlSearchParams, useUserMedia, useVModel, useVModels, useVibrate, useVirtualList, useWakeLock, useWebNotification, useWebSocket, useWebWorker, useWebWorkerFn, useWindowFocus, useWindowScroll, useWindowSize };\n"], + "mappings": "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", + "names": ["get", "set", "ref", "keys", "invoke", "toRef", "toRefs", "toValue", "window", "document", "timestamp", "navigator", "events", "getValue", "ref", "defaults", "toRef", "set", "onUpdated", "preventDefault", "toRefs", "now", "keys", "get", "isReadonly"] +} diff --git a/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js b/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js new file mode 100644 index 0000000..42925ac --- /dev/null +++ b/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js @@ -0,0 +1,12824 @@ +// node_modules/@vue/shared/dist/shared.esm-bundler.js +function makeMap(str) { + const map2 = /* @__PURE__ */ Object.create(null); + for (const key of str.split(",")) map2[key] = 1; + return (val) => val in map2; +} +var EMPTY_OBJ = true ? Object.freeze({}) : {}; +var EMPTY_ARR = true ? Object.freeze([]) : []; +var NOOP = () => { +}; +var NO = () => false; +var isOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && // uppercase letter +(key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97); +var isModelListener = (key) => key.startsWith("onUpdate:"); +var extend = Object.assign; +var remove = (arr, el) => { + const i = arr.indexOf(el); + if (i > -1) { + arr.splice(i, 1); + } +}; +var hasOwnProperty = Object.prototype.hasOwnProperty; +var hasOwn = (val, key) => hasOwnProperty.call(val, key); +var isArray = Array.isArray; +var isMap = (val) => toTypeString(val) === "[object Map]"; +var isSet = (val) => toTypeString(val) === "[object Set]"; +var isDate = (val) => toTypeString(val) === "[object Date]"; +var isRegExp = (val) => toTypeString(val) === "[object RegExp]"; +var isFunction = (val) => typeof val === "function"; +var isString = (val) => typeof val === "string"; +var isSymbol = (val) => typeof val === "symbol"; +var isObject = (val) => val !== null && typeof val === "object"; +var isPromise = (val) => { + return (isObject(val) || isFunction(val)) && isFunction(val.then) && isFunction(val.catch); +}; +var objectToString = Object.prototype.toString; +var toTypeString = (value) => objectToString.call(value); +var toRawType = (value) => { + return toTypeString(value).slice(8, -1); +}; +var isPlainObject = (val) => toTypeString(val) === "[object Object]"; +var isIntegerKey = (key) => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key; +var isReservedProp = makeMap( + // the leading comma is intentional so empty string "" is also included + ",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted" +); +var isBuiltInDirective = makeMap( + "bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo" +); +var cacheStringFunction = (fn) => { + const cache = /* @__PURE__ */ Object.create(null); + return (str) => { + const hit = cache[str]; + return hit || (cache[str] = fn(str)); + }; +}; +var camelizeRE = /-\w/g; +var camelize = cacheStringFunction( + (str) => { + return str.replace(camelizeRE, (c) => c.slice(1).toUpperCase()); + } +); +var hyphenateRE = /\B([A-Z])/g; +var hyphenate = cacheStringFunction( + (str) => str.replace(hyphenateRE, "-$1").toLowerCase() +); +var capitalize = cacheStringFunction((str) => { + return str.charAt(0).toUpperCase() + str.slice(1); +}); +var toHandlerKey = cacheStringFunction( + (str) => { + const s = str ? `on${capitalize(str)}` : ``; + return s; + } +); +var hasChanged = (value, oldValue) => !Object.is(value, oldValue); +var invokeArrayFns = (fns, ...arg) => { + for (let i = 0; i < fns.length; i++) { + fns[i](...arg); + } +}; +var def = (obj, key, value, writable = false) => { + Object.defineProperty(obj, key, { + configurable: true, + enumerable: false, + writable, + value + }); +}; +var looseToNumber = (val) => { + const n = parseFloat(val); + return isNaN(n) ? val : n; +}; +var toNumber = (val) => { + const n = isString(val) ? Number(val) : NaN; + return isNaN(n) ? val : n; +}; +var _globalThis; +var getGlobalThis = () => { + return _globalThis || (_globalThis = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {}); +}; +var GLOBALS_ALLOWED = "Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol"; +var isGloballyAllowed = makeMap(GLOBALS_ALLOWED); +function normalizeStyle(value) { + if (isArray(value)) { + const res = {}; + for (let i = 0; i < value.length; i++) { + const item = value[i]; + const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item); + if (normalized) { + for (const key in normalized) { + res[key] = normalized[key]; + } + } + } + return res; + } else if (isString(value) || isObject(value)) { + return value; + } +} +var listDelimiterRE = /;(?![^(]*\))/g; +var propertyDelimiterRE = /:([^]+)/; +var styleCommentRE = /\/\*[^]*?\*\//g; +function parseStringStyle(cssText) { + const ret = {}; + cssText.replace(styleCommentRE, "").split(listDelimiterRE).forEach((item) => { + if (item) { + const tmp = item.split(propertyDelimiterRE); + tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim()); + } + }); + return ret; +} +function stringifyStyle(styles) { + if (!styles) return ""; + if (isString(styles)) return styles; + let ret = ""; + for (const key in styles) { + const value = styles[key]; + if (isString(value) || typeof value === "number") { + const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key); + ret += `${normalizedKey}:${value};`; + } + } + return ret; +} +function normalizeClass(value) { + let res = ""; + if (isString(value)) { + res = value; + } else if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + const normalized = normalizeClass(value[i]); + if (normalized) { + res += normalized + " "; + } + } + } else if (isObject(value)) { + for (const name in value) { + if (value[name]) { + res += name + " "; + } + } + } + return res.trim(); +} +function normalizeProps(props) { + if (!props) return null; + let { class: klass, style } = props; + if (klass && !isString(klass)) { + props.class = normalizeClass(klass); + } + if (style) { + props.style = normalizeStyle(style); + } + return props; +} +var HTML_TAGS = "html,body,base,head,link,meta,style,title,address,article,aside,footer,header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot"; +var SVG_TAGS = "svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view"; +var MATH_TAGS = "annotation,annotation-xml,maction,maligngroup,malignmark,math,menclose,merror,mfenced,mfrac,mfraction,mglyph,mi,mlabeledtr,mlongdiv,mmultiscripts,mn,mo,mover,mpadded,mphantom,mprescripts,mroot,mrow,ms,mscarries,mscarry,msgroup,msline,mspace,msqrt,msrow,mstack,mstyle,msub,msubsup,msup,mtable,mtd,mtext,mtr,munder,munderover,none,semantics"; +var VOID_TAGS = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr"; +var isHTMLTag = makeMap(HTML_TAGS); +var isSVGTag = makeMap(SVG_TAGS); +var isMathMLTag = makeMap(MATH_TAGS); +var isVoidTag = makeMap(VOID_TAGS); +var specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`; +var isSpecialBooleanAttr = makeMap(specialBooleanAttrs); +var isBooleanAttr = makeMap( + specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected` +); +function includeBooleanAttr(value) { + return !!value || value === ""; +} +var isKnownHtmlAttr = makeMap( + `accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,inert,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap` +); +var isKnownSvgAttr = makeMap( + `xmlns,accent-height,accumulate,additive,alignment-baseline,alphabetic,amplitude,arabic-form,ascent,attributeName,attributeType,azimuth,baseFrequency,baseline-shift,baseProfile,bbox,begin,bias,by,calcMode,cap-height,class,clip,clipPathUnits,clip-path,clip-rule,color,color-interpolation,color-interpolation-filters,color-profile,color-rendering,contentScriptType,contentStyleType,crossorigin,cursor,cx,cy,d,decelerate,descent,diffuseConstant,direction,display,divisor,dominant-baseline,dur,dx,dy,edgeMode,elevation,enable-background,end,exponent,fill,fill-opacity,fill-rule,filter,filterRes,filterUnits,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,format,from,fr,fx,fy,g1,g2,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,glyphRef,gradientTransform,gradientUnits,hanging,height,href,hreflang,horiz-adv-x,horiz-origin-x,id,ideographic,image-rendering,in,in2,intercept,k,k1,k2,k3,k4,kernelMatrix,kernelUnitLength,kerning,keyPoints,keySplines,keyTimes,lang,lengthAdjust,letter-spacing,lighting-color,limitingConeAngle,local,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mask,maskContentUnits,maskUnits,mathematical,max,media,method,min,mode,name,numOctaves,offset,opacity,operator,order,orient,orientation,origin,overflow,overline-position,overline-thickness,panose-1,paint-order,path,pathLength,patternContentUnits,patternTransform,patternUnits,ping,pointer-events,points,pointsAtX,pointsAtY,pointsAtZ,preserveAlpha,preserveAspectRatio,primitiveUnits,r,radius,referrerPolicy,refX,refY,rel,rendering-intent,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,result,rotate,rx,ry,scale,seed,shape-rendering,slope,spacing,specularConstant,specularExponent,speed,spreadMethod,startOffset,stdDeviation,stemh,stemv,stitchTiles,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,string,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,style,surfaceScale,systemLanguage,tabindex,tableValues,target,targetX,targetY,text-anchor,text-decoration,text-rendering,textLength,to,transform,transform-origin,type,u1,u2,underline-position,underline-thickness,unicode,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan` +); +var isKnownMathMLAttr = makeMap( + `accent,accentunder,actiontype,align,alignmentscope,altimg,altimg-height,altimg-valign,altimg-width,alttext,bevelled,close,columnsalign,columnlines,columnspan,denomalign,depth,dir,display,displaystyle,encoding,equalcolumns,equalrows,fence,fontstyle,fontweight,form,frame,framespacing,groupalign,height,href,id,indentalign,indentalignfirst,indentalignlast,indentshift,indentshiftfirst,indentshiftlast,indextype,justify,largetop,largeop,lquote,lspace,mathbackground,mathcolor,mathsize,mathvariant,maxsize,minlabelspacing,mode,other,overflow,position,rowalign,rowlines,rowspan,rquote,rspace,scriptlevel,scriptminsize,scriptsizemultiplier,selection,separator,separators,shift,side,src,stackalign,stretchy,subscriptshift,superscriptshift,symmetric,voffset,width,widths,xlink:href,xlink:show,xlink:type,xmlns` +); +function isRenderableAttrValue(value) { + if (value == null) { + return false; + } + const type = typeof value; + return type === "string" || type === "number" || type === "boolean"; +} +var cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g; +function getEscapedCssVarName(key, doubleEscape) { + return key.replace( + cssVarNameEscapeSymbolsRE, + (s) => doubleEscape ? s === '"' ? '\\\\\\"' : `\\\\${s}` : `\\${s}` + ); +} +function looseCompareArrays(a, b) { + if (a.length !== b.length) return false; + let equal = true; + for (let i = 0; equal && i < a.length; i++) { + equal = looseEqual(a[i], b[i]); + } + return equal; +} +function looseEqual(a, b) { + if (a === b) return true; + let aValidType = isDate(a); + let bValidType = isDate(b); + if (aValidType || bValidType) { + return aValidType && bValidType ? a.getTime() === b.getTime() : false; + } + aValidType = isSymbol(a); + bValidType = isSymbol(b); + if (aValidType || bValidType) { + return a === b; + } + aValidType = isArray(a); + bValidType = isArray(b); + if (aValidType || bValidType) { + return aValidType && bValidType ? looseCompareArrays(a, b) : false; + } + aValidType = isObject(a); + bValidType = isObject(b); + if (aValidType || bValidType) { + if (!aValidType || !bValidType) { + return false; + } + const aKeysCount = Object.keys(a).length; + const bKeysCount = Object.keys(b).length; + if (aKeysCount !== bKeysCount) { + return false; + } + for (const key in a) { + const aHasKey = a.hasOwnProperty(key); + const bHasKey = b.hasOwnProperty(key); + if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key])) { + return false; + } + } + } + return String(a) === String(b); +} +function looseIndexOf(arr, val) { + return arr.findIndex((item) => looseEqual(item, val)); +} +var isRef = (val) => { + return !!(val && val["__v_isRef"] === true); +}; +var toDisplayString = (val) => { + return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? isRef(val) ? toDisplayString(val.value) : JSON.stringify(val, replacer, 2) : String(val); +}; +var replacer = (_key, val) => { + if (isRef(val)) { + return replacer(_key, val.value); + } else if (isMap(val)) { + return { + [`Map(${val.size})`]: [...val.entries()].reduce( + (entries, [key, val2], i) => { + entries[stringifySymbol(key, i) + " =>"] = val2; + return entries; + }, + {} + ) + }; + } else if (isSet(val)) { + return { + [`Set(${val.size})`]: [...val.values()].map((v) => stringifySymbol(v)) + }; + } else if (isSymbol(val)) { + return stringifySymbol(val); + } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) { + return String(val); + } + return val; +}; +var stringifySymbol = (v, i = "") => { + var _a; + return ( + // Symbol.description in es2019+ so we need to cast here to pass + // the lib: es2016 check + isSymbol(v) ? `Symbol(${(_a = v.description) != null ? _a : i})` : v + ); +}; +function normalizeCssVarValue(value) { + if (value == null) { + return "initial"; + } + if (typeof value === "string") { + return value === "" ? " " : value; + } + if (typeof value !== "number" || !Number.isFinite(value)) { + if (true) { + console.warn( + "[Vue warn] Invalid value used for CSS binding. Expected a string or a finite number but received:", + value + ); + } + } + return String(value); +} + +// node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js +function warn(msg, ...args) { + console.warn(`[Vue warn] ${msg}`, ...args); +} +var activeEffectScope; +var EffectScope = class { + constructor(detached = false) { + this.detached = detached; + this._active = true; + this._on = 0; + this.effects = []; + this.cleanups = []; + this._isPaused = false; + this.parent = activeEffectScope; + if (!detached && activeEffectScope) { + this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push( + this + ) - 1; + } + } + get active() { + return this._active; + } + pause() { + if (this._active) { + this._isPaused = true; + let i, l; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].pause(); + } + } + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].pause(); + } + } + } + /** + * Resumes the effect scope, including all child scopes and effects. + */ + resume() { + if (this._active) { + if (this._isPaused) { + this._isPaused = false; + let i, l; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].resume(); + } + } + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].resume(); + } + } + } + } + run(fn) { + if (this._active) { + const currentEffectScope = activeEffectScope; + try { + activeEffectScope = this; + return fn(); + } finally { + activeEffectScope = currentEffectScope; + } + } else if (true) { + warn(`cannot run an inactive effect scope.`); + } + } + /** + * This should only be called on non-detached scopes + * @internal + */ + on() { + if (++this._on === 1) { + this.prevScope = activeEffectScope; + activeEffectScope = this; + } + } + /** + * This should only be called on non-detached scopes + * @internal + */ + off() { + if (this._on > 0 && --this._on === 0) { + activeEffectScope = this.prevScope; + this.prevScope = void 0; + } + } + stop(fromParent) { + if (this._active) { + this._active = false; + let i, l; + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].stop(); + } + this.effects.length = 0; + for (i = 0, l = this.cleanups.length; i < l; i++) { + this.cleanups[i](); + } + this.cleanups.length = 0; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].stop(true); + } + this.scopes.length = 0; + } + if (!this.detached && this.parent && !fromParent) { + const last = this.parent.scopes.pop(); + if (last && last !== this) { + this.parent.scopes[this.index] = last; + last.index = this.index; + } + } + this.parent = void 0; + } + } +}; +function effectScope(detached) { + return new EffectScope(detached); +} +function getCurrentScope() { + return activeEffectScope; +} +function onScopeDispose(fn, failSilently = false) { + if (activeEffectScope) { + activeEffectScope.cleanups.push(fn); + } else if (!failSilently) { + warn( + `onScopeDispose() is called when there is no active effect scope to be associated with.` + ); + } +} +var activeSub; +var pausedQueueEffects = /* @__PURE__ */ new WeakSet(); +var ReactiveEffect = class { + constructor(fn) { + this.fn = fn; + this.deps = void 0; + this.depsTail = void 0; + this.flags = 1 | 4; + this.next = void 0; + this.cleanup = void 0; + this.scheduler = void 0; + if (activeEffectScope && activeEffectScope.active) { + activeEffectScope.effects.push(this); + } + } + pause() { + this.flags |= 64; + } + resume() { + if (this.flags & 64) { + this.flags &= -65; + if (pausedQueueEffects.has(this)) { + pausedQueueEffects.delete(this); + this.trigger(); + } + } + } + /** + * @internal + */ + notify() { + if (this.flags & 2 && !(this.flags & 32)) { + return; + } + if (!(this.flags & 8)) { + batch(this); + } + } + run() { + if (!(this.flags & 1)) { + return this.fn(); + } + this.flags |= 2; + cleanupEffect(this); + prepareDeps(this); + const prevEffect = activeSub; + const prevShouldTrack = shouldTrack; + activeSub = this; + shouldTrack = true; + try { + return this.fn(); + } finally { + if (activeSub !== this) { + warn( + "Active effect was not restored correctly - this is likely a Vue internal bug." + ); + } + cleanupDeps(this); + activeSub = prevEffect; + shouldTrack = prevShouldTrack; + this.flags &= -3; + } + } + stop() { + if (this.flags & 1) { + for (let link = this.deps; link; link = link.nextDep) { + removeSub(link); + } + this.deps = this.depsTail = void 0; + cleanupEffect(this); + this.onStop && this.onStop(); + this.flags &= -2; + } + } + trigger() { + if (this.flags & 64) { + pausedQueueEffects.add(this); + } else if (this.scheduler) { + this.scheduler(); + } else { + this.runIfDirty(); + } + } + /** + * @internal + */ + runIfDirty() { + if (isDirty(this)) { + this.run(); + } + } + get dirty() { + return isDirty(this); + } +}; +var batchDepth = 0; +var batchedSub; +var batchedComputed; +function batch(sub, isComputed = false) { + sub.flags |= 8; + if (isComputed) { + sub.next = batchedComputed; + batchedComputed = sub; + return; + } + sub.next = batchedSub; + batchedSub = sub; +} +function startBatch() { + batchDepth++; +} +function endBatch() { + if (--batchDepth > 0) { + return; + } + if (batchedComputed) { + let e = batchedComputed; + batchedComputed = void 0; + while (e) { + const next = e.next; + e.next = void 0; + e.flags &= -9; + e = next; + } + } + let error; + while (batchedSub) { + let e = batchedSub; + batchedSub = void 0; + while (e) { + const next = e.next; + e.next = void 0; + e.flags &= -9; + if (e.flags & 1) { + try { + ; + e.trigger(); + } catch (err) { + if (!error) error = err; + } + } + e = next; + } + } + if (error) throw error; +} +function prepareDeps(sub) { + for (let link = sub.deps; link; link = link.nextDep) { + link.version = -1; + link.prevActiveLink = link.dep.activeLink; + link.dep.activeLink = link; + } +} +function cleanupDeps(sub) { + let head; + let tail = sub.depsTail; + let link = tail; + while (link) { + const prev = link.prevDep; + if (link.version === -1) { + if (link === tail) tail = prev; + removeSub(link); + removeDep(link); + } else { + head = link; + } + link.dep.activeLink = link.prevActiveLink; + link.prevActiveLink = void 0; + link = prev; + } + sub.deps = head; + sub.depsTail = tail; +} +function isDirty(sub) { + for (let link = sub.deps; link; link = link.nextDep) { + if (link.dep.version !== link.version || link.dep.computed && (refreshComputed(link.dep.computed) || link.dep.version !== link.version)) { + return true; + } + } + if (sub._dirty) { + return true; + } + return false; +} +function refreshComputed(computed3) { + if (computed3.flags & 4 && !(computed3.flags & 16)) { + return; + } + computed3.flags &= -17; + if (computed3.globalVersion === globalVersion) { + return; + } + computed3.globalVersion = globalVersion; + if (!computed3.isSSR && computed3.flags & 128 && (!computed3.deps && !computed3._dirty || !isDirty(computed3))) { + return; + } + computed3.flags |= 2; + const dep = computed3.dep; + const prevSub = activeSub; + const prevShouldTrack = shouldTrack; + activeSub = computed3; + shouldTrack = true; + try { + prepareDeps(computed3); + const value = computed3.fn(computed3._value); + if (dep.version === 0 || hasChanged(value, computed3._value)) { + computed3.flags |= 128; + computed3._value = value; + dep.version++; + } + } catch (err) { + dep.version++; + throw err; + } finally { + activeSub = prevSub; + shouldTrack = prevShouldTrack; + cleanupDeps(computed3); + computed3.flags &= -3; + } +} +function removeSub(link, soft = false) { + const { dep, prevSub, nextSub } = link; + if (prevSub) { + prevSub.nextSub = nextSub; + link.prevSub = void 0; + } + if (nextSub) { + nextSub.prevSub = prevSub; + link.nextSub = void 0; + } + if (dep.subsHead === link) { + dep.subsHead = nextSub; + } + if (dep.subs === link) { + dep.subs = prevSub; + if (!prevSub && dep.computed) { + dep.computed.flags &= -5; + for (let l = dep.computed.deps; l; l = l.nextDep) { + removeSub(l, true); + } + } + } + if (!soft && !--dep.sc && dep.map) { + dep.map.delete(dep.key); + } +} +function removeDep(link) { + const { prevDep, nextDep } = link; + if (prevDep) { + prevDep.nextDep = nextDep; + link.prevDep = void 0; + } + if (nextDep) { + nextDep.prevDep = prevDep; + link.nextDep = void 0; + } +} +function effect(fn, options) { + if (fn.effect instanceof ReactiveEffect) { + fn = fn.effect.fn; + } + const e = new ReactiveEffect(fn); + if (options) { + extend(e, options); + } + try { + e.run(); + } catch (err) { + e.stop(); + throw err; + } + const runner = e.run.bind(e); + runner.effect = e; + return runner; +} +function stop(runner) { + runner.effect.stop(); +} +var shouldTrack = true; +var trackStack = []; +function pauseTracking() { + trackStack.push(shouldTrack); + shouldTrack = false; +} +function resetTracking() { + const last = trackStack.pop(); + shouldTrack = last === void 0 ? true : last; +} +function cleanupEffect(e) { + const { cleanup } = e; + e.cleanup = void 0; + if (cleanup) { + const prevSub = activeSub; + activeSub = void 0; + try { + cleanup(); + } finally { + activeSub = prevSub; + } + } +} +var globalVersion = 0; +var Link = class { + constructor(sub, dep) { + this.sub = sub; + this.dep = dep; + this.version = dep.version; + this.nextDep = this.prevDep = this.nextSub = this.prevSub = this.prevActiveLink = void 0; + } +}; +var Dep = class { + // TODO isolatedDeclarations "__v_skip" + constructor(computed3) { + this.computed = computed3; + this.version = 0; + this.activeLink = void 0; + this.subs = void 0; + this.map = void 0; + this.key = void 0; + this.sc = 0; + this.__v_skip = true; + if (true) { + this.subsHead = void 0; + } + } + track(debugInfo) { + if (!activeSub || !shouldTrack || activeSub === this.computed) { + return; + } + let link = this.activeLink; + if (link === void 0 || link.sub !== activeSub) { + link = this.activeLink = new Link(activeSub, this); + if (!activeSub.deps) { + activeSub.deps = activeSub.depsTail = link; + } else { + link.prevDep = activeSub.depsTail; + activeSub.depsTail.nextDep = link; + activeSub.depsTail = link; + } + addSub(link); + } else if (link.version === -1) { + link.version = this.version; + if (link.nextDep) { + const next = link.nextDep; + next.prevDep = link.prevDep; + if (link.prevDep) { + link.prevDep.nextDep = next; + } + link.prevDep = activeSub.depsTail; + link.nextDep = void 0; + activeSub.depsTail.nextDep = link; + activeSub.depsTail = link; + if (activeSub.deps === link) { + activeSub.deps = next; + } + } + } + if (activeSub.onTrack) { + activeSub.onTrack( + extend( + { + effect: activeSub + }, + debugInfo + ) + ); + } + return link; + } + trigger(debugInfo) { + this.version++; + globalVersion++; + this.notify(debugInfo); + } + notify(debugInfo) { + startBatch(); + try { + if (true) { + for (let head = this.subsHead; head; head = head.nextSub) { + if (head.sub.onTrigger && !(head.sub.flags & 8)) { + head.sub.onTrigger( + extend( + { + effect: head.sub + }, + debugInfo + ) + ); + } + } + } + for (let link = this.subs; link; link = link.prevSub) { + if (link.sub.notify()) { + ; + link.sub.dep.notify(); + } + } + } finally { + endBatch(); + } + } +}; +function addSub(link) { + link.dep.sc++; + if (link.sub.flags & 4) { + const computed3 = link.dep.computed; + if (computed3 && !link.dep.subs) { + computed3.flags |= 4 | 16; + for (let l = computed3.deps; l; l = l.nextDep) { + addSub(l); + } + } + const currentTail = link.dep.subs; + if (currentTail !== link) { + link.prevSub = currentTail; + if (currentTail) currentTail.nextSub = link; + } + if (link.dep.subsHead === void 0) { + link.dep.subsHead = link; + } + link.dep.subs = link; + } +} +var targetMap = /* @__PURE__ */ new WeakMap(); +var ITERATE_KEY = Symbol( + true ? "Object iterate" : "" +); +var MAP_KEY_ITERATE_KEY = Symbol( + true ? "Map keys iterate" : "" +); +var ARRAY_ITERATE_KEY = Symbol( + true ? "Array iterate" : "" +); +function track(target, type, key) { + if (shouldTrack && activeSub) { + let depsMap = targetMap.get(target); + if (!depsMap) { + targetMap.set(target, depsMap = /* @__PURE__ */ new Map()); + } + let dep = depsMap.get(key); + if (!dep) { + depsMap.set(key, dep = new Dep()); + dep.map = depsMap; + dep.key = key; + } + if (true) { + dep.track({ + target, + type, + key + }); + } else { + dep.track(); + } + } +} +function trigger(target, type, key, newValue, oldValue, oldTarget) { + const depsMap = targetMap.get(target); + if (!depsMap) { + globalVersion++; + return; + } + const run = (dep) => { + if (dep) { + if (true) { + dep.trigger({ + target, + type, + key, + newValue, + oldValue, + oldTarget + }); + } else { + dep.trigger(); + } + } + }; + startBatch(); + if (type === "clear") { + depsMap.forEach(run); + } else { + const targetIsArray = isArray(target); + const isArrayIndex = targetIsArray && isIntegerKey(key); + if (targetIsArray && key === "length") { + const newLength = Number(newValue); + depsMap.forEach((dep, key2) => { + if (key2 === "length" || key2 === ARRAY_ITERATE_KEY || !isSymbol(key2) && key2 >= newLength) { + run(dep); + } + }); + } else { + if (key !== void 0 || depsMap.has(void 0)) { + run(depsMap.get(key)); + } + if (isArrayIndex) { + run(depsMap.get(ARRAY_ITERATE_KEY)); + } + switch (type) { + case "add": + if (!targetIsArray) { + run(depsMap.get(ITERATE_KEY)); + if (isMap(target)) { + run(depsMap.get(MAP_KEY_ITERATE_KEY)); + } + } else if (isArrayIndex) { + run(depsMap.get("length")); + } + break; + case "delete": + if (!targetIsArray) { + run(depsMap.get(ITERATE_KEY)); + if (isMap(target)) { + run(depsMap.get(MAP_KEY_ITERATE_KEY)); + } + } + break; + case "set": + if (isMap(target)) { + run(depsMap.get(ITERATE_KEY)); + } + break; + } + } + } + endBatch(); +} +function getDepFromReactive(object, key) { + const depMap = targetMap.get(object); + return depMap && depMap.get(key); +} +function reactiveReadArray(array) { + const raw = toRaw(array); + if (raw === array) return raw; + track(raw, "iterate", ARRAY_ITERATE_KEY); + return isShallow(array) ? raw : raw.map(toReactive); +} +function shallowReadArray(arr) { + track(arr = toRaw(arr), "iterate", ARRAY_ITERATE_KEY); + return arr; +} +function toWrapped(target, item) { + if (isReadonly(target)) { + return isReactive(target) ? toReadonly(toReactive(item)) : toReadonly(item); + } + return toReactive(item); +} +var arrayInstrumentations = { + __proto__: null, + [Symbol.iterator]() { + return iterator(this, Symbol.iterator, (item) => toWrapped(this, item)); + }, + concat(...args) { + return reactiveReadArray(this).concat( + ...args.map((x) => isArray(x) ? reactiveReadArray(x) : x) + ); + }, + entries() { + return iterator(this, "entries", (value) => { + value[1] = toWrapped(this, value[1]); + return value; + }); + }, + every(fn, thisArg) { + return apply(this, "every", fn, thisArg, void 0, arguments); + }, + filter(fn, thisArg) { + return apply( + this, + "filter", + fn, + thisArg, + (v) => v.map((item) => toWrapped(this, item)), + arguments + ); + }, + find(fn, thisArg) { + return apply( + this, + "find", + fn, + thisArg, + (item) => toWrapped(this, item), + arguments + ); + }, + findIndex(fn, thisArg) { + return apply(this, "findIndex", fn, thisArg, void 0, arguments); + }, + findLast(fn, thisArg) { + return apply( + this, + "findLast", + fn, + thisArg, + (item) => toWrapped(this, item), + arguments + ); + }, + findLastIndex(fn, thisArg) { + return apply(this, "findLastIndex", fn, thisArg, void 0, arguments); + }, + // flat, flatMap could benefit from ARRAY_ITERATE but are not straight-forward to implement + forEach(fn, thisArg) { + return apply(this, "forEach", fn, thisArg, void 0, arguments); + }, + includes(...args) { + return searchProxy(this, "includes", args); + }, + indexOf(...args) { + return searchProxy(this, "indexOf", args); + }, + join(separator) { + return reactiveReadArray(this).join(separator); + }, + // keys() iterator only reads `length`, no optimization required + lastIndexOf(...args) { + return searchProxy(this, "lastIndexOf", args); + }, + map(fn, thisArg) { + return apply(this, "map", fn, thisArg, void 0, arguments); + }, + pop() { + return noTracking(this, "pop"); + }, + push(...args) { + return noTracking(this, "push", args); + }, + reduce(fn, ...args) { + return reduce(this, "reduce", fn, args); + }, + reduceRight(fn, ...args) { + return reduce(this, "reduceRight", fn, args); + }, + shift() { + return noTracking(this, "shift"); + }, + // slice could use ARRAY_ITERATE but also seems to beg for range tracking + some(fn, thisArg) { + return apply(this, "some", fn, thisArg, void 0, arguments); + }, + splice(...args) { + return noTracking(this, "splice", args); + }, + toReversed() { + return reactiveReadArray(this).toReversed(); + }, + toSorted(comparer) { + return reactiveReadArray(this).toSorted(comparer); + }, + toSpliced(...args) { + return reactiveReadArray(this).toSpliced(...args); + }, + unshift(...args) { + return noTracking(this, "unshift", args); + }, + values() { + return iterator(this, "values", (item) => toWrapped(this, item)); + } +}; +function iterator(self2, method, wrapValue) { + const arr = shallowReadArray(self2); + const iter = arr[method](); + if (arr !== self2 && !isShallow(self2)) { + iter._next = iter.next; + iter.next = () => { + const result = iter._next(); + if (!result.done) { + result.value = wrapValue(result.value); + } + return result; + }; + } + return iter; +} +var arrayProto = Array.prototype; +function apply(self2, method, fn, thisArg, wrappedRetFn, args) { + const arr = shallowReadArray(self2); + const needsWrap = arr !== self2 && !isShallow(self2); + const methodFn = arr[method]; + if (methodFn !== arrayProto[method]) { + const result2 = methodFn.apply(self2, args); + return needsWrap ? toReactive(result2) : result2; + } + let wrappedFn = fn; + if (arr !== self2) { + if (needsWrap) { + wrappedFn = function(item, index) { + return fn.call(this, toWrapped(self2, item), index, self2); + }; + } else if (fn.length > 2) { + wrappedFn = function(item, index) { + return fn.call(this, item, index, self2); + }; + } + } + const result = methodFn.call(arr, wrappedFn, thisArg); + return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result; +} +function reduce(self2, method, fn, args) { + const arr = shallowReadArray(self2); + let wrappedFn = fn; + if (arr !== self2) { + if (!isShallow(self2)) { + wrappedFn = function(acc, item, index) { + return fn.call(this, acc, toWrapped(self2, item), index, self2); + }; + } else if (fn.length > 3) { + wrappedFn = function(acc, item, index) { + return fn.call(this, acc, item, index, self2); + }; + } + } + return arr[method](wrappedFn, ...args); +} +function searchProxy(self2, method, args) { + const arr = toRaw(self2); + track(arr, "iterate", ARRAY_ITERATE_KEY); + const res = arr[method](...args); + if ((res === -1 || res === false) && isProxy(args[0])) { + args[0] = toRaw(args[0]); + return arr[method](...args); + } + return res; +} +function noTracking(self2, method, args = []) { + pauseTracking(); + startBatch(); + const res = toRaw(self2)[method].apply(self2, args); + endBatch(); + resetTracking(); + return res; +} +var isNonTrackableKeys = makeMap(`__proto__,__v_isRef,__isVue`); +var builtInSymbols = new Set( + Object.getOwnPropertyNames(Symbol).filter((key) => key !== "arguments" && key !== "caller").map((key) => Symbol[key]).filter(isSymbol) +); +function hasOwnProperty2(key) { + if (!isSymbol(key)) key = String(key); + const obj = toRaw(this); + track(obj, "has", key); + return obj.hasOwnProperty(key); +} +var BaseReactiveHandler = class { + constructor(_isReadonly = false, _isShallow = false) { + this._isReadonly = _isReadonly; + this._isShallow = _isShallow; + } + get(target, key, receiver) { + if (key === "__v_skip") return target["__v_skip"]; + const isReadonly2 = this._isReadonly, isShallow2 = this._isShallow; + if (key === "__v_isReactive") { + return !isReadonly2; + } else if (key === "__v_isReadonly") { + return isReadonly2; + } else if (key === "__v_isShallow") { + return isShallow2; + } else if (key === "__v_raw") { + if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || // receiver is not the reactive proxy, but has the same prototype + // this means the receiver is a user proxy of the reactive proxy + Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) { + return target; + } + return; + } + const targetIsArray = isArray(target); + if (!isReadonly2) { + let fn; + if (targetIsArray && (fn = arrayInstrumentations[key])) { + return fn; + } + if (key === "hasOwnProperty") { + return hasOwnProperty2; + } + } + const res = Reflect.get( + target, + key, + // if this is a proxy wrapping a ref, return methods using the raw ref + // as receiver so that we don't have to call `toRaw` on the ref in all + // its class methods + isRef2(target) ? target : receiver + ); + if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { + return res; + } + if (!isReadonly2) { + track(target, "get", key); + } + if (isShallow2) { + return res; + } + if (isRef2(res)) { + const value = targetIsArray && isIntegerKey(key) ? res : res.value; + return isReadonly2 && isObject(value) ? readonly(value) : value; + } + if (isObject(res)) { + return isReadonly2 ? readonly(res) : reactive(res); + } + return res; + } +}; +var MutableReactiveHandler = class extends BaseReactiveHandler { + constructor(isShallow2 = false) { + super(false, isShallow2); + } + set(target, key, value, receiver) { + let oldValue = target[key]; + const isArrayWithIntegerKey = isArray(target) && isIntegerKey(key); + if (!this._isShallow) { + const isOldValueReadonly = isReadonly(oldValue); + if (!isShallow(value) && !isReadonly(value)) { + oldValue = toRaw(oldValue); + value = toRaw(value); + } + if (!isArrayWithIntegerKey && isRef2(oldValue) && !isRef2(value)) { + if (isOldValueReadonly) { + if (true) { + warn( + `Set operation on key "${String(key)}" failed: target is readonly.`, + target[key] + ); + } + return true; + } else { + oldValue.value = value; + return true; + } + } + } + const hadKey = isArrayWithIntegerKey ? Number(key) < target.length : hasOwn(target, key); + const result = Reflect.set( + target, + key, + value, + isRef2(target) ? target : receiver + ); + if (target === toRaw(receiver)) { + if (!hadKey) { + trigger(target, "add", key, value); + } else if (hasChanged(value, oldValue)) { + trigger(target, "set", key, value, oldValue); + } + } + return result; + } + deleteProperty(target, key) { + const hadKey = hasOwn(target, key); + const oldValue = target[key]; + const result = Reflect.deleteProperty(target, key); + if (result && hadKey) { + trigger(target, "delete", key, void 0, oldValue); + } + return result; + } + has(target, key) { + const result = Reflect.has(target, key); + if (!isSymbol(key) || !builtInSymbols.has(key)) { + track(target, "has", key); + } + return result; + } + ownKeys(target) { + track( + target, + "iterate", + isArray(target) ? "length" : ITERATE_KEY + ); + return Reflect.ownKeys(target); + } +}; +var ReadonlyReactiveHandler = class extends BaseReactiveHandler { + constructor(isShallow2 = false) { + super(true, isShallow2); + } + set(target, key) { + if (true) { + warn( + `Set operation on key "${String(key)}" failed: target is readonly.`, + target + ); + } + return true; + } + deleteProperty(target, key) { + if (true) { + warn( + `Delete operation on key "${String(key)}" failed: target is readonly.`, + target + ); + } + return true; + } +}; +var mutableHandlers = new MutableReactiveHandler(); +var readonlyHandlers = new ReadonlyReactiveHandler(); +var shallowReactiveHandlers = new MutableReactiveHandler(true); +var shallowReadonlyHandlers = new ReadonlyReactiveHandler(true); +var toShallow = (value) => value; +var getProto = (v) => Reflect.getPrototypeOf(v); +function createIterableMethod(method, isReadonly2, isShallow2) { + return function(...args) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const targetIsMap = isMap(rawTarget); + const isPair = method === "entries" || method === Symbol.iterator && targetIsMap; + const isKeyOnly = method === "keys" && targetIsMap; + const innerIterator = target[method](...args); + const wrap = isShallow2 ? toShallow : isReadonly2 ? toReadonly : toReactive; + !isReadonly2 && track( + rawTarget, + "iterate", + isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY + ); + return { + // iterator protocol + next() { + const { value, done } = innerIterator.next(); + return done ? { value, done } : { + value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), + done + }; + }, + // iterable protocol + [Symbol.iterator]() { + return this; + } + }; + }; +} +function createReadonlyMethod(type) { + return function(...args) { + if (true) { + const key = args[0] ? `on key "${args[0]}" ` : ``; + warn( + `${capitalize(type)} operation ${key}failed: target is readonly.`, + toRaw(this) + ); + } + return type === "delete" ? false : type === "clear" ? void 0 : this; + }; +} +function createInstrumentations(readonly2, shallow) { + const instrumentations = { + get(key) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const rawKey = toRaw(key); + if (!readonly2) { + if (hasChanged(key, rawKey)) { + track(rawTarget, "get", key); + } + track(rawTarget, "get", rawKey); + } + const { has } = getProto(rawTarget); + const wrap = shallow ? toShallow : readonly2 ? toReadonly : toReactive; + if (has.call(rawTarget, key)) { + return wrap(target.get(key)); + } else if (has.call(rawTarget, rawKey)) { + return wrap(target.get(rawKey)); + } else if (target !== rawTarget) { + target.get(key); + } + }, + get size() { + const target = this["__v_raw"]; + !readonly2 && track(toRaw(target), "iterate", ITERATE_KEY); + return target.size; + }, + has(key) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const rawKey = toRaw(key); + if (!readonly2) { + if (hasChanged(key, rawKey)) { + track(rawTarget, "has", key); + } + track(rawTarget, "has", rawKey); + } + return key === rawKey ? target.has(key) : target.has(key) || target.has(rawKey); + }, + forEach(callback, thisArg) { + const observed = this; + const target = observed["__v_raw"]; + const rawTarget = toRaw(target); + const wrap = shallow ? toShallow : readonly2 ? toReadonly : toReactive; + !readonly2 && track(rawTarget, "iterate", ITERATE_KEY); + return target.forEach((value, key) => { + return callback.call(thisArg, wrap(value), wrap(key), observed); + }); + } + }; + extend( + instrumentations, + readonly2 ? { + add: createReadonlyMethod("add"), + set: createReadonlyMethod("set"), + delete: createReadonlyMethod("delete"), + clear: createReadonlyMethod("clear") + } : { + add(value) { + if (!shallow && !isShallow(value) && !isReadonly(value)) { + value = toRaw(value); + } + const target = toRaw(this); + const proto = getProto(target); + const hadKey = proto.has.call(target, value); + if (!hadKey) { + target.add(value); + trigger(target, "add", value, value); + } + return this; + }, + set(key, value) { + if (!shallow && !isShallow(value) && !isReadonly(value)) { + value = toRaw(value); + } + const target = toRaw(this); + const { has, get } = getProto(target); + let hadKey = has.call(target, key); + if (!hadKey) { + key = toRaw(key); + hadKey = has.call(target, key); + } else if (true) { + checkIdentityKeys(target, has, key); + } + const oldValue = get.call(target, key); + target.set(key, value); + if (!hadKey) { + trigger(target, "add", key, value); + } else if (hasChanged(value, oldValue)) { + trigger(target, "set", key, value, oldValue); + } + return this; + }, + delete(key) { + const target = toRaw(this); + const { has, get } = getProto(target); + let hadKey = has.call(target, key); + if (!hadKey) { + key = toRaw(key); + hadKey = has.call(target, key); + } else if (true) { + checkIdentityKeys(target, has, key); + } + const oldValue = get ? get.call(target, key) : void 0; + const result = target.delete(key); + if (hadKey) { + trigger(target, "delete", key, void 0, oldValue); + } + return result; + }, + clear() { + const target = toRaw(this); + const hadItems = target.size !== 0; + const oldTarget = true ? isMap(target) ? new Map(target) : new Set(target) : void 0; + const result = target.clear(); + if (hadItems) { + trigger( + target, + "clear", + void 0, + void 0, + oldTarget + ); + } + return result; + } + } + ); + const iteratorMethods = [ + "keys", + "values", + "entries", + Symbol.iterator + ]; + iteratorMethods.forEach((method) => { + instrumentations[method] = createIterableMethod(method, readonly2, shallow); + }); + return instrumentations; +} +function createInstrumentationGetter(isReadonly2, shallow) { + const instrumentations = createInstrumentations(isReadonly2, shallow); + return (target, key, receiver) => { + if (key === "__v_isReactive") { + return !isReadonly2; + } else if (key === "__v_isReadonly") { + return isReadonly2; + } else if (key === "__v_raw") { + return target; + } + return Reflect.get( + hasOwn(instrumentations, key) && key in target ? instrumentations : target, + key, + receiver + ); + }; +} +var mutableCollectionHandlers = { + get: createInstrumentationGetter(false, false) +}; +var shallowCollectionHandlers = { + get: createInstrumentationGetter(false, true) +}; +var readonlyCollectionHandlers = { + get: createInstrumentationGetter(true, false) +}; +var shallowReadonlyCollectionHandlers = { + get: createInstrumentationGetter(true, true) +}; +function checkIdentityKeys(target, has, key) { + const rawKey = toRaw(key); + if (rawKey !== key && has.call(target, rawKey)) { + const type = toRawType(target); + warn( + `Reactive ${type} contains both the raw and reactive versions of the same object${type === `Map` ? ` as keys` : ``}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.` + ); + } +} +var reactiveMap = /* @__PURE__ */ new WeakMap(); +var shallowReactiveMap = /* @__PURE__ */ new WeakMap(); +var readonlyMap = /* @__PURE__ */ new WeakMap(); +var shallowReadonlyMap = /* @__PURE__ */ new WeakMap(); +function targetTypeMap(rawType) { + switch (rawType) { + case "Object": + case "Array": + return 1; + case "Map": + case "Set": + case "WeakMap": + case "WeakSet": + return 2; + default: + return 0; + } +} +function getTargetType(value) { + return value["__v_skip"] || !Object.isExtensible(value) ? 0 : targetTypeMap(toRawType(value)); +} +function reactive(target) { + if (isReadonly(target)) { + return target; + } + return createReactiveObject( + target, + false, + mutableHandlers, + mutableCollectionHandlers, + reactiveMap + ); +} +function shallowReactive(target) { + return createReactiveObject( + target, + false, + shallowReactiveHandlers, + shallowCollectionHandlers, + shallowReactiveMap + ); +} +function readonly(target) { + return createReactiveObject( + target, + true, + readonlyHandlers, + readonlyCollectionHandlers, + readonlyMap + ); +} +function shallowReadonly(target) { + return createReactiveObject( + target, + true, + shallowReadonlyHandlers, + shallowReadonlyCollectionHandlers, + shallowReadonlyMap + ); +} +function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) { + if (!isObject(target)) { + if (true) { + warn( + `value cannot be made ${isReadonly2 ? "readonly" : "reactive"}: ${String( + target + )}` + ); + } + return target; + } + if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) { + return target; + } + const targetType = getTargetType(target); + if (targetType === 0) { + return target; + } + const existingProxy = proxyMap.get(target); + if (existingProxy) { + return existingProxy; + } + const proxy = new Proxy( + target, + targetType === 2 ? collectionHandlers : baseHandlers + ); + proxyMap.set(target, proxy); + return proxy; +} +function isReactive(value) { + if (isReadonly(value)) { + return isReactive(value["__v_raw"]); + } + return !!(value && value["__v_isReactive"]); +} +function isReadonly(value) { + return !!(value && value["__v_isReadonly"]); +} +function isShallow(value) { + return !!(value && value["__v_isShallow"]); +} +function isProxy(value) { + return value ? !!value["__v_raw"] : false; +} +function toRaw(observed) { + const raw = observed && observed["__v_raw"]; + return raw ? toRaw(raw) : observed; +} +function markRaw(value) { + if (!hasOwn(value, "__v_skip") && Object.isExtensible(value)) { + def(value, "__v_skip", true); + } + return value; +} +var toReactive = (value) => isObject(value) ? reactive(value) : value; +var toReadonly = (value) => isObject(value) ? readonly(value) : value; +function isRef2(r) { + return r ? r["__v_isRef"] === true : false; +} +function ref(value) { + return createRef(value, false); +} +function shallowRef(value) { + return createRef(value, true); +} +function createRef(rawValue, shallow) { + if (isRef2(rawValue)) { + return rawValue; + } + return new RefImpl(rawValue, shallow); +} +var RefImpl = class { + constructor(value, isShallow2) { + this.dep = new Dep(); + this["__v_isRef"] = true; + this["__v_isShallow"] = false; + this._rawValue = isShallow2 ? value : toRaw(value); + this._value = isShallow2 ? value : toReactive(value); + this["__v_isShallow"] = isShallow2; + } + get value() { + if (true) { + this.dep.track({ + target: this, + type: "get", + key: "value" + }); + } else { + this.dep.track(); + } + return this._value; + } + set value(newValue) { + const oldValue = this._rawValue; + const useDirectValue = this["__v_isShallow"] || isShallow(newValue) || isReadonly(newValue); + newValue = useDirectValue ? newValue : toRaw(newValue); + if (hasChanged(newValue, oldValue)) { + this._rawValue = newValue; + this._value = useDirectValue ? newValue : toReactive(newValue); + if (true) { + this.dep.trigger({ + target: this, + type: "set", + key: "value", + newValue, + oldValue + }); + } else { + this.dep.trigger(); + } + } + } +}; +function triggerRef(ref2) { + if (ref2.dep) { + if (true) { + ref2.dep.trigger({ + target: ref2, + type: "set", + key: "value", + newValue: ref2._value + }); + } else { + ref2.dep.trigger(); + } + } +} +function unref(ref2) { + return isRef2(ref2) ? ref2.value : ref2; +} +function toValue(source) { + return isFunction(source) ? source() : unref(source); +} +var shallowUnwrapHandlers = { + get: (target, key, receiver) => key === "__v_raw" ? target : unref(Reflect.get(target, key, receiver)), + set: (target, key, value, receiver) => { + const oldValue = target[key]; + if (isRef2(oldValue) && !isRef2(value)) { + oldValue.value = value; + return true; + } else { + return Reflect.set(target, key, value, receiver); + } + } +}; +function proxyRefs(objectWithRefs) { + return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers); +} +var CustomRefImpl = class { + constructor(factory) { + this["__v_isRef"] = true; + this._value = void 0; + const dep = this.dep = new Dep(); + const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep)); + this._get = get; + this._set = set; + } + get value() { + return this._value = this._get(); + } + set value(newVal) { + this._set(newVal); + } +}; +function customRef(factory) { + return new CustomRefImpl(factory); +} +function toRefs(object) { + if (!isProxy(object)) { + warn(`toRefs() expects a reactive object but received a plain one.`); + } + const ret = isArray(object) ? new Array(object.length) : {}; + for (const key in object) { + ret[key] = propertyToRef(object, key); + } + return ret; +} +var ObjectRefImpl = class { + constructor(_object, _key, _defaultValue) { + this._object = _object; + this._key = _key; + this._defaultValue = _defaultValue; + this["__v_isRef"] = true; + this._value = void 0; + this._raw = toRaw(_object); + let shallow = true; + let obj = _object; + if (!isArray(_object) || !isIntegerKey(String(_key))) { + do { + shallow = !isProxy(obj) || isShallow(obj); + } while (shallow && (obj = obj["__v_raw"])); + } + this._shallow = shallow; + } + get value() { + let val = this._object[this._key]; + if (this._shallow) { + val = unref(val); + } + return this._value = val === void 0 ? this._defaultValue : val; + } + set value(newVal) { + if (this._shallow && isRef2(this._raw[this._key])) { + const nestedRef = this._object[this._key]; + if (isRef2(nestedRef)) { + nestedRef.value = newVal; + return; + } + } + this._object[this._key] = newVal; + } + get dep() { + return getDepFromReactive(this._raw, this._key); + } +}; +var GetterRefImpl = class { + constructor(_getter) { + this._getter = _getter; + this["__v_isRef"] = true; + this["__v_isReadonly"] = true; + this._value = void 0; + } + get value() { + return this._value = this._getter(); + } +}; +function toRef(source, key, defaultValue) { + if (isRef2(source)) { + return source; + } else if (isFunction(source)) { + return new GetterRefImpl(source); + } else if (isObject(source) && arguments.length > 1) { + return propertyToRef(source, key, defaultValue); + } else { + return ref(source); + } +} +function propertyToRef(source, key, defaultValue) { + return new ObjectRefImpl(source, key, defaultValue); +} +var ComputedRefImpl = class { + constructor(fn, setter, isSSR) { + this.fn = fn; + this.setter = setter; + this._value = void 0; + this.dep = new Dep(this); + this.__v_isRef = true; + this.deps = void 0; + this.depsTail = void 0; + this.flags = 16; + this.globalVersion = globalVersion - 1; + this.next = void 0; + this.effect = this; + this["__v_isReadonly"] = !setter; + this.isSSR = isSSR; + } + /** + * @internal + */ + notify() { + this.flags |= 16; + if (!(this.flags & 8) && // avoid infinite self recursion + activeSub !== this) { + batch(this, true); + return true; + } else if (true) ; + } + get value() { + const link = true ? this.dep.track({ + target: this, + type: "get", + key: "value" + }) : this.dep.track(); + refreshComputed(this); + if (link) { + link.version = this.dep.version; + } + return this._value; + } + set value(newValue) { + if (this.setter) { + this.setter(newValue); + } else if (true) { + warn("Write operation failed: computed value is readonly"); + } + } +}; +function computed(getterOrOptions, debugOptions, isSSR = false) { + let getter; + let setter; + if (isFunction(getterOrOptions)) { + getter = getterOrOptions; + } else { + getter = getterOrOptions.get; + setter = getterOrOptions.set; + } + const cRef = new ComputedRefImpl(getter, setter, isSSR); + if (debugOptions && !isSSR) { + cRef.onTrack = debugOptions.onTrack; + cRef.onTrigger = debugOptions.onTrigger; + } + return cRef; +} +var TrackOpTypes = { + "GET": "get", + "HAS": "has", + "ITERATE": "iterate" +}; +var TriggerOpTypes = { + "SET": "set", + "ADD": "add", + "DELETE": "delete", + "CLEAR": "clear" +}; +var INITIAL_WATCHER_VALUE = {}; +var cleanupMap = /* @__PURE__ */ new WeakMap(); +var activeWatcher = void 0; +function getCurrentWatcher() { + return activeWatcher; +} +function onWatcherCleanup(cleanupFn, failSilently = false, owner = activeWatcher) { + if (owner) { + let cleanups = cleanupMap.get(owner); + if (!cleanups) cleanupMap.set(owner, cleanups = []); + cleanups.push(cleanupFn); + } else if (!failSilently) { + warn( + `onWatcherCleanup() was called when there was no active watcher to associate with.` + ); + } +} +function watch(source, cb, options = EMPTY_OBJ) { + const { immediate, deep, once, scheduler, augmentJob, call } = options; + const warnInvalidSource = (s) => { + (options.onWarn || warn)( + `Invalid watch source: `, + s, + `A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.` + ); + }; + const reactiveGetter = (source2) => { + if (deep) return source2; + if (isShallow(source2) || deep === false || deep === 0) + return traverse(source2, 1); + return traverse(source2); + }; + let effect2; + let getter; + let cleanup; + let boundCleanup; + let forceTrigger = false; + let isMultiSource = false; + if (isRef2(source)) { + getter = () => source.value; + forceTrigger = isShallow(source); + } else if (isReactive(source)) { + getter = () => reactiveGetter(source); + forceTrigger = true; + } else if (isArray(source)) { + isMultiSource = true; + forceTrigger = source.some((s) => isReactive(s) || isShallow(s)); + getter = () => source.map((s) => { + if (isRef2(s)) { + return s.value; + } else if (isReactive(s)) { + return reactiveGetter(s); + } else if (isFunction(s)) { + return call ? call(s, 2) : s(); + } else { + warnInvalidSource(s); + } + }); + } else if (isFunction(source)) { + if (cb) { + getter = call ? () => call(source, 2) : source; + } else { + getter = () => { + if (cleanup) { + pauseTracking(); + try { + cleanup(); + } finally { + resetTracking(); + } + } + const currentEffect = activeWatcher; + activeWatcher = effect2; + try { + return call ? call(source, 3, [boundCleanup]) : source(boundCleanup); + } finally { + activeWatcher = currentEffect; + } + }; + } + } else { + getter = NOOP; + warnInvalidSource(source); + } + if (cb && deep) { + const baseGetter = getter; + const depth = deep === true ? Infinity : deep; + getter = () => traverse(baseGetter(), depth); + } + const scope = getCurrentScope(); + const watchHandle = () => { + effect2.stop(); + if (scope && scope.active) { + remove(scope.effects, effect2); + } + }; + if (once && cb) { + const _cb = cb; + cb = (...args) => { + _cb(...args); + watchHandle(); + }; + } + let oldValue = isMultiSource ? new Array(source.length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE; + const job = (immediateFirstRun) => { + if (!(effect2.flags & 1) || !effect2.dirty && !immediateFirstRun) { + return; + } + if (cb) { + const newValue = effect2.run(); + if (deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => hasChanged(v, oldValue[i])) : hasChanged(newValue, oldValue))) { + if (cleanup) { + cleanup(); + } + const currentWatcher = activeWatcher; + activeWatcher = effect2; + try { + const args = [ + newValue, + // pass undefined as the old value when it's changed for the first time + oldValue === INITIAL_WATCHER_VALUE ? void 0 : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue, + boundCleanup + ]; + oldValue = newValue; + call ? call(cb, 3, args) : ( + // @ts-expect-error + cb(...args) + ); + } finally { + activeWatcher = currentWatcher; + } + } + } else { + effect2.run(); + } + }; + if (augmentJob) { + augmentJob(job); + } + effect2 = new ReactiveEffect(getter); + effect2.scheduler = scheduler ? () => scheduler(job, false) : job; + boundCleanup = (fn) => onWatcherCleanup(fn, false, effect2); + cleanup = effect2.onStop = () => { + const cleanups = cleanupMap.get(effect2); + if (cleanups) { + if (call) { + call(cleanups, 4); + } else { + for (const cleanup2 of cleanups) cleanup2(); + } + cleanupMap.delete(effect2); + } + }; + if (true) { + effect2.onTrack = options.onTrack; + effect2.onTrigger = options.onTrigger; + } + if (cb) { + if (immediate) { + job(true); + } else { + oldValue = effect2.run(); + } + } else if (scheduler) { + scheduler(job.bind(null, true), true); + } else { + effect2.run(); + } + watchHandle.pause = effect2.pause.bind(effect2); + watchHandle.resume = effect2.resume.bind(effect2); + watchHandle.stop = watchHandle; + return watchHandle; +} +function traverse(value, depth = Infinity, seen) { + if (depth <= 0 || !isObject(value) || value["__v_skip"]) { + return value; + } + seen = seen || /* @__PURE__ */ new Map(); + if ((seen.get(value) || 0) >= depth) { + return value; + } + seen.set(value, depth); + depth--; + if (isRef2(value)) { + traverse(value.value, depth, seen); + } else if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + traverse(value[i], depth, seen); + } + } else if (isSet(value) || isMap(value)) { + value.forEach((v) => { + traverse(v, depth, seen); + }); + } else if (isPlainObject(value)) { + for (const key in value) { + traverse(value[key], depth, seen); + } + for (const key of Object.getOwnPropertySymbols(value)) { + if (Object.prototype.propertyIsEnumerable.call(value, key)) { + traverse(value[key], depth, seen); + } + } + } + return value; +} + +// node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js +var stack = []; +function pushWarningContext(vnode) { + stack.push(vnode); +} +function popWarningContext() { + stack.pop(); +} +var isWarning = false; +function warn$1(msg, ...args) { + if (isWarning) return; + isWarning = true; + pauseTracking(); + const instance = stack.length ? stack[stack.length - 1].component : null; + const appWarnHandler = instance && instance.appContext.config.warnHandler; + const trace = getComponentTrace(); + if (appWarnHandler) { + callWithErrorHandling( + appWarnHandler, + instance, + 11, + [ + // eslint-disable-next-line no-restricted-syntax + msg + args.map((a) => { + var _a, _b; + return (_b = (_a = a.toString) == null ? void 0 : _a.call(a)) != null ? _b : JSON.stringify(a); + }).join(""), + instance && instance.proxy, + trace.map( + ({ vnode }) => `at <${formatComponentName(instance, vnode.type)}>` + ).join("\n"), + trace + ] + ); + } else { + const warnArgs = [`[Vue warn]: ${msg}`, ...args]; + if (trace.length && // avoid spamming console during tests + true) { + warnArgs.push(` +`, ...formatTrace(trace)); + } + console.warn(...warnArgs); + } + resetTracking(); + isWarning = false; +} +function getComponentTrace() { + let currentVNode = stack[stack.length - 1]; + if (!currentVNode) { + return []; + } + const normalizedStack = []; + while (currentVNode) { + const last = normalizedStack[0]; + if (last && last.vnode === currentVNode) { + last.recurseCount++; + } else { + normalizedStack.push({ + vnode: currentVNode, + recurseCount: 0 + }); + } + const parentInstance = currentVNode.component && currentVNode.component.parent; + currentVNode = parentInstance && parentInstance.vnode; + } + return normalizedStack; +} +function formatTrace(trace) { + const logs = []; + trace.forEach((entry, i) => { + logs.push(...i === 0 ? [] : [` +`], ...formatTraceEntry(entry)); + }); + return logs; +} +function formatTraceEntry({ vnode, recurseCount }) { + const postfix = recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``; + const isRoot = vnode.component ? vnode.component.parent == null : false; + const open = ` at <${formatComponentName( + vnode.component, + vnode.type, + isRoot + )}`; + const close = `>` + postfix; + return vnode.props ? [open, ...formatProps(vnode.props), close] : [open + close]; +} +function formatProps(props) { + const res = []; + const keys = Object.keys(props); + keys.slice(0, 3).forEach((key) => { + res.push(...formatProp(key, props[key])); + }); + if (keys.length > 3) { + res.push(` ...`); + } + return res; +} +function formatProp(key, value, raw) { + if (isString(value)) { + value = JSON.stringify(value); + return raw ? value : [`${key}=${value}`]; + } else if (typeof value === "number" || typeof value === "boolean" || value == null) { + return raw ? value : [`${key}=${value}`]; + } else if (isRef2(value)) { + value = formatProp(key, toRaw(value.value), true); + return raw ? value : [`${key}=Ref<`, value, `>`]; + } else if (isFunction(value)) { + return [`${key}=fn${value.name ? `<${value.name}>` : ``}`]; + } else { + value = toRaw(value); + return raw ? value : [`${key}=`, value]; + } +} +function assertNumber(val, type) { + if (false) return; + if (val === void 0) { + return; + } else if (typeof val !== "number") { + warn$1(`${type} is not a valid number - got ${JSON.stringify(val)}.`); + } else if (isNaN(val)) { + warn$1(`${type} is NaN - the duration expression might be incorrect.`); + } +} +var ErrorCodes = { + "SETUP_FUNCTION": 0, + "0": "SETUP_FUNCTION", + "RENDER_FUNCTION": 1, + "1": "RENDER_FUNCTION", + "NATIVE_EVENT_HANDLER": 5, + "5": "NATIVE_EVENT_HANDLER", + "COMPONENT_EVENT_HANDLER": 6, + "6": "COMPONENT_EVENT_HANDLER", + "VNODE_HOOK": 7, + "7": "VNODE_HOOK", + "DIRECTIVE_HOOK": 8, + "8": "DIRECTIVE_HOOK", + "TRANSITION_HOOK": 9, + "9": "TRANSITION_HOOK", + "APP_ERROR_HANDLER": 10, + "10": "APP_ERROR_HANDLER", + "APP_WARN_HANDLER": 11, + "11": "APP_WARN_HANDLER", + "FUNCTION_REF": 12, + "12": "FUNCTION_REF", + "ASYNC_COMPONENT_LOADER": 13, + "13": "ASYNC_COMPONENT_LOADER", + "SCHEDULER": 14, + "14": "SCHEDULER", + "COMPONENT_UPDATE": 15, + "15": "COMPONENT_UPDATE", + "APP_UNMOUNT_CLEANUP": 16, + "16": "APP_UNMOUNT_CLEANUP" +}; +var ErrorTypeStrings$1 = { + ["sp"]: "serverPrefetch hook", + ["bc"]: "beforeCreate hook", + ["c"]: "created hook", + ["bm"]: "beforeMount hook", + ["m"]: "mounted hook", + ["bu"]: "beforeUpdate hook", + ["u"]: "updated", + ["bum"]: "beforeUnmount hook", + ["um"]: "unmounted hook", + ["a"]: "activated hook", + ["da"]: "deactivated hook", + ["ec"]: "errorCaptured hook", + ["rtc"]: "renderTracked hook", + ["rtg"]: "renderTriggered hook", + [0]: "setup function", + [1]: "render function", + [2]: "watcher getter", + [3]: "watcher callback", + [4]: "watcher cleanup function", + [5]: "native event handler", + [6]: "component event handler", + [7]: "vnode hook", + [8]: "directive hook", + [9]: "transition hook", + [10]: "app errorHandler", + [11]: "app warnHandler", + [12]: "ref function", + [13]: "async component loader", + [14]: "scheduler flush", + [15]: "component update", + [16]: "app unmount cleanup function" +}; +function callWithErrorHandling(fn, instance, type, args) { + try { + return args ? fn(...args) : fn(); + } catch (err) { + handleError(err, instance, type); + } +} +function callWithAsyncErrorHandling(fn, instance, type, args) { + if (isFunction(fn)) { + const res = callWithErrorHandling(fn, instance, type, args); + if (res && isPromise(res)) { + res.catch((err) => { + handleError(err, instance, type); + }); + } + return res; + } + if (isArray(fn)) { + const values = []; + for (let i = 0; i < fn.length; i++) { + values.push(callWithAsyncErrorHandling(fn[i], instance, type, args)); + } + return values; + } else if (true) { + warn$1( + `Invalid value type passed to callWithAsyncErrorHandling(): ${typeof fn}` + ); + } +} +function handleError(err, instance, type, throwInDev = true) { + const contextVNode = instance ? instance.vnode : null; + const { errorHandler, throwUnhandledErrorInProduction } = instance && instance.appContext.config || EMPTY_OBJ; + if (instance) { + let cur = instance.parent; + const exposedInstance = instance.proxy; + const errorInfo = true ? ErrorTypeStrings$1[type] : `https://vuejs.org/error-reference/#runtime-${type}`; + while (cur) { + const errorCapturedHooks = cur.ec; + if (errorCapturedHooks) { + for (let i = 0; i < errorCapturedHooks.length; i++) { + if (errorCapturedHooks[i](err, exposedInstance, errorInfo) === false) { + return; + } + } + } + cur = cur.parent; + } + if (errorHandler) { + pauseTracking(); + callWithErrorHandling(errorHandler, null, 10, [ + err, + exposedInstance, + errorInfo + ]); + resetTracking(); + return; + } + } + logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction); +} +function logError(err, type, contextVNode, throwInDev = true, throwInProd = false) { + if (true) { + const info = ErrorTypeStrings$1[type]; + if (contextVNode) { + pushWarningContext(contextVNode); + } + warn$1(`Unhandled error${info ? ` during execution of ${info}` : ``}`); + if (contextVNode) { + popWarningContext(); + } + if (throwInDev) { + throw err; + } else { + console.error(err); + } + } else if (throwInProd) { + throw err; + } else { + console.error(err); + } +} +var queue = []; +var flushIndex = -1; +var pendingPostFlushCbs = []; +var activePostFlushCbs = null; +var postFlushIndex = 0; +var resolvedPromise = Promise.resolve(); +var currentFlushPromise = null; +var RECURSION_LIMIT = 100; +function nextTick(fn) { + const p2 = currentFlushPromise || resolvedPromise; + return fn ? p2.then(this ? fn.bind(this) : fn) : p2; +} +function findInsertionIndex(id) { + let start = flushIndex + 1; + let end = queue.length; + while (start < end) { + const middle = start + end >>> 1; + const middleJob = queue[middle]; + const middleJobId = getId(middleJob); + if (middleJobId < id || middleJobId === id && middleJob.flags & 2) { + start = middle + 1; + } else { + end = middle; + } + } + return start; +} +function queueJob(job) { + if (!(job.flags & 1)) { + const jobId = getId(job); + const lastJob = queue[queue.length - 1]; + if (!lastJob || // fast path when the job id is larger than the tail + !(job.flags & 2) && jobId >= getId(lastJob)) { + queue.push(job); + } else { + queue.splice(findInsertionIndex(jobId), 0, job); + } + job.flags |= 1; + queueFlush(); + } +} +function queueFlush() { + if (!currentFlushPromise) { + currentFlushPromise = resolvedPromise.then(flushJobs); + } +} +function queuePostFlushCb(cb) { + if (!isArray(cb)) { + if (activePostFlushCbs && cb.id === -1) { + activePostFlushCbs.splice(postFlushIndex + 1, 0, cb); + } else if (!(cb.flags & 1)) { + pendingPostFlushCbs.push(cb); + cb.flags |= 1; + } + } else { + pendingPostFlushCbs.push(...cb); + } + queueFlush(); +} +function flushPreFlushCbs(instance, seen, i = flushIndex + 1) { + if (true) { + seen = seen || /* @__PURE__ */ new Map(); + } + for (; i < queue.length; i++) { + const cb = queue[i]; + if (cb && cb.flags & 2) { + if (instance && cb.id !== instance.uid) { + continue; + } + if (checkRecursiveUpdates(seen, cb)) { + continue; + } + queue.splice(i, 1); + i--; + if (cb.flags & 4) { + cb.flags &= -2; + } + cb(); + if (!(cb.flags & 4)) { + cb.flags &= -2; + } + } + } +} +function flushPostFlushCbs(seen) { + if (pendingPostFlushCbs.length) { + const deduped = [...new Set(pendingPostFlushCbs)].sort( + (a, b) => getId(a) - getId(b) + ); + pendingPostFlushCbs.length = 0; + if (activePostFlushCbs) { + activePostFlushCbs.push(...deduped); + return; + } + activePostFlushCbs = deduped; + if (true) { + seen = seen || /* @__PURE__ */ new Map(); + } + for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) { + const cb = activePostFlushCbs[postFlushIndex]; + if (checkRecursiveUpdates(seen, cb)) { + continue; + } + if (cb.flags & 4) { + cb.flags &= -2; + } + if (!(cb.flags & 8)) cb(); + cb.flags &= -2; + } + activePostFlushCbs = null; + postFlushIndex = 0; + } +} +var getId = (job) => job.id == null ? job.flags & 2 ? -1 : Infinity : job.id; +function flushJobs(seen) { + if (true) { + seen = seen || /* @__PURE__ */ new Map(); + } + const check = true ? (job) => checkRecursiveUpdates(seen, job) : NOOP; + try { + for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { + const job = queue[flushIndex]; + if (job && !(job.flags & 8)) { + if (check(job)) { + continue; + } + if (job.flags & 4) { + job.flags &= ~1; + } + callWithErrorHandling( + job, + job.i, + job.i ? 15 : 14 + ); + if (!(job.flags & 4)) { + job.flags &= ~1; + } + } + } + } finally { + for (; flushIndex < queue.length; flushIndex++) { + const job = queue[flushIndex]; + if (job) { + job.flags &= -2; + } + } + flushIndex = -1; + queue.length = 0; + flushPostFlushCbs(seen); + currentFlushPromise = null; + if (queue.length || pendingPostFlushCbs.length) { + flushJobs(seen); + } + } +} +function checkRecursiveUpdates(seen, fn) { + const count = seen.get(fn) || 0; + if (count > RECURSION_LIMIT) { + const instance = fn.i; + const componentName = instance && getComponentName(instance.type); + handleError( + `Maximum recursive updates exceeded${componentName ? ` in component <${componentName}>` : ``}. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.`, + null, + 10 + ); + return true; + } + seen.set(fn, count + 1); + return false; +} +var isHmrUpdating = false; +var hmrDirtyComponents = /* @__PURE__ */ new Map(); +if (true) { + getGlobalThis().__VUE_HMR_RUNTIME__ = { + createRecord: tryWrap(createRecord), + rerender: tryWrap(rerender), + reload: tryWrap(reload) + }; +} +var map = /* @__PURE__ */ new Map(); +function registerHMR(instance) { + const id = instance.type.__hmrId; + let record = map.get(id); + if (!record) { + createRecord(id, instance.type); + record = map.get(id); + } + record.instances.add(instance); +} +function unregisterHMR(instance) { + map.get(instance.type.__hmrId).instances.delete(instance); +} +function createRecord(id, initialDef) { + if (map.has(id)) { + return false; + } + map.set(id, { + initialDef: normalizeClassComponent(initialDef), + instances: /* @__PURE__ */ new Set() + }); + return true; +} +function normalizeClassComponent(component) { + return isClassComponent(component) ? component.__vccOpts : component; +} +function rerender(id, newRender) { + const record = map.get(id); + if (!record) { + return; + } + record.initialDef.render = newRender; + [...record.instances].forEach((instance) => { + if (newRender) { + instance.render = newRender; + normalizeClassComponent(instance.type).render = newRender; + } + instance.renderCache = []; + isHmrUpdating = true; + if (!(instance.job.flags & 8)) { + instance.update(); + } + isHmrUpdating = false; + }); +} +function reload(id, newComp) { + const record = map.get(id); + if (!record) return; + newComp = normalizeClassComponent(newComp); + updateComponentDef(record.initialDef, newComp); + const instances = [...record.instances]; + for (let i = 0; i < instances.length; i++) { + const instance = instances[i]; + const oldComp = normalizeClassComponent(instance.type); + let dirtyInstances = hmrDirtyComponents.get(oldComp); + if (!dirtyInstances) { + if (oldComp !== record.initialDef) { + updateComponentDef(oldComp, newComp); + } + hmrDirtyComponents.set(oldComp, dirtyInstances = /* @__PURE__ */ new Set()); + } + dirtyInstances.add(instance); + instance.appContext.propsCache.delete(instance.type); + instance.appContext.emitsCache.delete(instance.type); + instance.appContext.optionsCache.delete(instance.type); + if (instance.ceReload) { + dirtyInstances.add(instance); + instance.ceReload(newComp.styles); + dirtyInstances.delete(instance); + } else if (instance.parent) { + queueJob(() => { + if (!(instance.job.flags & 8)) { + isHmrUpdating = true; + instance.parent.update(); + isHmrUpdating = false; + dirtyInstances.delete(instance); + } + }); + } else if (instance.appContext.reload) { + instance.appContext.reload(); + } else if (typeof window !== "undefined") { + window.location.reload(); + } else { + console.warn( + "[HMR] Root or manually mounted instance modified. Full reload required." + ); + } + if (instance.root.ce && instance !== instance.root) { + instance.root.ce._removeChildStyle(oldComp); + } + } + queuePostFlushCb(() => { + hmrDirtyComponents.clear(); + }); +} +function updateComponentDef(oldComp, newComp) { + extend(oldComp, newComp); + for (const key in oldComp) { + if (key !== "__file" && !(key in newComp)) { + delete oldComp[key]; + } + } +} +function tryWrap(fn) { + return (id, arg) => { + try { + return fn(id, arg); + } catch (e) { + console.error(e); + console.warn( + `[HMR] Something went wrong during Vue component hot-reload. Full reload required.` + ); + } + }; +} +var devtools$1; +var buffer = []; +var devtoolsNotInstalled = false; +function emit$1(event, ...args) { + if (devtools$1) { + devtools$1.emit(event, ...args); + } else if (!devtoolsNotInstalled) { + buffer.push({ event, args }); + } +} +function setDevtoolsHook$1(hook, target) { + var _a, _b; + devtools$1 = hook; + if (devtools$1) { + devtools$1.enabled = true; + buffer.forEach(({ event, args }) => devtools$1.emit(event, ...args)); + buffer = []; + } else if ( + // handle late devtools injection - only do this if we are in an actual + // browser environment to avoid the timer handle stalling test runner exit + // (#4815) + typeof window !== "undefined" && // some envs mock window but not fully + window.HTMLElement && // also exclude jsdom + // eslint-disable-next-line no-restricted-syntax + !((_b = (_a = window.navigator) == null ? void 0 : _a.userAgent) == null ? void 0 : _b.includes("jsdom")) + ) { + const replay = target.__VUE_DEVTOOLS_HOOK_REPLAY__ = target.__VUE_DEVTOOLS_HOOK_REPLAY__ || []; + replay.push((newHook) => { + setDevtoolsHook$1(newHook, target); + }); + setTimeout(() => { + if (!devtools$1) { + target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null; + devtoolsNotInstalled = true; + buffer = []; + } + }, 3e3); + } else { + devtoolsNotInstalled = true; + buffer = []; + } +} +function devtoolsInitApp(app, version2) { + emit$1("app:init", app, version2, { + Fragment, + Text, + Comment, + Static + }); +} +function devtoolsUnmountApp(app) { + emit$1("app:unmount", app); +} +var devtoolsComponentAdded = createDevtoolsComponentHook( + "component:added" + /* COMPONENT_ADDED */ +); +var devtoolsComponentUpdated = createDevtoolsComponentHook( + "component:updated" + /* COMPONENT_UPDATED */ +); +var _devtoolsComponentRemoved = createDevtoolsComponentHook( + "component:removed" + /* COMPONENT_REMOVED */ +); +var devtoolsComponentRemoved = (component) => { + if (devtools$1 && typeof devtools$1.cleanupBuffer === "function" && // remove the component if it wasn't buffered + !devtools$1.cleanupBuffer(component)) { + _devtoolsComponentRemoved(component); + } +}; +function createDevtoolsComponentHook(hook) { + return (component) => { + emit$1( + hook, + component.appContext.app, + component.uid, + component.parent ? component.parent.uid : void 0, + component + ); + }; +} +var devtoolsPerfStart = createDevtoolsPerformanceHook( + "perf:start" + /* PERFORMANCE_START */ +); +var devtoolsPerfEnd = createDevtoolsPerformanceHook( + "perf:end" + /* PERFORMANCE_END */ +); +function createDevtoolsPerformanceHook(hook) { + return (component, type, time) => { + emit$1(hook, component.appContext.app, component.uid, component, type, time); + }; +} +function devtoolsComponentEmit(component, event, params) { + emit$1( + "component:emit", + component.appContext.app, + component, + event, + params + ); +} +var currentRenderingInstance = null; +var currentScopeId = null; +function setCurrentRenderingInstance(instance) { + const prev = currentRenderingInstance; + currentRenderingInstance = instance; + currentScopeId = instance && instance.type.__scopeId || null; + return prev; +} +function pushScopeId(id) { + currentScopeId = id; +} +function popScopeId() { + currentScopeId = null; +} +var withScopeId = (_id) => withCtx; +function withCtx(fn, ctx = currentRenderingInstance, isNonScopedSlot) { + if (!ctx) return fn; + if (fn._n) { + return fn; + } + const renderFnWithContext = (...args) => { + if (renderFnWithContext._d) { + setBlockTracking(-1); + } + const prevInstance = setCurrentRenderingInstance(ctx); + let res; + try { + res = fn(...args); + } finally { + setCurrentRenderingInstance(prevInstance); + if (renderFnWithContext._d) { + setBlockTracking(1); + } + } + if (true) { + devtoolsComponentUpdated(ctx); + } + return res; + }; + renderFnWithContext._n = true; + renderFnWithContext._c = true; + renderFnWithContext._d = true; + return renderFnWithContext; +} +function validateDirectiveName(name) { + if (isBuiltInDirective(name)) { + warn$1("Do not use built-in directive ids as custom directive id: " + name); + } +} +function withDirectives(vnode, directives) { + if (currentRenderingInstance === null) { + warn$1(`withDirectives can only be used inside render functions.`); + return vnode; + } + const instance = getComponentPublicInstance(currentRenderingInstance); + const bindings = vnode.dirs || (vnode.dirs = []); + for (let i = 0; i < directives.length; i++) { + let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]; + if (dir) { + if (isFunction(dir)) { + dir = { + mounted: dir, + updated: dir + }; + } + if (dir.deep) { + traverse(value); + } + bindings.push({ + dir, + instance, + value, + oldValue: void 0, + arg, + modifiers + }); + } + } + return vnode; +} +function invokeDirectiveHook(vnode, prevVNode, instance, name) { + const bindings = vnode.dirs; + const oldBindings = prevVNode && prevVNode.dirs; + for (let i = 0; i < bindings.length; i++) { + const binding = bindings[i]; + if (oldBindings) { + binding.oldValue = oldBindings[i].value; + } + let hook = binding.dir[name]; + if (hook) { + pauseTracking(); + callWithAsyncErrorHandling(hook, instance, 8, [ + vnode.el, + binding, + vnode, + prevVNode + ]); + resetTracking(); + } + } +} +function provide(key, value) { + if (true) { + if (!currentInstance || currentInstance.isMounted) { + warn$1(`provide() can only be used inside setup().`); + } + } + if (currentInstance) { + let provides = currentInstance.provides; + const parentProvides = currentInstance.parent && currentInstance.parent.provides; + if (parentProvides === provides) { + provides = currentInstance.provides = Object.create(parentProvides); + } + provides[key] = value; + } +} +function inject(key, defaultValue, treatDefaultAsFactory = false) { + const instance = getCurrentInstance(); + if (instance || currentApp) { + let provides = currentApp ? currentApp._context.provides : instance ? instance.parent == null || instance.ce ? instance.vnode.appContext && instance.vnode.appContext.provides : instance.parent.provides : void 0; + if (provides && key in provides) { + return provides[key]; + } else if (arguments.length > 1) { + return treatDefaultAsFactory && isFunction(defaultValue) ? defaultValue.call(instance && instance.proxy) : defaultValue; + } else if (true) { + warn$1(`injection "${String(key)}" not found.`); + } + } else if (true) { + warn$1(`inject() can only be used inside setup() or functional components.`); + } +} +function hasInjectionContext() { + return !!(getCurrentInstance() || currentApp); +} +var ssrContextKey = Symbol.for("v-scx"); +var useSSRContext = () => { + { + const ctx = inject(ssrContextKey); + if (!ctx) { + warn$1( + `Server rendering context not provided. Make sure to only call useSSRContext() conditionally in the server build.` + ); + } + return ctx; + } +}; +function watchEffect(effect2, options) { + return doWatch(effect2, null, options); +} +function watchPostEffect(effect2, options) { + return doWatch( + effect2, + null, + true ? extend({}, options, { flush: "post" }) : { flush: "post" } + ); +} +function watchSyncEffect(effect2, options) { + return doWatch( + effect2, + null, + true ? extend({}, options, { flush: "sync" }) : { flush: "sync" } + ); +} +function watch2(source, cb, options) { + if (!isFunction(cb)) { + warn$1( + `\`watch(fn, options?)\` signature has been moved to a separate API. Use \`watchEffect(fn, options?)\` instead. \`watch\` now only supports \`watch(source, cb, options?) signature.` + ); + } + return doWatch(source, cb, options); +} +function doWatch(source, cb, options = EMPTY_OBJ) { + const { immediate, deep, flush, once } = options; + if (!cb) { + if (immediate !== void 0) { + warn$1( + `watch() "immediate" option is only respected when using the watch(source, callback, options?) signature.` + ); + } + if (deep !== void 0) { + warn$1( + `watch() "deep" option is only respected when using the watch(source, callback, options?) signature.` + ); + } + if (once !== void 0) { + warn$1( + `watch() "once" option is only respected when using the watch(source, callback, options?) signature.` + ); + } + } + const baseWatchOptions = extend({}, options); + if (true) baseWatchOptions.onWarn = warn$1; + const runsImmediately = cb && immediate || !cb && flush !== "post"; + let ssrCleanup; + if (isInSSRComponentSetup) { + if (flush === "sync") { + const ctx = useSSRContext(); + ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = []); + } else if (!runsImmediately) { + const watchStopHandle = () => { + }; + watchStopHandle.stop = NOOP; + watchStopHandle.resume = NOOP; + watchStopHandle.pause = NOOP; + return watchStopHandle; + } + } + const instance = currentInstance; + baseWatchOptions.call = (fn, type, args) => callWithAsyncErrorHandling(fn, instance, type, args); + let isPre = false; + if (flush === "post") { + baseWatchOptions.scheduler = (job) => { + queuePostRenderEffect(job, instance && instance.suspense); + }; + } else if (flush !== "sync") { + isPre = true; + baseWatchOptions.scheduler = (job, isFirstRun) => { + if (isFirstRun) { + job(); + } else { + queueJob(job); + } + }; + } + baseWatchOptions.augmentJob = (job) => { + if (cb) { + job.flags |= 4; + } + if (isPre) { + job.flags |= 2; + if (instance) { + job.id = instance.uid; + job.i = instance; + } + } + }; + const watchHandle = watch(source, cb, baseWatchOptions); + if (isInSSRComponentSetup) { + if (ssrCleanup) { + ssrCleanup.push(watchHandle); + } else if (runsImmediately) { + watchHandle(); + } + } + return watchHandle; +} +function instanceWatch(source, value, options) { + const publicThis = this.proxy; + const getter = isString(source) ? source.includes(".") ? createPathGetter(publicThis, source) : () => publicThis[source] : source.bind(publicThis, publicThis); + let cb; + if (isFunction(value)) { + cb = value; + } else { + cb = value.handler; + options = value; + } + const reset = setCurrentInstance(this); + const res = doWatch(getter, cb.bind(publicThis), options); + reset(); + return res; +} +function createPathGetter(ctx, path) { + const segments = path.split("."); + return () => { + let cur = ctx; + for (let i = 0; i < segments.length && cur; i++) { + cur = cur[segments[i]]; + } + return cur; + }; +} +var TeleportEndKey = Symbol("_vte"); +var isTeleport = (type) => type.__isTeleport; +var isTeleportDisabled = (props) => props && (props.disabled || props.disabled === ""); +var isTeleportDeferred = (props) => props && (props.defer || props.defer === ""); +var isTargetSVG = (target) => typeof SVGElement !== "undefined" && target instanceof SVGElement; +var isTargetMathML = (target) => typeof MathMLElement === "function" && target instanceof MathMLElement; +var resolveTarget = (props, select) => { + const targetSelector = props && props.to; + if (isString(targetSelector)) { + if (!select) { + warn$1( + `Current renderer does not support string target for Teleports. (missing querySelector renderer option)` + ); + return null; + } else { + const target = select(targetSelector); + if (!target && !isTeleportDisabled(props)) { + warn$1( + `Failed to locate Teleport target with selector "${targetSelector}". Note the target element must exist before the component is mounted - i.e. the target cannot be rendered by the component itself, and ideally should be outside of the entire Vue component tree.` + ); + } + return target; + } + } else { + if (!targetSelector && !isTeleportDisabled(props)) { + warn$1(`Invalid Teleport target: ${targetSelector}`); + } + return targetSelector; + } +}; +var TeleportImpl = { + name: "Teleport", + __isTeleport: true, + process(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, internals) { + const { + mc: mountChildren, + pc: patchChildren, + pbc: patchBlockChildren, + o: { insert, querySelector, createText, createComment } + } = internals; + const disabled = isTeleportDisabled(n2.props); + let { shapeFlag, children, dynamicChildren } = n2; + if (isHmrUpdating) { + optimized = false; + dynamicChildren = null; + } + if (n1 == null) { + const placeholder = n2.el = true ? createComment("teleport start") : createText(""); + const mainAnchor = n2.anchor = true ? createComment("teleport end") : createText(""); + insert(placeholder, container, anchor); + insert(mainAnchor, container, anchor); + const mount = (container2, anchor2) => { + if (shapeFlag & 16) { + mountChildren( + children, + container2, + anchor2, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized + ); + } + }; + const mountToTarget = () => { + const target = n2.target = resolveTarget(n2.props, querySelector); + const targetAnchor = prepareAnchor(target, n2, createText, insert); + if (target) { + if (namespace !== "svg" && isTargetSVG(target)) { + namespace = "svg"; + } else if (namespace !== "mathml" && isTargetMathML(target)) { + namespace = "mathml"; + } + if (parentComponent && parentComponent.isCE) { + (parentComponent.ce._teleportTargets || (parentComponent.ce._teleportTargets = /* @__PURE__ */ new Set())).add(target); + } + if (!disabled) { + mount(target, targetAnchor); + updateCssVars(n2, false); + } + } else if (!disabled) { + warn$1( + "Invalid Teleport target on mount:", + target, + `(${typeof target})` + ); + } + }; + if (disabled) { + mount(container, mainAnchor); + updateCssVars(n2, true); + } + if (isTeleportDeferred(n2.props)) { + n2.el.__isMounted = false; + queuePostRenderEffect(() => { + mountToTarget(); + delete n2.el.__isMounted; + }, parentSuspense); + } else { + mountToTarget(); + } + } else { + if (isTeleportDeferred(n2.props) && n1.el.__isMounted === false) { + queuePostRenderEffect(() => { + TeleportImpl.process( + n1, + n2, + container, + anchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + internals + ); + }, parentSuspense); + return; + } + n2.el = n1.el; + n2.targetStart = n1.targetStart; + const mainAnchor = n2.anchor = n1.anchor; + const target = n2.target = n1.target; + const targetAnchor = n2.targetAnchor = n1.targetAnchor; + const wasDisabled = isTeleportDisabled(n1.props); + const currentContainer = wasDisabled ? container : target; + const currentAnchor = wasDisabled ? mainAnchor : targetAnchor; + if (namespace === "svg" || isTargetSVG(target)) { + namespace = "svg"; + } else if (namespace === "mathml" || isTargetMathML(target)) { + namespace = "mathml"; + } + if (dynamicChildren) { + patchBlockChildren( + n1.dynamicChildren, + dynamicChildren, + currentContainer, + parentComponent, + parentSuspense, + namespace, + slotScopeIds + ); + traverseStaticChildren(n1, n2, false); + } else if (!optimized) { + patchChildren( + n1, + n2, + currentContainer, + currentAnchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + false + ); + } + if (disabled) { + if (!wasDisabled) { + moveTeleport( + n2, + container, + mainAnchor, + internals, + 1 + ); + } else { + if (n2.props && n1.props && n2.props.to !== n1.props.to) { + n2.props.to = n1.props.to; + } + } + } else { + if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) { + const nextTarget = n2.target = resolveTarget( + n2.props, + querySelector + ); + if (nextTarget) { + moveTeleport( + n2, + nextTarget, + null, + internals, + 0 + ); + } else if (true) { + warn$1( + "Invalid Teleport target on update:", + target, + `(${typeof target})` + ); + } + } else if (wasDisabled) { + moveTeleport( + n2, + target, + targetAnchor, + internals, + 1 + ); + } + } + updateCssVars(n2, disabled); + } + }, + remove(vnode, parentComponent, parentSuspense, { um: unmount, o: { remove: hostRemove } }, doRemove) { + const { + shapeFlag, + children, + anchor, + targetStart, + targetAnchor, + target, + props + } = vnode; + if (target) { + hostRemove(targetStart); + hostRemove(targetAnchor); + } + doRemove && hostRemove(anchor); + if (shapeFlag & 16) { + const shouldRemove = doRemove || !isTeleportDisabled(props); + for (let i = 0; i < children.length; i++) { + const child = children[i]; + unmount( + child, + parentComponent, + parentSuspense, + shouldRemove, + !!child.dynamicChildren + ); + } + } + }, + move: moveTeleport, + hydrate: hydrateTeleport +}; +function moveTeleport(vnode, container, parentAnchor, { o: { insert }, m: move }, moveType = 2) { + if (moveType === 0) { + insert(vnode.targetAnchor, container, parentAnchor); + } + const { el, anchor, shapeFlag, children, props } = vnode; + const isReorder = moveType === 2; + if (isReorder) { + insert(el, container, parentAnchor); + } + if (!isReorder || isTeleportDisabled(props)) { + if (shapeFlag & 16) { + for (let i = 0; i < children.length; i++) { + move( + children[i], + container, + parentAnchor, + 2 + ); + } + } + } + if (isReorder) { + insert(anchor, container, parentAnchor); + } +} +function hydrateTeleport(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized, { + o: { nextSibling, parentNode, querySelector, insert, createText } +}, hydrateChildren) { + function hydrateDisabledTeleport(node2, vnode2, targetStart, targetAnchor) { + vnode2.anchor = hydrateChildren( + nextSibling(node2), + vnode2, + parentNode(node2), + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + vnode2.targetStart = targetStart; + vnode2.targetAnchor = targetAnchor; + } + const target = vnode.target = resolveTarget( + vnode.props, + querySelector + ); + const disabled = isTeleportDisabled(vnode.props); + if (target) { + const targetNode = target._lpa || target.firstChild; + if (vnode.shapeFlag & 16) { + if (disabled) { + hydrateDisabledTeleport( + node, + vnode, + targetNode, + targetNode && nextSibling(targetNode) + ); + } else { + vnode.anchor = nextSibling(node); + let targetAnchor = targetNode; + while (targetAnchor) { + if (targetAnchor && targetAnchor.nodeType === 8) { + if (targetAnchor.data === "teleport start anchor") { + vnode.targetStart = targetAnchor; + } else if (targetAnchor.data === "teleport anchor") { + vnode.targetAnchor = targetAnchor; + target._lpa = vnode.targetAnchor && nextSibling(vnode.targetAnchor); + break; + } + } + targetAnchor = nextSibling(targetAnchor); + } + if (!vnode.targetAnchor) { + prepareAnchor(target, vnode, createText, insert); + } + hydrateChildren( + targetNode && nextSibling(targetNode), + vnode, + target, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } + } + updateCssVars(vnode, disabled); + } else if (disabled) { + if (vnode.shapeFlag & 16) { + hydrateDisabledTeleport(node, vnode, node, nextSibling(node)); + } + } + return vnode.anchor && nextSibling(vnode.anchor); +} +var Teleport = TeleportImpl; +function updateCssVars(vnode, isDisabled) { + const ctx = vnode.ctx; + if (ctx && ctx.ut) { + let node, anchor; + if (isDisabled) { + node = vnode.el; + anchor = vnode.anchor; + } else { + node = vnode.targetStart; + anchor = vnode.targetAnchor; + } + while (node && node !== anchor) { + if (node.nodeType === 1) node.setAttribute("data-v-owner", ctx.uid); + node = node.nextSibling; + } + ctx.ut(); + } +} +function prepareAnchor(target, vnode, createText, insert) { + const targetStart = vnode.targetStart = createText(""); + const targetAnchor = vnode.targetAnchor = createText(""); + targetStart[TeleportEndKey] = targetAnchor; + if (target) { + insert(targetStart, target); + insert(targetAnchor, target); + } + return targetAnchor; +} +var leaveCbKey = Symbol("_leaveCb"); +var enterCbKey = Symbol("_enterCb"); +function useTransitionState() { + const state = { + isMounted: false, + isLeaving: false, + isUnmounting: false, + leavingVNodes: /* @__PURE__ */ new Map() + }; + onMounted(() => { + state.isMounted = true; + }); + onBeforeUnmount(() => { + state.isUnmounting = true; + }); + return state; +} +var TransitionHookValidator = [Function, Array]; +var BaseTransitionPropsValidators = { + mode: String, + appear: Boolean, + persisted: Boolean, + // enter + onBeforeEnter: TransitionHookValidator, + onEnter: TransitionHookValidator, + onAfterEnter: TransitionHookValidator, + onEnterCancelled: TransitionHookValidator, + // leave + onBeforeLeave: TransitionHookValidator, + onLeave: TransitionHookValidator, + onAfterLeave: TransitionHookValidator, + onLeaveCancelled: TransitionHookValidator, + // appear + onBeforeAppear: TransitionHookValidator, + onAppear: TransitionHookValidator, + onAfterAppear: TransitionHookValidator, + onAppearCancelled: TransitionHookValidator +}; +var recursiveGetSubtree = (instance) => { + const subTree = instance.subTree; + return subTree.component ? recursiveGetSubtree(subTree.component) : subTree; +}; +var BaseTransitionImpl = { + name: `BaseTransition`, + props: BaseTransitionPropsValidators, + setup(props, { slots }) { + const instance = getCurrentInstance(); + const state = useTransitionState(); + return () => { + const children = slots.default && getTransitionRawChildren(slots.default(), true); + if (!children || !children.length) { + return; + } + const child = findNonCommentChild(children); + const rawProps = toRaw(props); + const { mode } = rawProps; + if (mode && mode !== "in-out" && mode !== "out-in" && mode !== "default") { + warn$1(`invalid mode: ${mode}`); + } + if (state.isLeaving) { + return emptyPlaceholder(child); + } + const innerChild = getInnerChild$1(child); + if (!innerChild) { + return emptyPlaceholder(child); + } + let enterHooks = resolveTransitionHooks( + innerChild, + rawProps, + state, + instance, + // #11061, ensure enterHooks is fresh after clone + (hooks) => enterHooks = hooks + ); + if (innerChild.type !== Comment) { + setTransitionHooks(innerChild, enterHooks); + } + let oldInnerChild = instance.subTree && getInnerChild$1(instance.subTree); + if (oldInnerChild && oldInnerChild.type !== Comment && !isSameVNodeType(oldInnerChild, innerChild) && recursiveGetSubtree(instance).type !== Comment) { + let leavingHooks = resolveTransitionHooks( + oldInnerChild, + rawProps, + state, + instance + ); + setTransitionHooks(oldInnerChild, leavingHooks); + if (mode === "out-in" && innerChild.type !== Comment) { + state.isLeaving = true; + leavingHooks.afterLeave = () => { + state.isLeaving = false; + if (!(instance.job.flags & 8)) { + instance.update(); + } + delete leavingHooks.afterLeave; + oldInnerChild = void 0; + }; + return emptyPlaceholder(child); + } else if (mode === "in-out" && innerChild.type !== Comment) { + leavingHooks.delayLeave = (el, earlyRemove, delayedLeave) => { + const leavingVNodesCache = getLeavingNodesForType( + state, + oldInnerChild + ); + leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild; + el[leaveCbKey] = () => { + earlyRemove(); + el[leaveCbKey] = void 0; + delete enterHooks.delayedLeave; + oldInnerChild = void 0; + }; + enterHooks.delayedLeave = () => { + delayedLeave(); + delete enterHooks.delayedLeave; + oldInnerChild = void 0; + }; + }; + } else { + oldInnerChild = void 0; + } + } else if (oldInnerChild) { + oldInnerChild = void 0; + } + return child; + }; + } +}; +function findNonCommentChild(children) { + let child = children[0]; + if (children.length > 1) { + let hasFound = false; + for (const c of children) { + if (c.type !== Comment) { + if (hasFound) { + warn$1( + " can only be used on a single element or component. Use for lists." + ); + break; + } + child = c; + hasFound = true; + if (false) break; + } + } + } + return child; +} +var BaseTransition = BaseTransitionImpl; +function getLeavingNodesForType(state, vnode) { + const { leavingVNodes } = state; + let leavingVNodesCache = leavingVNodes.get(vnode.type); + if (!leavingVNodesCache) { + leavingVNodesCache = /* @__PURE__ */ Object.create(null); + leavingVNodes.set(vnode.type, leavingVNodesCache); + } + return leavingVNodesCache; +} +function resolveTransitionHooks(vnode, props, state, instance, postClone) { + const { + appear, + mode, + persisted = false, + onBeforeEnter, + onEnter, + onAfterEnter, + onEnterCancelled, + onBeforeLeave, + onLeave, + onAfterLeave, + onLeaveCancelled, + onBeforeAppear, + onAppear, + onAfterAppear, + onAppearCancelled + } = props; + const key = String(vnode.key); + const leavingVNodesCache = getLeavingNodesForType(state, vnode); + const callHook3 = (hook, args) => { + hook && callWithAsyncErrorHandling( + hook, + instance, + 9, + args + ); + }; + const callAsyncHook = (hook, args) => { + const done = args[1]; + callHook3(hook, args); + if (isArray(hook)) { + if (hook.every((hook2) => hook2.length <= 1)) done(); + } else if (hook.length <= 1) { + done(); + } + }; + const hooks = { + mode, + persisted, + beforeEnter(el) { + let hook = onBeforeEnter; + if (!state.isMounted) { + if (appear) { + hook = onBeforeAppear || onBeforeEnter; + } else { + return; + } + } + if (el[leaveCbKey]) { + el[leaveCbKey]( + true + /* cancelled */ + ); + } + const leavingVNode = leavingVNodesCache[key]; + if (leavingVNode && isSameVNodeType(vnode, leavingVNode) && leavingVNode.el[leaveCbKey]) { + leavingVNode.el[leaveCbKey](); + } + callHook3(hook, [el]); + }, + enter(el) { + let hook = onEnter; + let afterHook = onAfterEnter; + let cancelHook = onEnterCancelled; + if (!state.isMounted) { + if (appear) { + hook = onAppear || onEnter; + afterHook = onAfterAppear || onAfterEnter; + cancelHook = onAppearCancelled || onEnterCancelled; + } else { + return; + } + } + let called = false; + const done = el[enterCbKey] = (cancelled) => { + if (called) return; + called = true; + if (cancelled) { + callHook3(cancelHook, [el]); + } else { + callHook3(afterHook, [el]); + } + if (hooks.delayedLeave) { + hooks.delayedLeave(); + } + el[enterCbKey] = void 0; + }; + if (hook) { + callAsyncHook(hook, [el, done]); + } else { + done(); + } + }, + leave(el, remove2) { + const key2 = String(vnode.key); + if (el[enterCbKey]) { + el[enterCbKey]( + true + /* cancelled */ + ); + } + if (state.isUnmounting) { + return remove2(); + } + callHook3(onBeforeLeave, [el]); + let called = false; + const done = el[leaveCbKey] = (cancelled) => { + if (called) return; + called = true; + remove2(); + if (cancelled) { + callHook3(onLeaveCancelled, [el]); + } else { + callHook3(onAfterLeave, [el]); + } + el[leaveCbKey] = void 0; + if (leavingVNodesCache[key2] === vnode) { + delete leavingVNodesCache[key2]; + } + }; + leavingVNodesCache[key2] = vnode; + if (onLeave) { + callAsyncHook(onLeave, [el, done]); + } else { + done(); + } + }, + clone(vnode2) { + const hooks2 = resolveTransitionHooks( + vnode2, + props, + state, + instance, + postClone + ); + if (postClone) postClone(hooks2); + return hooks2; + } + }; + return hooks; +} +function emptyPlaceholder(vnode) { + if (isKeepAlive(vnode)) { + vnode = cloneVNode(vnode); + vnode.children = null; + return vnode; + } +} +function getInnerChild$1(vnode) { + if (!isKeepAlive(vnode)) { + if (isTeleport(vnode.type) && vnode.children) { + return findNonCommentChild(vnode.children); + } + return vnode; + } + if (vnode.component) { + return vnode.component.subTree; + } + const { shapeFlag, children } = vnode; + if (children) { + if (shapeFlag & 16) { + return children[0]; + } + if (shapeFlag & 32 && isFunction(children.default)) { + return children.default(); + } + } +} +function setTransitionHooks(vnode, hooks) { + if (vnode.shapeFlag & 6 && vnode.component) { + vnode.transition = hooks; + setTransitionHooks(vnode.component.subTree, hooks); + } else if (vnode.shapeFlag & 128) { + vnode.ssContent.transition = hooks.clone(vnode.ssContent); + vnode.ssFallback.transition = hooks.clone(vnode.ssFallback); + } else { + vnode.transition = hooks; + } +} +function getTransitionRawChildren(children, keepComment = false, parentKey) { + let ret = []; + let keyedFragmentCount = 0; + for (let i = 0; i < children.length; i++) { + let child = children[i]; + const key = parentKey == null ? child.key : String(parentKey) + String(child.key != null ? child.key : i); + if (child.type === Fragment) { + if (child.patchFlag & 128) keyedFragmentCount++; + ret = ret.concat( + getTransitionRawChildren(child.children, keepComment, key) + ); + } else if (keepComment || child.type !== Comment) { + ret.push(key != null ? cloneVNode(child, { key }) : child); + } + } + if (keyedFragmentCount > 1) { + for (let i = 0; i < ret.length; i++) { + ret[i].patchFlag = -2; + } + } + return ret; +} +function defineComponent(options, extraOptions) { + return isFunction(options) ? ( + // #8236: extend call and options.name access are considered side-effects + // by Rollup, so we have to wrap it in a pure-annotated IIFE. + (() => extend({ name: options.name }, extraOptions, { setup: options }))() + ) : options; +} +function useId() { + const i = getCurrentInstance(); + if (i) { + return (i.appContext.config.idPrefix || "v") + "-" + i.ids[0] + i.ids[1]++; + } else if (true) { + warn$1( + `useId() is called when there is no active component instance to be associated with.` + ); + } + return ""; +} +function markAsyncBoundary(instance) { + instance.ids = [instance.ids[0] + instance.ids[2]++ + "-", 0, 0]; +} +var knownTemplateRefs = /* @__PURE__ */ new WeakSet(); +function useTemplateRef(key) { + const i = getCurrentInstance(); + const r = shallowRef(null); + if (i) { + const refs = i.refs === EMPTY_OBJ ? i.refs = {} : i.refs; + let desc; + if ((desc = Object.getOwnPropertyDescriptor(refs, key)) && !desc.configurable) { + warn$1(`useTemplateRef('${key}') already exists.`); + } else { + Object.defineProperty(refs, key, { + enumerable: true, + get: () => r.value, + set: (val) => r.value = val + }); + } + } else if (true) { + warn$1( + `useTemplateRef() is called when there is no active component instance to be associated with.` + ); + } + const ret = true ? readonly(r) : r; + if (true) { + knownTemplateRefs.add(ret); + } + return ret; +} +var pendingSetRefMap = /* @__PURE__ */ new WeakMap(); +function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) { + if (isArray(rawRef)) { + rawRef.forEach( + (r, i) => setRef( + r, + oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), + parentSuspense, + vnode, + isUnmount + ) + ); + return; + } + if (isAsyncWrapper(vnode) && !isUnmount) { + if (vnode.shapeFlag & 512 && vnode.type.__asyncResolved && vnode.component.subTree.component) { + setRef(rawRef, oldRawRef, parentSuspense, vnode.component.subTree); + } + return; + } + const refValue = vnode.shapeFlag & 4 ? getComponentPublicInstance(vnode.component) : vnode.el; + const value = isUnmount ? null : refValue; + const { i: owner, r: ref2 } = rawRef; + if (!owner) { + warn$1( + `Missing ref owner context. ref cannot be used on hoisted vnodes. A vnode with ref must be created inside the render function.` + ); + return; + } + const oldRef = oldRawRef && oldRawRef.r; + const refs = owner.refs === EMPTY_OBJ ? owner.refs = {} : owner.refs; + const setupState = owner.setupState; + const rawSetupState = toRaw(setupState); + const canSetSetupRef = setupState === EMPTY_OBJ ? NO : (key) => { + if (true) { + if (hasOwn(rawSetupState, key) && !isRef2(rawSetupState[key])) { + warn$1( + `Template ref "${key}" used on a non-ref value. It will not work in the production build.` + ); + } + if (knownTemplateRefs.has(rawSetupState[key])) { + return false; + } + } + return hasOwn(rawSetupState, key); + }; + const canSetRef = (ref22) => { + return !knownTemplateRefs.has(ref22); + }; + if (oldRef != null && oldRef !== ref2) { + invalidatePendingSetRef(oldRawRef); + if (isString(oldRef)) { + refs[oldRef] = null; + if (canSetSetupRef(oldRef)) { + setupState[oldRef] = null; + } + } else if (isRef2(oldRef)) { + if (canSetRef(oldRef)) { + oldRef.value = null; + } + const oldRawRefAtom = oldRawRef; + if (oldRawRefAtom.k) refs[oldRawRefAtom.k] = null; + } + } + if (isFunction(ref2)) { + callWithErrorHandling(ref2, owner, 12, [value, refs]); + } else { + const _isString = isString(ref2); + const _isRef = isRef2(ref2); + if (_isString || _isRef) { + const doSet = () => { + if (rawRef.f) { + const existing = _isString ? canSetSetupRef(ref2) ? setupState[ref2] : refs[ref2] : canSetRef(ref2) || !rawRef.k ? ref2.value : refs[rawRef.k]; + if (isUnmount) { + isArray(existing) && remove(existing, refValue); + } else { + if (!isArray(existing)) { + if (_isString) { + refs[ref2] = [refValue]; + if (canSetSetupRef(ref2)) { + setupState[ref2] = refs[ref2]; + } + } else { + const newVal = [refValue]; + if (canSetRef(ref2)) { + ref2.value = newVal; + } + if (rawRef.k) refs[rawRef.k] = newVal; + } + } else if (!existing.includes(refValue)) { + existing.push(refValue); + } + } + } else if (_isString) { + refs[ref2] = value; + if (canSetSetupRef(ref2)) { + setupState[ref2] = value; + } + } else if (_isRef) { + if (canSetRef(ref2)) { + ref2.value = value; + } + if (rawRef.k) refs[rawRef.k] = value; + } else if (true) { + warn$1("Invalid template ref type:", ref2, `(${typeof ref2})`); + } + }; + if (value) { + const job = () => { + doSet(); + pendingSetRefMap.delete(rawRef); + }; + job.id = -1; + pendingSetRefMap.set(rawRef, job); + queuePostRenderEffect(job, parentSuspense); + } else { + invalidatePendingSetRef(rawRef); + doSet(); + } + } else if (true) { + warn$1("Invalid template ref type:", ref2, `(${typeof ref2})`); + } + } +} +function invalidatePendingSetRef(rawRef) { + const pendingSetRef = pendingSetRefMap.get(rawRef); + if (pendingSetRef) { + pendingSetRef.flags |= 8; + pendingSetRefMap.delete(rawRef); + } +} +var hasLoggedMismatchError = false; +var logMismatchError = () => { + if (hasLoggedMismatchError) { + return; + } + console.error("Hydration completed but contains mismatches."); + hasLoggedMismatchError = true; +}; +var isSVGContainer = (container) => container.namespaceURI.includes("svg") && container.tagName !== "foreignObject"; +var isMathMLContainer = (container) => container.namespaceURI.includes("MathML"); +var getContainerType = (container) => { + if (container.nodeType !== 1) return void 0; + if (isSVGContainer(container)) return "svg"; + if (isMathMLContainer(container)) return "mathml"; + return void 0; +}; +var isComment = (node) => node.nodeType === 8; +function createHydrationFunctions(rendererInternals) { + const { + mt: mountComponent, + p: patch, + o: { + patchProp: patchProp2, + createText, + nextSibling, + parentNode, + remove: remove2, + insert, + createComment + } + } = rendererInternals; + const hydrate2 = (vnode, container) => { + if (!container.hasChildNodes()) { + warn$1( + `Attempting to hydrate existing markup but container is empty. Performing full mount instead.` + ); + patch(null, vnode, container); + flushPostFlushCbs(); + container._vnode = vnode; + return; + } + hydrateNode(container.firstChild, vnode, null, null, null); + flushPostFlushCbs(); + container._vnode = vnode; + }; + const hydrateNode = (node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized = false) => { + optimized = optimized || !!vnode.dynamicChildren; + const isFragmentStart = isComment(node) && node.data === "["; + const onMismatch = () => handleMismatch( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + isFragmentStart + ); + const { type, ref: ref2, shapeFlag, patchFlag } = vnode; + let domType = node.nodeType; + vnode.el = node; + if (true) { + def(node, "__vnode", vnode, true); + def(node, "__vueParentComponent", parentComponent, true); + } + if (patchFlag === -2) { + optimized = false; + vnode.dynamicChildren = null; + } + let nextNode = null; + switch (type) { + case Text: + if (domType !== 3) { + if (vnode.children === "") { + insert(vnode.el = createText(""), parentNode(node), node); + nextNode = node; + } else { + nextNode = onMismatch(); + } + } else { + if (node.data !== vnode.children) { + warn$1( + `Hydration text mismatch in`, + node.parentNode, + ` + - rendered on server: ${JSON.stringify( + node.data + )} + - expected on client: ${JSON.stringify(vnode.children)}` + ); + logMismatchError(); + node.data = vnode.children; + } + nextNode = nextSibling(node); + } + break; + case Comment: + if (isTemplateNode(node)) { + nextNode = nextSibling(node); + replaceNode( + vnode.el = node.content.firstChild, + node, + parentComponent + ); + } else if (domType !== 8 || isFragmentStart) { + nextNode = onMismatch(); + } else { + nextNode = nextSibling(node); + } + break; + case Static: + if (isFragmentStart) { + node = nextSibling(node); + domType = node.nodeType; + } + if (domType === 1 || domType === 3) { + nextNode = node; + const needToAdoptContent = !vnode.children.length; + for (let i = 0; i < vnode.staticCount; i++) { + if (needToAdoptContent) + vnode.children += nextNode.nodeType === 1 ? nextNode.outerHTML : nextNode.data; + if (i === vnode.staticCount - 1) { + vnode.anchor = nextNode; + } + nextNode = nextSibling(nextNode); + } + return isFragmentStart ? nextSibling(nextNode) : nextNode; + } else { + onMismatch(); + } + break; + case Fragment: + if (!isFragmentStart) { + nextNode = onMismatch(); + } else { + nextNode = hydrateFragment( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } + break; + default: + if (shapeFlag & 1) { + if ((domType !== 1 || vnode.type.toLowerCase() !== node.tagName.toLowerCase()) && !isTemplateNode(node)) { + nextNode = onMismatch(); + } else { + nextNode = hydrateElement( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } + } else if (shapeFlag & 6) { + vnode.slotScopeIds = slotScopeIds; + const container = parentNode(node); + if (isFragmentStart) { + nextNode = locateClosingAnchor(node); + } else if (isComment(node) && node.data === "teleport start") { + nextNode = locateClosingAnchor(node, node.data, "teleport end"); + } else { + nextNode = nextSibling(node); + } + mountComponent( + vnode, + container, + null, + parentComponent, + parentSuspense, + getContainerType(container), + optimized + ); + if (isAsyncWrapper(vnode) && !vnode.type.__asyncResolved) { + let subTree; + if (isFragmentStart) { + subTree = createVNode(Fragment); + subTree.anchor = nextNode ? nextNode.previousSibling : container.lastChild; + } else { + subTree = node.nodeType === 3 ? createTextVNode("") : createVNode("div"); + } + subTree.el = node; + vnode.component.subTree = subTree; + } + } else if (shapeFlag & 64) { + if (domType !== 8) { + nextNode = onMismatch(); + } else { + nextNode = vnode.type.hydrate( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized, + rendererInternals, + hydrateChildren + ); + } + } else if (shapeFlag & 128) { + nextNode = vnode.type.hydrate( + node, + vnode, + parentComponent, + parentSuspense, + getContainerType(parentNode(node)), + slotScopeIds, + optimized, + rendererInternals, + hydrateNode + ); + } else if (true) { + warn$1("Invalid HostVNode type:", type, `(${typeof type})`); + } + } + if (ref2 != null) { + setRef(ref2, null, parentSuspense, vnode); + } + return nextNode; + }; + const hydrateElement = (el, vnode, parentComponent, parentSuspense, slotScopeIds, optimized) => { + optimized = optimized || !!vnode.dynamicChildren; + const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode; + const forcePatch = type === "input" || type === "option"; + if (true) { + if (dirs) { + invokeDirectiveHook(vnode, null, parentComponent, "created"); + } + let needCallTransitionHooks = false; + if (isTemplateNode(el)) { + needCallTransitionHooks = needTransition( + null, + // no need check parentSuspense in hydration + transition + ) && parentComponent && parentComponent.vnode.props && parentComponent.vnode.props.appear; + const content = el.content.firstChild; + if (needCallTransitionHooks) { + const cls = content.getAttribute("class"); + if (cls) content.$cls = cls; + transition.beforeEnter(content); + } + replaceNode(content, el, parentComponent); + vnode.el = el = content; + } + if (shapeFlag & 16 && // skip if element has innerHTML / textContent + !(props && (props.innerHTML || props.textContent))) { + let next = hydrateChildren( + el.firstChild, + vnode, + el, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + let hasWarned2 = false; + while (next) { + if (!isMismatchAllowed( + el, + 1 + /* CHILDREN */ + )) { + if (!hasWarned2) { + warn$1( + `Hydration children mismatch on`, + el, + ` +Server rendered element contains more child nodes than client vdom.` + ); + hasWarned2 = true; + } + logMismatchError(); + } + const cur = next; + next = next.nextSibling; + remove2(cur); + } + } else if (shapeFlag & 8) { + let clientText = vnode.children; + if (clientText[0] === "\n" && (el.tagName === "PRE" || el.tagName === "TEXTAREA")) { + clientText = clientText.slice(1); + } + const { textContent } = el; + if (textContent !== clientText && // innerHTML normalize \r\n or \r into a single \n in the DOM + textContent !== clientText.replace(/\r\n|\r/g, "\n")) { + if (!isMismatchAllowed( + el, + 0 + /* TEXT */ + )) { + warn$1( + `Hydration text content mismatch on`, + el, + ` + - rendered on server: ${textContent} + - expected on client: ${clientText}` + ); + logMismatchError(); + } + el.textContent = vnode.children; + } + } + if (props) { + if (true) { + const isCustomElement = el.tagName.includes("-"); + for (const key in props) { + if (// #11189 skip if this node has directives that have created hooks + // as it could have mutated the DOM in any possible way + !(dirs && dirs.some((d) => d.dir.created)) && propHasMismatch(el, key, props[key], vnode, parentComponent)) { + logMismatchError(); + } + if (forcePatch && (key.endsWith("value") || key === "indeterminate") || isOn(key) && !isReservedProp(key) || // force hydrate v-bind with .prop modifiers + key[0] === "." || isCustomElement) { + patchProp2(el, key, null, props[key], void 0, parentComponent); + } + } + } else if (props.onClick) { + patchProp2( + el, + "onClick", + null, + props.onClick, + void 0, + parentComponent + ); + } else if (patchFlag & 4 && isReactive(props.style)) { + for (const key in props.style) props.style[key]; + } + } + let vnodeHooks; + if (vnodeHooks = props && props.onVnodeBeforeMount) { + invokeVNodeHook(vnodeHooks, parentComponent, vnode); + } + if (dirs) { + invokeDirectiveHook(vnode, null, parentComponent, "beforeMount"); + } + if ((vnodeHooks = props && props.onVnodeMounted) || dirs || needCallTransitionHooks) { + queueEffectWithSuspense(() => { + vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode); + needCallTransitionHooks && transition.enter(el); + dirs && invokeDirectiveHook(vnode, null, parentComponent, "mounted"); + }, parentSuspense); + } + } + return el.nextSibling; + }; + const hydrateChildren = (node, parentVNode, container, parentComponent, parentSuspense, slotScopeIds, optimized) => { + optimized = optimized || !!parentVNode.dynamicChildren; + const children = parentVNode.children; + const l = children.length; + let hasWarned2 = false; + for (let i = 0; i < l; i++) { + const vnode = optimized ? children[i] : children[i] = normalizeVNode(children[i]); + const isText = vnode.type === Text; + if (node) { + if (isText && !optimized) { + if (i + 1 < l && normalizeVNode(children[i + 1]).type === Text) { + insert( + createText( + node.data.slice(vnode.children.length) + ), + container, + nextSibling(node) + ); + node.data = vnode.children; + } + } + node = hydrateNode( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } else if (isText && !vnode.children) { + insert(vnode.el = createText(""), container); + } else { + if (!isMismatchAllowed( + container, + 1 + /* CHILDREN */ + )) { + if (!hasWarned2) { + warn$1( + `Hydration children mismatch on`, + container, + ` +Server rendered element contains fewer child nodes than client vdom.` + ); + hasWarned2 = true; + } + logMismatchError(); + } + patch( + null, + vnode, + container, + null, + parentComponent, + parentSuspense, + getContainerType(container), + slotScopeIds + ); + } + } + return node; + }; + const hydrateFragment = (node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized) => { + const { slotScopeIds: fragmentSlotScopeIds } = vnode; + if (fragmentSlotScopeIds) { + slotScopeIds = slotScopeIds ? slotScopeIds.concat(fragmentSlotScopeIds) : fragmentSlotScopeIds; + } + const container = parentNode(node); + const next = hydrateChildren( + nextSibling(node), + vnode, + container, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + if (next && isComment(next) && next.data === "]") { + return nextSibling(vnode.anchor = next); + } else { + logMismatchError(); + insert(vnode.anchor = createComment(`]`), container, next); + return next; + } + }; + const handleMismatch = (node, vnode, parentComponent, parentSuspense, slotScopeIds, isFragment) => { + if (!isMismatchAllowed( + node.parentElement, + 1 + /* CHILDREN */ + )) { + warn$1( + `Hydration node mismatch: +- rendered on server:`, + node, + node.nodeType === 3 ? `(text)` : isComment(node) && node.data === "[" ? `(start of fragment)` : ``, + ` +- expected on client:`, + vnode.type + ); + logMismatchError(); + } + vnode.el = null; + if (isFragment) { + const end = locateClosingAnchor(node); + while (true) { + const next2 = nextSibling(node); + if (next2 && next2 !== end) { + remove2(next2); + } else { + break; + } + } + } + const next = nextSibling(node); + const container = parentNode(node); + remove2(node); + patch( + null, + vnode, + container, + next, + parentComponent, + parentSuspense, + getContainerType(container), + slotScopeIds + ); + if (parentComponent) { + parentComponent.vnode.el = vnode.el; + updateHOCHostEl(parentComponent, vnode.el); + } + return next; + }; + const locateClosingAnchor = (node, open = "[", close = "]") => { + let match = 0; + while (node) { + node = nextSibling(node); + if (node && isComment(node)) { + if (node.data === open) match++; + if (node.data === close) { + if (match === 0) { + return nextSibling(node); + } else { + match--; + } + } + } + } + return node; + }; + const replaceNode = (newNode, oldNode, parentComponent) => { + const parentNode2 = oldNode.parentNode; + if (parentNode2) { + parentNode2.replaceChild(newNode, oldNode); + } + let parent = parentComponent; + while (parent) { + if (parent.vnode.el === oldNode) { + parent.vnode.el = parent.subTree.el = newNode; + } + parent = parent.parent; + } + }; + const isTemplateNode = (node) => { + return node.nodeType === 1 && node.tagName === "TEMPLATE"; + }; + return [hydrate2, hydrateNode]; +} +function propHasMismatch(el, key, clientValue, vnode, instance) { + let mismatchType; + let mismatchKey; + let actual; + let expected; + if (key === "class") { + if (el.$cls) { + actual = el.$cls; + delete el.$cls; + } else { + actual = el.getAttribute("class"); + } + expected = normalizeClass(clientValue); + if (!isSetEqual(toClassSet(actual || ""), toClassSet(expected))) { + mismatchType = 2; + mismatchKey = `class`; + } + } else if (key === "style") { + actual = el.getAttribute("style") || ""; + expected = isString(clientValue) ? clientValue : stringifyStyle(normalizeStyle(clientValue)); + const actualMap = toStyleMap(actual); + const expectedMap = toStyleMap(expected); + if (vnode.dirs) { + for (const { dir, value } of vnode.dirs) { + if (dir.name === "show" && !value) { + expectedMap.set("display", "none"); + } + } + } + if (instance) { + resolveCssVars(instance, vnode, expectedMap); + } + if (!isMapEqual(actualMap, expectedMap)) { + mismatchType = 3; + mismatchKey = "style"; + } + } else if (el instanceof SVGElement && isKnownSvgAttr(key) || el instanceof HTMLElement && (isBooleanAttr(key) || isKnownHtmlAttr(key))) { + if (isBooleanAttr(key)) { + actual = el.hasAttribute(key); + expected = includeBooleanAttr(clientValue); + } else if (clientValue == null) { + actual = el.hasAttribute(key); + expected = false; + } else { + if (el.hasAttribute(key)) { + actual = el.getAttribute(key); + } else if (key === "value" && el.tagName === "TEXTAREA") { + actual = el.value; + } else { + actual = false; + } + expected = isRenderableAttrValue(clientValue) ? String(clientValue) : false; + } + if (actual !== expected) { + mismatchType = 4; + mismatchKey = key; + } + } + if (mismatchType != null && !isMismatchAllowed(el, mismatchType)) { + const format = (v) => v === false ? `(not rendered)` : `${mismatchKey}="${v}"`; + const preSegment = `Hydration ${MismatchTypeString[mismatchType]} mismatch on`; + const postSegment = ` + - rendered on server: ${format(actual)} + - expected on client: ${format(expected)} + Note: this mismatch is check-only. The DOM will not be rectified in production due to performance overhead. + You should fix the source of the mismatch.`; + { + warn$1(preSegment, el, postSegment); + } + return true; + } + return false; +} +function toClassSet(str) { + return new Set(str.trim().split(/\s+/)); +} +function isSetEqual(a, b) { + if (a.size !== b.size) { + return false; + } + for (const s of a) { + if (!b.has(s)) { + return false; + } + } + return true; +} +function toStyleMap(str) { + const styleMap = /* @__PURE__ */ new Map(); + for (const item of str.split(";")) { + let [key, value] = item.split(":"); + key = key.trim(); + value = value && value.trim(); + if (key && value) { + styleMap.set(key, value); + } + } + return styleMap; +} +function isMapEqual(a, b) { + if (a.size !== b.size) { + return false; + } + for (const [key, value] of a) { + if (value !== b.get(key)) { + return false; + } + } + return true; +} +function resolveCssVars(instance, vnode, expectedMap) { + const root = instance.subTree; + if (instance.getCssVars && (vnode === root || root && root.type === Fragment && root.children.includes(vnode))) { + const cssVars = instance.getCssVars(); + for (const key in cssVars) { + const value = normalizeCssVarValue(cssVars[key]); + expectedMap.set(`--${getEscapedCssVarName(key, false)}`, value); + } + } + if (vnode === root && instance.parent) { + resolveCssVars(instance.parent, instance.vnode, expectedMap); + } +} +var allowMismatchAttr = "data-allow-mismatch"; +var MismatchTypeString = { + [ + 0 + /* TEXT */ + ]: "text", + [ + 1 + /* CHILDREN */ + ]: "children", + [ + 2 + /* CLASS */ + ]: "class", + [ + 3 + /* STYLE */ + ]: "style", + [ + 4 + /* ATTRIBUTE */ + ]: "attribute" +}; +function isMismatchAllowed(el, allowedType) { + if (allowedType === 0 || allowedType === 1) { + while (el && !el.hasAttribute(allowMismatchAttr)) { + el = el.parentElement; + } + } + const allowedAttr = el && el.getAttribute(allowMismatchAttr); + if (allowedAttr == null) { + return false; + } else if (allowedAttr === "") { + return true; + } else { + const list = allowedAttr.split(","); + if (allowedType === 0 && list.includes("children")) { + return true; + } + return list.includes(MismatchTypeString[allowedType]); + } +} +var requestIdleCallback = getGlobalThis().requestIdleCallback || ((cb) => setTimeout(cb, 1)); +var cancelIdleCallback = getGlobalThis().cancelIdleCallback || ((id) => clearTimeout(id)); +var hydrateOnIdle = (timeout = 1e4) => (hydrate2) => { + const id = requestIdleCallback(hydrate2, { timeout }); + return () => cancelIdleCallback(id); +}; +function elementIsVisibleInViewport(el) { + const { top, left, bottom, right } = el.getBoundingClientRect(); + const { innerHeight, innerWidth } = window; + return (top > 0 && top < innerHeight || bottom > 0 && bottom < innerHeight) && (left > 0 && left < innerWidth || right > 0 && right < innerWidth); +} +var hydrateOnVisible = (opts) => (hydrate2, forEach) => { + const ob = new IntersectionObserver((entries) => { + for (const e of entries) { + if (!e.isIntersecting) continue; + ob.disconnect(); + hydrate2(); + break; + } + }, opts); + forEach((el) => { + if (!(el instanceof Element)) return; + if (elementIsVisibleInViewport(el)) { + hydrate2(); + ob.disconnect(); + return false; + } + ob.observe(el); + }); + return () => ob.disconnect(); +}; +var hydrateOnMediaQuery = (query) => (hydrate2) => { + if (query) { + const mql = matchMedia(query); + if (mql.matches) { + hydrate2(); + } else { + mql.addEventListener("change", hydrate2, { once: true }); + return () => mql.removeEventListener("change", hydrate2); + } + } +}; +var hydrateOnInteraction = (interactions = []) => (hydrate2, forEach) => { + if (isString(interactions)) interactions = [interactions]; + let hasHydrated = false; + const doHydrate = (e) => { + if (!hasHydrated) { + hasHydrated = true; + teardown(); + hydrate2(); + e.target.dispatchEvent(new e.constructor(e.type, e)); + } + }; + const teardown = () => { + forEach((el) => { + for (const i of interactions) { + el.removeEventListener(i, doHydrate); + } + }); + }; + forEach((el) => { + for (const i of interactions) { + el.addEventListener(i, doHydrate, { once: true }); + } + }); + return teardown; +}; +function forEachElement(node, cb) { + if (isComment(node) && node.data === "[") { + let depth = 1; + let next = node.nextSibling; + while (next) { + if (next.nodeType === 1) { + const result = cb(next); + if (result === false) { + break; + } + } else if (isComment(next)) { + if (next.data === "]") { + if (--depth === 0) break; + } else if (next.data === "[") { + depth++; + } + } + next = next.nextSibling; + } + } else { + cb(node); + } +} +var isAsyncWrapper = (i) => !!i.type.__asyncLoader; +function defineAsyncComponent(source) { + if (isFunction(source)) { + source = { loader: source }; + } + const { + loader, + loadingComponent, + errorComponent, + delay = 200, + hydrate: hydrateStrategy, + timeout, + // undefined = never times out + suspensible = true, + onError: userOnError + } = source; + let pendingRequest = null; + let resolvedComp; + let retries = 0; + const retry = () => { + retries++; + pendingRequest = null; + return load(); + }; + const load = () => { + let thisRequest; + return pendingRequest || (thisRequest = pendingRequest = loader().catch((err) => { + err = err instanceof Error ? err : new Error(String(err)); + if (userOnError) { + return new Promise((resolve2, reject) => { + const userRetry = () => resolve2(retry()); + const userFail = () => reject(err); + userOnError(err, userRetry, userFail, retries + 1); + }); + } else { + throw err; + } + }).then((comp) => { + if (thisRequest !== pendingRequest && pendingRequest) { + return pendingRequest; + } + if (!comp) { + warn$1( + `Async component loader resolved to undefined. If you are using retry(), make sure to return its return value.` + ); + } + if (comp && (comp.__esModule || comp[Symbol.toStringTag] === "Module")) { + comp = comp.default; + } + if (comp && !isObject(comp) && !isFunction(comp)) { + throw new Error(`Invalid async component load result: ${comp}`); + } + resolvedComp = comp; + return comp; + })); + }; + return defineComponent({ + name: "AsyncComponentWrapper", + __asyncLoader: load, + __asyncHydrate(el, instance, hydrate2) { + let patched = false; + (instance.bu || (instance.bu = [])).push(() => patched = true); + const performHydrate = () => { + if (patched) { + if (true) { + warn$1( + `Skipping lazy hydration for component '${getComponentName(resolvedComp) || resolvedComp.__file}': it was updated before lazy hydration performed.` + ); + } + return; + } + hydrate2(); + }; + const doHydrate = hydrateStrategy ? () => { + const teardown = hydrateStrategy( + performHydrate, + (cb) => forEachElement(el, cb) + ); + if (teardown) { + (instance.bum || (instance.bum = [])).push(teardown); + } + } : performHydrate; + if (resolvedComp) { + doHydrate(); + } else { + load().then(() => !instance.isUnmounted && doHydrate()); + } + }, + get __asyncResolved() { + return resolvedComp; + }, + setup() { + const instance = currentInstance; + markAsyncBoundary(instance); + if (resolvedComp) { + return () => createInnerComp(resolvedComp, instance); + } + const onError = (err) => { + pendingRequest = null; + handleError( + err, + instance, + 13, + !errorComponent + ); + }; + if (suspensible && instance.suspense || isInSSRComponentSetup) { + return load().then((comp) => { + return () => createInnerComp(comp, instance); + }).catch((err) => { + onError(err); + return () => errorComponent ? createVNode(errorComponent, { + error: err + }) : null; + }); + } + const loaded = ref(false); + const error = ref(); + const delayed = ref(!!delay); + if (delay) { + setTimeout(() => { + delayed.value = false; + }, delay); + } + if (timeout != null) { + setTimeout(() => { + if (!loaded.value && !error.value) { + const err = new Error( + `Async component timed out after ${timeout}ms.` + ); + onError(err); + error.value = err; + } + }, timeout); + } + load().then(() => { + loaded.value = true; + if (instance.parent && isKeepAlive(instance.parent.vnode)) { + instance.parent.update(); + } + }).catch((err) => { + onError(err); + error.value = err; + }); + return () => { + if (loaded.value && resolvedComp) { + return createInnerComp(resolvedComp, instance); + } else if (error.value && errorComponent) { + return createVNode(errorComponent, { + error: error.value + }); + } else if (loadingComponent && !delayed.value) { + return createInnerComp( + loadingComponent, + instance + ); + } + }; + } + }); +} +function createInnerComp(comp, parent) { + const { ref: ref2, props, children, ce } = parent.vnode; + const vnode = createVNode(comp, props, children); + vnode.ref = ref2; + vnode.ce = ce; + delete parent.vnode.ce; + return vnode; +} +var isKeepAlive = (vnode) => vnode.type.__isKeepAlive; +var KeepAliveImpl = { + name: `KeepAlive`, + // Marker for special handling inside the renderer. We are not using a === + // check directly on KeepAlive in the renderer, because importing it directly + // would prevent it from being tree-shaken. + __isKeepAlive: true, + props: { + include: [String, RegExp, Array], + exclude: [String, RegExp, Array], + max: [String, Number] + }, + setup(props, { slots }) { + const instance = getCurrentInstance(); + const sharedContext = instance.ctx; + if (!sharedContext.renderer) { + return () => { + const children = slots.default && slots.default(); + return children && children.length === 1 ? children[0] : children; + }; + } + const cache = /* @__PURE__ */ new Map(); + const keys = /* @__PURE__ */ new Set(); + let current = null; + if (true) { + instance.__v_cache = cache; + } + const parentSuspense = instance.suspense; + const { + renderer: { + p: patch, + m: move, + um: _unmount, + o: { createElement } + } + } = sharedContext; + const storageContainer = createElement("div"); + sharedContext.activate = (vnode, container, anchor, namespace, optimized) => { + const instance2 = vnode.component; + move(vnode, container, anchor, 0, parentSuspense); + patch( + instance2.vnode, + vnode, + container, + anchor, + instance2, + parentSuspense, + namespace, + vnode.slotScopeIds, + optimized + ); + queuePostRenderEffect(() => { + instance2.isDeactivated = false; + if (instance2.a) { + invokeArrayFns(instance2.a); + } + const vnodeHook = vnode.props && vnode.props.onVnodeMounted; + if (vnodeHook) { + invokeVNodeHook(vnodeHook, instance2.parent, vnode); + } + }, parentSuspense); + if (true) { + devtoolsComponentAdded(instance2); + } + }; + sharedContext.deactivate = (vnode) => { + const instance2 = vnode.component; + invalidateMount(instance2.m); + invalidateMount(instance2.a); + move(vnode, storageContainer, null, 1, parentSuspense); + queuePostRenderEffect(() => { + if (instance2.da) { + invokeArrayFns(instance2.da); + } + const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted; + if (vnodeHook) { + invokeVNodeHook(vnodeHook, instance2.parent, vnode); + } + instance2.isDeactivated = true; + }, parentSuspense); + if (true) { + devtoolsComponentAdded(instance2); + } + if (true) { + instance2.__keepAliveStorageContainer = storageContainer; + } + }; + function unmount(vnode) { + resetShapeFlag(vnode); + _unmount(vnode, instance, parentSuspense, true); + } + function pruneCache(filter) { + cache.forEach((vnode, key) => { + const name = getComponentName( + isAsyncWrapper(vnode) ? vnode.type.__asyncResolved || {} : vnode.type + ); + if (name && !filter(name)) { + pruneCacheEntry(key); + } + }); + } + function pruneCacheEntry(key) { + const cached = cache.get(key); + if (cached && (!current || !isSameVNodeType(cached, current))) { + unmount(cached); + } else if (current) { + resetShapeFlag(current); + } + cache.delete(key); + keys.delete(key); + } + watch2( + () => [props.include, props.exclude], + ([include, exclude]) => { + include && pruneCache((name) => matches(include, name)); + exclude && pruneCache((name) => !matches(exclude, name)); + }, + // prune post-render after `current` has been updated + { flush: "post", deep: true } + ); + let pendingCacheKey = null; + const cacheSubtree = () => { + if (pendingCacheKey != null) { + if (isSuspense(instance.subTree.type)) { + queuePostRenderEffect(() => { + cache.set(pendingCacheKey, getInnerChild(instance.subTree)); + }, instance.subTree.suspense); + } else { + cache.set(pendingCacheKey, getInnerChild(instance.subTree)); + } + } + }; + onMounted(cacheSubtree); + onUpdated(cacheSubtree); + onBeforeUnmount(() => { + cache.forEach((cached) => { + const { subTree, suspense } = instance; + const vnode = getInnerChild(subTree); + if (cached.type === vnode.type && cached.key === vnode.key) { + resetShapeFlag(vnode); + const da = vnode.component.da; + da && queuePostRenderEffect(da, suspense); + return; + } + unmount(cached); + }); + }); + return () => { + pendingCacheKey = null; + if (!slots.default) { + return current = null; + } + const children = slots.default(); + const rawVNode = children[0]; + if (children.length > 1) { + if (true) { + warn$1(`KeepAlive should contain exactly one component child.`); + } + current = null; + return children; + } else if (!isVNode(rawVNode) || !(rawVNode.shapeFlag & 4) && !(rawVNode.shapeFlag & 128)) { + current = null; + return rawVNode; + } + let vnode = getInnerChild(rawVNode); + if (vnode.type === Comment) { + current = null; + return vnode; + } + const comp = vnode.type; + const name = getComponentName( + isAsyncWrapper(vnode) ? vnode.type.__asyncResolved || {} : comp + ); + const { include, exclude, max } = props; + if (include && (!name || !matches(include, name)) || exclude && name && matches(exclude, name)) { + vnode.shapeFlag &= -257; + current = vnode; + return rawVNode; + } + const key = vnode.key == null ? comp : vnode.key; + const cachedVNode = cache.get(key); + if (vnode.el) { + vnode = cloneVNode(vnode); + if (rawVNode.shapeFlag & 128) { + rawVNode.ssContent = vnode; + } + } + pendingCacheKey = key; + if (cachedVNode) { + vnode.el = cachedVNode.el; + vnode.component = cachedVNode.component; + if (vnode.transition) { + setTransitionHooks(vnode, vnode.transition); + } + vnode.shapeFlag |= 512; + keys.delete(key); + keys.add(key); + } else { + keys.add(key); + if (max && keys.size > parseInt(max, 10)) { + pruneCacheEntry(keys.values().next().value); + } + } + vnode.shapeFlag |= 256; + current = vnode; + return isSuspense(rawVNode.type) ? rawVNode : vnode; + }; + } +}; +var KeepAlive = KeepAliveImpl; +function matches(pattern, name) { + if (isArray(pattern)) { + return pattern.some((p2) => matches(p2, name)); + } else if (isString(pattern)) { + return pattern.split(",").includes(name); + } else if (isRegExp(pattern)) { + pattern.lastIndex = 0; + return pattern.test(name); + } + return false; +} +function onActivated(hook, target) { + registerKeepAliveHook(hook, "a", target); +} +function onDeactivated(hook, target) { + registerKeepAliveHook(hook, "da", target); +} +function registerKeepAliveHook(hook, type, target = currentInstance) { + const wrappedHook = hook.__wdc || (hook.__wdc = () => { + let current = target; + while (current) { + if (current.isDeactivated) { + return; + } + current = current.parent; + } + return hook(); + }); + injectHook(type, wrappedHook, target); + if (target) { + let current = target.parent; + while (current && current.parent) { + if (isKeepAlive(current.parent.vnode)) { + injectToKeepAliveRoot(wrappedHook, type, target, current); + } + current = current.parent; + } + } +} +function injectToKeepAliveRoot(hook, type, target, keepAliveRoot) { + const injected = injectHook( + type, + hook, + keepAliveRoot, + true + /* prepend */ + ); + onUnmounted(() => { + remove(keepAliveRoot[type], injected); + }, target); +} +function resetShapeFlag(vnode) { + vnode.shapeFlag &= -257; + vnode.shapeFlag &= -513; +} +function getInnerChild(vnode) { + return vnode.shapeFlag & 128 ? vnode.ssContent : vnode; +} +function injectHook(type, hook, target = currentInstance, prepend = false) { + if (target) { + const hooks = target[type] || (target[type] = []); + const wrappedHook = hook.__weh || (hook.__weh = (...args) => { + pauseTracking(); + const reset = setCurrentInstance(target); + const res = callWithAsyncErrorHandling(hook, target, type, args); + reset(); + resetTracking(); + return res; + }); + if (prepend) { + hooks.unshift(wrappedHook); + } else { + hooks.push(wrappedHook); + } + return wrappedHook; + } else if (true) { + const apiName = toHandlerKey(ErrorTypeStrings$1[type].replace(/ hook$/, "")); + warn$1( + `${apiName} is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup(). If you are using async setup(), make sure to register lifecycle hooks before the first await statement.` + ); + } +} +var createHook = (lifecycle) => (hook, target = currentInstance) => { + if (!isInSSRComponentSetup || lifecycle === "sp") { + injectHook(lifecycle, (...args) => hook(...args), target); + } +}; +var onBeforeMount = createHook("bm"); +var onMounted = createHook("m"); +var onBeforeUpdate = createHook( + "bu" +); +var onUpdated = createHook("u"); +var onBeforeUnmount = createHook( + "bum" +); +var onUnmounted = createHook("um"); +var onServerPrefetch = createHook( + "sp" +); +var onRenderTriggered = createHook("rtg"); +var onRenderTracked = createHook("rtc"); +function onErrorCaptured(hook, target = currentInstance) { + injectHook("ec", hook, target); +} +var COMPONENTS = "components"; +var DIRECTIVES = "directives"; +function resolveComponent(name, maybeSelfReference) { + return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name; +} +var NULL_DYNAMIC_COMPONENT = Symbol.for("v-ndc"); +function resolveDynamicComponent(component) { + if (isString(component)) { + return resolveAsset(COMPONENTS, component, false) || component; + } else { + return component || NULL_DYNAMIC_COMPONENT; + } +} +function resolveDirective(name) { + return resolveAsset(DIRECTIVES, name); +} +function resolveAsset(type, name, warnMissing = true, maybeSelfReference = false) { + const instance = currentRenderingInstance || currentInstance; + if (instance) { + const Component = instance.type; + if (type === COMPONENTS) { + const selfName = getComponentName( + Component, + false + ); + if (selfName && (selfName === name || selfName === camelize(name) || selfName === capitalize(camelize(name)))) { + return Component; + } + } + const res = ( + // local registration + // check instance[type] first which is resolved for options API + resolve(instance[type] || Component[type], name) || // global registration + resolve(instance.appContext[type], name) + ); + if (!res && maybeSelfReference) { + return Component; + } + if (warnMissing && !res) { + const extra = type === COMPONENTS ? ` +If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.` : ``; + warn$1(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`); + } + return res; + } else if (true) { + warn$1( + `resolve${capitalize(type.slice(0, -1))} can only be used in render() or setup().` + ); + } +} +function resolve(registry, name) { + return registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))]); +} +function renderList(source, renderItem, cache, index) { + let ret; + const cached = cache && cache[index]; + const sourceIsArray = isArray(source); + if (sourceIsArray || isString(source)) { + const sourceIsReactiveArray = sourceIsArray && isReactive(source); + let needsWrap = false; + let isReadonlySource = false; + if (sourceIsReactiveArray) { + needsWrap = !isShallow(source); + isReadonlySource = isReadonly(source); + source = shallowReadArray(source); + } + ret = new Array(source.length); + for (let i = 0, l = source.length; i < l; i++) { + ret[i] = renderItem( + needsWrap ? isReadonlySource ? toReadonly(toReactive(source[i])) : toReactive(source[i]) : source[i], + i, + void 0, + cached && cached[i] + ); + } + } else if (typeof source === "number") { + if (!Number.isInteger(source)) { + warn$1(`The v-for range expect an integer value but got ${source}.`); + } + ret = new Array(source); + for (let i = 0; i < source; i++) { + ret[i] = renderItem(i + 1, i, void 0, cached && cached[i]); + } + } else if (isObject(source)) { + if (source[Symbol.iterator]) { + ret = Array.from( + source, + (item, i) => renderItem(item, i, void 0, cached && cached[i]) + ); + } else { + const keys = Object.keys(source); + ret = new Array(keys.length); + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + ret[i] = renderItem(source[key], key, i, cached && cached[i]); + } + } + } else { + ret = []; + } + if (cache) { + cache[index] = ret; + } + return ret; +} +function createSlots(slots, dynamicSlots) { + for (let i = 0; i < dynamicSlots.length; i++) { + const slot = dynamicSlots[i]; + if (isArray(slot)) { + for (let j = 0; j < slot.length; j++) { + slots[slot[j].name] = slot[j].fn; + } + } else if (slot) { + slots[slot.name] = slot.key ? (...args) => { + const res = slot.fn(...args); + if (res) res.key = slot.key; + return res; + } : slot.fn; + } + } + return slots; +} +function renderSlot(slots, name, props = {}, fallback, noSlotted) { + if (currentRenderingInstance.ce || currentRenderingInstance.parent && isAsyncWrapper(currentRenderingInstance.parent) && currentRenderingInstance.parent.ce) { + const hasProps = Object.keys(props).length > 0; + if (name !== "default") props.name = name; + return openBlock(), createBlock( + Fragment, + null, + [createVNode("slot", props, fallback && fallback())], + hasProps ? -2 : 64 + ); + } + let slot = slots[name]; + if (slot && slot.length > 1) { + warn$1( + `SSR-optimized slot function detected in a non-SSR-optimized render function. You need to mark this component with $dynamic-slots in the parent template.` + ); + slot = () => []; + } + if (slot && slot._c) { + slot._d = false; + } + openBlock(); + const validSlotContent = slot && ensureValidVNode(slot(props)); + const slotKey = props.key || // slot content array of a dynamic conditional slot may have a branch + // key attached in the `createSlots` helper, respect that + validSlotContent && validSlotContent.key; + const rendered = createBlock( + Fragment, + { + key: (slotKey && !isSymbol(slotKey) ? slotKey : `_${name}`) + // #7256 force differentiate fallback content from actual content + (!validSlotContent && fallback ? "_fb" : "") + }, + validSlotContent || (fallback ? fallback() : []), + validSlotContent && slots._ === 1 ? 64 : -2 + ); + if (!noSlotted && rendered.scopeId) { + rendered.slotScopeIds = [rendered.scopeId + "-s"]; + } + if (slot && slot._c) { + slot._d = true; + } + return rendered; +} +function ensureValidVNode(vnodes) { + return vnodes.some((child) => { + if (!isVNode(child)) return true; + if (child.type === Comment) return false; + if (child.type === Fragment && !ensureValidVNode(child.children)) + return false; + return true; + }) ? vnodes : null; +} +function toHandlers(obj, preserveCaseIfNecessary) { + const ret = {}; + if (!isObject(obj)) { + warn$1(`v-on with no argument expects an object value.`); + return ret; + } + for (const key in obj) { + ret[preserveCaseIfNecessary && /[A-Z]/.test(key) ? `on:${key}` : toHandlerKey(key)] = obj[key]; + } + return ret; +} +var getPublicInstance = (i) => { + if (!i) return null; + if (isStatefulComponent(i)) return getComponentPublicInstance(i); + return getPublicInstance(i.parent); +}; +var publicPropertiesMap = ( + // Move PURE marker to new line to workaround compiler discarding it + // due to type annotation + extend(/* @__PURE__ */ Object.create(null), { + $: (i) => i, + $el: (i) => i.vnode.el, + $data: (i) => i.data, + $props: (i) => true ? shallowReadonly(i.props) : i.props, + $attrs: (i) => true ? shallowReadonly(i.attrs) : i.attrs, + $slots: (i) => true ? shallowReadonly(i.slots) : i.slots, + $refs: (i) => true ? shallowReadonly(i.refs) : i.refs, + $parent: (i) => getPublicInstance(i.parent), + $root: (i) => getPublicInstance(i.root), + $host: (i) => i.ce, + $emit: (i) => i.emit, + $options: (i) => __VUE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type, + $forceUpdate: (i) => i.f || (i.f = () => { + queueJob(i.update); + }), + $nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)), + $watch: (i) => __VUE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP + }) +); +var isReservedPrefix = (key) => key === "_" || key === "$"; +var hasSetupBinding = (state, key) => state !== EMPTY_OBJ && !state.__isScriptSetup && hasOwn(state, key); +var PublicInstanceProxyHandlers = { + get({ _: instance }, key) { + if (key === "__v_skip") { + return true; + } + const { ctx, setupState, data, props, accessCache, type, appContext } = instance; + if (key === "__isVue") { + return true; + } + if (key[0] !== "$") { + const n = accessCache[key]; + if (n !== void 0) { + switch (n) { + case 1: + return setupState[key]; + case 2: + return data[key]; + case 4: + return ctx[key]; + case 3: + return props[key]; + } + } else if (hasSetupBinding(setupState, key)) { + accessCache[key] = 1; + return setupState[key]; + } else if (__VUE_OPTIONS_API__ && data !== EMPTY_OBJ && hasOwn(data, key)) { + accessCache[key] = 2; + return data[key]; + } else if (hasOwn(props, key)) { + accessCache[key] = 3; + return props[key]; + } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { + accessCache[key] = 4; + return ctx[key]; + } else if (!__VUE_OPTIONS_API__ || shouldCacheAccess) { + accessCache[key] = 0; + } + } + const publicGetter = publicPropertiesMap[key]; + let cssModule, globalProperties; + if (publicGetter) { + if (key === "$attrs") { + track(instance.attrs, "get", ""); + markAttrsAccessed(); + } else if (key === "$slots") { + track(instance, "get", key); + } + return publicGetter(instance); + } else if ( + // css module (injected by vue-loader) + (cssModule = type.__cssModules) && (cssModule = cssModule[key]) + ) { + return cssModule; + } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { + accessCache[key] = 4; + return ctx[key]; + } else if ( + // global properties + globalProperties = appContext.config.globalProperties, hasOwn(globalProperties, key) + ) { + { + return globalProperties[key]; + } + } else if (currentRenderingInstance && (!isString(key) || // #1091 avoid internal isRef/isVNode checks on component instance leading + // to infinite warning loop + key.indexOf("__v") !== 0)) { + if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) { + warn$1( + `Property ${JSON.stringify( + key + )} must be accessed via $data because it starts with a reserved character ("$" or "_") and is not proxied on the render context.` + ); + } else if (instance === currentRenderingInstance) { + warn$1( + `Property ${JSON.stringify(key)} was accessed during render but is not defined on instance.` + ); + } + } + }, + set({ _: instance }, key, value) { + const { data, setupState, ctx } = instance; + if (hasSetupBinding(setupState, key)) { + setupState[key] = value; + return true; + } else if (setupState.__isScriptSetup && hasOwn(setupState, key)) { + warn$1(`Cannot mutate - - - - + {{> footer }} - \ No newline at end of file + + + + + + + diff --git a/index.html b/index.html index aabe323..516c13b 100644 --- a/index.html +++ b/index.html @@ -1,934 +1,1466 @@ + + + - - - - BentoPDF - The Privacy First PDF Toolkit - - - - - - - - - - - + + BentoPDF - Free Online PDF Tools | Privacy-First PDF Toolkit + + + + + - - -
-
-
-

- The - PDF Toolkit built - for - privacy. -

- -
- - - No Signups + + + + + + + + + + + + + + + + + + + + + {{> navbar }} + + +
+
+ + Love BentoPDF? Help us keep it free and open source! - - - Unlimited Use - - - - Works Offline - -
- - -
+
- -
-
-

Used by companies and - people working at

+
+
+
+

+ The + + PDF Toolkit + + + built for privacy. +

+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
- -
-

- BentoPDF? -

-
-
-
- -

No Signup

-
-

- Start instantly, no accounts or emails. -

-
-
-
- -

No Uploads

-
-

- 100% client-side, your files never leave your device. -

-
-
-
- -

Forever Free

-
-

- All tools, no trials, no paywalls. -

-
-
-
- -

No Limits

-
-

- Use as much as you want, no hidden caps. -

-
-
-
- -

Batch Processing

-
-

Handle unlimited PDFs in one - go. -

-
-
-
- -

Lightning Fast

-
-

- Process PDFs instantly, without waiting or delays. -

-
-
-
- -
- -
-

- Get Started with Tools -

-

Click a tool to open the file uploader

-
- -
-
-
- - + class="flex flex-wrap justify-center items-center gap-2 sm:gap-4 mb-12 mt-12" + > + + + No Signups - - - - + + + Unlimited Use + + + + Works Offline
-
-
-
+ + - + +
+
+

+ Used by companies and people working at +

+
+ + + + - +
+ +
+ +
+

+ + BentoPDF? +

+
+
+
+ +

+ No Signup +

+
+

+ Start instantly, no accounts or emails. +

+
+
+
+ +

+ No Uploads +

+
+

+ 100% client-side, your files never leave your device. +

+
+
+
+ +

+ Forever Free +

+
+

+ All tools, no trials, no paywalls. +

+
+
+
+ +

+ No Limits +

+
+

+ Use as much as you want, no hidden caps. +

+
+
+
+ +

+ Batch Processing +

+
+

+ Handle unlimited PDFs in one go. +

+
+
+
+ +

+ Lightning Fast +

+
+

+ Process PDFs instantly, without waiting or delays. +

+
+
+
+ +
+ +
+

+ Get Started with + + Tools +

+

+ Click a tool to open the file uploader

-
- + +
+
+ + - - - + {{> footer }} - - - - \ No newline at end of file + + + + diff --git a/nginx-ipv6.sh b/nginx-ipv6.sh new file mode 100644 index 0000000..f2a5ee5 --- /dev/null +++ b/nginx-ipv6.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# @see: https://github.com/nginx/docker-nginx-unprivileged/tree/main/stable/alpine-slim + +set -e + +entrypoint_log() { + if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then + echo "$@" + fi +} + +if [ "$DISABLE_IPV6" = "true" ]; then + entrypoint_log "Disabling the Nginx IPv6 listener" + sed -i '/^[[:space:]]*listen[[:space:]]*\[::\]:[0-9]*/s/^/#/' /etc/nginx/nginx.conf +fi + +exit 0 diff --git a/nginx.conf b/nginx.conf index 23dc8a7..25615f2 100644 --- a/nginx.conf +++ b/nginx.conf @@ -11,10 +11,13 @@ http { } default_type application/octet-stream; + gzip_static on; + gzip on; gzip_vary on; gzip_min_length 1024; - gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + gzip_comp_level 9; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json application/wasm application/x-javascript text/x-component; server { listen 8080; @@ -22,22 +25,93 @@ http { server_name localhost; root /usr/share/nginx/html; index index.html; - - rewrite ^/(en|de|zh|vi)/(.*)$ /$2 last; + absolute_redirect off; + + location ~ ^/(en|ar|be|da|de|es|fr|id|it|nl|pt|tr|vi|zh|zh-TW)(/.*)?$ { + try_files $uri $uri/ $uri.html /$1/index.html /index.html; + expires 5m; + add_header Cache-Control "public, must-revalidate"; + add_header Cross-Origin-Embedder-Policy "require-corp" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; + } + + location ~ ^/(.+?)/(en|ar|be|da|de|es|fr|id|it|nl|pt|tr|vi|zh|zh-TW)(/.*)?$ { + try_files $uri $uri/ $uri.html /$1/$2/index.html /$1/index.html /index.html; + expires 5m; + add_header Cache-Control "public, must-revalidate"; + add_header Cross-Origin-Embedder-Policy "require-corp" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; + } + + location ~* \.html$ { + expires 1h; + add_header Cache-Control "public, must-revalidate"; + add_header Cross-Origin-Embedder-Policy "require-corp" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; + } + + location ~* /libreoffice-wasm/soffice\.wasm\.gz$ { + gzip off; + types {} default_type application/wasm; + add_header Content-Encoding gzip; + add_header Vary "Accept-Encoding"; + add_header Cache-Control "public, immutable"; + add_header Cross-Origin-Embedder-Policy "require-corp" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; + } + + location ~* /libreoffice-wasm/soffice\.data\.gz$ { + gzip off; + types {} default_type application/octet-stream; + add_header Content-Encoding gzip; + add_header Vary "Accept-Encoding"; + add_header Cache-Control "public, immutable"; + add_header Cross-Origin-Embedder-Policy "require-corp" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; + } + + location ~* \.(wasm|wasm\.gz|data|data\.gz)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + add_header Cross-Origin-Embedder-Policy "require-corp" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; + } + + location ~* \.(js|mjs|css|woff|woff2|ttf|eot|otf)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + add_header Cross-Origin-Embedder-Policy "require-corp" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; + } + + location ~* \.(png|jpg|jpeg|gif|ico|svg|webp|avif|mp4|webm)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + location ~* \.json$ { + expires 1w; + add_header Cache-Control "public, must-revalidate"; + } + + location ~* \.pdf$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } location / { try_files $uri $uri/ $uri.html /index.html; - } - - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control "public, immutable"; + expires 5m; + add_header Cache-Control "public, must-revalidate"; + add_header Cross-Origin-Embedder-Policy "require-corp" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; } add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Cross-Origin-Opener-Policy "same-origin" always; + add_header Cross-Origin-Embedder-Policy "require-corp" always; add_header Cross-Origin-Resource-Policy "cross-origin" always; } } diff --git a/package-lock.json b/package-lock.json index f3e394e..2576cc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "bento-pdf", - "version": "1.11.2", + "version": "2.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bento-pdf", - "version": "1.11.2", - "license": "Apache-2.0", + "version": "2.4.1", + "license": "AGPL-3.0-only", "dependencies": { "@fontsource/cedarville-cursive": "^5.2.7", "@fontsource/dancing-script": "^5.2.8", @@ -16,90 +16,397 @@ "@fontsource/kalam": "^5.2.8", "@fontsource/lato": "^5.2.7", "@fontsource/merriweather": "^5.2.11", + "@kenjiuno/msgreader": "^1.28.0", + "@matbee/libreoffice-converter": "^2.5.0", "@neslinesli93/qpdf-wasm": "^0.3.0", "@pdf-lib/fontkit": "^1.1.1", - "@tailwindcss/vite": "^4.1.15", + "@phosphor-icons/web": "^2.1.2", + "@retejs/lit-plugin": "^2.0.7", + "@tailwindcss/vite": "^4.2.1", + "@types/markdown-it": "^14.1.2", + "@types/node-forge": "^1.3.14", + "@types/papaparse": "^5.5.2", "archiver": "^7.0.1", "blob-stream": "^0.1.3", - "cropperjs": "^1.6.1", - "embedpdf-snippet": "file:vendor/embedpdf/embedpdf-snippet-1.5.0.tgz", + "bwip-js": "^4.8.0", + "cropperjs": "^1.6.2", + "diff": "^8.0.3", + "embedpdf-snippet": "file:vendor/embedpdf/embedpdf-snippet-2.3.0.tgz", "heic2any": "^0.0.4", + "highlight.js": "^11.11.1", "html2canvas": "^1.4.1", - "i18next": "^25.7.2", - "i18next-browser-languagedetector": "^8.2.0", + "i18next": "^25.8.13", + "i18next-browser-languagedetector": "^8.2.1", "i18next-http-backend": "^3.0.2", - "jspdf": "^3.0.3", + "jspdf": "^4.2.0", + "jspdf-autotable": "^5.0.2", "jszip": "^3.10.1", - "lucide": "^0.546.0", + "lit": "^3.3.2", + "lucide": "^0.575.0", + "markdown-it": "^14.1.1", + "markdown-it-abbr": "^2.0.0", + "markdown-it-anchor": "^9.2.0", + "markdown-it-deflist": "^3.0.0", + "markdown-it-emoji": "^3.0.0", + "markdown-it-footnote": "^4.0.0", + "markdown-it-ins": "^4.0.0", + "markdown-it-mark": "^4.0.0", + "markdown-it-sub": "^2.0.0", + "markdown-it-sup": "^2.0.0", + "markdown-it-task-lists": "^2.1.1", + "markdown-it-toc-done-right": "^4.2.0", + "mermaid": "^11.12.3", + "microdiff": "^1.5.0", + "node-forge": "^1.3.3", + "papaparse": "^5.5.3", "pdf-lib": "^1.17.1", - "pdfjs-dist": "^5.4.296", + "pdfjs-dist": "^5.4.624", "pdfkit": "^0.17.2", - "sortablejs": "^1.15.6", + "pixelmatch": "^7.1.0", + "postal-mime": "^2.7.3", + "rete": "^2.0.6", + "rete-area-plugin": "^2.1.5", + "rete-connection-plugin": "^2.0.5", + "rete-engine": "^2.1.1", + "rete-render-utils": "^2.0.3", + "sortablejs": "^1.15.7", "tailwindcss": "^4.1.14", - "terser": "^5.44.0", - "tesseract.js": "^6.0.1", + "terser": "^5.46.0", + "tesseract.js": "^7.0.0", "tiff": "^7.1.2", - "utif": "^3.1.0" + "utif": "^3.1.0", + "vite-plugin-static-copy": "^3.2.0", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + "zgapdfsigner": "^2.7.5" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@testing-library/dom": "^10.4.1", "@types/blob-stream": "^0.1.33", "@types/html2canvas": "^1.0.0", - "@types/pdfkit": "^0.17.3", + "@types/pdfkit": "^0.17.5", "@types/sortablejs": "^1.15.8", "@types/utif": "^3.0.6", - "@vitest/coverage-v8": "^3.2.4", - "@vitest/ui": "^3.2.4", - "jsdom": "^27.0.1", - "prettier": "^3.6.2", + "@vitejs/plugin-basic-ssl": "^2.1.4", + "@vitest/coverage-v8": "^4.0.18", + "@vitest/ui": "^4.0.18", + "eslint": "^10.0.2", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.4.0", + "husky": "^9.1.7", + "jsdom": "^28.1.0", + "lint-staged": "^16.3.1", + "prettier": "^3.8.1", "typescript": "~5.9.3", - "vite": "^7.1.11", - "vite-plugin-node-polyfills": "^0.24.0", - "vitest": "^3.2.4" + "typescript-eslint": "^8.56.1", + "vite": "^7.3.1", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-handlebars": "^2.0.0", + "vite-plugin-node-polyfills": "^0.25.0", + "vitepress": "^1.6.4", + "vitest": "^4.0.18", + "vue": "^3.5.29" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } + "license": "MIT" }, - "node_modules/@asamuzakjp/css-color": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", - "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "node_modules/@algolia/abtesting": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.12.2.tgz", + "integrity": "sha512-oWknd6wpfNrmRcH0vzed3UPX0i17o4kYLM5OMITyMVM2xLgaRbIafoxL0e8mcrNNb0iORCJA0evnNDKRYth5WQ==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.1" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.46.2.tgz", + "integrity": "sha512-oRSUHbylGIuxrlzdPA8FPJuwrLLRavOhAmFGgdAvMcX47XsyM+IOGa9tc7/K5SPvBqn4nhppOCEz7BrzOPWc4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, "engines": { - "node": "20 || >=22" + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.46.2.tgz", + "integrity": "sha512-EPBN2Oruw0maWOF4OgGPfioTvd+gmiNwx0HmD9IgmlS+l75DatcBkKOPNJN+0z3wBQWUO5oq602ATxIfmTQ8bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.46.2.tgz", + "integrity": "sha512-Hj8gswSJNKZ0oyd0wWissqyasm+wTz1oIsv5ZmLarzOZAp3vFEda8bpDQ8PUhO+DfkbiLyVnAxsPe4cGzWtqkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.46.2.tgz", + "integrity": "sha512-6dBZko2jt8FmQcHCbmNLB0kCV079Mx/DJcySTL3wirgDBUH7xhY1pOuUTLMiGkqM5D8moVZTvTdRKZUJRkrwBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.46.2.tgz", + "integrity": "sha512-1waE2Uqh/PHNeDXGn/PM/WrmYOBiUGSVxAWqiJIj73jqPqvfzZgzdakHscIVaDl6Cp+j5dwjsZ5LCgaUr6DtmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.46.2.tgz", + "integrity": "sha512-EgOzTZkyDcNL6DV0V/24+oBJ+hKo0wNgyrOX/mePBM9bc9huHxIY2352sXmoZ648JXXY2x//V1kropF/Spx83w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.46.2.tgz", + "integrity": "sha512-ZsOJqu4HOG5BlvIFnMU0YKjQ9ZI6r3C31dg2jk5kMWPSdhJpYL9xa5hEe7aieE+707dXeMI4ej3diy6mXdZpgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.46.2", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.46.2.tgz", + "integrity": "sha512-1Uw2OslTWiOFDtt83y0bGiErJYy5MizadV0nHnOoHFWMoDqWW0kQoMFI65pXqRSkVvit5zjXSLik2xMiyQJDWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.46.2", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.46.2.tgz", + "integrity": "sha512-xk9f+DPtNcddWN6E7n1hyNNsATBCHIqAvVGG2EAGHJc4AFYL18uM/kMTiOKXE/LKDPyy1JhIerrh9oYb7RBrgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.46.2.tgz", + "integrity": "sha512-NApbTPj9LxGzNw4dYnZmj2BoXiAc8NmbbH6qBNzQgXklGklt/xldTvu+FACN6ltFsTzoNU6j2mWNlHQTKGC5+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.46.2.tgz", + "integrity": "sha512-ekotpCwpSp033DIIrsTpYlGUCF6momkgupRV/FA3m62SreTSZUKjgK6VTNyG7TtYfq9YFm/pnh65bATP/ZWJEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.46.2.tgz", + "integrity": "sha512-gKE+ZFi/6y7saTr34wS0SqYFDcjHW4Wminv8PDZEi0/mE99+hSrbKgJWxo2ztb5eqGirQTgIh1AMVacGGWM1iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.46.2.tgz", + "integrity": "sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.2.tgz", - "integrity": "sha512-ccKogJI+0aiDhOahdjANIc9SDixSud1gbwdVrhn7kMopAtLXqsz9MKmQQtIl6Y5aC2IYq+j4dz/oedL2AVMmVQ==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", "dev": true, "license": "MIT", "dependencies": { @@ -107,17 +414,7 @@ "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.2" - } - }, - "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" + "lru-cache": "^11.2.6" } }, "node_modules/@asamuzakjp/nwsapi": { @@ -161,12 +458,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -175,1413 +472,19 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", - "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz", - "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", - "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", - "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-flow": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", - "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", - "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.0", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.0", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.3", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.0", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.3", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-flow": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.27.1.tgz", - "integrity": "sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-flow-strip-types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.28.3.tgz", - "integrity": "sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.6", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1601,10 +504,68 @@ "node": ">=18" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", + "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", + "license": "MIT" + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", + "integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.1.2", + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.2.tgz", + "integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz", + "integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.2.tgz", + "integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==", + "license": "Apache-2.0" + }, "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true, "funding": [ { @@ -1618,13 +579,13 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", "dev": true, "funding": [ { @@ -1638,17 +599,17 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", "dev": true, "funding": [ { @@ -1662,21 +623,21 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "funding": [ { @@ -1689,18 +650,17 @@ } ], "license": "MIT", - "peer": true, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", - "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.28.tgz", + "integrity": "sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==", "dev": true, "funding": [ { @@ -1712,18 +672,12 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } + "license": "MIT-0" }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, "funding": [ { @@ -1736,19 +690,69 @@ } ], "license": "MIT", - "peer": true, "engines": { - "node": ">=18" + "node": ">=20.19.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } } }, "node_modules/@embedpdf/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.5.0.tgz", - "integrity": "sha512-Yrh9XoVaT8cUgzgqpJ7hx5wg6BqQrCFirqqlSwVb+Ly9oNn4fZbR9GycIWmzJOU5XBnaOJjXfQSaDyoNP0woNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-2.3.0.tgz", + "integrity": "sha512-aPD7lNSCOLc5Nos9xGA3qAT5jFZdrTT7IVcpxtM1BOKa1FI0XmotJ8vgzcRxH/FLwUASC4xwR9QxzTKp2aLsZQ==", "license": "MIT", "dependencies": { - "@embedpdf/engines": "1.5.0", - "@embedpdf/models": "1.5.0" + "@embedpdf/engines": "2.3.0", + "@embedpdf/models": "2.3.0" }, "peerDependencies": { "preact": "^10.26.4", @@ -1759,13 +763,20 @@ } }, "node_modules/@embedpdf/engines": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.5.0.tgz", - "integrity": "sha512-/GzhjHFHWfOaX7vjgFJX/pyq668wYjoda1bZ9MpwF/EF000Wwy2Q0AOhprjldPFz8ASKjwKwqsXmaqrK99yOAQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-2.3.0.tgz", + "integrity": "sha512-QxNY58E2HgNgnbsTt5TnDUNvKoyabkf5IniGsiN5+rx6f4SFDpCnz3h1VJxNReWDyn9e16QlkUfgXX0qQWd3iQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0", - "@embedpdf/pdfium": "1.5.0" + "@embedpdf/fonts-arabic": "1.0.0", + "@embedpdf/fonts-hebrew": "1.0.0", + "@embedpdf/fonts-jp": "1.0.0", + "@embedpdf/fonts-kr": "1.0.0", + "@embedpdf/fonts-latin": "1.0.0", + "@embedpdf/fonts-sc": "1.0.0", + "@embedpdf/fonts-tc": "1.0.0", + "@embedpdf/models": "2.3.0", + "@embedpdf/pdfium": "2.3.0" }, "peerDependencies": { "preact": "^10.26.4", @@ -1775,81 +786,161 @@ "vue": ">=3.2.0" } }, + "node_modules/@embedpdf/fonts-arabic": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@embedpdf/fonts-arabic/-/fonts-arabic-1.0.0.tgz", + "integrity": "sha512-SnGvQb+LwPZQO2WjjvlmXrJZolJUfLYbLZQSaYUw1vrQyMyJKT4LewvJGG+hZ+Yz2fz7OMIQ+4Gc98mGODZtOg==", + "license": "OFL-1.1" + }, + "node_modules/@embedpdf/fonts-hebrew": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@embedpdf/fonts-hebrew/-/fonts-hebrew-1.0.0.tgz", + "integrity": "sha512-5HVAKGL7VqPeTxxADDrSqAFBxfmAXdP8fIqrPwJIKkqdK2643bOer8CqnnpO3/nPoFhkzxhttWMB9BGiqSW62w==", + "license": "OFL-1.1" + }, + "node_modules/@embedpdf/fonts-jp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@embedpdf/fonts-jp/-/fonts-jp-1.0.0.tgz", + "integrity": "sha512-BY2tv/mcICUUKf+M/bizf3RU65PMqKClJ/e5o9mgMibxyML0OQvEDwYMRPODQkKgJKXCO3ScHmVvcmXp6kt+fA==", + "license": "OFL-1.1" + }, + "node_modules/@embedpdf/fonts-kr": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@embedpdf/fonts-kr/-/fonts-kr-1.0.0.tgz", + "integrity": "sha512-bh88HXSvOBS581kgmihWY7Ijp9hBsvlmXogFG5LSNx9UBAobRcakZiFMGieRBc06hUSkpo7WhjaFM/z/SfQ8dQ==", + "license": "OFL-1.1" + }, + "node_modules/@embedpdf/fonts-latin": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@embedpdf/fonts-latin/-/fonts-latin-1.0.0.tgz", + "integrity": "sha512-LLYysdr8O6sRNzhmW3PbF3AeA8xnqvOi4XLFfIfNlW5uEZ+qsJdcfd78Q78sFJMhlaOAYFMziMMsnOzmx463rA==", + "license": "OFL-1.1" + }, + "node_modules/@embedpdf/fonts-sc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@embedpdf/fonts-sc/-/fonts-sc-1.0.0.tgz", + "integrity": "sha512-ETXl7XCwaQLSSvMO3EUDwMNqtL64kX2LlFxarTRi/NsIGGOIxUurGfKtrkmtnKHrWy1jAJSt6oxK2uJhvdvQIw==", + "license": "OFL-1.1" + }, + "node_modules/@embedpdf/fonts-tc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@embedpdf/fonts-tc/-/fonts-tc-1.0.0.tgz", + "integrity": "sha512-rGZJbVD6DYS5BbXdpEMnWkpVF0Knar+bsiyb2o3+YRx7O8eyFubEBQUSUInirQk69HA6fc3GhYCg7TyC/oD76Q==", + "license": "OFL-1.1" + }, "node_modules/@embedpdf/models": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.5.0.tgz", - "integrity": "sha512-x/1li3jdag+IzfZkcfRLKLqASLep4v6dgVi3z0JArwaicFra8k1IY2xaVTrwcZyx7pRb/rxvoO9yLHW0Y34NFw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-2.3.0.tgz", + "integrity": "sha512-YAH3YdXl/UOhVcvMPd6mtU+tJ3veh24Q5swRDfuWUsJ3L2CcAG2P+4pjj4EAwvWUQcmN/HlVOjVQL0PkbkytKw==", "license": "MIT" }, "node_modules/@embedpdf/pdfium": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.5.0.tgz", - "integrity": "sha512-PI32t2U4ThZC907n2Iwr8E5WqmC574G83u3V9ysNFl29N9kasrY9RiLSzU4W/yQvXPjIbpQHBsbMKXLjCFBI9w==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-2.3.0.tgz", + "integrity": "sha512-AIWHDDG24we1r8sWVO9Uae6V2ISXji2gIkZS3+CjtYowaBCpMTSu4QEQRnjQam2EWrEMVIJOXwBfx11TZKrxWA==", "license": "MIT" }, "node_modules/@embedpdf/plugin-annotation": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.5.0.tgz", - "integrity": "sha512-mxEPI6xYwOGaf9fYfoywuj6nwA10eHFPBuN066MzwphDk6DOHJGZ3Vq8zNQBXh20c/Lb25PL718D7MZWxZLUHg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-2.3.0.tgz", + "integrity": "sha512-TIN/OiDTg5tCNsebp1SWnS6aa7nnDvRrrZe3jx7Sg5IMEiZc6P3z+0aOjJtvoz0cp3Xi7Bb0PQsTLwo+bdfpVg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0", - "@embedpdf/utils": "1.5.0" + "@embedpdf/models": "2.3.0", + "@embedpdf/utils": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-history": "1.5.0", - "@embedpdf/plugin-interaction-manager": "1.5.0", - "@embedpdf/plugin-selection": "1.5.0", + "@embedpdf/core": "2.3.0", + "@embedpdf/plugin-history": "2.3.0", + "@embedpdf/plugin-interaction-manager": "2.3.0", + "@embedpdf/plugin-selection": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", + "svelte": ">=5 <6", "vue": ">=3.2.0" } }, "node_modules/@embedpdf/plugin-attachment": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-attachment/-/plugin-attachment-1.5.0.tgz", - "integrity": "sha512-ByIEUDIR7C9H8CnzqsyTFuVOmD7tVme9iHBR668STAuQuK59T23OZbLKWeVzp+iB1o6w7ARxSRejQ3MesvcMMA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-attachment/-/plugin-attachment-2.3.0.tgz", + "integrity": "sha512-ADZh4Fqm/n7FzwVo9YeogEePzbS0Novnn6ZF+RV25NF0hDnHjTVaCNitk6tae3R6qhhMSSx+vAC3DHHNhhwI1A==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "preact": "^10.26.4", - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@embedpdf/plugin-bookmark": { - "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.5.0" - }, - "peerDependencies": { - "@embedpdf/core": "1.5.0", + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", + "svelte": ">=5 <6", + "vue": ">=3.2.0" + } + }, + "node_modules/@embedpdf/plugin-bookmark": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-2.3.0.tgz", + "integrity": "sha512-7XO2NntgRb/Jk1XN/EOf7+yVaOPVVFvBuF0xlCqnz2BGAnMNrTn8QE73FtluJBgNhuK9LwDT2C4W+BTD2gd59Q==", + "license": "MIT", + "dependencies": { + "@embedpdf/models": "2.3.0" + }, + "peerDependencies": { + "@embedpdf/core": "2.3.0", + "preact": "^10.26.4", + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "svelte": ">=5 <6", "vue": ">=3.2.0" } }, "node_modules/@embedpdf/plugin-capture": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-capture/-/plugin-capture-1.5.0.tgz", - "integrity": "sha512-h9pZ7x+pXjJYMkmXMwbnTNl5+S2IzSYbJUMMVYG++pSAXzeeNjr2z1XiSzjvNCK/x0ChwEDV6tZHfyCNV74Jjw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-capture/-/plugin-capture-2.3.0.tgz", + "integrity": "sha512-Q4Btp8f1lafJx32laxCGaX3H3oPfuuwg1I/pbm7wVmlzr+rsnCqqlQzzVqBI/EnCgn5kHH6vPHRogsP0KykvQg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-interaction-manager": "1.5.0", - "@embedpdf/plugin-render": "1.5.0", + "@embedpdf/core": "2.3.0", + "@embedpdf/plugin-interaction-manager": "2.3.0", + "@embedpdf/plugin-render": "2.3.0", + "preact": "^10.26.4", + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "svelte": ">=5 <6", + "vue": ">=3.2.0" + } + }, + "node_modules/@embedpdf/plugin-commands": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-commands/-/plugin-commands-2.3.0.tgz", + "integrity": "sha512-zji1CsEk1nEiPS9bGQw02SewaYVZgTs3i561jLRiW+61IuXy9lCBCjsVrkQsRf0RQKydB1UykDw37ibxue68tg==", + "license": "MIT", + "dependencies": { + "@embedpdf/models": "2.3.0" + }, + "peerDependencies": { + "@embedpdf/core": "2.3.0", + "preact": "^10.26.4", + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "svelte": ">=5 <6", + "vue": ">=3.2.0" + } + }, + "node_modules/@embedpdf/plugin-document-manager": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-document-manager/-/plugin-document-manager-2.3.0.tgz", + "integrity": "sha512-hdKaWU1sjlLgXo2iWF4N734lklCfSO5Tj1xqk+0omxOpnVL1Ed5fzFO2N584pMkfFn1xo9Y2JPHSUtCdzF7/EQ==", + "license": "MIT", + "dependencies": { + "@embedpdf/models": "2.3.0" + }, + "peerDependencies": { + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1858,15 +949,15 @@ } }, "node_modules/@embedpdf/plugin-export": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.5.0.tgz", - "integrity": "sha512-luk68mNW9l2X31qk4b02phKaqDl9aDXUAgHVz1EWrgwXQ3Oz9WEdu60utYARYDiepDo3Caadll8RwctYSf/anA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-2.3.0.tgz", + "integrity": "sha512-Xa048lKnc1jehWbaWv5qER1RVIHhHqt+JhgzAlqFSURXmzowbUzVEDBZ7fYImXRkpqp+ZeyBhWfZ60DBNE55Cw==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1875,15 +966,15 @@ } }, "node_modules/@embedpdf/plugin-fullscreen": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-fullscreen/-/plugin-fullscreen-1.5.0.tgz", - "integrity": "sha512-n2oIhc33vYgdKNaU4ZMYWt1CnNKxdDsZTUHtNK/K/dOywDFPNnVmnEjerdwfmT1Iyf+HJ9UzXQHFO1oODXBlIg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-fullscreen/-/plugin-fullscreen-2.3.0.tgz", + "integrity": "sha512-jFDVwW8qphDZ6HQS0iCfYVKHEoaY90vnqcGXFdU0y+hH85PGywbWPy4RRgoHo1CbKsV4xJIyazydHuuDDZicnQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1892,31 +983,15 @@ } }, "node_modules/@embedpdf/plugin-history": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.5.0.tgz", - "integrity": "sha512-p7PTNNaIr4gH3jLwX+eLJe1DeUXgi21kVGN6SRx/pocH8esg4jqoOeD/YiRRZoZnPOiy0jBXVhkPkwSmY7a2hQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-2.3.0.tgz", + "integrity": "sha512-+fr/kjK2Z9BiC53IMlUZvWjkD6iilcI3XCUKQPXRgS5MDAuwpVlgdAtc+3VAMlG3IddElxVFdvvxRO9R89k5Mg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "preact": "^10.26.4", - "react": ">=16.8.0", - "react-dom": ">=16.8.0", - "vue": ">=3.2.0" - } - }, - "node_modules/@embedpdf/plugin-interaction-manager": { - "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.5.0" - }, - "peerDependencies": { - "@embedpdf/core": "1.5.0", + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1924,16 +999,33 @@ "vue": ">=3.2.0" } }, - "node_modules/@embedpdf/plugin-loader": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.5.0.tgz", - "integrity": "sha512-P4YpIZfaW69etYIjphyaL4cGl2pB14h3OdTE0tRQ2pZYZHFLTvlt4q9B3PVSdhlSrHK5nob7jfLGon2U7xCslg==", + "node_modules/@embedpdf/plugin-i18n": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-i18n/-/plugin-i18n-2.3.0.tgz", + "integrity": "sha512-OsrYlbgEh21u65SYP5BMssTudEM6Ysl1Te5z1nttT80w28xa9y1QgcO5g7RcVL4/Fqx5Ok5MznAtMRhScTx6kw==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", + "@embedpdf/core": "2.3.0", + "preact": "^10.26.4", + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "svelte": ">=5 <6", + "vue": ">=3.2.0" + } + }, + "node_modules/@embedpdf/plugin-interaction-manager": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-2.3.0.tgz", + "integrity": "sha512-1/tDLPoQm6skNe/WOd6QD7SA0XRKphbJHi/s9XY4fhGgBvlD5XHFrYxtmrsaheYjqIBFtAWWZ3m5lAXRaO/igA==", + "license": "MIT", + "dependencies": { + "@embedpdf/models": "2.3.0" + }, + "peerDependencies": { + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1942,17 +1034,17 @@ } }, "node_modules/@embedpdf/plugin-pan": { - "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==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-2.3.0.tgz", + "integrity": "sha512-5yGxLpn28PHKCYx3tjzeVir7D5vHZ0Fk9HJRJr4K+Uqbg8pYFavb9tseXzPE4FcqpejqZo2DZyfo54ErQFXEyQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-interaction-manager": "1.5.0", - "@embedpdf/plugin-viewport": "1.5.0", + "@embedpdf/core": "2.3.0", + "@embedpdf/plugin-interaction-manager": "2.3.0", + "@embedpdf/plugin-viewport": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1961,15 +1053,15 @@ } }, "node_modules/@embedpdf/plugin-print": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-1.5.0.tgz", - "integrity": "sha512-rjorvNxAZfO9X4cFZVU9fHnldMWqMceJGmr3mH+yj7KdHePvNDDP+omyZyZKtxlUZENaeDI2h6k5z0GbhBz6sQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-2.3.0.tgz", + "integrity": "sha512-LNxvXm3rZkRXXC41IArBDiwPLzSflmBmxxi+L+91xvw8n/FWUeXfWwQn7oQEAGq9Ha/3pEVHTls48QSFZN0mhg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=18.0.0", "react-dom": ">=18.0.0", @@ -1978,34 +1070,35 @@ } }, "node_modules/@embedpdf/plugin-redaction": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-1.5.0.tgz", - "integrity": "sha512-txiukr5UKAGvJzl6dVBmmIT1v3r/t4e2qYm1hqU2faGgNCa2dwk79x9mDBlvWwxlJXCDFuFE+7Ps9/nU6qmU2w==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-2.3.0.tgz", + "integrity": "sha512-un6AQL5Pqcm9v1tCV9Mb3NeowsGUtlCT/198k4nd+SWOMWNsbuFqI+rWOGV3auqXRGSzKj0gnt29t8aaeLpLeA==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0", - "@embedpdf/utils": "1.5.0" + "@embedpdf/models": "2.3.0", + "@embedpdf/utils": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-interaction-manager": "1.5.0", - "@embedpdf/plugin-selection": "1.5.0", + "@embedpdf/core": "2.3.0", + "@embedpdf/plugin-interaction-manager": "2.3.0", + "@embedpdf/plugin-selection": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", + "svelte": ">=5 <6", "vue": ">=3.2.0" } }, "node_modules/@embedpdf/plugin-render": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.5.0.tgz", - "integrity": "sha512-ywwSj0ByrlkvrJIHKRzqxARkOZriki8VJUC+T4MV8fGyF4CzvCRJyKlPktahFz+VxhoodqTh7lBCib68dH+GvA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-2.3.0.tgz", + "integrity": "sha512-UyQncK5NTokuEVISUcxPOXpZP4SItn4MjfeEaPsTXJkSRjHL4g3mU3iWy0nXJMCOT10OB+5m7qQ0/KkF4f+b5w==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -2014,15 +1107,15 @@ } }, "node_modules/@embedpdf/plugin-rotate": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.5.0.tgz", - "integrity": "sha512-5EmBCsq0VfrE3xWY6ofuVm8S6aK95EbAycRIk1wczcmTdvpsuXZ6P2ZaECUgYMcpZ6uAg4/kGf8X8VVZuCihSQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-2.3.0.tgz", + "integrity": "sha512-vibDXHA0L2LlMrmkSuanmdtUpc2JPBuQybiGwf9F4wlleKN3f7uSWxZsHdVAxWdzsaG+/26QTGl75otZLnVuig==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -2031,16 +1124,16 @@ } }, "node_modules/@embedpdf/plugin-scroll": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.5.0.tgz", - "integrity": "sha512-RNmTZCZ8X1mA8cw9M7TMDuhO9GtkOalGha2bBL3En3D1IlDRS7PzNNMSMV7eqT7OQICSTltlpJ8p8Qi5esvL/Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-2.3.0.tgz", + "integrity": "sha512-8pdaSY9QuqdX22Ykw2jKn07Rx6FIsDdj/O0+mlbccY/ISofj9WEFNeQgnOY64OUTDyurJYqpYvq6QqvgbGLs+A==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-viewport": "1.5.0", + "@embedpdf/core": "2.3.0", + "@embedpdf/plugin-viewport": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -2049,16 +1142,15 @@ } }, "node_modules/@embedpdf/plugin-search": { - "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==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-2.3.0.tgz", + "integrity": "sha512-VNXmNf7fIIRWGVwf2kIUeUeLkUTJlq9AGjUO2TyuYJTWTsmfT4LEqPDDpwC6NDVFhzWE6xwbb3bxvY/9bqBMzw==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-loader": "1.5.0", + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -2067,17 +1159,17 @@ } }, "node_modules/@embedpdf/plugin-selection": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.5.0.tgz", - "integrity": "sha512-zrxLBAZQoPswDuf9q9DrYaQc6B0Ysc2U1hueTjNH/4+ydfl0BFXZkKR63C2e3YmWtXvKjkoIj0GyPzsiBORLUw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-2.3.0.tgz", + "integrity": "sha512-+emaY4vff3ynAf5C3PfCOlleQIqiImbBpb6zkG5SVUa9Vn5x0SfYGT4Jumtbzq8XBknC1QIRKVlplC9BcnjcmQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0", + "@embedpdf/utils": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-interaction-manager": "1.5.0", - "@embedpdf/plugin-viewport": "1.5.0", + "@embedpdf/core": "2.3.0", + "@embedpdf/plugin-interaction-manager": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -2086,16 +1178,15 @@ } }, "node_modules/@embedpdf/plugin-spread": { - "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==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-2.3.0.tgz", + "integrity": "sha512-sFqYKwzKGPaCXn6hAyv6GHdVTlL2vg3poxRNd2W5kLQo07YtHlSjXr/XAhaGT/a4GtR9rtbSJ4hWNJjzIcwE0g==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-loader": "1.5.0", + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -2104,16 +1195,16 @@ } }, "node_modules/@embedpdf/plugin-thumbnail": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.5.0.tgz", - "integrity": "sha512-Z2qpyyr5s2M6460KDGu1Vk6rdbQFIoCpnyFAT6e7UaTIKkqJSNpmjqMsBU5PosYCFu/cClpHPvS7tg9/IKAk6g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-2.3.0.tgz", + "integrity": "sha512-CAOnipeBtdKSHGBuIm5420GykUw7k2rB7Z9GwouTbbycS7Cw+kiaGpOfHfenoKPTlWMkHYAwFcZiWKV3XG/nRQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-render": "1.5.0", + "@embedpdf/core": "2.3.0", + "@embedpdf/plugin-render": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -2122,18 +1213,18 @@ } }, "node_modules/@embedpdf/plugin-tiling": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.5.0.tgz", - "integrity": "sha512-0Vx9elHNpMM+zv8hEoZXBEm8Q0+4kU52LxOlTYRr1A5FskF836sUct6g1ngwK1bmfbAfpz+62PnYI2EeilDZig==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-2.3.0.tgz", + "integrity": "sha512-6VJ042WksIyZVWyvXq1nf0Ct+U4Pl6+QUDy1ThJefwk/HKDfWU2zEr/+1STJKVWgfUx5QRdipf6Jghd+HnOg3Q==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-render": "1.5.0", - "@embedpdf/plugin-scroll": "1.5.0", - "@embedpdf/plugin-viewport": "1.5.0", + "@embedpdf/core": "2.3.0", + "@embedpdf/plugin-render": "2.3.0", + "@embedpdf/plugin-scroll": "2.3.0", + "@embedpdf/plugin-viewport": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -2142,30 +1233,35 @@ } }, "node_modules/@embedpdf/plugin-ui": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-ui/-/plugin-ui-1.5.0.tgz", - "integrity": "sha512-4zW6sRz1b+extrcDxy2gOz01sG7GkuxBUu/sJVpKnBrKzBNix2smzY8SK25nkJY6zT+iP+cPdUoN/r4Atd8Ppg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-ui/-/plugin-ui-2.3.0.tgz", + "integrity": "sha512-TTCMBMzQBvD10OiW2v2ptyoVO0bPNAGfL6uHrgaVqUeWr1LlADKrbC+KaSbpUqoMxpPE4knVPi1F3g9X47G69A==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", + "@embedpdf/core": "2.3.0", + "@embedpdf/plugin-render": "2.3.0", + "@embedpdf/plugin-scroll": "2.3.0", + "@embedpdf/plugin-viewport": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "react-dom": ">=16.8.0", + "svelte": ">=5 <6", + "vue": ">=3.2.0" } }, "node_modules/@embedpdf/plugin-viewport": { - "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==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-2.3.0.tgz", + "integrity": "sha512-3NQp3hVfRF7DMUPNAVOfZsqQQrugEfY0voRUrQI90eyi16GFntN3CP9Mc5cOp2jnUICMYlirQ/om+KCseMHS2Q==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", + "@embedpdf/core": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -2174,19 +1270,17 @@ } }, "node_modules/@embedpdf/plugin-zoom": { - "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==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-2.3.0.tgz", + "integrity": "sha512-wnBqK02ku0zCViqQfSD1Vohy+aBUogXrqUTwo1/1QFEphmgnCHnHbEUduh9M0ghcT4s26pBgbJqCrShpGYAdvQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.5.0", - "hammerjs": "^2.0.8" + "@embedpdf/models": "2.3.0" }, "peerDependencies": { - "@embedpdf/core": "1.5.0", - "@embedpdf/plugin-interaction-manager": "1.5.0", - "@embedpdf/plugin-scroll": "1.5.0", - "@embedpdf/plugin-viewport": "1.5.0", + "@embedpdf/core": "2.3.0", + "@embedpdf/plugin-scroll": "2.3.0", + "@embedpdf/plugin-viewport": "2.3.0", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -2195,21 +1289,22 @@ } }, "node_modules/@embedpdf/utils": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.5.0.tgz", - "integrity": "sha512-L6jsAPQPGM8ne+MMFAd5gqXb1RNEgNyh16VvVUVKcVnJlBhwil59nVeEQ0cwPhjF5qVeY6MQDIOjBzJqkgXOYg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-2.3.0.tgz", + "integrity": "sha512-9DV+tu+GsnijchNSG/NzslnxTGIUH6j2MxBR8QOoZLsWETEVaMLkHtbvzXPyMOx/5RlvBn8wR0jNKTNptOCnXQ==", "license": "MIT", "peerDependencies": { "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", + "svelte": ">=5 <6", "vue": ">=3.2.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", - "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -2223,9 +1318,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", - "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -2239,9 +1334,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", - "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -2255,9 +1350,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", - "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -2271,9 +1366,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", - "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -2287,9 +1382,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", - "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -2303,9 +1398,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", - "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -2319,9 +1414,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", - "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -2335,9 +1430,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", - "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -2351,9 +1446,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", - "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -2367,9 +1462,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", - "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -2383,9 +1478,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", - "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -2399,9 +1494,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", - "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -2415,9 +1510,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", - "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -2431,9 +1526,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", - "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -2447,9 +1542,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", - "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -2463,9 +1558,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", - "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -2479,9 +1574,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", - "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -2495,9 +1590,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", - "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -2511,9 +1606,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", - "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -2527,9 +1622,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", - "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -2543,9 +1638,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", - "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -2559,9 +1654,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", - "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -2575,9 +1670,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", - "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -2591,9 +1686,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", - "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -2607,9 +1702,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", - "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -2622,6 +1717,152 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", + "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.2", + "debug": "^4.3.1", + "minimatch": "^10.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", + "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", + "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", + "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.14.1.tgz", + "integrity": "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@fontsource/cedarville-cursive": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@fontsource/cedarville-cursive/-/cedarville-cursive-5.2.7.tgz", @@ -2685,6 +1926,85 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.65", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.65.tgz", + "integrity": "sha512-v/O0UeqrDz6ASuRVE5g2Puo5aWyej4M/CxX6WYDBARgswwxK0mp3VQbGgPFEAAUU9QN02IjTgjMuO021gpWf2w==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2702,95 +2022,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2846,10 +2077,73 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kenjiuno/decompressrtf": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@kenjiuno/decompressrtf/-/decompressrtf-0.1.4.tgz", + "integrity": "sha512-v9c/iFz17jRWyd2cRnrvJg4VOg/4I/VCk+bG8JnoX2gJ9sAesPzo3uTqcmlVXdpasTI8hChpBVw00pghKe3qTQ==", + "license": "BSD-2-Clause" + }, + "node_modules/@kenjiuno/msgreader": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@kenjiuno/msgreader/-/msgreader-1.28.0.tgz", + "integrity": "sha512-+iv2rWCGRHmX/3sBwXZzkThEuuywGJjnYsvxj6Kp1L/FDMICQcFrtqN+6MFrnh2d+umtfGtX904wxaYEDZ52MQ==", + "license": "Apache-2.0", + "dependencies": { + "@kenjiuno/decompressrtf": "^0.1.3", + "iconv-lite": "^0.6.3" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz", + "integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@matbee/libreoffice-converter": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@matbee/libreoffice-converter/-/libreoffice-converter-2.5.0.tgz", + "integrity": "sha512-uOJhx6GL3xbiQp5KBAUU7PcdXvj2/aTBXEtnFQQ5QI5jh8vReodFoBTToxRDaqhLxz4YjKTY22tc15IAa7qHJA==", + "license": "MPL-2.0", + "dependencies": { + "zod": "^4.1.13" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "sharp": ">=0.32.0" + }, + "peerDependenciesMeta": { + "sharp": { + "optional": true + } + } + }, + "node_modules/@mermaid-js/parser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz", + "integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==", + "license": "MIT", + "dependencies": { + "langium": "^4.0.0" + } + }, "node_modules/@napi-rs/canvas": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", - "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.88.tgz", + "integrity": "sha512-/p08f93LEbsL5mDZFQ3DBxcPv/I4QG9EDYRRq1WNlCOXVfAHBTHMSVMwxlqG/AtnSfUr9+vgfN7MKiyDo0+Weg==", "license": "MIT", "optional": true, "workspaces": [ @@ -2858,23 +2152,28 @@ "engines": { "node": ">= 10" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.80", - "@napi-rs/canvas-darwin-arm64": "0.1.80", - "@napi-rs/canvas-darwin-x64": "0.1.80", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.80", - "@napi-rs/canvas-linux-arm64-musl": "0.1.80", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80", - "@napi-rs/canvas-linux-x64-gnu": "0.1.80", - "@napi-rs/canvas-linux-x64-musl": "0.1.80", - "@napi-rs/canvas-win32-x64-msvc": "0.1.80" + "@napi-rs/canvas-android-arm64": "0.1.88", + "@napi-rs/canvas-darwin-arm64": "0.1.88", + "@napi-rs/canvas-darwin-x64": "0.1.88", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.88", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.88", + "@napi-rs/canvas-linux-arm64-musl": "0.1.88", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.88", + "@napi-rs/canvas-linux-x64-gnu": "0.1.88", + "@napi-rs/canvas-linux-x64-musl": "0.1.88", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.88", + "@napi-rs/canvas-win32-x64-msvc": "0.1.88" } }, "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz", - "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.88.tgz", + "integrity": "sha512-KEaClPnZuVxJ8smUWjV1wWFkByBO/D+vy4lN+Dm5DFH514oqwukxKGeck9xcKJhaWJGjfruGmYGiwRe//+/zQQ==", "cpu": [ "arm64" ], @@ -2885,12 +2184,16 @@ ], "engines": { "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz", - "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.88.tgz", + "integrity": "sha512-Xgywz0dDxOKSgx3eZnK85WgGMmGrQEW7ZLA/E7raZdlEE+xXCozobgqz2ZvYigpB6DJFYkqnwHjqCOTSDGlFdg==", "cpu": [ "arm64" ], @@ -2901,12 +2204,16 @@ ], "engines": { "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz", - "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.88.tgz", + "integrity": "sha512-Yz4wSCIQOUgNucgk+8NFtQxQxZV5NO8VKRl9ePKE6XoNyNVC8JDqtvhh3b3TPqKK8W5p2EQpAr1rjjm0mfBxdg==", "cpu": [ "x64" ], @@ -2917,12 +2224,16 @@ ], "engines": { "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz", - "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.88.tgz", + "integrity": "sha512-9gQM2SlTo76hYhxHi2XxWTAqpTOb+JtxMPEIr+H5nAhHhyEtNmTSDRtz93SP7mGd2G3Ojf2oF5tP9OdgtgXyKg==", "cpu": [ "arm" ], @@ -2933,12 +2244,16 @@ ], "engines": { "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz", - "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.88.tgz", + "integrity": "sha512-7qgaOBMXuVRk9Fzztzr3BchQKXDxGbY+nwsovD3I/Sx81e+sX0ReEDYHTItNb0Je4NHbAl7D0MKyd4SvUc04sg==", "cpu": [ "arm64" ], @@ -2949,12 +2264,16 @@ ], "engines": { "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz", - "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.88.tgz", + "integrity": "sha512-kYyNrUsHLkoGHBc77u4Unh067GrfiCUMbGHC2+OTxbeWfZkPt2o32UOQkhnSswKd9Fko/wSqqGkY956bIUzruA==", "cpu": [ "arm64" ], @@ -2965,12 +2284,16 @@ ], "engines": { "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz", - "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.88.tgz", + "integrity": "sha512-HVuH7QgzB0yavYdNZDRyAsn/ejoXB0hn8twwFnOqUbCCdkV+REna7RXjSR7+PdfW0qMQ2YYWsLvVBT5iL/mGpw==", "cpu": [ "riscv64" ], @@ -2981,12 +2304,16 @@ ], "engines": { "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz", - "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.88.tgz", + "integrity": "sha512-hvcvKIcPEQrvvJtJnwD35B3qk6umFJ8dFIr8bSymfrSMem0EQsfn1ztys8ETIFndTwdNWJKWluvxztA41ivsEw==", "cpu": [ "x64" ], @@ -2997,12 +2324,16 @@ ], "engines": { "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz", - "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.88.tgz", + "integrity": "sha512-eSMpGYY2xnZSQ6UxYJ6plDboxq4KeJ4zT5HaVkUnbObNN6DlbJe0Mclh3wifAmquXfrlgTZt6zhHsUgz++AK6g==", "cpu": [ "x64" ], @@ -3013,12 +2344,36 @@ ], "engines": { "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-arm64-msvc": { + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.88.tgz", + "integrity": "sha512-qcIFfEgHrchyYqRrxsCeTQgpJZ/GqHiqPcU/Fvw/ARVlQeDX1VyFH+X+0gCR2tca6UJrq96vnW+5o7buCq+erA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz", - "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.88.tgz", + "integrity": "sha512-ROVqbfS4QyZxYkqmaIBBpbz/BQvAR+05FXM5PAtTYVc0uyY8Y4BHJSMdGAaMf6TdIVRsQsiq+FG/dH9XhvWCFQ==", "cpu": [ "x64" ], @@ -3029,6 +2384,10 @@ ], "engines": { "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@neslinesli93/qpdf-wasm": { @@ -3064,6 +2423,12 @@ "pako": "^1.0.10" } }, + "node_modules/@phosphor-icons/web": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@phosphor-icons/web/-/web-2.1.2.tgz", + "integrity": "sha512-rPAR9o/bEcp4Cw4DEeZHXf+nlGCMNGkNDRizYHM47NLxz9vvEHp/Tt6FMK1NcWadzw/pFDPnRBGi/ofRya958A==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3081,6 +2446,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@retejs/lit-plugin": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@retejs/lit-plugin/-/lit-plugin-2.0.7.tgz", + "integrity": "sha512-jnrZ10lwmoxCi9eqViblAi7D8VxMrsGiS/tkn55YOR19xoGxF2Z9rzmdg0RfR8qsNHubHsQDjDFmGbL8tbDmbA==", + "license": "MIT", + "peerDependencies": { + "lit": "^3.0.0", + "rete": "^2.0.0", + "rete-area-plugin": "^2.0.0", + "rete-render-utils": "^2.0.0" + } + }, "node_modules/@rollup/plugin-inject": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", @@ -3104,6 +2481,13 @@ } } }, + "node_modules/@rollup/plugin-inject/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", @@ -3127,10 +2511,30 @@ } } }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", - "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -3141,9 +2545,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", - "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -3154,9 +2558,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", - "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -3167,9 +2571,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", - "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -3180,9 +2584,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", - "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -3193,9 +2597,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", - "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -3206,9 +2610,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", - "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -3219,9 +2623,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", - "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -3232,9 +2636,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", - "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -3245,9 +2649,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", - "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -3258,9 +2662,22 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", - "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -3271,9 +2688,22 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", - "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -3284,9 +2714,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", - "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -3297,9 +2727,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", - "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -3310,9 +2740,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", - "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -3323,9 +2753,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", - "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -3336,9 +2766,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", - "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -3348,10 +2778,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", - "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -3362,9 +2805,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", - "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -3375,9 +2818,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", - "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -3388,9 +2831,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", - "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -3401,9 +2844,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", - "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -3413,6 +2856,100 @@ "win32" ] }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@sveltejs/acorn-typescript": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz", @@ -3424,9 +2961,9 @@ } }, "node_modules/@swc/helpers": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -3439,47 +2976,47 @@ "license": "0BSD" }, "node_modules/@tailwindcss/node": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.15.tgz", - "integrity": "sha512-HF4+7QxATZWY3Jr8OlZrBSXmwT3Watj0OogeDvdUY/ByXJHQ+LBtqA2brDb3sBxYslIFx6UP94BJ4X6a4L9Bmw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.0", - "lightningcss": "1.30.2", - "magic-string": "^0.30.19", + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.15" + "tailwindcss": "4.2.1" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.15.tgz", - "integrity": "sha512-krhX+UOOgnsUuks2SR7hFafXmLQrKxB4YyRTERuCE59JlYL+FawgaAlSkOYmDRJdf1Q+IFNDMl9iRnBW7QBDfQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", "license": "MIT", "engines": { - "node": ">= 10" + "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.15", - "@tailwindcss/oxide-darwin-arm64": "4.1.15", - "@tailwindcss/oxide-darwin-x64": "4.1.15", - "@tailwindcss/oxide-freebsd-x64": "4.1.15", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.15", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.15", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.15", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.15", - "@tailwindcss/oxide-linux-x64-musl": "4.1.15", - "@tailwindcss/oxide-wasm32-wasi": "4.1.15", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.15", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.15" + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.15.tgz", - "integrity": "sha512-TkUkUgAw8At4cBjCeVCRMc/guVLKOU1D+sBPrHt5uVcGhlbVKxrCaCW9OKUIBv1oWkjh4GbunD/u/Mf0ql6kEA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", "cpu": [ "arm64" ], @@ -3489,13 +3026,13 @@ "android" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.15.tgz", - "integrity": "sha512-xt5XEJpn2piMSfvd1UFN6jrWXyaKCwikP4Pidcf+yfHTSzSpYhG3dcMktjNkQO3JiLCp+0bG0HoWGvz97K162w==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", "cpu": [ "arm64" ], @@ -3505,13 +3042,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.15.tgz", - "integrity": "sha512-TnWaxP6Bx2CojZEXAV2M01Yl13nYPpp0EtGpUrY+LMciKfIXiLL2r/SiSRpagE5Fp2gX+rflp/Os1VJDAyqymg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", "cpu": [ "x64" ], @@ -3521,13 +3058,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.15.tgz", - "integrity": "sha512-quISQDWqiB6Cqhjc3iWptXVZHNVENsWoI77L1qgGEHNIdLDLFnw3/AfY7DidAiiCIkGX/MjIdB3bbBZR/G2aJg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", "cpu": [ "x64" ], @@ -3537,13 +3074,13 @@ "freebsd" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.15.tgz", - "integrity": "sha512-ObG76+vPlab65xzVUQbExmDU9FIeYLQ5k2LrQdR2Ud6hboR+ZobXpDoKEYXf/uOezOfIYmy2Ta3w0ejkTg9yxg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", "cpu": [ "arm" ], @@ -3553,13 +3090,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.15.tgz", - "integrity": "sha512-4WbBacRmk43pkb8/xts3wnOZMDKsPFyEH/oisCm2q3aLZND25ufvJKcDUpAu0cS+CBOL05dYa8D4U5OWECuH/Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", "cpu": [ "arm64" ], @@ -3569,13 +3106,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.15.tgz", - "integrity": "sha512-AbvmEiteEj1nf42nE8skdHv73NoR+EwXVSgPY6l39X12Ex8pzOwwfi3Kc8GAmjsnsaDEbk+aj9NyL3UeyHcTLg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", "cpu": [ "arm64" ], @@ -3585,13 +3122,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.15.tgz", - "integrity": "sha512-+rzMVlvVgrXtFiS+ES78yWgKqpThgV19ISKD58Ck+YO5pO5KjyxLt7AWKsWMbY0R9yBDC82w6QVGz837AKQcHg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", "cpu": [ "x64" ], @@ -3601,13 +3138,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.15.tgz", - "integrity": "sha512-fPdEy7a8eQN9qOIK3Em9D3TO1z41JScJn8yxl/76mp4sAXFDfV4YXxsiptJcOwy6bGR+70ZSwFIZhTXzQeqwQg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", "cpu": [ "x64" ], @@ -3617,13 +3154,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.15.tgz", - "integrity": "sha512-sJ4yd6iXXdlgIMfIBXuVGp/NvmviEoMVWMOAGxtxhzLPp9LOj5k0pMEMZdjeMCl4C6Up+RM8T3Zgk+BMQ0bGcQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -3638,19 +3175,19 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.0.7", + "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" + "tslib": "^2.8.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.5.0", + "version": "1.8.1", "inBundle": true, "license": "MIT", "optional": true, @@ -3660,7 +3197,7 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.5.0", + "version": "1.8.1", "inBundle": true, "license": "MIT", "optional": true, @@ -3678,14 +3215,18 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.7", + "version": "1.1.1", "inBundle": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { @@ -3704,9 +3245,9 @@ "optional": true }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.15.tgz", - "integrity": "sha512-sJGE5faXnNQ1iXeqmRin7Ds/ru2fgCiaQZQQz3ZGIDtvbkeV85rAZ0QJFMDg0FrqsffZG96H1U9AQlNBRLsHVg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", "cpu": [ "arm64" ], @@ -3716,13 +3257,13 @@ "win32" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.15.tgz", - "integrity": "sha512-NLeHE7jUV6HcFKS504bpOohyi01zPXi2PXmjFfkzTph8xRxDdxkRsXm/xDO5uV5K3brrE1cCwbUYmFUSHR3u1w==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", "cpu": [ "x64" ], @@ -3732,18 +3273,18 @@ "win32" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/vite": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.15.tgz", - "integrity": "sha512-B6s60MZRTUil+xKoZoGe6i0Iar5VuW+pmcGlda2FX+guDuQ1G1sjiIy1W0frneVpeL/ZjZ4KEgWZHNrIm++2qA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.1.15", - "@tailwindcss/oxide": "4.1.15", - "tailwindcss": "4.1.15" + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" @@ -3787,13 +3328,267 @@ } }, "node_modules/@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { - "@types/deep-eql": "*" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" } }, "node_modules/@types/deep-eql": { @@ -3803,12 +3598,35 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/html2canvas": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/html2canvas/-/html2canvas-1.0.0.tgz", @@ -3820,14 +3638,61 @@ "html2canvas": "*" } }, - "node_modules/@types/node": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", - "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", - "devOptional": true, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "license": "MIT", "dependencies": { - "undici-types": "~7.14.0" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" } }, "node_modules/@types/pako": { @@ -3836,10 +3701,19 @@ "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", "license": "MIT" }, + "node_modules/@types/papaparse": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", + "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/pdfkit": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.17.3.tgz", - "integrity": "sha512-E4tp2qFaghqfS4K5TR4Gn1uTIkg0UAkhUgvVIszr5cS6ZmbioPWEkvhNDy3GtR9qdKC8DLQAnaaMlTcf346VsA==", + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.17.5.tgz", + "integrity": "sha512-T3ZHnvF91HsEco5ClhBCOuBwobZfPcI2jaiSHybkkKYq4KhVIIurod94JVKvDIG0JXT6o3KiERC0X0//m8dyrg==", "dev": true, "license": "MIT", "dependencies": { @@ -3854,9 +3728,9 @@ "optional": true }, "node_modules/@types/sortablejs": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz", - "integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.9.tgz", + "integrity": "sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==", "dev": true, "license": "MIT" }, @@ -3864,8 +3738,14 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/utif": { "version": "3.0.6", @@ -3877,33 +3757,287 @@ "@types/node": "*" } }, - "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.4.tgz", + "integrity": "sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", + "@vitest/utils": "4.0.18", + "ast-v8-to-istanbul": "^0.3.10", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -3912,39 +4046,40 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", "dev": true, "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { "msw": { @@ -3955,53 +4090,42 @@ } } }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { @@ -4009,76 +4133,69 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/ui": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.2.4.tgz", - "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.18.tgz", + "integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@vitest/utils": "3.2.4", + "@vitest/utils": "4.0.18", "fflate": "^0.8.2", "flatted": "^3.3.3", "pathe": "^2.0.3", - "sirv": "^3.0.1", - "tinyglobby": "^0.2.14", - "tinyrainbow": "^2.0.0" + "sirv": "^3.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "3.2.4" + "vitest": "4.0.18" } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vue/compiler-core": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz", - "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz", + "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==", "license": "MIT", - "peer": true, "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.25", - "entities": "^4.5.0", + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.29", + "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-core/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.12" }, @@ -4086,100 +4203,246 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, "node_modules/@vue/compiler-dom": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz", - "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz", + "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==", "license": "MIT", - "peer": true, "dependencies": { - "@vue/compiler-core": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-core": "3.5.29", + "@vue/shared": "3.5.29" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz", - "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz", + "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==", "license": "MIT", - "peer": true, "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.25", - "@vue/compiler-dom": "3.5.25", - "@vue/compiler-ssr": "3.5.25", - "@vue/shared": "3.5.25", + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.29", + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz", - "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz", + "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==", "license": "MIT", - "peer": true, "dependencies": { - "@vue/compiler-dom": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-dom": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" } }, "node_modules/@vue/reactivity": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.25.tgz", - "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz", + "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==", "license": "MIT", - "peer": true, "dependencies": { - "@vue/shared": "3.5.25" + "@vue/shared": "3.5.29" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.25.tgz", - "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.29.tgz", + "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==", "license": "MIT", - "peer": true, "dependencies": { - "@vue/reactivity": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/reactivity": "3.5.29", + "@vue/shared": "3.5.29" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz", - "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz", + "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==", "license": "MIT", - "peer": true, "dependencies": { - "@vue/reactivity": "3.5.25", - "@vue/runtime-core": "3.5.25", - "@vue/shared": "3.5.25", - "csstype": "^3.1.3" + "@vue/reactivity": "3.5.29", + "@vue/runtime-core": "3.5.29", + "@vue/shared": "3.5.29", + "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.25.tgz", - "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.29.tgz", + "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==", "license": "MIT", - "peer": true, "dependencies": { - "@vue/compiler-ssr": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29" }, "peerDependencies": { - "vue": "3.5.25" + "vue": "3.5.29" } }, "node_modules/@vue/shared": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.25.tgz", - "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.29.tgz", + "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } }, "node_modules/abort-controller": { "version": "3.0.0", @@ -4194,9 +4457,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4205,6 +4468,16 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -4215,6 +4488,65 @@ "node": ">= 14" } }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/algoliasearch": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.46.2.tgz", + "integrity": "sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.12.2", + "@algolia/client-abtesting": "5.46.2", + "@algolia/client-analytics": "5.46.2", + "@algolia/client-common": "5.46.2", + "@algolia/client-insights": "5.46.2", + "@algolia/client-personalization": "5.46.2", + "@algolia/client-query-suggestions": "5.46.2", + "@algolia/client-search": "5.46.2", + "@algolia/ingestion": "1.46.2", + "@algolia/monitoring": "1.46.2", + "@algolia/recommend": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -4239,6 +4571,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/archiver": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", @@ -4275,143 +4620,11 @@ "node": ">= 14" } }, - "node_modules/archiver-utils/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/archiver-utils/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/archiver-utils/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/archiver/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/archiver/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/archiver/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/archiver/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, "node_modules/aria-query": { "version": "5.3.0", @@ -4443,16 +4656,17 @@ "license": "MIT" }, "node_modules/assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "dev": true, "license": "MIT", "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" } }, "node_modules/assertion-error": { @@ -4466,27 +4680,17 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.5.tgz", - "integrity": "sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.30", + "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } }, - "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", @@ -4599,6 +4803,28 @@ "require-from-string": "^2.0.2" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/blob": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", @@ -4626,6 +4852,41 @@ "dev": true, "license": "MIT" }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -4707,27 +4968,6 @@ "node": ">= 0.10" } }, - "node_modules/browserify-rsa/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/browserify-sign": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", @@ -4749,25 +4989,51 @@ "node": ">= 0.10" } }, - "node_modules/browserify-sign/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/browserify-sign/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT" }, "node_modules/browserify-zlib": { @@ -4781,10 +5047,9 @@ } }, "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -4802,7 +5067,7 @@ "license": "MIT", "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "ieee754": "^1.2.1" } }, "node_modules/buffer-crc32": { @@ -4834,14 +5099,13 @@ "dev": true, "license": "MIT" }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, + "node_modules/bwip-js": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/bwip-js/-/bwip-js-4.8.0.tgz", + "integrity": "sha512-gUDkDHSTv8/DJhomSIbO0fX/Dx0MO/sgllLxJyJfu4WixCQe9nfGJzmHm64ZCbxo+gUYQEsQcRmqcwcwPRwUkg==", "license": "MIT", - "engines": { - "node": ">=8" + "bin": { + "bwip-js": "bin/bwip-js.js" } }, "node_modules/call-bind": { @@ -4914,31 +5178,126 @@ "node": ">=10.0.0" } }, - "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">= 16" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chevrotain": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", + "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.1.2", + "@chevrotain/gast": "11.1.2", + "@chevrotain/regexp-to-ast": "11.1.2", + "@chevrotain/types": "11.1.2", + "@chevrotain/utils": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/cipher-base": { @@ -4956,26 +5315,55 @@ "node": ">= 0.10" } }, - "node_modules/cipher-base/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/clone": { "version": "2.1.2", @@ -4986,21 +5374,6 @@ "node": ">=0.8" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -5011,20 +5384,6 @@ "node": ">=6" } }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5043,12 +5402,34 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/compress-commons": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", @@ -5065,75 +5446,12 @@ "node": ">= 14" } }, - "node_modules/compress-commons/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/compress-commons/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/compress-commons/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "license": "MIT" }, - "node_modules/compress-commons/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -5147,10 +5465,26 @@ "dev": true, "license": "MIT" }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/core-js": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", - "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", "hasInstallScript": true, "license": "MIT", "optional": true, @@ -5165,6 +5499,15 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -5190,75 +5533,6 @@ "node": ">= 14" } }, - "node_modules/crc32-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/crc32-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/crc32-stream/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/crc32-stream/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/create-ecdh": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", @@ -5314,9 +5588,9 @@ "license": "MIT" }, "node_modules/cropperjs": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.1.tgz", - "integrity": "sha512-F4wsi+XkDHCOMrHMYjrTEE4QBOrsHHN5/2VsVAaRq8P7E5z7xQpT75S+f/9WikmBEailas3+yo+6zPIomW+NOA==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.2.tgz", + "integrity": "sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==", "license": "MIT" }, "node_modules/cross-fetch": { @@ -5399,15 +5673,16 @@ } }, "node_modules/cssstyle": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz", - "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.1.0.tgz", + "integrity": "sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^4.0.3", - "@csstools/css-syntax-patches-for-csstree": "^1.0.14", - "css-tree": "^3.1.0" + "@asamuzakjp/css-color": "^5.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.6" }, "engines": { "node": ">=20" @@ -5417,59 +5692,535 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", - "peer": true + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", + "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } }, "node_modules/data-urls": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", - "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/data-urls/node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/data-urls/node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, - "node_modules/data-urls/node_modules/whatwg-url": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", - "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.0" - }, - "engines": { - "node": ">=20" - } + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", @@ -5496,15 +6247,12 @@ "dev": true, "license": "MIT" }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } + "license": "MIT" }, "node_modules/define-data-property": { "version": "1.1.4", @@ -5542,6 +6290,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -5573,18 +6330,41 @@ } }, "node_modules/devalue": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz", - "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", + "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", "license": "MIT", "peer": true }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dfa": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", "license": "MIT" }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -5625,11 +6405,13 @@ } }, "node_modules/dompurify": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", - "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", + "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", "license": "(MPL-2.0 OR Apache-2.0)", - "optional": true, + "engines": { + "node": ">=20" + }, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -5679,65 +6461,74 @@ "license": "MIT" }, "node_modules/embedpdf-snippet": { - "version": "1.0.0", - "resolved": "file:vendor/embedpdf/embedpdf-snippet-1.5.0.tgz", - "integrity": "sha512-33SKBKkJEpGpo6Msuq2T7fBkHCzqLc58xI+Ui1C3DLWDmMynwT81/RoB2IQga3Ul1uU07CJRl1/OAwXhrlYWyA==", + "version": "2.3.0", + "resolved": "file:vendor/embedpdf/embedpdf-snippet-2.3.0.tgz", + "integrity": "sha512-1rhNxdAcbj3OXqkjXVfo6IrhH67bIikHvrN97vBtlkIGCi5w9lsIYs5eXdgwOLKzWJGI1plMfjQV47l4hzteag==", "license": "MIT", "dependencies": { - "@embedpdf/core": "^1.5.0", - "@embedpdf/engines": "^1.5.0", - "@embedpdf/models": "^1.5.0", - "@embedpdf/pdfium": "^1.5.0", - "@embedpdf/plugin-annotation": "^1.5.0", - "@embedpdf/plugin-attachment": "^1.5.0", - "@embedpdf/plugin-bookmark": "^1.5.0", - "@embedpdf/plugin-capture": "^1.5.0", - "@embedpdf/plugin-export": "^1.5.0", - "@embedpdf/plugin-fullscreen": "^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-redaction": "^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-ui": "^1.5.0", - "@embedpdf/plugin-viewport": "^1.5.0", - "@embedpdf/plugin-zoom": "^1.5.0", - "preact": "^10.17.0" + "@embedpdf/core": "^2.3.0", + "@embedpdf/engines": "^2.3.0", + "@embedpdf/models": "^2.3.0", + "@embedpdf/pdfium": "^2.3.0", + "@embedpdf/plugin-annotation": "^2.3.0", + "@embedpdf/plugin-attachment": "^2.3.0", + "@embedpdf/plugin-bookmark": "^2.3.0", + "@embedpdf/plugin-capture": "^2.3.0", + "@embedpdf/plugin-commands": "^2.3.0", + "@embedpdf/plugin-document-manager": "^2.3.0", + "@embedpdf/plugin-export": "^2.3.0", + "@embedpdf/plugin-fullscreen": "^2.3.0", + "@embedpdf/plugin-history": "^2.3.0", + "@embedpdf/plugin-i18n": "^2.3.0", + "@embedpdf/plugin-interaction-manager": "^2.3.0", + "@embedpdf/plugin-pan": "^2.3.0", + "@embedpdf/plugin-print": "^2.3.0", + "@embedpdf/plugin-redaction": "^2.3.0", + "@embedpdf/plugin-render": "^2.3.0", + "@embedpdf/plugin-rotate": "^2.3.0", + "@embedpdf/plugin-scroll": "^2.3.0", + "@embedpdf/plugin-search": "^2.3.0", + "@embedpdf/plugin-selection": "^2.3.0", + "@embedpdf/plugin-spread": "^2.3.0", + "@embedpdf/plugin-thumbnail": "^2.3.0", + "@embedpdf/plugin-tiling": "^2.3.0", + "@embedpdf/plugin-ui": "^2.3.0", + "@embedpdf/plugin-viewport": "^2.3.0", + "@embedpdf/plugin-zoom": "^2.3.0", + "preact": "^10.17.0", + "tailwind-merge": "^3.4.0" } }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" } }, "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -5746,6 +6537,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -5786,17 +6590,10 @@ "node": ">= 0.4" } }, - "node_modules/es6-object-assign": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", - "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", - "dev": true, - "license": "MIT" - }, "node_modules/esbuild": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", - "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -5806,42 +6603,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.10", - "@esbuild/android-arm": "0.25.10", - "@esbuild/android-arm64": "0.25.10", - "@esbuild/android-x64": "0.25.10", - "@esbuild/darwin-arm64": "0.25.10", - "@esbuild/darwin-x64": "0.25.10", - "@esbuild/freebsd-arm64": "0.25.10", - "@esbuild/freebsd-x64": "0.25.10", - "@esbuild/linux-arm": "0.25.10", - "@esbuild/linux-arm64": "0.25.10", - "@esbuild/linux-ia32": "0.25.10", - "@esbuild/linux-loong64": "0.25.10", - "@esbuild/linux-mips64el": "0.25.10", - "@esbuild/linux-ppc64": "0.25.10", - "@esbuild/linux-riscv64": "0.25.10", - "@esbuild/linux-s390x": "0.25.10", - "@esbuild/linux-x64": "0.25.10", - "@esbuild/netbsd-arm64": "0.25.10", - "@esbuild/netbsd-x64": "0.25.10", - "@esbuild/openbsd-arm64": "0.25.10", - "@esbuild/openbsd-x64": "0.25.10", - "@esbuild/openharmony-arm64": "0.25.10", - "@esbuild/sunos-x64": "0.25.10", - "@esbuild/win32-arm64": "0.25.10", - "@esbuild/win32-ia32": "0.25.10", - "@esbuild/win32-x64": "0.25.10" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escape-string-regexp": { @@ -5858,222 +6645,107 @@ } }, "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", + "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.2", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.0", + "@eslint/plugin-kit": "^0.6.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^9.1.1", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^10.2.1", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", + "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" + "url": "https://opencollective.com/eslint" } }, "node_modules/esm-env": { @@ -6084,60 +6756,27 @@ "peer": true }, "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", + "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "node": "^20.19.0 || ^22.13.0 || >=24" }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6148,9 +6787,9 @@ } }, "node_modules/esrap": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.1.tgz", - "integrity": "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.2.tgz", + "integrity": "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ==", "license": "MIT", "peer": true, "dependencies": { @@ -6161,6 +6800,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -6173,16 +6813,31 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/event-target-shim": { "version": "5.0.1", @@ -6193,6 +6848,13 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -6223,9 +6885,9 @@ } }, "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6244,6 +6906,20 @@ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "license": "MIT" }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-png": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", @@ -6261,29 +6937,68 @@ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", "license": "(MIT AND Zlib)" }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "license": "MIT" }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/flatted": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", @@ -6291,6 +7006,36 @@ "dev": true, "license": "ISC" }, + "node_modules/focus-trap": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.7.1.tgz", + "integrity": "sha512-Pkp8m55GjxBLnhBoT6OXdMvfRr4TjMAKLvFM566zlIryq5plbhaTmLAJWTGR0EkRwLjEte1lCOG9MxF1ipJrOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fontkit": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", @@ -6340,16 +7085,19 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=12" } }, "node_modules/fsevents": { @@ -6386,6 +7134,19 @@ "node": ">= 0.4" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -6445,6 +7206,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -6455,12 +7229,12 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -6469,6 +7243,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/globals": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -6488,13 +7275,32 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/hammerjs": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", - "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, "engines": { - "node": ">=0.8.0" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, "node_modules/has-flag": { @@ -6563,27 +7369,6 @@ "node": ">= 0.10" } }, - "node_modules/hash-base/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -6608,12 +7393,59 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/heic2any": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/heic2any/-/heic2any-0.0.4.tgz", "integrity": "sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA==", "license": "MIT" }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -6626,17 +7458,24 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/html-escaper": { @@ -6646,6 +7485,17 @@ "dev": true, "license": "MIT" }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/html2canvas": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", @@ -6694,10 +7544,26 @@ "node": ">= 14" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/i18next": { - "version": "25.7.2", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.2.tgz", - "integrity": "sha512-58b4kmLpLv1buWUEwegMDUqZVR5J+rT+WTRFaBGL7lxDuJQQ0NrJFrq+eT2N94aYVR1k1Sr13QITNOL88tZCuw==", + "version": "25.8.13", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.13.tgz", + "integrity": "sha512-E0vzjBY1yM+nsFrtgkjLhST2NBkirkvOVoQa0MSldhsuZ3jUge7ZNpuwG0Cfc74zwo5ZwRzg3uOgT+McBn32iA==", "funding": [ { "type": "individual", @@ -6726,9 +7592,9 @@ } }, "node_modules/i18next-browser-languagedetector": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", - "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", + "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2" @@ -6747,7 +7613,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -6782,18 +7647,47 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "license": "MIT" }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/iobuffer": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", @@ -6817,6 +7711,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -6846,13 +7752,29 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-generator-function": { @@ -6875,6 +7797,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-nan": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", @@ -6892,6 +7826,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -6962,10 +7905,24 @@ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", "license": "MIT" }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -7009,50 +7966,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -7101,39 +8014,39 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/jsdom": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz", - "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/dom-selector": "^6.7.2", - "cssstyle": "^5.3.1", - "data-urls": "^6.0.0", + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^4.0.0", + "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "parse5": "^8.0.0", - "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", + "undici": "^7.21.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.1.0", - "ws": "^8.18.3", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { "canvas": "^3.0.0" @@ -7144,60 +8057,66 @@ } } }, - "node_modules/jsdom/node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { - "punycode": "^2.3.1" + "universalify": "^2.0.0" }, - "engines": { - "node": ">=20" - } - }, - "node_modules/jsdom/node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, - "node_modules/jsdom/node_modules/whatwg-url": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", - "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.0" - }, - "engines": { - "node": ">=20" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, "node_modules/jspdf": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz", - "integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.0.tgz", + "integrity": "sha512-hR/hnRevAXXlrjeqU5oahOE+Ln9ORJUB5brLHHqH67A+RBQZuFr5GkbI9XQI8OUFSEezKegsi45QRpc4bGj75Q==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.9", + "@babel/runtime": "^7.28.6", "fast-png": "^6.2.0", "fflate": "^0.8.1" }, "optionalDependencies": { "canvg": "^3.0.11", "core-js": "^3.6.0", - "dompurify": "^3.2.4", + "dompurify": "^3.3.1", "html2canvas": "^1.0.0-rc.5" } }, + "node_modules/jspdf-autotable": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.7.tgz", + "integrity": "sha512-2wr7H6liNDBYNwt25hMQwXkEWFOEopgKIvR1Eukuw6Zmprm/ZcnmLTQEjW7Xx3FCbD3v7pflLcnMAv/h1jFDQw==", + "license": "MIT", + "peerDependencies": { + "jspdf": "^2 || ^3 || ^4" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -7210,6 +8129,105 @@ "setimmediate": "^1.0.5" } }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/katex": { + "version": "0.16.27", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.27.tgz", + "integrity": "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/langium": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", + "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.1.1", + "chevrotain-allstar": "~0.3.1", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.1.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -7222,6 +8240,56 @@ "node": ">= 0.6.3" } }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -7232,9 +8300,9 @@ } }, "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -7247,23 +8315,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", "cpu": [ "arm64" ], @@ -7281,9 +8349,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", "cpu": [ "arm64" ], @@ -7301,9 +8369,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", "cpu": [ "x64" ], @@ -7321,9 +8389,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", "cpu": [ "x64" ], @@ -7341,9 +8409,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", "cpu": [ "arm" ], @@ -7361,9 +8429,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", "cpu": [ "arm64" ], @@ -7381,9 +8449,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", "cpu": [ "arm64" ], @@ -7401,9 +8469,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", "cpu": [ "x64" ], @@ -7421,9 +8489,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", "cpu": [ "x64" ], @@ -7441,9 +8509,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", "cpu": [ "arm64" ], @@ -7461,9 +8529,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", "cpu": [ "x64" ], @@ -7499,6 +8567,144 @@ "node": ">= 0.4" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lint-staged": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.3.1.tgz", + "integrity": "sha512-bqvvquXzFBAlSbluugR4KXAe4XnO/QZcKVszpkBtqLWa2KEiVy8n6Xp38OeUbv/gOJOX4Vo9u5pFt/ADvbm42Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.3", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "string-argv": "^0.3.2", + "tinyexec": "^1.0.2", + "yaml": "^2.8.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lit": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", + "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz", + "integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/locate-character": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", @@ -7507,36 +8713,136 @@ "peer": true }, "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/lucide": { - "version": "0.546.0", - "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.546.0.tgz", - "integrity": "sha512-YJES3MM/naQS4wJ0JLzTY3anooqWw5iTsPCffHbSMncdxJT2C5tmkCDAwIHMHG8TMtaQcu40KREMPss2qH/+yA==", + "version": "0.575.0", + "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.575.0.tgz", + "integrity": "sha512-+xwqZpvrqPioU8bSH49zH2xARfnKyZgIjdnfbex0CrURB3q4wNFhinYN1Z9Q3lE16Q/6N9iEXnStvyS3c70RKw==", "license": "ISC" }, "node_modules/lz-string": { @@ -7559,15 +8865,137 @@ } }, "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-abbr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-abbr/-/markdown-it-abbr-2.0.0.tgz", + "integrity": "sha512-of7C8pXSjXjDojW4neNP+jD7inUYH/DO0Ca+K/4FUEccg0oHAEX/nfscw0jfz66PJbYWOAT9U8mjO21X5p6aAw==", + "license": "MIT" + }, + "node_modules/markdown-it-anchor": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-9.2.0.tgz", + "integrity": "sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==", + "license": "Unlicense", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it-deflist": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-deflist/-/markdown-it-deflist-3.0.0.tgz", + "integrity": "sha512-OxPmQ/keJZwbubjiQWOvKLHwpV2wZ5I3Smc81OjhwbfJsjdRrvD5aLTQxmZzzePeO0kbGzAo3Krk4QLgA8PWLg==", + "license": "MIT" + }, + "node_modules/markdown-it-emoji": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-3.0.0.tgz", + "integrity": "sha512-+rUD93bXHubA4arpEZO3q80so0qgoFJEKRkRbjKX8RTdca89v2kfyF+xR3i2sQTwql9tpPZPOQN5B+PunspXRg==", + "license": "MIT" + }, + "node_modules/markdown-it-footnote": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-4.0.0.tgz", + "integrity": "sha512-WYJ7urf+khJYl3DqofQpYfEYkZKbmXmwxQV8c8mO/hGIhgZ1wOe7R4HLFNwqx7TjILbnC98fuyeSsin19JdFcQ==", + "license": "MIT" + }, + "node_modules/markdown-it-ins": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-ins/-/markdown-it-ins-4.0.0.tgz", + "integrity": "sha512-sWbjK2DprrkINE4oYDhHdCijGT+MIDhEupjSHLXe5UXeVr5qmVxs/nTUVtgi0Oh/qtF+QKV0tNWDhQBEPxiMew==", + "license": "MIT" + }, + "node_modules/markdown-it-mark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-4.0.0.tgz", + "integrity": "sha512-YLhzaOsU9THO/cal0lUjfMjrqSMPjjyjChYM7oyj4DnyaXEzA8gnW6cVJeyCrCVeyesrY2PlEdUYJSPFYL4Nkg==", + "license": "MIT" + }, + "node_modules/markdown-it-sub": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-2.0.0.tgz", + "integrity": "sha512-iCBKgwCkfQBRg2vApy9vx1C1Tu6D8XYo8NvevI3OlwzBRmiMtsJ2sXupBgEA7PPxiDwNni3qIUkhZ6j5wofDUA==", + "license": "MIT" + }, + "node_modules/markdown-it-sup": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-2.0.0.tgz", + "integrity": "sha512-5VgmdKlkBd8sgXuoDoxMpiU+BiEt3I49GItBzzw7Mxq9CxvnhE/k09HFli09zgfFDRixDQDfDxi0mgBCXtaTvA==", + "license": "MIT" + }, + "node_modules/markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "license": "ISC" + }, + "node_modules/markdown-it-toc-done-right": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/markdown-it-toc-done-right/-/markdown-it-toc-done-right-4.2.0.tgz", + "integrity": "sha512-UB/IbzjWazwTlNAX0pvWNlJS8NKsOQ4syrXZQ/C72j+jirrsjVRT627lCaylrKJFBQWfRsPmIVQie8x38DEhAQ==", + "license": "MIT" + }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" } }, "node_modules/math-intrinsics": { @@ -7592,6 +9020,28 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdn-data": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", @@ -7599,6 +9049,154 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/mermaid": { + "version": "11.12.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz", + "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.1", + "@mermaid-js/parser": "^1.0.0", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.13", + "dayjs": "^1.11.18", + "dompurify": "^3.2.5", + "katex": "^0.16.22", + "khroma": "^2.1.0", + "lodash-es": "^4.17.23", + "marked": "^16.2.1", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/microdiff": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/microdiff/-/microdiff-1.5.0.tgz", + "integrity": "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==", + "license": "MIT" + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -7620,6 +9218,19 @@ "dev": true, "license": "MIT" }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -7634,6 +9245,32 @@ "dev": true, "license": "MIT" }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -7643,6 +9280,32 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -7678,6 +9341,20 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -7698,6 +9375,44 @@ } } }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-forge": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-readable-to-web-readable-stream": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/node-readable-to-web-readable-stream/-/node-readable-to-web-readable-stream-0.4.2.tgz", + "integrity": "sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==", + "license": "MIT", + "optional": true + }, "node_modules/node-stdlib-browser": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-stdlib-browser/-/node-stdlib-browser-1.3.1.tgz", @@ -7737,92 +9452,29 @@ "node": ">=10" } }, - "node_modules/node-stdlib-browser/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/node-stdlib-browser/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/node-stdlib-browser/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/node-stdlib-browser/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/node-stdlib-browser/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/node-stdlib-browser/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/node-stdlib-browser/node_modules/pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=10" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, "node_modules/node-stdlib-browser/node_modules/punycode": { @@ -7896,6 +9548,66 @@ "node": ">= 0.4" } }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, "node_modules/opencollective-postinstall": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", @@ -7905,6 +9617,24 @@ "opencollective-postinstall": "index.js" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -7912,18 +9642,74 @@ "dev": true, "license": "MIT" }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "license": "(MIT AND Zlib)" }, + "node_modules/papaparse": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", + "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", + "license": "MIT" + }, "node_modules/parse-asn1": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", @@ -7941,27 +9727,6 @@ "node": ">= 0.10" } }, - "node_modules/parse-asn1/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", @@ -7975,6 +9740,19 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -7982,6 +9760,22 @@ "dev": true, "license": "MIT" }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -8024,19 +9818,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/pbkdf2": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", @@ -8055,26 +9838,14 @@ "node": ">= 0.10" } }, - "node_modules/pbkdf2/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "node_modules/pdf-fontkit": { + "version": "1.8.9", + "resolved": "https://registry.npmjs.org/pdf-fontkit/-/pdf-fontkit-1.8.9.tgz", + "integrity": "sha512-TTq+umfhlFjUuQYOq6dCKT/wLslCrX4zVr5gqrIvrGHfo+vJ3ETapZTb4YLOCErohX7pF+HxlXSZuiToSRhNmA==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.6" + } }, "node_modules/pdf-lib": { "version": "1.17.1", @@ -8089,15 +9860,16 @@ } }, "node_modules/pdfjs-dist": { - "version": "5.4.296", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", - "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "version": "5.4.624", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.624.tgz", + "integrity": "sha512-sm6TxKTtWv1Oh6n3C6J6a8odejb5uO4A4zo/2dgkHuC0iu8ZMAXOezEODkVaoVp8nX1Xzr+0WxFJJmUr45hQzg==", "license": "Apache-2.0", "engines": { "node": ">=20.16.0 || >=22.3.0" }, "optionalDependencies": { - "@napi-rs/canvas": "^0.1.80" + "@napi-rs/canvas": "^0.1.88", + "node-readable-to-web-readable-stream": "^0.4.2" } }, "node_modules/pdfkit": { @@ -8113,6 +9885,13 @@ "png-js": "^1.0.0" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -8127,22 +9906,83 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pixelmatch": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", + "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", + "license": "ISC", + "dependencies": { + "pngjs": "^7.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/png-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -8153,6 +9993,12 @@ "node": ">= 0.4" } }, + "node_modules/postal-mime": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-2.7.3.tgz", + "integrity": "sha512-MjhXadAJaWgYzevi46+3kLak8y6gbg0ku14O1gO/LNOuay8dO+1PtcSGvAdgDR0DoIsSaiIA8y/Ddw6MnrO0Tw==", + "license": "MIT-0" + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -8172,7 +10018,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -8183,9 +10028,9 @@ } }, "node_modules/preact": { - "version": "10.28.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.0.tgz", - "integrity": "sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==", + "version": "10.28.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", + "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", "license": "MIT", "funding": { "type": "opencollective", @@ -8193,17 +10038,19 @@ } }, "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "bin": { @@ -8259,6 +10106,17 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -8291,10 +10149,19 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8348,26 +10215,30 @@ } }, "node_modules/react": { - "version": "19.2.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", - "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", - "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", "peer": true, "dependencies": { - "scheduler": "^0.27.0" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^19.2.1" + "react": "^18.3.1" } }, "node_modules/react-is": { @@ -8378,18 +10249,19 @@ "license": "MIT" }, "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/readdir-glob": { @@ -8411,9 +10283,9 @@ } }, "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -8422,12 +10294,51 @@ "node": ">=10" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "license": "MIT" }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -8439,13 +10350,13 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -8459,12 +10370,96 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/restructure": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", "license": "MIT" }, + "node_modules/rete": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/rete/-/rete-2.0.6.tgz", + "integrity": "sha512-kPmlKCGFES2VWtY7Y7SCB8ZeXRMsgX5deza9cu4OwmfM/ZUimd461kC3hRyccoyVxE4POlHUx0gg2jcGfusHFg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + } + }, + "node_modules/rete-area-plugin": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/rete-area-plugin/-/rete-area-plugin-2.1.5.tgz", + "integrity": "sha512-iquEvwkQlcsO4cmgM3Z37TG0AWaE536dfA+lCJAze5YJzVx4RBaViUCqdB4dUA/utSytpBCkiDC4D3ztM9akGQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "peerDependencies": { + "rete": "^2.0.0" + } + }, + "node_modules/rete-connection-plugin": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/rete-connection-plugin/-/rete-connection-plugin-2.0.5.tgz", + "integrity": "sha512-KFtlOyEJRc0y9STVgo2T+t+j9u5fxiTxbyzPbMCm0uqncb3b8d2ABDIzvWoNo5zQAh2Oz/OvlUovupbzrGzpSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "peerDependencies": { + "rete": "^2.0.1", + "rete-area-plugin": "^2.0.0" + } + }, + "node_modules/rete-engine": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/rete-engine/-/rete-engine-2.1.1.tgz", + "integrity": "sha512-RrIQDQycD5QZlDYCG1FKu2GLOKTgeIPLxKefnTVoOk7xYAyzlQ4HhXRa+ldsGaFAZzQHOXPgbkqis9ZqSB36MA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "peerDependencies": { + "rete": "^2.0.1" + } + }, + "node_modules/rete-render-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/rete-render-utils/-/rete-render-utils-2.0.3.tgz", + "integrity": "sha512-Oz4W2PNayHocRvlzadb5BCNf+tDzJ8RhTwB3ucBPCdCLKZ974wWDiTSCRfA287L2hmHVzRfBdyAwC03K9eP+4g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "peerDependencies": { + "rete": "^2.0.0", + "rete-area-plugin": "^2.0.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rgbcolor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", @@ -8505,11 +10500,125 @@ "node": ">= 0.8" } }, - "node_modules/ripemd160/node_modules/safe-buffer": { + "node_modules/ripemd160/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ripemd160/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/ripemd160/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ripemd160/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ripemd160/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -8526,61 +10635,6 @@ ], "license": "MIT" }, - "node_modules/rollup": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", - "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.4", - "@rollup/rollup-android-arm64": "4.52.4", - "@rollup/rollup-darwin-arm64": "4.52.4", - "@rollup/rollup-darwin-x64": "4.52.4", - "@rollup/rollup-freebsd-arm64": "4.52.4", - "@rollup/rollup-freebsd-x64": "4.52.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", - "@rollup/rollup-linux-arm-musleabihf": "4.52.4", - "@rollup/rollup-linux-arm64-gnu": "4.52.4", - "@rollup/rollup-linux-arm64-musl": "4.52.4", - "@rollup/rollup-linux-loong64-gnu": "4.52.4", - "@rollup/rollup-linux-ppc64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-musl": "4.52.4", - "@rollup/rollup-linux-s390x-gnu": "4.52.4", - "@rollup/rollup-linux-x64-gnu": "4.52.4", - "@rollup/rollup-linux-x64-musl": "4.52.4", - "@rollup/rollup-openharmony-arm64": "4.52.4", - "@rollup/rollup-win32-arm64-msvc": "4.52.4", - "@rollup/rollup-win32-ia32-msvc": "4.52.4", - "@rollup/rollup-win32-x64-gnu": "4.52.4", - "@rollup/rollup-win32-x64-msvc": "4.52.4", - "fsevents": "~2.3.2" - } - }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -8603,7 +10657,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/saxes": { @@ -8620,29 +10673,36 @@ } }, "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, "license": "MIT", "peer": true }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -8688,27 +10748,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sha.js/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8730,6 +10769,23 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -8813,6 +10869,18 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/sirv": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", @@ -8828,10 +10896,40 @@ "node": ">=18" } }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/sortablejs": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz", - "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz", + "integrity": "sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==", "license": "MIT" }, "node_modules/source-map": { @@ -8862,6 +10960,27 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -8880,9 +10999,9 @@ } }, "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, @@ -8952,26 +11071,39 @@ } }, "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width-cjs": { @@ -8989,7 +11121,22 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -9001,6 +11148,36 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", @@ -9014,26 +11191,37 @@ "node": ">=8" } }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", "dev": true, "license": "MIT", "dependencies": { - "js-tokens": "^9.0.1" + "copy-anything": "^4" }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "engines": { + "node": ">=16" } }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -9061,9 +11249,9 @@ } }, "node_modules/svelte": { - "version": "5.45.5", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.45.5.tgz", - "integrity": "sha512-2074U+vObO5Zs8/qhxtBwdi6ZXNIhEBTzNmUFjiZexLxTdt9vq96D/0pnQELl6YcpLMD7pZ2dhXKByfGS8SAdg==", + "version": "5.53.6", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.6.tgz", + "integrity": "sha512-lP5DGF3oDDI9fhHcSpaBiJEkFLuS16h92DhM1L5K1lFm0WjOmUh1i2sNkBBk8rkxJRpob0dBE75jRfUzGZUOGA==", "license": "MIT", "peer": true, "dependencies": { @@ -9071,13 +11259,14 @@ "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", - "aria-query": "^5.3.1", + "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", - "devalue": "^5.5.0", + "devalue": "^5.6.3", "esm-env": "^1.2.1", - "esrap": "^2.2.1", + "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -9088,9 +11277,9 @@ } }, "node_modules/svelte/node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", "license": "Apache-2.0", "peer": true, "engines": { @@ -9114,10 +11303,27 @@ "dev": true, "license": "MIT" }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.15.tgz", - "integrity": "sha512-k2WLnWkYFkdpRv+Oby3EBXIyQC8/s1HOFMBUViwtAh6Z5uAozeUSMQlIsn/c6Q2iJzqG6aJT3wdPaRNj70iYxQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", "license": "MIT" }, "node_modules/tapable": { @@ -9145,11 +11351,10 @@ } }, "node_modules/terser": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", - "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -9163,10 +11368,16 @@ "node": ">=10" } }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/tesseract.js": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-6.0.1.tgz", - "integrity": "sha512-/sPvMvrCtgxnNRCjbTYbr7BRu0yfWDsMZQ2a/T5aN/L1t8wUQN6tTWv6p6FwzpoEBA0jrN2UD2SX4QQFRdoDbA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-7.0.0.tgz", + "integrity": "sha512-exPBkd+z+wM1BuMkx/Bjv43OeLBxhL5kKWsz/9JY+DXcXdiBjiAch0V49QR3oAJqCaL5qURE0vx9Eo+G5YE7mA==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -9176,58 +11387,17 @@ "node-fetch": "^2.6.9", "opencollective-postinstall": "^2.0.3", "regenerator-runtime": "^0.13.3", - "tesseract.js-core": "^6.0.0", - "wasm-feature-detect": "^1.2.11", + "tesseract.js-core": "^7.0.0", + "wasm-feature-detect": "^1.8.0", "zlibjs": "^0.3.1" } }, "node_modules/tesseract.js-core": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-6.0.0.tgz", - "integrity": "sha512-1Qncm/9oKM7xgrQXZXNB+NRh19qiXGhxlrR8EwFbK5SaUbPZnS5OMtP/ghtqfd23hsr1ZvZbZjeuAGcMxd/ooA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-7.0.0.tgz", + "integrity": "sha512-WnNH518NzmbSq9zgTPeoF8c+xmilS8rFIl1YKbk/ptuuc7p6cLNELNuPAzcmsYw450ca6bLa8j3t0VAtq435Vw==", "license": "Apache-2.0" }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", @@ -9247,13 +11417,13 @@ } }, "node_modules/tiff": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/tiff/-/tiff-7.1.2.tgz", - "integrity": "sha512-E5rXZJJc3Il0eR4u30PLE6R8DOx/IFyjgS1JhyYvKSEVAAHLTXVwGgZRmLE//K8Pt8d+y4M0Xae30wNGb9Qmxg==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/tiff/-/tiff-7.1.3.tgz", + "integrity": "sha512-YEEq3fT++2pdta/9P/vGG4QRMdZQoe6W6JNaWnIi6NvAsbeNITwFCtmWwL/BZvOi+uo2I3ohyOkD3sZfme+c6g==", "license": "MIT", "dependencies": { - "iobuffer": "^6.0.0", - "pako": "^2.1.0" + "fflate": "^0.8.2", + "iobuffer": "^6.0.0" } }, "node_modules/tiff/node_modules/iobuffer": { @@ -9262,12 +11432,6 @@ "integrity": "sha512-SZWYkWNfjIXIBYSDpXDYIgshqtbOPsi4lviawAEceR1Kqk+sHDlcQjWrzNQsii80AyBY0q5c8HCTNjqo74ul+Q==", "license": "MIT" }, - "node_modules/tiff/node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", - "license": "(MIT AND Zlib)" - }, "node_modules/timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -9295,11 +11459,13 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -9317,30 +11483,39 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", "dev": true, "license": "MIT", "engines": { @@ -9348,22 +11523,22 @@ } }, "node_modules/tldts": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", - "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.17" + "tldts-core": "^7.0.19" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", - "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", "dev": true, "license": "MIT" }, @@ -9382,33 +11557,17 @@ "node": ">= 0.4" } }, - "node_modules/to-buffer/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/to-buffer/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } }, "node_modules/totalist": { "version": "3.0.1", @@ -9434,10 +11593,50 @@ } }, "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } }, "node_modules/tslib": { "version": "1.14.1", @@ -9452,6 +11651,19 @@ "dev": true, "license": "MIT" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -9473,7 +11685,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9482,11 +11693,70 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", - "devOptional": true, + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/unicode-properties": { @@ -9515,6 +11785,99 @@ "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", "license": "MIT" }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/url": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", @@ -9574,14 +11937,56 @@ "base64-arraybuffer": "^1.0.2" } }, - "node_modules/vite": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", - "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "esbuild": "^0.25.0", + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -9649,38 +12054,531 @@ } } }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "node_modules/vite-plugin-compression": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz", + "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==", "dev": true, "license": "MIT", "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "chalk": "^4.1.2", + "debug": "^4.3.3", + "fs-extra": "^10.0.0" }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, + "node_modules/vite-plugin-handlebars": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-handlebars/-/vite-plugin-handlebars-2.0.0.tgz", + "integrity": "sha512-+J3It0nyhPzx4nT1I+fnWH+jShTEXzm6X0Tgsggdm9IYFD7/eJ6a3ROI13HTe0CVoyaxm/fPxH5HDAKyfz7T0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "handlebars": "^4.7.6", + "vite": "^5.0.0" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", "bin": { - "vite-node": "vite-node.mjs" + "esbuild": "bin/esbuild" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vite-plugin-handlebars/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } } }, "node_modules/vite-plugin-node-polyfills": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.24.0.tgz", - "integrity": "sha512-GA9QKLH+vIM8NPaGA+o2t8PDfFUl32J8rUp1zQfMKVJQiNkOX4unE51tR6ppl6iKw5yOrDAdSH7r/UIFLCVhLw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.25.0.tgz", + "integrity": "sha512-rHZ324W3LhfGPxWwQb2N048TThB6nVvnipsqBUJEzh3R9xeK9KI3si+GMQxCuAcpPJBVf0LpDtJ+beYzB3/chg==", "dev": true, "license": "MIT", "dependencies": { "@rollup/plugin-inject": "^5.0.5", - "node-stdlib-browser": "^1.2.0" + "node-stdlib-browser": "^1.3.1" }, "funding": { "url": "https://github.com/sponsors/davidmyersdev" @@ -9689,53 +12587,648 @@ "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "node_modules/vite-plugin-static-copy": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.2.0.tgz", + "integrity": "sha512-g2k9z8B/1Bx7D4wnFjPLx9dyYGrqWMLTpwTtPHhcU+ElNZP2O4+4OsyaficiDClus0dzVhdGvoGFYMJxoXZ12Q==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.6.0", + "p-map": "^7.0.4", + "picocolors": "^1.1.1", + "tinyglobby": "^0.2.15" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/sapphi-red" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vitepress/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/vitepress/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitepress/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, @@ -9743,13 +13236,19 @@ "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { "optional": true }, "@vitest/ui": { @@ -9763,6 +13262,19 @@ } } }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -9770,18 +13282,66 @@ "dev": true, "license": "MIT" }, - "node_modules/vue": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.25.tgz", - "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==", + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", "license": "MIT", - "peer": true, "dependencies": { - "@vue/compiler-dom": "3.5.25", - "@vue/compiler-sfc": "3.5.25", - "@vue/runtime-dom": "3.5.25", - "@vue/server-renderer": "3.5.25", - "@vue/shared": "3.5.25" + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", + "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-sfc": "3.5.29", + "@vue/runtime-dom": "3.5.29", + "@vue/server-renderer": "3.5.29", + "@vue/shared": "3.5.29" }, "peerDependencies": { "typescript": "*" @@ -9812,42 +13372,38 @@ "license": "Apache-2.0" }, "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, "license": "MIT", "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/which": { @@ -9904,6 +13460,40 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", @@ -9922,26 +13512,69 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/xlsx": { + "version": "0.20.3", + "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==", + "license": "Apache-2.0", + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" } }, "node_modules/xml-name-validator": { @@ -9971,6 +13604,22 @@ "node": ">=0.4" } }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "devOptional": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -9984,6 +13633,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zgapdfsigner": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/zgapdfsigner/-/zgapdfsigner-2.7.5.tgz", + "integrity": "sha512-MXVFjyyhb2RSYf1f+JXFBllH7EVZI4V/wS6nMxXJwtqgve4GplkX7gPkpVoByhKdFRIX8JB4hSP4w65d7gT/zg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "1.15.6", + "node-forge": "1.3.1", + "pdf-fontkit": "1.8.9", + "pdf-lib": "1.17.1" + } + }, "node_modules/zimmerframe": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", @@ -10005,75 +13666,6 @@ "node": ">= 14" } }, - "node_modules/zip-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/zip-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/zip-stream/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/zip-stream/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/zlibjs": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", @@ -10082,6 +13674,26 @@ "engines": { "node": "*" } + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 549f22c..81929d7 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,18 @@ { "name": "bento-pdf", "private": true, - "version": "1.11.2", - "license": "Apache-2.0", + "version": "2.4.1", + "license": "AGPL-3.0-only", "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "tsc && vite build && NODE_OPTIONS='--max-old-space-size=3072' node scripts/generate-i18n-pages.mjs && node scripts/generate-sitemap.mjs", + "build:with-docs": "npm run build && npm run docs:build && node scripts/include-docs-in-dist.js", + "build:gzip": "COMPRESSION_MODE=g npm run build", + "build:brotli": "COMPRESSION_MODE=b npm run build", + "build:original": "COMPRESSION_MODE=o npm run build", + "build:all": "COMPRESSION_MODE=all npm run build", + "build:production": "VITE_USE_CDN=true npm run build:with-docs", "preview": "vite preview", "obfuscate": "node scripts/build.mjs", "test": "vitest", @@ -22,23 +28,41 @@ "release:major": "node scripts/release.js major", "serve:simple": "SIMPLE_MODE=true npm run build && npm run preview -- --port 3000", "serve": "npm run build && npm run preview -- --port 3000", - "package": "node scripts/package-dist.js" + "package": "node scripts/package-dist.js", + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "prepare": "husky" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@testing-library/dom": "^10.4.1", "@types/blob-stream": "^0.1.33", "@types/html2canvas": "^1.0.0", - "@types/pdfkit": "^0.17.3", + "@types/pdfkit": "^0.17.5", "@types/sortablejs": "^1.15.8", "@types/utif": "^3.0.6", - "@vitest/coverage-v8": "^3.2.4", - "@vitest/ui": "^3.2.4", - "jsdom": "^27.0.1", - "prettier": "^3.6.2", + "@vitejs/plugin-basic-ssl": "^2.1.4", + "@vitest/coverage-v8": "^4.0.18", + "@vitest/ui": "^4.0.18", + "eslint": "^10.0.2", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.4.0", + "husky": "^9.1.7", + "jsdom": "^28.1.0", + "lint-staged": "^16.3.1", + "prettier": "^3.8.1", "typescript": "~5.9.3", - "vite": "^7.1.11", - "vite-plugin-node-polyfills": "^0.24.0", - "vitest": "^3.2.4" + "typescript-eslint": "^8.56.1", + "vite": "^7.3.1", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-handlebars": "^2.0.0", + "vite-plugin-node-polyfills": "^0.25.0", + "vitepress": "^1.6.4", + "vitest": "^4.0.18", + "vue": "^3.5.29" }, "dependencies": { "@fontsource/cedarville-cursive": "^5.2.7", @@ -48,29 +72,81 @@ "@fontsource/kalam": "^5.2.8", "@fontsource/lato": "^5.2.7", "@fontsource/merriweather": "^5.2.11", + "@kenjiuno/msgreader": "^1.28.0", + "@matbee/libreoffice-converter": "^2.5.0", "@neslinesli93/qpdf-wasm": "^0.3.0", "@pdf-lib/fontkit": "^1.1.1", - "@tailwindcss/vite": "^4.1.15", + "@phosphor-icons/web": "^2.1.2", + "@retejs/lit-plugin": "^2.0.7", + "@tailwindcss/vite": "^4.2.1", + "@types/markdown-it": "^14.1.2", + "@types/node-forge": "^1.3.14", + "@types/papaparse": "^5.5.2", "archiver": "^7.0.1", "blob-stream": "^0.1.3", - "cropperjs": "^1.6.1", + "bwip-js": "^4.8.0", + "cropperjs": "^1.6.2", + "diff": "^8.0.3", + "embedpdf-snippet": "file:vendor/embedpdf/embedpdf-snippet-2.3.0.tgz", "heic2any": "^0.0.4", + "highlight.js": "^11.11.1", "html2canvas": "^1.4.1", - "i18next": "^25.7.2", - "i18next-browser-languagedetector": "^8.2.0", + "i18next": "^25.8.13", + "i18next-browser-languagedetector": "^8.2.1", "i18next-http-backend": "^3.0.2", - "jspdf": "^3.0.3", + "jspdf": "^4.2.0", + "jspdf-autotable": "^5.0.2", "jszip": "^3.10.1", - "lucide": "^0.546.0", + "lit": "^3.3.2", + "lucide": "^0.575.0", + "markdown-it": "^14.1.1", + "markdown-it-abbr": "^2.0.0", + "markdown-it-anchor": "^9.2.0", + "markdown-it-deflist": "^3.0.0", + "markdown-it-emoji": "^3.0.0", + "markdown-it-footnote": "^4.0.0", + "markdown-it-ins": "^4.0.0", + "markdown-it-mark": "^4.0.0", + "markdown-it-sub": "^2.0.0", + "markdown-it-sup": "^2.0.0", + "markdown-it-task-lists": "^2.1.1", + "markdown-it-toc-done-right": "^4.2.0", + "mermaid": "^11.12.3", + "microdiff": "^1.5.0", + "node-forge": "^1.3.3", + "papaparse": "^5.5.3", "pdf-lib": "^1.17.1", - "pdfjs-dist": "^5.4.296", + "pdfjs-dist": "^5.4.624", "pdfkit": "^0.17.2", - "sortablejs": "^1.15.6", + "pixelmatch": "^7.1.0", + "postal-mime": "^2.7.3", + "rete": "^2.0.6", + "rete-area-plugin": "^2.1.5", + "rete-connection-plugin": "^2.0.5", + "rete-engine": "^2.1.1", + "rete-render-utils": "^2.0.3", + "sortablejs": "^1.15.7", "tailwindcss": "^4.1.14", - "terser": "^5.44.0", - "tesseract.js": "^6.0.1", + "terser": "^5.46.0", + "tesseract.js": "^7.0.0", "tiff": "^7.1.2", "utif": "^3.1.0", - "embedpdf-snippet": "file:vendor/embedpdf/embedpdf-snippet-1.5.0.tgz" + "vite-plugin-static-copy": "^3.2.0", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + "zgapdfsigner": "^2.7.5" + }, + "lint-staged": { + "*.{js,ts,mjs,cjs}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json,md,html,css}": [ + "prettier --write" + ] + }, + "overrides": { + "node-forge": "^1.3.3", + "react": "^18.3.1", + "react-dom": "^18.3.1" } } diff --git a/pdf-converter.html b/pdf-converter.html new file mode 100644 index 0000000..eb02e3d --- /dev/null +++ b/pdf-converter.html @@ -0,0 +1,545 @@ + + + + + + + + PDF Converter - Free Online PDF Converter Tools | BentoPDF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{> navbar }} + + +
+
+

+ PDF Converter - Free Online PDF Conversion Tools +

+

+ Convert your PDFs to and from 40+ different file formats with + BentoPDF's comprehensive conversion tools. Transform PDFs to Word, + Excel, images, text, and many other formats - or convert files into + PDFs. All conversions happen locally in your browser, ensuring + complete privacy. No file uploads, no cloud processing, completely + free. +

+
+
+ + +
+

+ All PDF Converter Tools +

+ +
+ + +

Word to PDF

+

DOCX, DOC to PDF

+
+ + +

Excel to PDF

+

XLSX, XLS to PDF

+
+ + +

PowerPoint to PDF

+

PPTX, PPT to PDF

+
+ + +

JPG to PDF

+

Convert images

+
+ + +

PNG to PDF

+

Convert images

+
+ + +

Image to PDF

+

Any image to PDF

+
+ + +

WebP to PDF

+

Modern format

+
+ + +

SVG to PDF

+

Vector to PDF

+
+ + +

HEIC to PDF

+

iPhone photos

+
+ + +

TIFF to PDF

+

Convert TIFF

+
+ + +

BMP to PDF

+

Convert BMP

+
+ + +

Text to PDF

+

Plain text

+
+ + +

Markdown to PDF

+

MD to PDF

+
+ + +

CSV to PDF

+

Spreadsheet

+
+ + +

JSON to PDF

+

Data to PDF

+
+ + +

PDF to Word

+

Editable DOCX

+
+ + +

PDF to Excel

+

Extract tables

+
+ + +

PDF to JPG

+

Convert to images

+
+ + +

PDF to PNG

+

Convert to images

+
+ + +

PDF to Text

+

Extract text

+
+ + +

PDF to Markdown

+

Extract as MD

+
+ + +

PDF to CSV

+

Extract tables

+
+ + +

PDF to JSON

+

Structured data

+
+ + +

PDF to SVG

+

Vector output

+
+ + +

PDF to WebP

+

Modern format

+
+ + +

PDF to TIFF

+

Convert to TIFF

+
+ + +

PDF to BMP

+

Convert to BMP

+
+ + +

PDF to Greyscale

+

Remove colors

+
+ + +

PDF to PDF/A

+

Archive format

+
+ + +

PDF to ZIP

+

Package files

+
+ + +

ODT to PDF

+

OpenDocument

+
+ + +

ODS to PDF

+

OpenDocument

+
+ + +

ODP to PDF

+

OpenDocument

+
+ + +

ODG to PDF

+

Graphics

+
+ + +

RTF to PDF

+

Rich text

+
+ + +

EPUB to PDF

+

E-book format

+
+ + +

MOBI to PDF

+

Kindle format

+
+ + +

CBZ to PDF

+

Comic archive

+
+ + +

FB2 to PDF

+

FictionBook

+
+ + +

PSD to PDF

+

Photoshop

+
+ + +

XPS to PDF

+

Microsoft XPS

+
+ + +

Pages to PDF

+

Apple Pages

+
+ + +

PUB to PDF

+

Publisher

+
+ + +

VSD to PDF

+

Visio

+
+ + +

WPD to PDF

+

WordPerfect

+
+ + +

WPS to PDF

+

WPS Office

+
+ + +

XML to PDF

+

XML data

+
+
+
+ + +
+
+

+ Why Use BentoPDF PDF Converter Tools? +

+ +
+
+ +

+ 100% Privacy-First +

+

+ All operations happen in your browser. Your files never leave your + device, never uploaded to servers. Complete privacy guaranteed. +

+
+ +
+ +

+ Unlimited & Free +

+

+ Use all tools as many times as you want, with files of any size. + No file limits, no tool limits. Completely free forever. +

+
+ +
+ +

+ Lightning Fast +

+

+ Browser-based processing means instant results. No waiting for + uploads or downloads. Works offline after page load. +

+
+ +
+ +

+ No Signup Required +

+

+ Start using tools immediately. No account creation, no email, no + personal information needed. Just open and use. +

+
+
+
+
+ + +
+
+

+ Frequently Asked Questions +

+ +
+
+ + Are these pdf converter tools really free? + + +

+ Yes! All BentoPDF pdf converter tools are 100% free with no hidden + fees, no premium tiers, and no subscription required. Use + unlimited tools, unlimited times. +

+
+ +
+ + Are my files private and secure? + + +

+ Absolutely! All processing happens entirely in your browser. Your + files never leave your device, are never uploaded to servers. + Complete privacy guaranteed. +

+
+ +
+ + Is there a file size limit? + + +

+ No file size limits! Process files of any size. Browser-based + processing means no artificial limitations. +

+
+ +
+ + Do I need to install software? + + +

+ No installation required! All tools are web-based and work + directly in your browser. Just open the page and start using - + works on any device. +

+
+
+
+
+ + + {{> footer }} + + + + + + + + + + + + + diff --git a/pdf-editor.html b/pdf-editor.html new file mode 100644 index 0000000..0e1a0ad --- /dev/null +++ b/pdf-editor.html @@ -0,0 +1,414 @@ + + + + + + + + PDF Editor - Free Online PDF Editor Tools | BentoPDF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{> navbar }} + + +
+
+

+ PDF Editor - Free Online PDF Editing Tools +

+

+ Edit and enhance your PDF files with BentoPDF's comprehensive suite of + 25+ editing tools. Compress file sizes, rotate pages, crop documents, + add watermarks, insert page numbers, change colors, and much more. All + editing happens in your browser - your files stay completely private. + No signup, no limits, completely free. +

+
+
+ + +
+

+ All PDF Editor Tools +

+ + +
+ + +
+
+

+ Why Use BentoPDF PDF Editor Tools? +

+ +
+
+ +

+ 100% Privacy-First +

+

+ All operations happen in your browser. Your files never leave your + device, never uploaded to servers. Complete privacy guaranteed. +

+
+ +
+ +

+ Unlimited & Free +

+

+ Use all tools as many times as you want, with files of any size. + No file limits, no tool limits. Completely free forever. +

+
+ +
+ +

+ Lightning Fast +

+

+ Browser-based processing means instant results. No waiting for + uploads or downloads. Works offline after page load. +

+
+ +
+ +

+ No Signup Required +

+

+ Start using tools immediately. No account creation, no email, no + personal information needed. Just open and use. +

+
+
+
+
+ + +
+
+

+ Frequently Asked Questions +

+ +
+
+ + Are these pdf editor tools really free? + + +

+ Yes! All BentoPDF pdf editor tools are 100% free with no hidden + fees, no premium tiers, and no subscription required. Use + unlimited tools, unlimited times. +

+
+ +
+ + Are my files private and secure? + + +

+ Absolutely! All processing happens entirely in your browser. Your + files never leave your device, are never uploaded to servers. + Complete privacy guaranteed. +

+
+ +
+ + Is there a file size limit? + + +

+ No file size limits! Process files of any size. Browser-based + processing means no artificial limitations. +

+
+ +
+ + Do I need to install software? + + +

+ No installation required! All tools are web-based and work + directly in your browser. Just open the page and start using - + works on any device. +

+
+
+
+
+ + + {{> footer }} + + + + + + + + + + + + + diff --git a/pdf-merge-split.html b/pdf-merge-split.html new file mode 100644 index 0000000..a250c11 --- /dev/null +++ b/pdf-merge-split.html @@ -0,0 +1,372 @@ + + + + + + + + + PDF Merge & Split - Free Online PDF Merge & Split Tools | BentoPDF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{> navbar }} + + +
+
+

+ PDF Merge & Split Tools - Combine & Separate PDFs Free +

+

+ Combine multiple PDF files into one document or split large PDFs into + smaller files with BentoPDF's merge and split tools. Reorder pages, + extract specific sections, alternate merge documents, and organize + your PDFs exactly how you need them. Browser-based processing ensures + your files stay private. No limits on file count or size. +

+
+
+ + +
+

+ All PDF Merge & Split Tools +

+ + +
+ + +
+
+

+ Why Use BentoPDF PDF Merge & Split Tools? +

+ +
+
+ +

+ 100% Privacy-First +

+

+ All operations happen in your browser. Your files never leave your + device, never uploaded to servers. Complete privacy guaranteed. +

+
+ +
+ +

+ Unlimited & Free +

+

+ Use all tools as many times as you want, with files of any size. + No file limits, no tool limits. Completely free forever. +

+
+ +
+ +

+ Lightning Fast +

+

+ Browser-based processing means instant results. No waiting for + uploads or downloads. Works offline after page load. +

+
+ +
+ +

+ No Signup Required +

+

+ Start using tools immediately. No account creation, no email, no + personal information needed. Just open and use. +

+
+
+
+
+ + +
+
+

+ Frequently Asked Questions +

+ +
+
+ + Are these pdf merge & split tools really free? + + +

+ Yes! All BentoPDF pdf merge & split tools are 100% free with no + hidden fees, no premium tiers, and no subscription required. Use + unlimited tools, unlimited times. +

+
+ +
+ + Are my files private and secure? + + +

+ Absolutely! All processing happens entirely in your browser. Your + files never leave your device, are never uploaded to servers. + Complete privacy guaranteed. +

+
+ +
+ + Is there a file size limit? + + +

+ No file size limits! Process files of any size. Browser-based + processing means no artificial limitations. +

+
+ +
+ + Do I need to install software? + + +

+ No installation required! All tools are web-based and work + directly in your browser. Just open the page and start using - + works on any device. +

+
+
+
+
+ + + {{> footer }} + + + + + + + + + + + + + diff --git a/pdf-security.html b/pdf-security.html new file mode 100644 index 0000000..58db29a --- /dev/null +++ b/pdf-security.html @@ -0,0 +1,349 @@ + + + + + + + + PDF Security - Free Online PDF Security Tools | BentoPDF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{> navbar }} + + +
+
+

+ PDF Security Tools - Protect & Secure Your PDFs Free +

+

+ Protect and secure your PDF documents with BentoPDF's comprehensive + security tools. Encrypt with passwords, add digital signatures, manage + permissions, remove sensitive data, and more. All security operations + happen locally in your browser for maximum privacy. No cloud uploads, + no data retention - your documents stay completely confidential. +

+
+
+ + +
+

+ All PDF Security Tools +

+ + +
+ + +
+
+

+ Why Use BentoPDF PDF Security Tools? +

+ +
+
+ +

+ 100% Privacy-First +

+

+ All operations happen in your browser. Your files never leave your + device, never uploaded to servers. Complete privacy guaranteed. +

+
+ +
+ +

+ Unlimited & Free +

+

+ Use all tools as many times as you want, with files of any size. + No file limits, no tool limits. Completely free forever. +

+
+ +
+ +

+ Lightning Fast +

+

+ Browser-based processing means instant results. No waiting for + uploads or downloads. Works offline after page load. +

+
+ +
+ +

+ No Signup Required +

+

+ Start using tools immediately. No account creation, no email, no + personal information needed. Just open and use. +

+
+
+
+
+ + +
+
+

+ Frequently Asked Questions +

+ +
+
+ + Are these pdf security tools really free? + + +

+ Yes! All BentoPDF pdf security tools are 100% free with no hidden + fees, no premium tiers, and no subscription required. Use + unlimited tools, unlimited times. +

+
+ +
+ + Are my files private and secure? + + +

+ Absolutely! All processing happens entirely in your browser. Your + files never leave your device, are never uploaded to servers. + Complete privacy guaranteed. +

+
+ +
+ + Is there a file size limit? + + +

+ No file size limits! Process files of any size. Browser-based + processing means no artificial limitations. +

+
+ +
+ + Do I need to install software? + + +

+ No installation required! All tools are web-based and work + directly in your browser. Just open the page and start using - + works on any device. +

+
+
+
+
+ + + {{> footer }} + + + + + + + + + + + + + diff --git a/privacy.html b/privacy.html index bddeb00..78de50c 100644 --- a/privacy.html +++ b/privacy.html @@ -1,284 +1,231 @@ + + + + + Privacy Policy - Your Data Stays Private | BentoPDF + + + - - - - Privacy Policy - BentoPDF - - - - - - + + - - + + -
-
-

- Privacy Policy -

-

- Last Updated: September 14, 2025 -

+ + + + + + + -
-
- -
-
-
-
-
- Bento PDF Logo - BentoPDF -
-

- © 2025 BentoPDF. All rights reserved. +

-
-

Company

-
    +

    1.1 The Client-Side Principle

    +

    + Unlike other online PDF services, BentoPDF does not upload your + files to a server for processing. The tools you use are powered by + JavaScript and WebAssembly libraries that run directly on your + device. This means your data never leaves your computer, providing + you with the highest level of privacy and security. +

    + +

    2. Information We Do Not Collect

    +

    + Because of our client-side architecture, we are technically + incapable of collecting the following information: +

    +
    • - About Us + The content of your PDF files or any other documents you use with + our tools.
    • +
    • Any personal data contained within your documents.
    • +
    • Filenames of your documents.
    • - FAQ -
    • -
    • - Contact Us + Any derived information or metadata from your files, beyond what + is necessary for the tool to function during your active session + (and this is immediately discarded).
    -
-
-

Legal

-
    +

    3. Information We May Collect (Non-Personal Data)

    +

    + To improve our website and services, we may collect anonymous, + non-personally identifiable information. This type of data helps us + understand how users interact with our site, which tools are most + popular, and how we can improve the user experience. This includes: +

    +
    • - Licensing + Usage Analytics: Anonymized data such as which + tools are used, how often they are used, and which features are + accessed. This is aggregated and cannot be tied back to an + individual user or document.
    • - Terms and Conditions -
    • -
    • - Privacy Policy + Performance Data: Anonymized error reports or + performance metrics to help us identify and fix bugs. This data + contains no personal information or file content.
    -
+

+ We use privacy-respecting analytics platforms for this purpose. + Specifically, we use + Simple Analytics + to track anonymous visit counts. This means we can see how many + users visit our site, but + we never collect personal information or identify individual + users. Simple Analytics is fully GDPR-compliant and respects user + privacy. We do not use tracking cookies for advertising or + cross-site profiling. +

-
-

Follow Us

- +

4. Third-Party Libraries

+

+ BentoPDF is built using powerful, open-source libraries like + PDF-lib.js and PDF.js. These libraries are trusted by developers + worldwide and operate under the same client-side principle. While we + have vetted these libraries, we encourage you to review their + respective privacy policies for your own peace of mind. +

+ +

5. Security

+

+ Since your files are never transmitted over the internet to our + servers, you are protected from potential data breaches during + transit or storage on a server. The security of your documents is in + your hands and protected by the security of your own computer and + web browser. +

+ +

6. Children's Privacy

+

+ Our services are not directed at individuals under the age of 13. We + do not knowingly collect any personal information from children. If + you believe a child has provided us with personal information, + please contact us, and we will take steps to delete such + information. +

+ +

7. Changes to This Privacy Policy

+

+ We may update this Privacy Policy from time to time. We will notify + you of any changes by posting the new policy on this page and + updating the "Last Updated" date at the top. You are advised to + review this Privacy Policy periodically for any changes. +

+ +

8. Contact Us

+

+ If you have any questions about this Privacy Policy, please contact + us at + contact@bentopdf.com. +

-
+
-
- - - - - - \ No newline at end of file + {{> footer }} + + + + + + + + diff --git a/public/images/apple-touch-icon.png b/public/images/apple-touch-icon.png new file mode 100644 index 0000000..b2b9f30 Binary files /dev/null and b/public/images/apple-touch-icon.png differ diff --git a/public/images/badge.svg b/public/images/badge.svg new file mode 100644 index 0000000..9a87f3f --- /dev/null +++ b/public/images/badge.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/favicon-192x192.png b/public/images/favicon-192x192.png new file mode 100644 index 0000000..7438a89 Binary files /dev/null and b/public/images/favicon-192x192.png differ diff --git a/public/images/favicon-512x512.png b/public/images/favicon-512x512.png new file mode 100644 index 0000000..7926d09 Binary files /dev/null and b/public/images/favicon-512x512.png differ diff --git a/public/images/favicon-no-bg.svg b/public/images/favicon-no-bg.svg new file mode 100644 index 0000000..01d9df7 --- /dev/null +++ b/public/images/favicon-no-bg.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/images/favicon.png b/public/images/favicon.png index a5fa7be..345a195 100644 Binary files a/public/images/favicon.png and b/public/images/favicon.png differ diff --git a/public/images/favicon.svg b/public/images/favicon.svg index 01d9df7..90ddc27 100644 --- a/public/images/favicon.svg +++ b/public/images/favicon.svg @@ -1,5 +1,7 @@ - + + + diff --git a/public/images/og-pdf-converter.png b/public/images/og-pdf-converter.png new file mode 100644 index 0000000..13f5c31 Binary files /dev/null and b/public/images/og-pdf-converter.png differ diff --git a/public/images/og-pdf-editor.png b/public/images/og-pdf-editor.png new file mode 100644 index 0000000..8c97d44 Binary files /dev/null and b/public/images/og-pdf-editor.png differ diff --git a/public/images/og-pdf-merge-split.png b/public/images/og-pdf-merge-split.png new file mode 100644 index 0000000..6104cf4 Binary files /dev/null and b/public/images/og-pdf-merge-split.png differ diff --git a/public/images/og-pdf-security.png b/public/images/og-pdf-security.png new file mode 100644 index 0000000..c98fd1b Binary files /dev/null and b/public/images/og-pdf-security.png differ diff --git a/public/images/og-tools.png b/public/images/og-tools.png new file mode 100644 index 0000000..f4e453d Binary files /dev/null and b/public/images/og-tools.png differ diff --git a/public/libreoffice-wasm/browser.worker.global.js b/public/libreoffice-wasm/browser.worker.global.js new file mode 100644 index 0000000..db95057 --- /dev/null +++ b/public/libreoffice-wasm/browser.worker.global.js @@ -0,0 +1,3 @@ +(function(){'use strict';var S=class extends Error{code;details;constructor(e,t,r){super(t),this.name="ConversionError",this.code=e,this.details=r;}},q={pdf:"writer_pdf_Export",docx:"MS Word 2007 XML",doc:"MS Word 97",odt:"writer8",rtf:"Rich Text Format",txt:"Text",html:"HTML (StarWriter)",xlsx:"Calc MS Excel 2007 XML",xls:"MS Excel 97",ods:"calc8",csv:"Text - txt - csv (StarCalc)",pptx:"Impress MS PowerPoint 2007 XML",ppt:"MS PowerPoint 97",odp:"impress8",png:"writer_png_Export",jpg:"writer_jpg_Export",svg:"writer_svg_Export"};var te={pdf:"application/pdf",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",doc:"application/msword",odt:"application/vnd.oasis.opendocument.text",rtf:"application/rtf",txt:"text/plain",html:"text/html",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",xls:"application/vnd.ms-excel",ods:"application/vnd.oasis.opendocument.spreadsheet",csv:"text/csv",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",ppt:"application/vnd.ms-powerpoint",odp:"application/vnd.oasis.opendocument.presentation",png:"image/png",jpg:"image/jpeg",svg:"image/svg+xml"},re={doc:"doc",docx:"docx",xls:"xls",xlsx:"xlsx",ppt:"ppt",pptx:"pptx",odt:"odt",ods:"ods",odp:"odp",odg:"odg",odf:"odf",rtf:"rtf",txt:"txt",html:"html",htm:"html",csv:"csv",xml:"xml",epub:"epub",pdf:"pdf"},ge={0:["pdf","docx","doc","odt","rtf","txt","html","png"],1:["pdf","xlsx","xls","ods","csv","html","png"],2:["pdf","pptx","ppt","odp","png","svg","html"],3:["pdf","png","svg","html"],4:["pdf"]};function oe(l){return ge[l]||["pdf"]}var U={pdf:"pdf",docx:"docx",doc:"doc",odt:"odt",rtf:"rtf",txt:"txt",html:"html",xlsx:"xlsx",xls:"xls",ods:"ods",csv:"csv",pptx:"pptx",ppt:"ppt",odp:"odp",png:"png",jpg:"jpg",svg:"svg"},K={pdf:"",csv:"44,34,76,1,,0,false,true,false,false,false,-1",txt:"UTF8"},ne={doc:"text",docx:"text",odt:"text",rtf:"text",txt:"text",html:"text",htm:"text",epub:"text",xml:"text",xls:"spreadsheet",xlsx:"spreadsheet",ods:"spreadsheet",csv:"spreadsheet",ppt:"presentation",pptx:"presentation",odp:"presentation",odg:"drawing",odf:"drawing",pdf:"drawing"},fe={text:["pdf","docx","doc","odt","rtf","txt","html","png"],spreadsheet:["pdf","xlsx","xls","ods","csv","html","png"],presentation:["pdf","pptx","ppt","odp","png","svg","html"],drawing:["pdf","png","svg","html"],other:["pdf"]};function V(l){let e=l.toLowerCase(),t=ne[e];return t?fe[t]:["pdf"]}function ee(l,e){return V(l).includes(e.toLowerCase())}function ie(l,e){let t=l.toLowerCase(),r=e.toLowerCase(),n=V(t),o=ne[t]||"unknown",s="";return o==="drawing"&&["docx","doc","xlsx","xls","pptx","ppt"].includes(r)?s="PDF files are imported as Draw documents and cannot be exported to Office formats. ":o==="spreadsheet"&&["docx","doc","pptx","ppt"].includes(r)?s="Spreadsheet documents cannot be converted to word processing or presentation formats. ":o==="presentation"&&["docx","doc","xlsx","xls"].includes(r)?s="Presentation documents cannot be converted to word processing or spreadsheet formats. ":o==="text"&&["xlsx","xls","pptx","ppt"].includes(r)&&(s="Text documents cannot be converted to spreadsheet or presentation formats. "),`Cannot convert ${t.toUpperCase()} to ${r.toUpperCase()}. ${s}Valid output formats for ${t.toUpperCase()}: ${n.join(", ")}`}var I=class{lok;docPtr;options;inputPath="";constructor(e,t,r={}){this.lok=e,this.docPtr=t,this.options={maxResponseChars:r.maxResponseChars??8e3,...r};}getDocPtr(){return this.docPtr}getLokBindings(){return this.lok}save(){try{return this.inputPath?(this.lok.postUnoCommand(this.docPtr,".uno:Save"),this.createResult({path:this.inputPath})):this.createErrorResult("No input path set","Use saveAs() to specify a path")}catch(e){return this.createErrorResult(`Save failed: ${String(e)}`)}}saveAs(e,t){try{return this.lok.documentSaveAs(this.docPtr,e,t,""),this.createResult({path:e})}catch(r){return this.createErrorResult(`SaveAs failed: ${String(r)}`)}}close(){try{return this.lok.documentDestroy(this.docPtr),this.docPtr=0,this.createResult(void 0)}catch(e){return this.createErrorResult(`Close failed: ${String(e)}`)}}getEditMode(){return this.lok.getEditMode(this.docPtr)}enableEditMode(){try{if(this.lok.getEditMode(this.docPtr)===1)return this.createResult({editMode:1});this.lok.postUnoCommand(this.docPtr,".uno:Edit");let t=this.lok.getEditMode(this.docPtr);return {success:!0,verified:t===1,data:{editMode:t}}}catch(e){return this.createErrorResult(`Failed to enable edit mode: ${String(e)}`)}}undo(){try{return this.lok.postUnoCommand(this.docPtr,".uno:Undo"),this.createResult(void 0)}catch(e){return this.createErrorResult(`Undo failed: ${String(e)}`)}}redo(){try{return this.lok.postUnoCommand(this.docPtr,".uno:Redo"),this.createResult(void 0)}catch(e){return this.createErrorResult(`Redo failed: ${String(e)}`)}}find(e,t){try{let r=JSON.stringify({"SearchItem.SearchString":{type:"string",value:e},"SearchItem.Backward":{type:"boolean",value:!1},"SearchItem.SearchAll":{type:"boolean",value:!0},"SearchItem.MatchCase":{type:"boolean",value:t?.caseSensitive??!1},"SearchItem.WordOnly":{type:"boolean",value:t?.wholeWord??!1}});this.lok.postUnoCommand(this.docPtr,".uno:ExecuteSearch",r);let n=this.lok.getTextSelection(this.docPtr,"text/plain"),o=n!==null&&n.length>0;return this.createResult({matches:o?1:0,firstMatch:o?{x:0,y:0}:void 0})}catch(r){return this.createErrorResult(`Find failed: ${String(r)}`)}}findAndReplaceAll(e,t,r){try{let n=JSON.stringify({"SearchItem.SearchString":{type:"string",value:e},"SearchItem.ReplaceString":{type:"string",value:t},"SearchItem.Command":{type:"long",value:3},"SearchItem.MatchCase":{type:"boolean",value:r?.caseSensitive??!1},"SearchItem.WordOnly":{type:"boolean",value:r?.wholeWord??!1}});return this.lok.postUnoCommand(this.docPtr,".uno:ExecuteSearch",n),this.createResult({replacements:-1})}catch(n){return this.createErrorResult(`Replace failed: ${String(n)}`)}}getStateChanges(){try{let e=this.lok.pollStateChanges();return this.createResult(e)}catch(e){return this.createErrorResult(`Failed to poll state changes: ${String(e)}`)}}flushAndPollState(){try{this.lok.flushCallbacks(this.docPtr);let e=this.lok.pollStateChanges();return this.createResult(e)}catch(e){return this.createErrorResult(`Failed to flush and poll state: ${String(e)}`)}}clearCallbackQueue(){this.lok.clearCallbackQueue();}select(e){try{let t=this.lok.getTextSelection(this.docPtr,"text/plain");return this.createResult({selected:t||""})}catch(t){return this.createErrorResult(`Select failed: ${String(t)}`)}}getSelection(){try{let e=this.lok.getTextSelection(this.docPtr,"text/plain");return this.createResult({text:e||"",range:{type:"text",start:{paragraph:0,character:0}}})}catch(e){return this.createErrorResult(`GetSelection failed: ${String(e)}`)}}clearSelection(){try{return this.lok.resetSelection(this.docPtr),this.createResult(void 0)}catch(e){return this.createErrorResult(`ClearSelection failed: ${String(e)}`)}}createResult(e){return {success:true,verified:true,data:e}}createErrorResult(e,t){return {success:false,verified:false,error:e,suggestion:t}}createResultWithTruncation(e,t){return {success:true,verified:true,data:e,truncated:t}}truncateContent(e,t){let r=t??this.options.maxResponseChars,n=e.length;if(n<=r)return {content:e,truncated:false,original:n,returned:n};let o=e.slice(0,r),s=o.lastIndexOf(" ");return s>r*.8&&(o=o.slice(0,s)),{content:o,truncated:true,original:n,returned:o.length}}truncateArray(e,t,r){let n=e.length,o=0,s=0;for(let i of e){let a=r(i);if(o+a.length>t)break;o+=a.length,s++;}return {items:e.slice(0,s),truncated:s0;){let o=(n-1)%26;r=String.fromCharCode(65+o)+r,n=Math.floor((n-1)/26);}return `${r}${e+1}`}normalizeCellRef(e){return typeof e=="string"?this.a1ToRowCol(e):e}setInputPath(e){this.inputPath=e;}isOpen(){return this.docPtr!==0}};var z=class extends I{cachedParagraphs=null;getDocumentType(){return "writer"}getStructure(e){try{let t=this.getParagraphsInternal(),r=e?.maxResponseChars??this.options.maxResponseChars,n=t.map((i,a)=>({index:a,preview:i.slice(0,100)+(i.length>100?"...":""),style:"Normal",charCount:i.length})),o=this.truncateArray(n,r,i=>JSON.stringify(i)),s={type:"writer",paragraphs:o.items,pageCount:this.lok.documentGetParts(this.docPtr),wordCount:t.join(" ").split(/\s+/).filter(i=>i.length>0).length};return o.truncated?this.createResultWithTruncation(s,{original:o.original,returned:o.returned,message:`Showing ${o.returned} of ${o.original} paragraphs. Use getParagraphs(start, count) to paginate.`}):this.createResult(s)}catch(t){return this.createErrorResult(`Failed to get structure: ${String(t)}`)}}getParagraph(e){try{let t=this.getParagraphsInternal();if(e<0||e>=t.length)return this.createErrorResult(`Paragraph index ${e} out of range (0-${t.length-1})`,"Use getStructure() to see available paragraphs");let r=t[e];return this.createResult({index:e,text:r,style:"Normal",charCount:r.length})}catch(t){return this.createErrorResult(`Failed to get paragraph: ${String(t)}`)}}getParagraphs(e,t){try{let r=this.getParagraphsInternal();if(e<0||e>=r.length)return this.createErrorResult(`Start index ${e} out of range`,`Valid range: 0-${r.length-1}`);let n=Math.min(e+t,r.length),o=r.slice(e,n).map((s,i)=>({index:e+i,text:s,style:"Normal",charCount:s.length}));return this.createResult(o)}catch(r){return this.createErrorResult(`Failed to get paragraphs: ${String(r)}`)}}insertParagraph(e,t){try{let r=this.getParagraphsInternal(),n=t?.afterIndex!==void 0?t.afterIndex+1:r.length;n>0&&n<=r.length&&this.lok.postUnoCommand(this.docPtr,".uno:GoToEndOfDoc"),this.lok.postUnoCommand(this.docPtr,".uno:InsertPara");let o=JSON.stringify({Text:{type:"string",value:e}});if(this.lok.postUnoCommand(this.docPtr,".uno:InsertText",o),t?.style&&t.style!=="Normal"){let d={"Heading 1":"Heading 1","Heading 2":"Heading 2","Heading 3":"Heading 3",List:"List"}[t.style];if(d){let h=JSON.stringify({Template:{type:"string",value:d},Family:{type:"short",value:2}});this.lok.postUnoCommand(this.docPtr,".uno:StyleApply",h);}}return this.cachedParagraphs=null,{success:!0,verified:this.getParagraphsInternal().length>r.length,data:{index:n}}}catch(r){return this.createErrorResult(`Failed to insert paragraph: ${String(r)}`)}}replaceParagraph(e,t){try{let r=this.getParagraphsInternal();if(e<0||e>=r.length)return this.createErrorResult(`Paragraph index ${e} out of range`,`Valid range: 0-${r.length-1}`);let n=r[e],o=JSON.stringify({"SearchItem.SearchString":{type:"string",value:n},"SearchItem.ReplaceString":{type:"string",value:t},"SearchItem.Command":{type:"long",value:2}});return this.lok.postUnoCommand(this.docPtr,".uno:ExecuteSearch",o),this.cachedParagraphs=null,this.createResult({oldText:n})}catch(r){return this.createErrorResult(`Failed to replace paragraph: ${String(r)}`)}}deleteParagraph(e){try{let t=this.getParagraphsInternal();if(e<0||e>=t.length)return this.createErrorResult(`Paragraph index ${e} out of range`,`Valid range: 0-${t.length-1}`);let r=t[e],n=this.replaceParagraph(e,"");return n.success?this.createResult({deletedText:r}):this.createErrorResult(n.error||"Failed to delete")}catch(t){return this.createErrorResult(`Failed to delete paragraph: ${String(t)}`)}}insertText(e,t){try{let r=JSON.stringify({Text:{type:"string",value:e}});return this.lok.postUnoCommand(this.docPtr,".uno:InsertText",r),this.cachedParagraphs=null,this.createResult(void 0)}catch(r){return this.createErrorResult(`Failed to insert text: ${String(r)}`)}}deleteText(e,t){try{let r=this.lok.getTextSelection(this.docPtr,"text/plain");return this.lok.postUnoCommand(this.docPtr,".uno:Delete"),this.cachedParagraphs=null,this.createResult({deleted:r||""})}catch(r){return this.createErrorResult(`Failed to delete text: ${String(r)}`)}}replaceText(e,t,r){try{let n=r?.all?3:2,o=JSON.stringify({"SearchItem.SearchString":{type:"string",value:e},"SearchItem.ReplaceString":{type:"string",value:t},"SearchItem.Command":{type:"long",value:n}});return this.lok.postUnoCommand(this.docPtr,".uno:ExecuteSearch",o),this.cachedParagraphs=null,this.createResult({replacements:-1})}catch(n){return this.createErrorResult(`Failed to replace text: ${String(n)}`)}}formatText(e,t){try{if(t.bold!==void 0&&this.lok.postUnoCommand(this.docPtr,".uno:Bold"),t.italic!==void 0&&this.lok.postUnoCommand(this.docPtr,".uno:Italic"),t.underline!==void 0&&this.lok.postUnoCommand(this.docPtr,".uno:Underline"),t.fontSize!==void 0){let r=JSON.stringify({FontHeight:{type:"float",value:t.fontSize}});this.lok.postUnoCommand(this.docPtr,".uno:FontHeight",r);}if(t.fontName!==void 0){let r=JSON.stringify({CharFontName:{type:"string",value:t.fontName}});this.lok.postUnoCommand(this.docPtr,".uno:CharFontName",r);}return this.createResult(void 0)}catch(r){return this.createErrorResult(`Failed to format text: ${String(r)}`)}}getFormat(e){try{this.lok.clearCallbackQueue(),this.lok.postUnoCommand(this.docPtr,".uno:CharRightSel"),this.lok.flushCallbacks(this.docPtr),this.lok.postUnoCommand(this.docPtr,".uno:CharLeft"),this.lok.flushCallbacks(this.docPtr);let t=this.lok.pollStateChanges(),r={},n=t.get(".uno:Bold");n!==void 0&&(r.bold=n==="true");let o=t.get(".uno:Italic");o!==void 0&&(r.italic=o==="true");let s=t.get(".uno:Underline");s!==void 0&&(r.underline=s==="true");let i=t.get(".uno:FontHeight");if(i!==void 0){let d=parseFloat(i);isNaN(d)||(r.fontSize=d);}let a=t.get(".uno:CharFontName");return a!==void 0&&a.length>0&&(r.fontName=a),this.createResult(r)}catch(t){return this.createErrorResult(`Failed to get format: ${String(t)}`)}}getSelectionFormat(){try{let e=this.lok.pollStateChanges();if(e.size===0){this.lok.clearCallbackQueue(),this.lok.postUnoCommand(this.docPtr,".uno:SelectWord"),this.lok.flushCallbacks(this.docPtr);let t=this.lok.pollStateChanges();return this.createResult(t)}return this.createResult(e)}catch(e){return this.createErrorResult(`Failed to get selection format: ${String(e)}`)}}getParagraphsInternal(){if(this.cachedParagraphs)return this.cachedParagraphs;let e=this.lok.getAllText(this.docPtr);return e?(this.cachedParagraphs=e.split(/\n\n|\r\n\r\n/).map(t=>t.trim()).filter(t=>t.length>0),this.cachedParagraphs):[]}};var G=class extends I{getDocumentType(){return "calc"}getStructure(e){try{let t=this.lok.documentGetParts(this.docPtr),r=[];for(let o=0;o0&&i.row>0?`A1:${this.rowColToA1(i.row-1,i.col-1)}`:"A1",rowCount:i.row,colCount:i.col});}let n={type:"calc",sheets:r};return this.createResult(n)}catch(t){return this.createErrorResult(`Failed to get structure: ${String(t)}`)}}getSheetNames(){try{let e=this.lok.documentGetParts(this.docPtr),t=[];for(let r=0;ra){m=!0;break}h+=_.length,g.push(O);}g.length>0&&d.push(g);}return m?this.createResultWithTruncation(d,{original:(s-n+1)*(i-o+1),returned:d.reduce((u,g)=>u+g.length,0),message:"Range truncated due to size. Use smaller ranges to paginate."}):this.createResult(d)}catch(n){return this.createErrorResult(`Failed to get cells: ${String(n)}`)}}setCellValue(e,t,r){try{this.selectSheet(r);let{row:n,col:o}=this.normalizeCellRef(e),s=this.rowColToA1(n,o);this.goToCell(s);let i=this.getCellValueInternal(),a=typeof t=="number"?t.toString():t,d=JSON.stringify({StringName:{type:"string",value:a}});this.lok.postUnoCommand(this.docPtr,".uno:EnterString",d);let h=this.getCellValueInternal(),m=typeof t=="number"?t.toString():t;return {success:!0,verified:h===m||h===t,data:{oldValue:i,newValue:h}}}catch(n){return this.createErrorResult(`Failed to set cell value: ${String(n)}`)}}setCellFormula(e,t,r){try{this.selectSheet(r);let{row:n,col:o}=this.normalizeCellRef(e),s=this.rowColToA1(n,o);this.goToCell(s);let i=JSON.stringify({StringName:{type:"string",value:t}});this.lok.postUnoCommand(this.docPtr,".uno:EnterString",i);let a=this.getCellValueInternal();return this.createResult({calculatedValue:a})}catch(n){return this.createErrorResult(`Failed to set formula: ${String(n)}`)}}setCells(e,t,r){try{this.selectSheet(r);let{startRow:n,startCol:o}=this.normalizeRangeRef(e),s=0;for(let i=0;i=0&&this.lok.documentSetPart(this.docPtr,t);}getSheetIndexByName(e){let t=this.lok.documentGetParts(this.docPtr);for(let r=0;r=t)return this.createErrorResult(`Invalid slide index: ${e}. Valid range: 0-${t-1}`,"Use getStructure() to see available slides");this.lok.documentSetPart(this.docPtr,e);let r=this.lok.getAllText(this.docPtr)||"",n=this.parseTextFrames(r),o={index:e,title:n.find(s=>s.type==="title")?.text,textFrames:n,hasNotes:!1};return this.createResult(o)}catch(t){return this.createErrorResult(`Failed to get slide: ${String(t)}`)}}getSlideCount(){try{let e=this.lok.documentGetParts(this.docPtr);return this.createResult(e)}catch(e){return this.createErrorResult(`Failed to get slide count: ${String(e)}`)}}addSlide(e){try{let t=this.lok.documentGetParts(this.docPtr);e?.afterSlide!==void 0?this.lok.documentSetPart(this.docPtr,e.afterSlide):this.lok.documentSetPart(this.docPtr,t-1),this.lok.postUnoCommand(this.docPtr,".uno:InsertPage"),e?.layout&&this.applySlideLayout(e.layout);let r=e?.afterSlide!==void 0?e.afterSlide+1:t;return {success:!0,verified:this.lok.documentGetParts(this.docPtr)>t,data:{index:r}}}catch(t){return this.createErrorResult(`Failed to add slide: ${String(t)}`)}}deleteSlide(e){try{let t=this.lok.documentGetParts(this.docPtr);return t<=1?this.createErrorResult("Cannot delete the last slide","A presentation must have at least one slide"):e<0||e>=t?this.createErrorResult(`Invalid slide index: ${e}`,`Valid range: 0-${t-1}`):(this.lok.documentSetPart(this.docPtr,e),this.lok.postUnoCommand(this.docPtr,".uno:DeletePage"),this.createResult(void 0))}catch(t){return this.createErrorResult(`Failed to delete slide: ${String(t)}`)}}duplicateSlide(e){try{let t=this.lok.documentGetParts(this.docPtr);return e<0||e>=t?this.createErrorResult(`Invalid slide index: ${e}`,`Valid range: 0-${t-1}`):(this.lok.documentSetPart(this.docPtr,e),this.lok.postUnoCommand(this.docPtr,".uno:DuplicatePage"),this.createResult({newIndex:e+1}))}catch(t){return this.createErrorResult(`Failed to duplicate slide: ${String(t)}`)}}moveSlide(e,t){try{let r=this.lok.documentGetParts(this.docPtr);if(e<0||e>=r||t<0||t>=r)return this.createErrorResult(`Invalid slide indices. Valid range: 0-${r-1}`,"Check slide indices are within bounds");if(e===t)return this.createErrorResult("Source and destination are the same","Provide different indices to move");this.lok.documentSetPart(this.docPtr,e);let n=JSON.stringify({Position:{type:"long",value:t}});return this.lok.postUnoCommand(this.docPtr,".uno:MovePageFirst",n),this.createResult(void 0)}catch(r){return this.createErrorResult(`Failed to move slide: ${String(r)}`)}}setSlideTitle(e,t){try{let r=this.lok.documentGetParts(this.docPtr);if(e<0||e>=r)return this.createErrorResult(`Invalid slide index: ${e}`);this.lok.documentSetPart(this.docPtr,e);let n=this.lok.getAllText(this.docPtr)||"",s=this.parseTextFrames(n).find(a=>a.type==="title")?.text;this.lok.postUnoCommand(this.docPtr,".uno:SelectAll");let i=JSON.stringify({Text:{type:"string",value:t}});return this.lok.postUnoCommand(this.docPtr,".uno:InsertText",i),this.createResult({oldTitle:s})}catch(r){return this.createErrorResult(`Failed to set slide title: ${String(r)}`)}}setSlideBody(e,t){try{let r=this.lok.documentGetParts(this.docPtr);if(e<0||e>=r)return this.createErrorResult(`Invalid slide index: ${e}`);this.lok.documentSetPart(this.docPtr,e);let n=this.lok.getAllText(this.docPtr)||"",s=this.parseTextFrames(n).find(a=>a.type==="body")?.text;this.lok.postUnoCommand(this.docPtr,".uno:NextObject");let i=JSON.stringify({Text:{type:"string",value:t}});return this.lok.postUnoCommand(this.docPtr,".uno:InsertText",i),this.createResult({oldBody:s})}catch(r){return this.createErrorResult(`Failed to set slide body: ${String(r)}`)}}setSlideNotes(e,t){try{let r=this.lok.documentGetParts(this.docPtr);if(e<0||e>=r)return this.createErrorResult(`Invalid slide index: ${e}`);this.lok.documentSetPart(this.docPtr,e),this.lok.postUnoCommand(this.docPtr,".uno:NotesMode");let n=JSON.stringify({Text:{type:"string",value:t}});return this.lok.postUnoCommand(this.docPtr,".uno:InsertText",n),this.lok.postUnoCommand(this.docPtr,".uno:NormalViewMode"),this.createResult(void 0)}catch(r){return this.createErrorResult(`Failed to set slide notes: ${String(r)}`)}}setSlideLayout(e,t){try{let r=this.lok.documentGetParts(this.docPtr);return e<0||e>=r?this.createErrorResult(`Invalid slide index: ${e}`):(this.lok.documentSetPart(this.docPtr,e),this.applySlideLayout(t),this.createResult(void 0))}catch(r){return this.createErrorResult(`Failed to set slide layout: ${String(r)}`)}}getSlideInfoInternal(e){this.lok.documentSetPart(this.docPtr,e);let t=this.lok.getAllText(this.docPtr)||"",r=this.parseTextFrames(t),n=r.find(o=>o.type==="title");return {index:e,title:n?.text,layout:this.detectLayout(r),textFrameCount:r.length}}parseTextFrames(e){let t=e.split(/\n\n+/).filter(n=>n.trim()),r=[];return t.forEach((n,o)=>{let s=o===0?"title":o===1?"body":"other";r.push({index:o,type:s,text:n.trim(),bounds:{x:0,y:0,width:0,height:0}});}),r}detectLayout(e){return e.length===0?"blank":e.length===1&&e[0]?.type==="title"?"title":e.length>=2?"titleContent":"blank"}applySlideLayout(e){let r={blank:0,title:1,titleContent:2,twoColumn:3}[e]??0,n=JSON.stringify({WhatLayout:{type:"long",value:r}});this.lok.postUnoCommand(this.docPtr,".uno:AssignLayout",n);}};var j=class extends I{isImportedPdf=false;getDocumentType(){return "draw"}setImportedPdf(e){this.isImportedPdf=e;}getStructure(e){try{let t=this.lok.documentGetParts(this.docPtr),r=[];for(let o=0;o=t)return this.createErrorResult(`Invalid page index: ${e}. Valid range: 0-${t-1}`,"Use getStructure() to see available pages");this.lok.documentSetPart(this.docPtr,e);let r=this.lok.documentGetDocumentSize(this.docPtr),n=this.getShapesOnPage(),o={index:e,shapes:n,size:{width:r.width,height:r.height}};return this.createResult(o)}catch(t){return this.createErrorResult(`Failed to get page: ${String(t)}`)}}getPageCount(){try{let e=this.lok.documentGetParts(this.docPtr);return this.createResult(e)}catch(e){return this.createErrorResult(`Failed to get page count: ${String(e)}`)}}addPage(e){try{let t=this.lok.documentGetParts(this.docPtr);e?.afterPage!==void 0?this.lok.documentSetPart(this.docPtr,e.afterPage):this.lok.documentSetPart(this.docPtr,t-1),this.lok.postUnoCommand(this.docPtr,".uno:InsertPage");let r=e?.afterPage!==void 0?e.afterPage+1:t;return this.createResult({index:r})}catch(t){return this.createErrorResult(`Failed to add page: ${String(t)}`)}}deletePage(e){try{let t=this.lok.documentGetParts(this.docPtr);return t<=1?this.createErrorResult("Cannot delete the last page","A drawing document must have at least one page"):e<0||e>=t?this.createErrorResult(`Invalid page index: ${e}`,`Valid range: 0-${t-1}`):(this.lok.documentSetPart(this.docPtr,e),this.lok.postUnoCommand(this.docPtr,".uno:DeletePage"),this.createResult(void 0))}catch(t){return this.createErrorResult(`Failed to delete page: ${String(t)}`)}}duplicatePage(e){try{let t=this.lok.documentGetParts(this.docPtr);return e<0||e>=t?this.createErrorResult(`Invalid page index: ${e}`,`Valid range: 0-${t-1}`):(this.lok.documentSetPart(this.docPtr,e),this.lok.postUnoCommand(this.docPtr,".uno:DuplicatePage"),this.createResult({newIndex:e+1}))}catch(t){return this.createErrorResult(`Failed to duplicate page: ${String(t)}`)}}addShape(e,t,r,n){try{let o=this.lok.documentGetParts(this.docPtr);if(e<0||e>=o)return this.createErrorResult(`Invalid page index: ${e}`);this.lok.documentSetPart(this.docPtr,e);let s=this.getShapeCommand(t),i=JSON.stringify({X:{type:"long",value:r.x},Y:{type:"long",value:r.y},Width:{type:"long",value:r.width},Height:{type:"long",value:r.height}});if(this.lok.postUnoCommand(this.docPtr,s,i),n?.text){let a=JSON.stringify({Text:{type:"string",value:n.text}});this.lok.postUnoCommand(this.docPtr,".uno:InsertText",a);}if(n?.fillColor){let a=JSON.stringify({FillColor:{type:"long",value:this.hexToNumber(n.fillColor)}});this.lok.postUnoCommand(this.docPtr,".uno:FillColor",a);}if(n?.lineColor){let a=JSON.stringify({LineColor:{type:"long",value:this.hexToNumber(n.lineColor)}});this.lok.postUnoCommand(this.docPtr,".uno:XLineColor",a);}return this.createResult({shapeIndex:0})}catch(o){return this.createErrorResult(`Failed to add shape: ${String(o)}`)}}addLine(e,t,r,n){try{let o=this.lok.documentGetParts(this.docPtr);if(e<0||e>=o)return this.createErrorResult(`Invalid page index: ${e}`);this.lok.documentSetPart(this.docPtr,e);let s=JSON.stringify({StartX:{type:"long",value:t.x},StartY:{type:"long",value:t.y},EndX:{type:"long",value:r.x},EndY:{type:"long",value:r.y}});if(this.lok.postUnoCommand(this.docPtr,".uno:Line",s),n?.lineColor){let i=JSON.stringify({LineColor:{type:"long",value:this.hexToNumber(n.lineColor)}});this.lok.postUnoCommand(this.docPtr,".uno:XLineColor",i);}if(n?.lineWidth){let i=JSON.stringify({LineWidth:{type:"long",value:n.lineWidth}});this.lok.postUnoCommand(this.docPtr,".uno:LineWidth",i);}return this.createResult({shapeIndex:0})}catch(o){return this.createErrorResult(`Failed to add line: ${String(o)}`)}}deleteShape(e,t){try{return this.lok.documentSetPart(this.docPtr,e),this.selectShape(t),this.lok.postUnoCommand(this.docPtr,".uno:Delete"),this.createResult(void 0)}catch(r){return this.createErrorResult(`Failed to delete shape: ${String(r)}`)}}setShapeText(e,t,r){try{this.lok.documentSetPart(this.docPtr,e),this.selectShape(t),this.lok.postUnoCommand(this.docPtr,".uno:EnterGroup"),this.lok.postUnoCommand(this.docPtr,".uno:SelectAll");let n=JSON.stringify({Text:{type:"string",value:r}});return this.lok.postUnoCommand(this.docPtr,".uno:InsertText",n),this.lok.postUnoCommand(this.docPtr,".uno:LeaveGroup"),this.createResult(void 0)}catch(n){return this.createErrorResult(`Failed to set shape text: ${String(n)}`)}}moveShape(e,t,r){try{this.lok.documentSetPart(this.docPtr,e),this.selectShape(t);let n=JSON.stringify({X:{type:"long",value:r.x},Y:{type:"long",value:r.y}});return this.lok.postUnoCommand(this.docPtr,".uno:SetObjectPosition",n),this.createResult(void 0)}catch(n){return this.createErrorResult(`Failed to move shape: ${String(n)}`)}}resizeShape(e,t,r){try{this.lok.documentSetPart(this.docPtr,e),this.selectShape(t);let n=JSON.stringify({Width:{type:"long",value:r.width},Height:{type:"long",value:r.height}});return this.lok.postUnoCommand(this.docPtr,".uno:Size",n),this.createResult(void 0)}catch(n){return this.createErrorResult(`Failed to resize shape: ${String(n)}`)}}getPageInfoInternal(e){this.lok.documentSetPart(this.docPtr,e);let t=this.lok.documentGetDocumentSize(this.docPtr),r=this.getShapesOnPage();return {index:e,shapeCount:r.length,size:{width:t.width,height:t.height}}}getShapesOnPage(){let e=this.lok.getAllText(this.docPtr)||"";return e.trim()?[{index:0,type:"text",text:e,bounds:{x:0,y:0,width:0,height:0}}]:[]}selectShape(e){let t=JSON.stringify({Index:{type:"long",value:e}});this.lok.postUnoCommand(this.docPtr,".uno:SelectObject",t);}getShapeCommand(e){return {rectangle:".uno:Rect",ellipse:".uno:Ellipse",line:".uno:Line",text:".uno:Text",image:".uno:InsertGraphic",group:".uno:FormatGroup",other:".uno:Rect"}[e]}hexToNumber(e){return parseInt(e.replace("#",""),16)}};var ye=0,Se=1,Ee=2,Pe=3;function se(l,e,t){let r=l.documentGetDocumentType(e);switch(r){case ye:return new z(l,e,t);case Se:return new G(l,e,t);case Ee:return new H(l,e,t);case Pe:return new j(l,e,t);default:throw new Error(`Unsupported document type: ${r}`)}}function Oe(l){return {0:"INVALIDATE_TILES",1:"INVALIDATE_VISIBLE_CURSOR",2:"TEXT_SELECTION",3:"TEXT_SELECTION_START",4:"TEXT_SELECTION_END",5:"CURSOR_VISIBLE",6:"GRAPHIC_SELECTION",7:"HYPERLINK_CLICKED",8:"STATE_CHANGED",9:"STATUS_INDICATOR_START",10:"STATUS_INDICATOR_SET_VALUE",11:"STATUS_INDICATOR_FINISH",12:"SEARCH_NOT_FOUND",13:"DOCUMENT_SIZE_CHANGED",14:"SET_PART",15:"SEARCH_RESULT_SELECTION",16:"UNO_COMMAND_RESULT",17:"CELL_CURSOR",18:"MOUSE_POINTER",19:"CELL_FORMULA",20:"DOCUMENT_PASSWORD",21:"DOCUMENT_PASSWORD_TO_MODIFY",22:"CONTEXT_MENU",23:"INVALIDATE_VIEW_CURSOR",24:"TEXT_VIEW_SELECTION",25:"CELL_VIEW_CURSOR",26:"GRAPHIC_VIEW_SELECTION",27:"VIEW_CURSOR_VISIBLE",28:"VIEW_LOCK",29:"REDLINE_TABLE_SIZE_CHANGED",30:"REDLINE_TABLE_ENTRY_MODIFIED",31:"COMMENT",32:"INVALIDATE_HEADER",33:"CELL_ADDRESS"}[l]||`UNKNOWN(${l})`}var J={nSize:0,destroy:4,documentLoad:8,getError:12,documentLoadWithOptions:16,freeError:20},ae={nSize:0,destroy:4,saveAs:8},_e=new TextEncoder,le=new TextDecoder,X=class{module;lokPtr=0;verbose;useShims;constructor(e,t=false){this.module=e,this.verbose=t,this.useShims=typeof this.module._lok_documentLoad=="function",this.useShims?this.log("Using direct LOK shim exports"):this.log("Using vtable traversal (shims not available)");}log(...e){this.verbose&&console.log("[LOK]",...e);}get HEAPU8(){return this.module.HEAPU8}get HEAPU32(){return this.module.HEAPU32}get HEAP32(){return this.module.HEAP32}allocString(e){let t=_e.encode(e+"\0"),r=this.module._malloc(t.length);return this.HEAPU8.set(t,r),r}readString(e){if(e===0)return null;let t=this.HEAPU8,r=e;for(;t[r]!==0;)r++;let n=t.slice(e,r);return le.decode(n)}readPtr(e){return this.HEAPU32[e>>2]||0}getFunc(e){return this.module.wasmTable.get(e)}initialize(e="/instdir/program"){this.log("Initializing with path:",e);let t=this.allocString(e);try{let r=this.module._libreofficekit_hook;if(typeof r!="function")throw new Error("libreofficekit_hook export not found on module");if(this.lokPtr=r(t),this.lokPtr===0)throw new Error("Failed to initialize LibreOfficeKit");this.log("LOK initialized, ptr:",this.lokPtr);}finally{this.module._free(t);}}getError(){if(this.lokPtr===0)return null;if(this.useShims&&this.module._lok_getError){let o=this.module._lok_getError(this.lokPtr);return this.readString(o)}let e=this.readPtr(this.lokPtr),t=this.readPtr(e+J.getError);if(t===0)return null;let n=this.getFunc(t)(this.lokPtr);return this.readString(n)}getVersionInfo(){return "LibreOffice WASM"}toFileUrl(e){return e.startsWith("file://")?e:"file://"+(e.startsWith("/")?e:"/"+e)}documentLoad(e){if(this.lokPtr===0)throw new Error("LOK not initialized");let t=this.toFileUrl(e);this.log("Loading document:",e,"->",t);let r=this.allocString(t);try{let n=Date.now(),o;if(this.useShims&&this.module._lok_documentLoad)o=this.module._lok_documentLoad(this.lokPtr,r);else {let s=this.readPtr(this.lokPtr),i=this.readPtr(s+J.documentLoad);o=this.getFunc(i)(this.lokPtr,r);}if(this.log("Document loaded in",Date.now()-n,"ms, ptr:",o),o===0){let s=this.getError();throw new Error(`Failed to load document: ${s||"unknown error"}`)}return o}finally{this.module._free(r);}}documentLoadWithOptions(e,t){if(this.lokPtr===0)throw new Error("LOK not initialized");let r=this.toFileUrl(e);this.log("Loading document with options:",e,"->",r,t);let n=this.allocString(r),o=this.allocString(t);try{let s;if(this.useShims&&this.module._lok_documentLoadWithOptions)s=this.module._lok_documentLoadWithOptions(this.lokPtr,n,o);else {let i=this.readPtr(this.lokPtr),a=this.readPtr(i+J.documentLoadWithOptions);if(a===0)return this.documentLoad(e);s=this.getFunc(a)(this.lokPtr,n,o);}if(s===0){let i=this.getError();throw new Error(`Failed to load document: ${i||"unknown error"}`)}return this.log("Document loaded, ptr:",s),s}finally{this.module._free(n),this.module._free(o);}}documentSaveAs(e,t,r,n=""){if(e===0)throw new Error("Invalid document pointer");let o=this.toFileUrl(t);this.log("Saving document to:",t,"->",o,"format:",r);let s=this.allocString(o),i=this.allocString(r),a=this.allocString(n);try{let d;if(this.useShims&&this.module._lok_documentSaveAs)d=this.module._lok_documentSaveAs(e,s,i,a);else {let h=this.readPtr(e),m=this.readPtr(h+ae.saveAs);d=this.getFunc(m)(e,s,i,a);}if(this.log("Save result:",d),d===0)throw new Error("Failed to save document")}finally{this.module._free(s),this.module._free(i),this.module._free(a);}}documentDestroy(e){if(e===0)return;if(this.useShims&&this.module._lok_documentDestroy){this.module._lok_documentDestroy(e),this.log("Document destroyed (via shim)");return}let t=this.readPtr(e),r=this.readPtr(t+ae.destroy);r!==0&&(this.getFunc(r)(e),this.log("Document destroyed (via vtable)"));}destroy(){if(this.lokPtr!==0){try{if(this.useShims&&this.module._lok_destroy)this.module._lok_destroy(this.lokPtr),this.log("LOK destroyed (via shim)");else {let e=this.readPtr(this.lokPtr),t=this.readPtr(e+J.destroy);t!==0&&(this.getFunc(t)(this.lokPtr),this.log("LOK destroyed (via vtable)"));}}catch(e){this.log("LOK destroy error (ignored):",e);}this.lokPtr=0;}}isInitialized(){return this.lokPtr!==0}isUsingShims(){return this.useShims}documentGetParts(e){if(e===0)throw new Error("Invalid document pointer");return this.useShims&&this.module._lok_documentGetParts?this.module._lok_documentGetParts(e):(this.log("documentGetParts: shim not available"),0)}documentGetPart(e){if(e===0)throw new Error("Invalid document pointer");return this.useShims&&this.module._lok_documentGetPart?this.module._lok_documentGetPart(e):(this.log("documentGetPart: shim not available"),0)}documentSetPart(e,t){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentSetPart){this.module._lok_documentSetPart(e,t);return}this.log("documentSetPart: shim not available");}documentGetDocumentType(e){if(e===0)throw new Error("Invalid document pointer");return this.useShims&&this.module._lok_documentGetDocumentType?this.module._lok_documentGetDocumentType(e):(this.log("documentGetDocumentType: shim not available"),0)}documentGetDocumentSize(e){if(this.log(`documentGetDocumentSize called with docPtr: ${e}`),e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentGetDocumentSize){let t=this.module._malloc(8);try{this.module._lok_documentGetDocumentSize(e,t,t+4);let r=this.HEAP32[t>>2]??0,n=this.HEAP32[t+4>>2]??0;return this.log(`documentGetDocumentSize: ${r}x${n} twips`),{width:r,height:n}}finally{this.module._free(t);}}return this.log("documentGetDocumentSize: shim not available"),{width:0,height:0}}documentInitializeForRendering(e,t=""){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentInitializeForRendering){let r=this.allocString(t);try{this.module._lok_documentInitializeForRendering(e,r);}finally{this.module._free(r);}return}this.log("documentInitializeForRendering: shim not available");}documentPaintTile(e,t,r,n,o,s,i){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentPaintTile){let a=t*r*4,d=this.module._malloc(a);if(d===0)throw new Error(`Failed to allocate ${a} bytes for tile buffer`);try{this.module._lok_documentPaintTile(e,d,t,r,n,o,s,i);let h=new Uint8Array(a);return h.set(this.HEAPU8.subarray(d,d+a)),h}finally{this.module._free(d);}}return this.log("documentPaintTile: shim not available"),new Uint8Array(0)}documentGetTileMode(e){if(e===0)throw new Error("Invalid document pointer");return this.useShims&&this.module._lok_documentGetTileMode?this.module._lok_documentGetTileMode(e):(this.log("documentGetTileMode: shim not available"),0)}renderPage(e,t,r,n=0,o=false){this.documentInitializeForRendering(e);let s=this.documentGetDocumentType(e);if(s===2||s===3){console.log(`[LOK] Setting part to ${t} for presentation/drawing`),this.documentSetPart(e,t);let p=this.documentGetPart(e);console.log(`[LOK] Current part after setPart: ${p}`),o||(this.setEditMode(e,0),console.log("[LOK] Set edit mode to 0 (view mode) for presentation rendering"));}let i=this.documentGetDocumentSize(e);if(this.log("Document size (twips):",i),i.width===0||i.height===0)throw new Error("Failed to get document size");let a=0,d=0,h=i.width,m=i.height;if(s===0||s===1){let p=this.getPartPageRectangles(e);if(p){let _=this.parsePageRectangles(p)[t];_&&(a=_.x,d=_.y,h=_.width,m=_.height,this.log(`Page ${t} rectangle:`,_));}}let u=m/h,g=r,f=n>0?n:Math.round(r*u);console.log(`[LOK] Calling paintTile: ${g}x${f} from tile pos (${a}, ${d}) size (${h}x${m})`);let P=this.documentPaintTile(e,g,f,a,d,h,m);return console.log(`[LOK] paintTile returned ${P.length} bytes`),{data:P,width:g,height:f}}renderPageFullQuality(e,t,r=150,n,o=false){this.documentInitializeForRendering(e);let s=this.documentGetDocumentType(e);(s===2||s===3)&&(this.log(`Setting part to ${t} for presentation/drawing`),this.documentSetPart(e,t),o||(this.setEditMode(e,0),this.log("Set edit mode to 0 (view mode) for presentation rendering")));let i=this.documentGetDocumentSize(e);if(this.log("Document size (twips):",i),i.width===0||i.height===0)throw new Error("Failed to get document size");let a=0,d=0,h=i.width,m=i.height;if(s===0||s===1){let O=this.getPartPageRectangles(e);if(O){let k=this.parsePageRectangles(O)[t];k&&(a=k.x,d=k.y,h=k.width,m=k.height,this.log(`Page ${t} rectangle:`,k));}}let u=1440,g=Math.round(h*r/u),f=Math.round(m*r/u),P=r;if(n&&(g>n||f>n)){let O=n/Math.max(g,f);g=Math.round(g*O),f=Math.round(f*O),P=Math.round(r*O),this.log(`Capped dimensions to ${g}x${f} (effective DPI: ${P})`);}console.log(`[LOK] renderPageFullQuality: ${g}x${f} at ${P} DPI from tile (${a}, ${d}) size (${h}x${m}) twips`);let p=this.documentPaintTile(e,g,f,a,d,h,m);return console.log(`[LOK] renderPageFullQuality returned ${p.length} bytes`),{data:p,width:g,height:f,dpi:P}}getTextSelection(e,t="text/plain;charset=utf-8"){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentGetTextSelection){let r=this.allocString(t),n=this.module._malloc(4);try{let o=this.module._lok_documentGetTextSelection(e,r,n);if(o===0)return null;let s=this.readString(o);return this.module._free(o),s}finally{this.module._free(r),this.module._free(n);}}return this.log("getTextSelection: shim not available"),null}setTextSelection(e,t,r,n){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentSetTextSelection){this.module._lok_documentSetTextSelection(e,t,r,n);return}this.log("setTextSelection: shim not available");}getSelectionType(e){if(e===0)throw new Error("Invalid document pointer");return this.useShims&&this.module._lok_documentGetSelectionType?this.module._lok_documentGetSelectionType(e):(this.log("getSelectionType: shim not available"),0)}resetSelection(e){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentResetSelection){this.module._lok_documentResetSelection(e);return}this.log("resetSelection: shim not available");}postMouseEvent(e,t,r,n,o=1,s=1,i=0){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentPostMouseEvent){this.module._lok_documentPostMouseEvent(e,t,r,n,o,s,i);return}this.log("postMouseEvent: shim not available");}postKeyEvent(e,t,r,n){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentPostKeyEvent){this.module._lok_documentPostKeyEvent(e,t,r,n);return}this.log("postKeyEvent: shim not available");}postUnoCommand(e,t,r="{}",n=false){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentPostUnoCommand){let o=this.allocString(t),s=this.allocString(r);try{this.module._lok_documentPostUnoCommand(e,o,s,n?1:0);}finally{this.module._free(o),this.module._free(s);}return}this.log("postUnoCommand: shim not available");}getCommandValues(e,t){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentGetCommandValues){let r=this.allocString(t);try{let n=this.module._lok_documentGetCommandValues(e,r);if(n===0)return null;let o=this.readString(n);return this.module._free(n),o}finally{this.module._free(r);}}return this.log("getCommandValues: shim not available"),null}getPartPageRectangles(e){if(this.log(`getPartPageRectangles called with docPtr: ${e}`),e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentGetPartPageRectangles){this.log("getPartPageRectangles: using shim");let t=this.module._lok_documentGetPartPageRectangles(e);if(this.log(`getPartPageRectangles: resultPtr=${t}`),t===0)return null;let r=this.readString(t);return this.log(`getPartPageRectangles: result="${r?.slice(0,100)}..."`),this.module._free(t),r}return this.log("getPartPageRectangles: shim not available"),null}getPartInfo(e,t){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentGetPartInfo){let r=this.module._lok_documentGetPartInfo(e,t);if(r===0)return null;let n=this.readString(r);return this.module._free(r),n}return this.log("getPartInfo: shim not available"),null}getPartName(e,t){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentGetPartName){let r=this.module._lok_documentGetPartName(e,t);if(r===0)return null;let n=this.readString(r);return this.module._free(r),n}return this.log("getPartName: shim not available"),null}paste(e,t,r){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentPaste){let n=this.allocString(t),o,s;if(typeof r=="string"){let i=new TextEncoder().encode(r);s=i.length,o=this.module._malloc(s),this.HEAPU8.set(i,o);}else s=r.length,o=this.module._malloc(s),this.HEAPU8.set(r,o);try{return this.module._lok_documentPaste(e,n,o,s)!==0}finally{this.module._free(n),this.module._free(o);}}return this.log("paste: shim not available"),false}setClientZoom(e,t,r,n,o){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentSetClientZoom){this.module._lok_documentSetClientZoom(e,t,r,n,o);return}this.log("setClientZoom: shim not available");}setClientVisibleArea(e,t,r,n,o){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentSetClientVisibleArea){this.module._lok_documentSetClientVisibleArea(e,t,r,n,o);return}this.log("setClientVisibleArea: shim not available");}getA11yFocusedParagraph(e){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentGetA11yFocusedParagraph){let t=this.module._lok_documentGetA11yFocusedParagraph(e);if(t===0)return null;let r=this.readString(t);return this.module._free(t),r}return this.log("getA11yFocusedParagraph: shim not available"),null}getA11yCaretPosition(e){if(e===0)throw new Error("Invalid document pointer");return this.useShims&&this.module._lok_documentGetA11yCaretPosition?this.module._lok_documentGetA11yCaretPosition(e):(this.log("getA11yCaretPosition: shim not available"),-1)}setAccessibilityState(e,t,r){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentSetAccessibilityState){this.module._lok_documentSetAccessibilityState(e,t,r?1:0);return}this.log("setAccessibilityState: shim not available");}getDataArea(e,t){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentGetDataArea){let r=this.module._malloc(8),n=this.module._malloc(8);try{this.module._lok_documentGetDataArea(e,t,r,n);let o=this.HEAP32[r>>2]??0,s=this.HEAP32[n>>2]??0;return {col:o,row:s}}finally{this.module._free(r),this.module._free(n);}}return this.log("getDataArea: shim not available"),{col:0,row:0}}getEditMode(e){if(e===0)throw new Error("Invalid document pointer");return this.useShims&&this.module._lok_documentGetEditMode?this.module._lok_documentGetEditMode(e):(this.log("getEditMode: shim not available"),0)}setEditMode(e,t){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentSetEditMode){this.log(`setEditMode: setting mode to ${t}`),this.module._lok_documentSetEditMode(e,t);return}this.log("setEditMode: shim not available");}createView(e){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentCreateView){let t=this.module._lok_documentCreateView(e);return this.log(`createView: created view ${t}`),t}return this.log("createView: shim not available"),-1}createViewWithOptions(e,t){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentCreateViewWithOptions){let r=this.allocString(t);try{let n=this.module._lok_documentCreateViewWithOptions(e,r);return this.log(`createViewWithOptions: created view ${n}`),n}finally{this.module._free(r);}}return this.log("createViewWithOptions: shim not available"),-1}destroyView(e,t){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentDestroyView){this.log(`destroyView: destroying view ${t}`),this.module._lok_documentDestroyView(e,t);return}this.log("destroyView: shim not available");}setView(e,t){if(e===0)throw new Error("Invalid document pointer");if(this.useShims&&this.module._lok_documentSetView){this.log(`setView: setting active view to ${t}`),this.module._lok_documentSetView(e,t);return}this.log("setView: shim not available");}getView(e){if(e===0)throw new Error("Invalid document pointer");return this.useShims&&this.module._lok_documentGetView?this.module._lok_documentGetView(e):(this.log("getView: shim not available"),-1)}getViewsCount(e){if(e===0)throw new Error("Invalid document pointer");return this.useShims&&this.module._lok_documentGetViewsCount?this.module._lok_documentGetViewsCount(e):(this.log("getViewsCount: shim not available"),0)}enableSyncEvents(){this.useShims&&this.module._lok_enableSyncEvents?(this.module._lok_enableSyncEvents(),this.log("enableSyncEvents: Unipoll mode enabled")):this.log("enableSyncEvents: shim not available");}disableSyncEvents(){this.useShims&&this.module._lok_disableSyncEvents?(this.module._lok_disableSyncEvents(),this.log("disableSyncEvents: Unipoll mode disabled")):this.log("disableSyncEvents: shim not available");}registerCallback(e){if(e===0)throw new Error("Invalid document pointer");this.useShims&&this.module._lok_documentRegisterCallback?(this.module._lok_documentRegisterCallback(e),this.log("registerCallback: callback registered")):this.log("registerCallback: shim not available");}unregisterCallback(e){if(e===0)throw new Error("Invalid document pointer");this.useShims&&this.module._lok_documentUnregisterCallback?(this.module._lok_documentUnregisterCallback(e),this.log("unregisterCallback: callback unregistered")):this.log("unregisterCallback: shim not available");}hasCallbackEvents(){return this.useShims&&this.module._lok_hasCallbackEvents?this.module._lok_hasCallbackEvents()!==0:false}getCallbackEventCount(){return this.useShims&&this.module._lok_getCallbackEventCount?this.module._lok_getCallbackEventCount():0}pollCallback(){if(!this.useShims||!this.module._lok_pollCallback)return null;let e=4096,t=this.module._malloc(e),r=this.module._malloc(4);try{let n=this.module._lok_pollCallback(t,e,r);if(n===-1)return null;let o=this.module.HEAP32[r>>2]??0,s="";if(o>0){let i=Math.min(o,e-1),a=this.module.HEAPU8.slice(t,t+i);s=le.decode(a);}return {type:n,typeName:Oe(n),payload:s}}finally{this.module._free(t),this.module._free(r);}}pollAllCallbacks(){let e=[],t=this.pollCallback();for(;t!==null;)e.push(t),t=this.pollCallback();return e}clearCallbackQueue(){this.useShims&&this.module._lok_clearCallbackQueue&&(this.module._lok_clearCallbackQueue(),this.log("clearCallbackQueue: queue cleared"));}flushCallbacks(e){if(e===0)throw new Error("Invalid document pointer");let t=!!this.module._lok_flushCallbacks;this.log(`flushCallbacks: useShims=${this.useShims}, _lok_flushCallbacks exists=${t}`),this.useShims&&this.module._lok_flushCallbacks?(this.module._lok_flushCallbacks(e),this.log("flushCallbacks: callbacks flushed")):this.log("flushCallbacks: shim not available");}pollStateChanges(){let e=new Map,t=this.pollAllCallbacks();for(let r of t)if(r.type===8){let n=r.payload.indexOf("=");if(n!==-1){let o=r.payload.substring(0,n),s=r.payload.substring(n+1);e.set(o,s);}else e.set(r.payload,"");}return e}clickAndGetText(e,t,r){return this.postMouseEvent(e,0,t,r,1,1,0),this.postMouseEvent(e,1,t,r,1,1,0),this.getTextSelection(e,"text/plain;charset=utf-8")}doubleClickAndGetWord(e,t,r){return this.postMouseEvent(e,0,t,r,2,1,0),this.postMouseEvent(e,1,t,r,2,1,0),this.getTextSelection(e,"text/plain;charset=utf-8")}selectAll(e){this.postUnoCommand(e,".uno:SelectAll");}getAllText(e){this.log(`getAllText called with docPtr: ${e}`),this.selectAll(e);let t=this.getTextSelection(e,"text/plain;charset=utf-8");return this.log(`getAllText: text="${t?.slice(0,100)}..."`),this.resetSelection(e),t}parsePageRectangles(e){return e?e.split(";").filter(t=>t.trim()).map(t=>{let r=t.split(",").map(Number);return {x:r[0]??0,y:r[1]??0,width:r[2]??0,height:r[3]??0}}):[]}};var Y=class{module=null;lokBindings=null;initialized=false;initializing=false;options;corrupted=false;fsTracked=false;constructor(e={}){this.options={wasmPath:"./wasm",verbose:false,...e};}isCorruptionError(e){let t=e instanceof Error?e.message:e;return t.includes("memory access out of bounds")||t.includes("ComponentContext is not avail")||t.includes("unreachable")||t.includes("table index is out of bounds")||t.includes("null function")}async reinitialize(){if(this.options.verbose&&console.log("[LibreOfficeConverter] Reinitializing due to corruption..."),this.lokBindings){try{this.lokBindings.destroy();}catch{}this.lokBindings=null;}this.module=null,this.initialized=false,this.corrupted=false,await this.initialize();}async initializeWithModule(e){if(!this.initialized){if(this.initializing){for(;this.initializing;)await new Promise(t=>setTimeout(t,100));return}this.initializing=true;try{this.module=e,this.setupFileSystem(),this.initializeLibreOfficeKit(),this.initialized=!0,this.options.onReady?.();}catch(t){console.error("[LibreOfficeConverter] Initialization error:",t);let r=t instanceof S?t:new S("WASM_NOT_INITIALIZED",`Failed to initialize with module: ${String(t)}`);throw this.options.onError?.(r),r}finally{this.initializing=false;}}}async initialize(){if(!this.initialized){if(this.initializing){for(;this.initializing;)await new Promise(e=>setTimeout(e,100));return}this.initializing=true,this.emitProgress("loading",0,"Loading LibreOffice WASM module...");try{this.module=await this.loadModule(),this.emitProgress("initializing",50,"Setting up virtual filesystem..."),this.setupFileSystem(),this.emitProgress("initializing",60,"Initializing LibreOfficeKit..."),this.initializeLibreOfficeKit(),this.emitProgress("initializing",90,"LibreOfficeKit ready"),this.initialized=!0,this.emitProgress("complete",100,"LibreOffice ready"),this.options.onReady?.();}catch(e){console.error("[LibreOfficeConverter] Initialization error:",e);let t=e instanceof S?e:new S("WASM_NOT_INITIALIZED",`Failed to initialize WASM module: ${String(e)}`);throw this.options.onError?.(t),t}finally{this.initializing=false;}}}async loadModule(){let e=this.options.wasmPath||"./wasm",t=`${e}/soffice.js`,r={locateFile:s=>s.endsWith(".wasm")?`${e}/soffice.wasm`:s.endsWith(".data")?`${e}/soffice.data`:`${e}/${s}`,print:this.options.verbose?console.log:()=>{},printErr:this.options.verbose?console.error:()=>{}},n=document.createElement("script");n.src=t,await new Promise((s,i)=>{n.onload=()=>s(),n.onerror=()=>i(new Error(`Failed to load ${t}`)),document.head.appendChild(n);});let o=window.createSofficeModule;if(!o)throw new S("WASM_NOT_INITIALIZED","WASM module factory not found. Make sure soffice.js is loaded.");return new Promise((s,i)=>{let a={...r,onRuntimeInitialized:()=>{s(a);}};o(a).catch(i);})}setupFileSystem(){if(!this.module?.FS)throw new S("WASM_NOT_INITIALIZED","Filesystem not available");let e=this.module.FS,t=r=>{try{e.mkdir(r);}catch{}};t("/tmp"),t("/tmp/input"),t("/tmp/output");}initializeLibreOfficeKit(){if(!this.module)throw new S("WASM_NOT_INITIALIZED","Module not loaded");if(this.options.verbose&&this.module.FS){let e=this.module.FS;if(!this.fsTracked&&(this.fsTracked=true,e.trackingDelegate||(e.trackingDelegate={onOpen:r=>{console.log("[FS OPEN]",r);},onOpenFile:r=>{console.log("[FS OPEN FILE]",r);}}),typeof e.open=="function")){let r=e.open.bind(e);e.open=((n,o,s)=>{console.log("[FS OPEN CALL]",n);try{return r(n,o,s)}catch(i){throw i?.code==="ENOENT"&&console.log("[FS ENOENT]",n),i}});}let t=(r,n)=>{try{console.log(`[FS] ${r}:`,e.readdir(n));}catch(o){console.log(`[FS] ${r}: ERROR -`,o.message);}};t("ROOT","/"),t("PROGRAM DIR","/instdir/program"),t("SHARE DIR","/instdir/share"),t("REGISTRY DIR","/instdir/share/registry"),t("FILTER DIR","/instdir/share/filter"),t("CONFIG DIR","/instdir/share/config/soffice.cfg"),t("CONFIG FILTER","/instdir/share/config/soffice.cfg/filter"),t("IMPRESS MODULES","/instdir/share/config/soffice.cfg/modules/simpress");}this.lokBindings=new X(this.module,this.options.verbose);try{if(this.lokBindings.initialize("/instdir/program"),this.options.verbose){let e=this.lokBindings.getVersionInfo();e&&console.log("[LOK] Version:",e);}}catch(e){throw new S("WASM_NOT_INITIALIZED",`Failed to initialize LibreOfficeKit: ${String(e)}`)}}async convert(e,t,r="document"){if(this.corrupted&&await this.reinitialize(),!this.initialized||!this.module)throw new S("WASM_NOT_INITIALIZED","LibreOffice WASM not initialized. Call initialize() first.");let n=Date.now(),o=this.normalizeInput(e);if(o.length===0)throw new S("INVALID_INPUT","Empty document provided");let s=t.inputFormat||this.getExtensionFromFilename(r)||"docx",i=t.outputFormat;if(!q[i])throw new S("UNSUPPORTED_FORMAT",`Unsupported output format: ${i}`);if(!ee(s,i))throw new S("UNSUPPORTED_FORMAT",ie(s,i));let a=`/tmp/input/doc.${s}`,d=`/tmp/output/doc.${i}`;try{this.emitProgress("converting",10,"Writing input document..."),this.module.FS.writeFile(a,o),this.emitProgress("converting",30,"Converting document...");let h=await this.performConversion(a,d,t);this.emitProgress("complete",100,"Conversion complete");let u=`${this.getBasename(r)}.${i}`;return {data:h,mimeType:te[i],filename:u,duration:Date.now()-n}}catch(h){throw h instanceof Error&&this.isCorruptionError(h)&&(this.corrupted=true,this.options.verbose&&console.log("[LibreOfficeConverter] Corruption detected, will reinitialize on next convert")),h}finally{try{this.module?.FS.unlink(a);}catch{}try{this.module?.FS.unlink(d);}catch{}}}async performConversion(e,t,r){if(!this.module||!this.lokBindings)throw new S("WASM_NOT_INITIALIZED","Module not loaded");this.emitProgress("converting",40,"Loading document...");let n=0;try{let o=r.password?`,Password=${r.password}`:"";if(this.options.verbose)try{let a=this.module.FS.stat(e);console.log("[Convert] File exists before LOK load:",e,"size:",a.size);}catch(a){console.log("[Convert] File NOT found before LOK load:",e,a.message);}if(o?n=this.lokBindings.documentLoadWithOptions(e,o):n=this.lokBindings.documentLoad(e),n===0)throw new S("LOAD_FAILED","Failed to load document");this.emitProgress("converting",60,"Converting format...");let s=U[r.outputFormat],i=K[r.outputFormat]||"";if(r.outputFormat==="pdf"&&r.pdf){let a=[];if(r.pdf.pdfaLevel){let d={"PDF/A-1b":1,"PDF/A-2b":2,"PDF/A-3b":3};a.push(`SelectPdfVersion=${d[r.pdf.pdfaLevel]||0}`);}r.pdf.quality!==void 0&&a.push(`Quality=${r.pdf.quality}`),a.length>0&&(i=a.join(","));}if(["png","jpg","svg"].includes(r.outputFormat)&&r.image?.pageIndex!==void 0){let a=r.image.pageIndex+1;i?i+=`;PageRange=${a}-${a}`:i=`PageRange=${a}-${a}`;}this.emitProgress("converting",70,"Saving document..."),this.lokBindings.documentSaveAs(n,t,s,i),this.emitProgress("converting",90,"Reading output...");try{let a=this.module.FS.readFile(t);if(a.length===0)throw new S("CONVERSION_FAILED","Conversion produced empty output");return a}catch(a){throw new S("CONVERSION_FAILED",`Failed to read converted file: ${String(a)}`)}}catch(o){throw o instanceof S?o:new S("CONVERSION_FAILED",`Conversion failed: ${String(o)}`)}finally{if(n!==0&&this.lokBindings)try{this.lokBindings.documentDestroy(n);}catch{}}}async renderPagePreviews(e,t,r={}){if(!this.initialized||!this.module||!this.lokBindings)throw new S("WASM_NOT_INITIALIZED","Converter not initialized");let n=this.normalizeInput(e),o=(t.inputFormat||"docx").toLowerCase(),s=r.width??256,i=r.height??0,a=r.pageIndices??[],d=`/tmp/preview/doc.${o}`,h=this.module.FS;try{try{h.mkdir("/tmp/preview");}catch{}h.writeFile(d,n);let m=this.lokBindings.documentLoad(d);if(m===0)throw new S("LOAD_FAILED","Failed to load document for preview");try{let u=this.lokBindings.documentGetParts(m);this.options.verbose&&console.log(`[Preview] Document has ${u} pages/parts`);let g=a.length>0?a.filter(p=>p>=0&&pO),f=[],P=r.editMode??!1;for(let p of g){this.options.verbose&&console.log(`[Preview] Rendering page ${p+1}/${u}`);let O=this.lokBindings.renderPage(m,p,s,i,P);f.push({page:p,data:O.data,width:O.width,height:O.height});}return f}finally{this.lokBindings.documentDestroy(m);}}finally{try{h.unlink(d);}catch{}try{h.rmdir("/tmp/preview");}catch{}}}async renderPageFullQuality(e,t,r,n={}){if(!this.initialized||!this.module||!this.lokBindings)throw new S("WASM_NOT_INITIALIZED","Converter not initialized");let o=this.normalizeInput(e),s=(t.inputFormat||"docx").toLowerCase(),i=n.dpi??150,a=n.maxDimension,d=`/tmp/fullquality/doc.${s}`,h=this.module.FS;try{try{h.mkdir("/tmp/fullquality");}catch{}h.writeFile(d,o);let m=this.lokBindings.documentLoad(d);if(m===0)throw new S("LOAD_FAILED","Failed to load document for full quality render");try{let u=this.lokBindings.documentGetParts(m);if(r<0||r>=u)throw new S("CONVERSION_FAILED",`Page index ${r} out of range (0-${u-1})`);this.options.verbose&&console.log(`[FullQuality] Rendering page ${r+1}/${u} at ${i} DPI`);let g=n.editMode??!1,f=this.lokBindings.renderPageFullQuality(m,r,i,a,g);return {page:r,data:f.data,width:f.width,height:f.height,dpi:f.dpi}}finally{this.lokBindings.documentDestroy(m);}}finally{try{h.unlink(d);}catch{}try{h.rmdir("/tmp/fullquality");}catch{}}}async getPageCount(e,t){if(!this.initialized||!this.module||!this.lokBindings)throw new S("WASM_NOT_INITIALIZED","Converter not initialized");let r=this.normalizeInput(e),o=`/tmp/pagecount/doc.${(t.inputFormat||"docx").toLowerCase()}`,s=this.module.FS;try{try{s.mkdir("/tmp/pagecount");}catch{}s.writeFile(o,r);let i=this.lokBindings.documentLoad(o);if(i===0)throw new S("LOAD_FAILED","Failed to load document");try{return this.lokBindings.documentGetParts(i)}finally{this.lokBindings.documentDestroy(i);}}finally{try{s.unlink(o);}catch{}try{s.rmdir("/tmp/pagecount");}catch{}}}async getDocumentInfo(e,t){if(!this.initialized||!this.module||!this.lokBindings)throw new S("WASM_NOT_INITIALIZED","Converter not initialized");let r=this.normalizeInput(e),o=`/tmp/docinfo/doc.${(t.inputFormat||"docx").toLowerCase()}`,s=this.module.FS;try{try{s.mkdir("/tmp/docinfo");}catch{}s.writeFile(o,r);let i=this.lokBindings.documentLoad(o);if(i===0)throw new S("LOAD_FAILED","Failed to load document");try{let a=this.lokBindings.documentGetDocumentType(i),d=this.lokBindings.documentGetParts(i),h=oe(a),m={0:"Text Document",1:"Spreadsheet",2:"Presentation",3:"Drawing",4:"Other"};return {documentType:a,documentTypeName:m[a]||"Unknown",validOutputFormats:h,pageCount:d}}finally{this.lokBindings.documentDestroy(i);}}finally{try{s.unlink(o);}catch{}try{s.rmdir("/tmp/docinfo");}catch{}}}async getDocumentText(e,t){if(!this.initialized||!this.module||!this.lokBindings)throw new S("WASM_NOT_INITIALIZED","Converter not initialized");let r=this.normalizeInput(e),n=t.inputFormat?.toLowerCase();if(!n)throw new S("INVALID_INPUT","Input format is required");let o=`/tmp/inspect/doc.${n}`,s=this.module.FS;try{try{s.mkdir("/tmp/inspect");}catch{}s.writeFile(o,r);let i=this.lokBindings.documentLoad(o);if(i===0)throw new S("LOAD_FAILED","Failed to load document");try{return this.lokBindings.getAllText(i)}finally{this.lokBindings.documentDestroy(i);}}finally{try{s.unlink(o);}catch{}try{s.rmdir("/tmp/inspect");}catch{}}}async getPageNames(e,t){if(!this.initialized||!this.module||!this.lokBindings)throw new S("WASM_NOT_INITIALIZED","Converter not initialized");let r=this.normalizeInput(e),n=t.inputFormat?.toLowerCase();if(!n)throw new S("INVALID_INPUT","Input format is required");let o=`/tmp/names/doc.${n}`,s=this.module.FS;try{try{s.mkdir("/tmp/names");}catch{}s.writeFile(o,r);let i=this.lokBindings.documentLoad(o);if(i===0)throw new S("LOAD_FAILED","Failed to load document");try{let a=this.lokBindings.documentGetParts(i),d=[];for(let h=0;h1&&t.pop()?.toLowerCase()||null}getBasename(e){let t=e.lastIndexOf(".");return t>0?e.substring(0,t):e}emitProgress(e,t,r){this.options.onProgress?.({phase:e,percent:t,message:r});}};var de={"download-wasm":50,"download-data":30,compile:5,filesystem:7,"lok-init":7,ready:1,starting:0,loading:0,initializing:0,converting:0,complete:0},x={"download-wasm":0,"download-data":0,compile:0,filesystem:0,"lok-init":0,ready:0,starting:0,loading:0,initializing:0,converting:0,complete:0},he=0,Z=0;function me(){let l=0;for(let t of Object.keys(x))l+=x[t];let e=Math.min(100,Math.round(l));return e>Z&&(Z=e),Z}function Q(l){return `${(l/1048576).toFixed(1)} MB`}function ce(l,e,t){let r=de[l],n=t>0?e/t:0,o=r*n;o>x[l]&&(x[l]=o);let s=l==="download-wasm"?`Downloading WebAssembly... ${Q(e)} / ${Q(t)}`:`Downloading filesystem... ${Q(e)} / ${Q(t)}`;pe({percent:me(),message:s,phase:l,bytesLoaded:e,bytesTotal:t});}function $(l,e){let t=de[l];t>x[l]&&(x[l]=t),pe({percent:me(),message:e,phase:l});}function pe(l){self.postMessage({type:"progress",id:he,progress:l});}function ke(){let l=self.fetch;self.fetch=async function(r,n){let o=typeof r=="string"?r:r instanceof URL?r.href:r.url,s=null;o.includes("soffice.wasm")?(s="download-wasm",console.log("[Worker] Starting fetch download: soffice.wasm")):o.includes("soffice.data")&&(s="download-data",console.log("[Worker] Starting fetch download: soffice.data"));let i=await l(r,n);if(!s)return i;if(s==="download-wasm")return console.log("[Worker] Returning original response for soffice.wasm (streaming compile requires raw Response)"),i;let a=i.headers.get("Content-Length"),d=a?parseInt(a,10):0;if(!d||!i.body)return console.log(`[Worker] No content-length for ${o}, skipping progress tracking`),i;let h=i.body.getReader(),m=0,u=new ReadableStream({async start(g){for(;;){let{done:f,value:P}=await h.read();if(f){console.log("[Worker] Finished fetch download: soffice.data"),g.close();break}m+=P.length,ce(s,m,d),g.enqueue(P);}}});return new Response(u,{headers:i.headers,status:i.status,statusText:i.statusText})},console.log("[Worker] Installed progress-tracking fetch interceptor");let e=self.XMLHttpRequest,t=function(){let r=new e,n="",o=r.open.bind(r);r.open=function(i,a,d,h,m){return n=String(a),o(i,a,d??true,h,m)};let s=r.send.bind(r);return r.send=function(i){let a=null;if(n.includes("soffice.wasm")?(a="download-wasm",console.log("[Worker] Starting XHR download: soffice.wasm")):n.includes("soffice.data")&&(a="download-data",console.log("[Worker] Starting XHR download: soffice.data")),a){let d=a;r.addEventListener("progress",h=>{h.lengthComputable&&ce(d,h.loaded,h.total);}),r.addEventListener("load",()=>{console.log(`[Worker] Finished XHR download: ${d==="download-wasm"?"soffice.wasm":"soffice.data"}`);});}return s(i)},r};Object.defineProperty(t,"prototype",{value:e.prototype,writable:false}),self.XMLHttpRequest=t,console.log("[Worker] Installed progress-tracking XHR interceptor");}var E=null,c=null,D=null,w=false,v=null,W=new Map,be=0;function Ce(l){let e=0,t=Math.max(1,Math.floor(l.length/1e3));for(let r=0;r{let d;return i.endsWith(".wasm")?d=t:i.endsWith(".data")?d=r:i.includes(".worker.")?d=n:d=`${e.substring(0,e.lastIndexOf("/")+1)}${i}`,console.log("[Worker] locateFile called:",i,"scriptDir:",a,"-> result:",d),d},print:console.log,printErr:console.error},importScripts(e),$("compile","Compiling WebAssembly module..."),await new Promise((i,a)=>{let d=()=>{if(self.Module&&self.Module.calledRun)i();else if(self.Module?.onRuntimeInitialized){let h=self.Module.onRuntimeInitialized;self.Module.onRuntimeInitialized=()=>{h?.(),i();};}else self.Module&&(self.Module.onRuntimeInitialized=i);setTimeout(()=>a(new Error("WASM initialization timeout")),12e4);};self.Module&&self.Module.calledRun?i():d();}),E=self.Module,$("filesystem","Setting up filesystem...");try{let i=E;i.ENV?(i.ENV.SAL_LOG="+ALL",i.ENV.MAX_CONCURRENCY="1",console.log("[Worker] Set SAL_LOG to +ALL")):console.log("[Worker] ENV not available");}catch(i){console.log("[Worker] Could not set SAL_LOG:",i);}let s=E.FS;if(o){let i=s.open.bind(s);s.open=(a,d,h)=>{console.log("[FS OPEN CALL]",a);try{return i(a,d,h)}catch(m){throw m?.code==="ENOENT"&&console.log("[FS ENOENT]",a),m}};}try{s.mkdir("/tmp");}catch{}try{s.mkdir("/tmp/input");}catch{}try{s.mkdir("/tmp/output");}catch{}if($("lok-init","Initializing LibreOfficeKit..."),D=new Y({verbose:o}),await D.initializeWithModule(E),c=D.getLokBindings(),!c)throw new Error("Failed to get LOK bindings from converter");c.enableSyncEvents(),console.log("[LOK Worker] Enabled synchronous event dispatch (Unipoll mode)"),w=!0,$("ready","Ready"),y({type:"ready",id:l.id});}catch(s){y({type:"error",id:l.id,error:s instanceof Error?s.message:String(s)});}}var Le=["png","jpg","jpeg","svg"];function Re(l){if(!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let{inputData:e,inputExt:t,outputFormat:r,filterOptions:n,password:o}=l;if(!e||!r){y({type:"error",id:l.id,error:"Missing input data or output format"});return}let s=Le.includes(r.toLowerCase()),i=`/tmp/input/doc.${t||"docx"}`,a=`/tmp/output/doc.${r}`,d=0,h=[];try{if(R(l.id,10,"Writing input file..."),E.FS.writeFile(i,e),R(l.id,30,"Loading document..."),o?d=c.documentLoadWithOptions(i,`,Password=${o}`):d=c.documentLoad(i),d===0){let g=c.getError();throw new Error(g||"Failed to load document")}let m=c.documentGetParts(d),u=c.documentGetDocumentType(d);if(s&&m>1){R(l.id,40,`Exporting ${m} pages as ${r.toUpperCase()}...`);let g=U[r],f=n||K[r]||"",P=[],{width:p,height:O}=c.documentGetDocumentSize(d),_=O/p,k=1024,N=Math.round(k*_);for(let b=0;b0){let L=new Uint8Array(T.length);L.set(T),P.push({name:`page_${b+1}.${r}`,data:L});}else console.warn(`[Worker] Page ${b+1} export produced empty file`);}R(l.id,85,"Creating ZIP archive...");let M=ve(P);R(l.id,100,"Complete"),y({type:"result",id:l.id,data:M});}else {R(l.id,50,"Converting...");let g=U[r],f=n||K[r]||"";R(l.id,70,"Saving..."),c.documentSaveAs(d,a,g,f),h.push(a),R(l.id,90,"Reading output...");let P=E.FS.readFile(a);if(P.length===0)throw new Error("Conversion produced empty output");let p=new Uint8Array(P.length);p.set(P),R(l.id,100,"Complete"),y({type:"result",id:l.id,data:p});}}catch(m){y({type:"error",id:l.id,error:m instanceof Error?m.message:String(m)});}finally{if(d!==0)try{c.documentDestroy(d);}catch{}try{E.FS.unlink(i);}catch{}for(let m of h)try{E.FS.unlink(m);}catch{}}}function ve(l){let e=[],t=[],r=0;for(let m of l){let u=new TextEncoder().encode(m.name),g=new Uint8Array(30+u.length),f=new DataView(g.buffer);f.setUint32(0,67324752,true),f.setUint16(4,20,true),f.setUint16(6,0,true),f.setUint16(8,0,true),f.setUint16(10,0,true),f.setUint16(12,0,true),f.setUint32(14,ue(m.data),true),f.setUint32(18,m.data.length,true),f.setUint32(22,m.data.length,true),f.setUint16(26,u.length,true),f.setUint16(28,0,true),g.set(u,30),e.push(g),e.push(m.data);let P=new Uint8Array(46+u.length),p=new DataView(P.buffer);p.setUint32(0,33639248,true),p.setUint16(4,20,true),p.setUint16(6,20,true),p.setUint16(8,0,true),p.setUint16(10,0,true),p.setUint16(12,0,true),p.setUint16(14,0,true),p.setUint32(16,ue(m.data),true),p.setUint32(20,m.data.length,true),p.setUint32(24,m.data.length,true),p.setUint16(28,u.length,true),p.setUint16(30,0,true),p.setUint16(32,0,true),p.setUint16(34,0,true),p.setUint16(36,0,true),p.setUint32(38,0,true),p.setUint32(42,r,true),P.set(u,46),t.push(P),r+=g.length+m.data.length;}let n=r,o=0;for(let m of t)e.push(m),o+=m.length;let s=new Uint8Array(22),i=new DataView(s.buffer);i.setUint32(0,101010256,true),i.setUint16(4,0,true),i.setUint16(6,0,true),i.setUint16(8,l.length,true),i.setUint16(10,l.length,true),i.setUint32(12,o,true),i.setUint32(16,n,true),i.setUint16(20,0,true),e.push(s);let a=e.reduce((m,u)=>m+u.length,0),d=new Uint8Array(a),h=0;for(let m of e)d.set(m,h),h+=m.length;return d}function ue(l){let e=4294967295;for(let t=0;t>>1^(e&1?3988292384:0);}return (e^4294967295)>>>0}async function Ae(l){if(!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let{inputData:e,inputExt:t}=l;if(!e){y({type:"error",id:l.id,error:"Missing input data"});return}try{let{pageCount:r}=F(e,t||"docx");y({type:"pageCount",id:l.id,pageCount:r});}catch(r){y({type:"error",id:l.id,error:r instanceof Error?r.message:String(r)}),A();}}async function Ie(l){if(!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let{inputData:e,inputExt:t,maxWidth:r=256}=l;if(!e){y({type:"error",id:l.id,error:"Missing input data"});return}try{R(l.id,10,"Loading document for preview...");let{docPtr:n,pageCount:o}=F(e,t||"docx");R(l.id,20,`Rendering ${o} pages...`);let s=[];for(let a=0;aa.data.buffer);self.postMessage({type:"previews",id:l.id,previews:s},i);}catch(n){y({type:"error",id:l.id,error:n instanceof Error?n.message:String(n)}),A();}}async function xe(l){if(!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let{inputData:e,inputExt:t,maxWidth:r=256,pageIndex:n=0}=l;if(!e){y({type:"error",id:l.id,error:"Missing input data"});return}let o=-1,s=0;try{let i=F(e,t||"docx");s=i.docPtr;let a=i.pageCount;if(n<0||n>=a)throw new Error(`Page index ${n} out of range (0-${a-1})`);let d=c.documentGetDocumentType(s);console.log(`[Worker] handleRenderSinglePage: ${["TEXT","SPREADSHEET","PRESENTATION","DRAWING"][d]||"UNKNOWN"} document - creating view for rendering`),o=c.createView(s),o>=0&&(c.setView(s,o),console.log(`[Worker] handleRenderSinglePage: Created and set view ${o}`)),console.log(`[Worker] handleRenderSinglePage: calling renderPage for page ${n} at maxWidth=${r}...`);let m=c.renderPage(s,n,r);console.log(`[Worker] handleRenderSinglePage: renderPage returned ${m.data.length} bytes (${m.width}x${m.height})`);let u=new Uint8Array(m.data.length);u.set(m.data);let g={page:n+1,data:u,width:m.width,height:m.height};if(o>=0&&s!==0){try{c.destroyView(s,o);}catch{}console.log(`[Worker] handleRenderSinglePage: Destroyed view ${o}`);}self.postMessage({type:"singlePagePreview",id:l.id,preview:g},[u.buffer]);}catch(i){if(o>=0&&s!==0){try{c.destroyView(s,o);}catch{}console.log(`[Worker] handleRenderSinglePage: Destroyed view ${o} (on error)`);}let a=i instanceof Error?String(i.message):String(i);y({type:"error",id:l.id,error:a}),A();}}async function Fe(l){if(!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let{inputData:e,inputExt:t,maxWidth:r=256,pageIndex:n=0}=l;if(!e){y({type:"error",id:l.id,error:"Missing input data"});return}let o=`/tmp/output/page_${n}.png`;try{let{docPtr:s,pageCount:i}=F(e,t||"pdf"),a=c.documentGetDocumentType(s);if(n<0||n>=i)throw new Error(`Page index ${n} out of range (0-${i-1})`);(a===2||a===3)&&c.documentSetPart(s,n);let{width:d,height:h}=c.documentGetDocumentSize(s),m=h/d,u=Math.min(r,d),g=Math.round(u*m),f=`PixelWidth=${u};PixelHeight=${g}`;a===0&&(f+=`;PageRange=${n+1}-${n+1}`),c.documentSaveAs(s,o,"png",f);let P=E.FS.readFile(o);if(P.length===0)throw new Error("PNG export produced empty output");let p=new Uint8Array(P.length);p.set(P);let O={page:n+1,data:p,width:u,height:g,format:"png"};self.postMessage({type:"singlePagePreview",id:l.id,preview:O,isPng:!0},[p.buffer]);try{E.FS.unlink(o);}catch{}}catch(s){y({type:"error",id:l.id,error:s instanceof Error?s.message:String(s)}),A();try{E.FS.unlink(o);}catch{}}}async function De(l){if(!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let{inputData:e,inputExt:t,pageIndex:r=0,dpi:n=150,maxDimension:o,editMode:s=false}=l;if(!e){y({type:"error",id:l.id,error:"Missing input data"});return}let i=-1,a=0;try{let d=F(e,t||"docx");a=d.docPtr;let h=d.pageCount;if(r<0||r>=h)throw new Error(`Page index ${r} out of range (0-${h-1})`);let m=c.documentGetDocumentType(a);console.log(`[Worker] handleRenderPageFullQuality: ${["TEXT","SPREADSHEET","PRESENTATION","DRAWING"][m]||"UNKNOWN"} document at ${n} DPI, editMode=${s}`),i=c.createView(a),i>=0&&(c.setView(a,i),console.log(`[Worker] handleRenderPageFullQuality: Created and set view ${i}`));let g=c.renderPageFullQuality(a,r,n,o,s);console.log(`[Worker] handleRenderPageFullQuality: rendered ${g.data.length} bytes (${g.width}x${g.height} at ${g.dpi} DPI)`);let f=new Uint8Array(g.data.length);f.set(g.data);let P={page:r+1,data:f,width:g.width,height:g.height,dpi:g.dpi};if(i>=0&&a!==0)try{c.destroyView(a,i);}catch{}self.postMessage({type:"fullQualityPagePreview",id:l.id,preview:P},[f.buffer]);}catch(d){if(i>=0&&a!==0)try{c.destroyView(a,i);}catch{}let h=d instanceof Error?String(d.message):String(d);y({type:"error",id:l.id,error:h}),A();}}async function We(l){if(!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let{inputData:e,inputExt:t}=l;if(!e){y({type:"error",id:l.id,error:"Missing input data"});return}try{let{docPtr:r,pageCount:n}=F(e,t||"docx"),o=c.documentGetDocumentType(r),s={0:["pdf","docx","doc","odt","rtf","txt","html","png","jpg","svg"],1:["pdf","xlsx","xls","ods","csv","html","png","jpg","svg"],2:["pdf","pptx","ppt","odp","png","jpg","svg","html"],3:["pdf","png","jpg","svg","html"],4:["pdf"]},a={documentType:o,documentTypeName:{0:"Text Document",1:"Spreadsheet",2:"Presentation",3:"Drawing",4:"Other"}[o]||"Unknown",validOutputFormats:s[o]||["pdf"],pageCount:n};y({type:"documentInfo",id:l.id,documentInfo:a});}catch(r){y({type:"error",id:l.id,error:r instanceof Error?r.message:String(r)}),A();}}async function Ne(l){if(console.log("[LOK Worker] handleGetLokInfo called"),!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let{inputData:e,inputExt:t}=l;if(!e){y({type:"error",id:l.id,error:"Missing input data"});return}try{console.log("[LOK Worker] Getting or loading document...");let{docPtr:r}=F(e,t||"docx");console.log(`[LOK Worker] Got docPtr: ${r}`),console.log("[LOK Worker] Calling LOK methods...");let n=c.getPartPageRectangles(r);console.log(`[LOK Worker] pageRectangles: ${n}`);let o=c.documentGetDocumentSize(r);console.log(`[LOK Worker] documentSize: ${o.width}x${o.height}`);let s=c.getPartInfo(r,0);console.log(`[LOK Worker] partInfo: ${s}`);let i=c.getA11yFocusedParagraph(r);console.log(`[LOK Worker] a11yFocusedParagraph: ${i}`);let a=c.getA11yCaretPosition(r);console.log(`[LOK Worker] a11yCaretPosition: ${a}`);let d=c.getEditMode(r);console.log(`[LOK Worker] editMode (initial): ${d}`);let h=null,m=-1;m=c.getView(r),console.log(`[LOK Worker] Got existing view: ${m}`),m>=0&&(c.setView(r,m),console.log(`[LOK Worker] Set active view to ${m}`));let u=c.createView(r);console.log(`[LOK Worker] Created new view: ${u}`),u>=0&&(c.setView(r,u),console.log(`[LOK Worker] Switched to new view: ${u}`)),c.setEditMode(r,1),d=c.getEditMode(r),console.log(`[LOK Worker] editMode (after setEditMode): ${d}`),h=c.getAllText(r),console.log(`[LOK Worker] allText: ${h?.slice(0,100)||"null"}`),u>=0&&(c.destroyView(r,u),console.log(`[LOK Worker] Destroyed view: ${u}`));let g=null;if(s)try{g=JSON.parse(s);}catch{console.warn("[LOK Worker] Failed to parse partInfo JSON:",s);}let f=null;if(i)try{f=JSON.parse(i);}catch{console.warn("[LOK Worker] Failed to parse a11yFocusedParagraph JSON:",i);}let P={pageRectangles:n,documentSize:o,partInfo:g,a11yFocusedParagraph:f,a11yCaretPosition:a,editMode:d,allText:h};console.log("[LOK Worker] Posting lokInfo response"),y({type:"lokInfo",id:l.id,lokInfo:P});}catch(r){console.error("[LOK Worker] Error in handleGetLokInfo:",r),y({type:"error",id:l.id,error:r instanceof Error?r.message:String(r)}),A();}}async function Ue(l){if(console.log("[LOK Worker] handleEditText called"),!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let{inputData:e,inputExt:t,findText:r,replaceText:n,insertText:o}=l;if(!e){y({type:"error",id:l.id,error:"Missing input data"});return}A();let s=`/tmp/input/edit_doc.${t||"docx"}`,i=`/tmp/output/edited_doc.${t||"docx"}`,a=0,d=-1;try{if(console.log("[LOK Worker] Writing input file..."),E.FS.writeFile(s,e),console.log("[LOK Worker] Loading document for editing..."),a=c.documentLoad(s),a===0){let p=c.getError();throw new Error(p||"Failed to load document")}console.log(`[LOK Worker] Document loaded, docPtr=${a}`),c.documentInitializeForRendering(a),console.log("[LOK Worker] Document initialized for rendering"),d=c.getView(a),console.log(`[LOK Worker] Got existing view: ${d}`),d>=0&&(c.setView(a,d),console.log(`[LOK Worker] Set active view to ${d}`));let h=c.createView(a);console.log(`[LOK Worker] Created new view: ${h}`),h>=0&&(c.setView(a,h),console.log(`[LOK Worker] Switched to new view: ${h}`),d=h),c.setEditMode(a,1);let m=c.getEditMode(a);console.log(`[LOK Worker] Edit mode after setEditMode(1): ${m}`);let u="";if(r&&n!==void 0){console.log(`[LOK Worker] Attempting find/replace: "${r}" -> "${n}"`);let p=JSON.stringify({"SearchItem.SearchString":{type:"string",value:r},"SearchItem.ReplaceString":{type:"string",value:n},"SearchItem.Command":{type:"unsigned short",value:"3"}});try{c.postUnoCommand(a,".uno:ExecuteSearch",p),u=`Attempted replace all "${r}" with "${n}"`,console.log(`[LOK Worker] ${u}`);}catch(O){console.error("[LOK Worker] ExecuteSearch threw:",O),u=`ExecuteSearch failed: ${String(O)}`;}}else if(o){console.log(`[LOK Worker] Attempting to insert text: "${o}"`);let p=[];try{console.log("[LOK Worker] Clicking in document to establish focus..."),c.postMouseEvent(a,0,1e3,1e3,1,1,0),c.postMouseEvent(a,1,1e3,1e3,1,1,0),console.log("[LOK Worker] Posted mouse events for focus"),p.push("mouseEvents:ok");}catch(_){console.error("[LOK Worker] postMouseEvent threw:",_),p.push("mouseEvents:err");}try{c.postUnoCommand(a,".uno:GoToEndOfDoc"),console.log("[LOK Worker] Posted GoToEndOfDoc"),p.push("GoToEndOfDoc:ok");}catch(_){console.error("[LOK Worker] GoToEndOfDoc threw:",_),p.push("GoToEndOfDoc:err");}let O=!1;try{console.log("[LOK Worker] Trying paste() with text/plain..."),O=c.paste(a,"text/plain;charset=utf-8",o),console.log(`[LOK Worker] paste() returned: ${O}`),p.push(`paste:${O}`);}catch(_){console.error("[LOK Worker] paste() threw:",_),p.push("paste:err");}try{let _=JSON.stringify({Text:{type:"string",value:o}});c.postUnoCommand(a,".uno:InsertText",_),console.log("[LOK Worker] Posted InsertText"),p.push("InsertText:ok");}catch(_){console.error("[LOK Worker] InsertText threw:",_),p.push("InsertText:err");}try{console.log("[LOK Worker] Now trying postKeyEvent for each character...");for(let _=0;_=0)try{c.destroyView(a,d);}catch{}try{c.documentDestroy(a);}catch{}}if(E){try{E.FS.unlink(s);}catch{}try{E.FS.unlink(i);}catch{}}}}async function Ke(l){if(console.log("[LOK Worker] handleRenderPageRectangles called"),!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let{inputData:e,inputExt:t,maxWidth:r=256}=l;if(!e){y({type:"error",id:l.id,error:"Missing input data"});return}try{let{docPtr:n}=F(e,t||"docx");c.documentInitializeForRendering(n);let o=c.getPartPageRectangles(n);if(console.log(`[LOK Worker] Page rectangles string: ${o?.slice(0,100)||"null"}...`),!o||o.length===0){y({type:"pageRectangles",id:l.id,pageRectangles:[]});return}let s=c.parsePageRectangles(o);console.log(`[LOK Worker] Parsed ${s.length} page rectangles`);let i=[],a=[];for(let d=0;d=0&&c.setView(o,s);let a=c.createView(o);console.log(`[LOK Worker] Created new view: ${a}`),a>=0&&(c.setView(o,a),s=a),c.setEditMode(o,1);let d=c.getEditMode(o);console.log(`[LOK Worker] Edit mode: ${d}`),c.registerCallback(o),c.clearCallbackQueue(),console.log("[LOK Worker] Callback registered for STATE_CHANGED events");try{c.postMouseEvent(o,0,1e3,1e3,1,1,0),c.postMouseEvent(o,1,1e3,1e3,1,1,0),i.push({operation:"establishFocus",success:!0,result:"Mouse click events sent"});}catch(u){i.push({operation:"establishFocus",success:!1,error:String(u)});}console.log("[LOK Worker] Testing SelectAll + getTextSelection...");try{c.postUnoCommand(o,".uno:SelectAll");let u=c.getTextSelection(o,"text/plain;charset=utf-8"),g=u?.length||0;console.log(`[LOK Worker] SelectAll result: ${g} chars, preview: "${u?.slice(0,100)}..."`),i.push({operation:"SelectAll+getTextSelection",success:g>0,result:{textLength:g,preview:u?.slice(0,200)}});}catch(u){console.error("[LOK Worker] SelectAll error:",u),i.push({operation:"SelectAll+getTextSelection",success:!1,error:String(u)});}console.log("[LOK Worker] Testing getSelectionType...");try{let u=c.getSelectionType(o);console.log(`[LOK Worker] Selection type: ${u}`),i.push({operation:"getSelectionType",success:!0,result:{selectionType:u,meaning:u===0?"NONE":u===1?"TEXT":u===2?"CELL":"UNKNOWN"}});}catch(u){console.error("[LOK Worker] getSelectionType error:",u),i.push({operation:"getSelectionType",success:!1,error:String(u)});}console.log("[LOK Worker] Testing resetSelection...");try{c.resetSelection(o);let u=c.getSelectionType(o);console.log(`[LOK Worker] Selection type after reset: ${u}`),i.push({operation:"resetSelection",success:!0,result:{selectionTypeAfterReset:u}});}catch(u){console.error("[LOK Worker] resetSelection error:",u),i.push({operation:"resetSelection",success:!1,error:String(u)});}console.log("[LOK Worker] Testing GoToStartOfDoc + selection + Delete...");try{c.postUnoCommand(o,".uno:GoToStartOfDoc"),console.log("[LOK Worker] Sent GoToStartOfDoc"),c.postUnoCommand(o,".uno:WordRightSel"),console.log("[LOK Worker] Sent WordRightSel");let u=c.getTextSelection(o,"text/plain;charset=utf-8");console.log(`[LOK Worker] Selected text before delete: "${u}"`),c.postUnoCommand(o,".uno:Delete"),console.log("[LOK Worker] Sent Delete"),i.push({operation:"SelectWord+Delete",success:!0,result:{deletedText:u}});}catch(u){console.error("[LOK Worker] SelectWord+Delete error:",u),i.push({operation:"SelectWord+Delete",success:!1,error:String(u)});}console.log("[LOK Worker] Testing Undo...");try{c.postUnoCommand(o,".uno:Undo"),console.log("[LOK Worker] Sent Undo"),c.postUnoCommand(o,".uno:SelectAll");let u=c.getTextSelection(o,"text/plain;charset=utf-8");console.log(`[LOK Worker] Text after Undo: ${u?.length} chars`),i.push({operation:"Undo",success:!0,result:{textLengthAfterUndo:u?.length||0}});}catch(u){console.error("[LOK Worker] Undo error:",u),i.push({operation:"Undo",success:!1,error:String(u)});}console.log("[LOK Worker] Testing Redo...");try{c.postUnoCommand(o,".uno:Redo"),console.log("[LOK Worker] Sent Redo"),c.postUnoCommand(o,".uno:SelectAll");let u=c.getTextSelection(o,"text/plain;charset=utf-8");console.log(`[LOK Worker] Text after Redo: ${u?.length} chars`),i.push({operation:"Redo",success:!0,result:{textLengthAfterRedo:u?.length||0}});}catch(u){console.error("[LOK Worker] Redo error:",u),i.push({operation:"Redo",success:!1,error:String(u)});}console.log("[LOK Worker] Testing Bold formatting...");try{c.postUnoCommand(o,".uno:Undo"),c.postUnoCommand(o,".uno:GoToStartOfDoc"),c.postUnoCommand(o,".uno:WordRightSel");let u=c.getTextSelection(o,"text/plain;charset=utf-8");console.log(`[LOK Worker] Selected for bold: "${u}"`),c.postUnoCommand(o,".uno:Bold"),console.log("[LOK Worker] Sent Bold");let g=c.getCommandValues(o,".uno:Bold");console.log(`[LOK Worker] Bold state: ${g}`),i.push({operation:"Bold",success:!0,result:{selectedText:u,boldState:g}});}catch(u){console.error("[LOK Worker] Bold error:",u),i.push({operation:"Bold",success:!1,error:String(u)});}console.log("[LOK Worker] Testing Italic formatting...");try{c.postUnoCommand(o,".uno:GoRight"),c.postUnoCommand(o,".uno:WordRightSel");let u=c.getTextSelection(o,"text/plain;charset=utf-8");console.log(`[LOK Worker] Selected for italic: "${u}"`),c.postUnoCommand(o,".uno:Italic"),console.log("[LOK Worker] Sent Italic");let g=c.getCommandValues(o,".uno:Italic");console.log(`[LOK Worker] Italic state: ${g}`),i.push({operation:"Italic",success:!0,result:{selectedText:u,italicState:g}});}catch(u){console.error("[LOK Worker] Italic error:",u),i.push({operation:"Italic",success:!1,error:String(u)});}console.log("[LOK Worker] Testing getCharacterFormatting via STATE_CHANGED callbacks...");try{let u=c.getCallbackEventCount();console.log(`[LOK Worker] Existing events in queue: ${u}`);let g=c.pollStateChanges();console.log(`[LOK Worker] Existing STATE_CHANGED events: ${g.size}`);for(let[C,T]of g.entries())console.log(`[LOK Worker] ${C} = ${T}`);c.clearCallbackQueue(),c.postUnoCommand(o,".uno:GoToStartOfDoc"),c.flushCallbacks(o),c.postUnoCommand(o,".uno:WordRightSel"),c.flushCallbacks(o);let f=c.getCallbackEventCount(),P=c.hasCallbackEvents();console.log(`[LOK Worker] Event count after WordRightSel: ${f}, hasEvents: ${P}`);let p=c.pollStateChanges();console.log(`[LOK Worker] State changes after selection: ${p.size}`);for(let[C,T]of p.entries())console.log(`[LOK Worker] ${C} = ${T}`);for(let[C,T]of g.entries())p.has(C)||p.set(C,T);let O={};for(let[C,T]of p.entries())O[C]=T;console.log(`[LOK Worker] Received ${p.size} state changes:`);for(let[C,T]of p.entries())console.log(` ${C} = ${T}`);let _=p.get(".uno:Bold")??null,k=p.get(".uno:Italic")??null,N=p.get(".uno:Underline")??null,M=p.get(".uno:CharFontName")??null,b=p.get(".uno:FontHeight")??null,B=p.get(".uno:Color")??p.get(".uno:CharColor")??null;console.log("[LOK Worker] Character formatting from STATE_CHANGED:"),console.log(` Bold: ${_}`),console.log(` Italic: ${k}`),console.log(` Underline: ${N}`),console.log(` FontName: ${M}`),console.log(` FontSize: ${b}`),console.log(` Color: ${B}`),i.push({operation:"getCharacterFormatting",success:!0,result:{stateChangeCount:p.size,note:p.size===0?"Callback queue empty - C++ shims may need to be added to WASM build":void 0,bold:_,italic:k,underline:N,fontName:M,fontSize:b,color:B,allStates:O}});}catch(u){console.error("[LOK Worker] getCharacterFormatting error:",u),i.push({operation:"getCharacterFormatting",success:!1,error:String(u)});}console.log("[LOK Worker] Testing setTextSelection...");try{c.resetSelection(o),c.setTextSelection(o,0,500,500),c.setTextSelection(o,1,3e3,500);let u=c.getTextSelection(o,"text/plain;charset=utf-8");console.log(`[LOK Worker] Coordinate-selected text: "${u}"`),i.push({operation:"setTextSelection",success:!0,result:{selectedText:u}});}catch(u){console.error("[LOK Worker] setTextSelection error:",u),i.push({operation:"setTextSelection",success:!1,error:String(u)});}console.log("[LOK Worker] Testing document save...");try{let u=t||"docx",f={docx:"docx",doc:"doc",odt:"odt",xlsx:"xlsx",xls:"xls",ods:"ods",pptx:"pptx",ppt:"ppt",odp:"odp"}[u]||u;c.documentSaveAs(o,n,f,"");let P=E.FS.readFile(n);i.push({operation:"documentSave",success:P.length>0,result:{savedBytes:P.length,originalBytes:e.length}});}catch(u){console.error("[LOK Worker] Save error:",u),i.push({operation:"documentSave",success:!1,error:String(u)});}let m=`${i.filter(u=>u.success).length}/${i.length} operations succeeded`;console.log(`[LOK Worker] Test results summary: ${m}`),y({type:"testLokOperations",id:l.id,testLokOperationsResult:{operations:i,summary:m}});}catch(a){console.error("[LOK Worker] Error in handleTestLokOperations:",a),y({type:"error",id:l.id,error:a instanceof Error?a.message:String(a)});}finally{if(o!==0&&c){try{c.unregisterCallback(o);}catch{}if(s>=0)try{c.destroyView(o,s);}catch{}try{c.documentDestroy(o);}catch{}}if(E){try{E.FS.unlink(r);}catch{}try{E.FS.unlink(n);}catch{}}}}async function Me(l){if(!w||!E||!c){y({type:"error",id:l.id,error:"Worker not initialized"});return}let e=l.inputData,t=l.inputExt||"docx";if(!e||e.length===0){y({type:"error",id:l.id,error:"No input data provided"});return}try{let r=`session_${++be}_${Date.now()}`,n=`/tmp/edit_${r}.${t}`;E.FS.writeFile(n,e);let o=c.documentLoad(n);if(o===0){let h=c.getError();E.FS.unlink(n),y({type:"error",id:l.id,error:`Failed to load document: ${String(h)}`});return}c.documentInitializeForRendering(o);let s=c.createView(o);c.setView(o,s),c.registerCallback(o),c.postUnoCommand(o,".uno:Edit");let i=se(c,o),a=i.getDocumentType(),d=c.documentGetParts(o);W.set(r,{sessionId:r,docPtr:o,filePath:n,editor:i,documentType:a}),console.log(`[LOK Worker] Opened document session: ${r}, type: ${a}, pages: ${d}`),y({type:"editorSession",id:l.id,editorSession:{sessionId:r,documentType:a,pageCount:d}});}catch(r){console.error("[LOK Worker] Error in handleOpenDocument:",r),y({type:"error",id:l.id,error:r instanceof Error?r.message:String(r)});}}async function Be(l){let{sessionId:e,editorMethod:t,editorArgs:r}=l;if(!e||!t){y({type:"error",id:l.id,error:"Missing sessionId or editorMethod"});return}let n=W.get(e);if(!n){y({type:"error",id:l.id,error:`Session not found: ${e}`});return}try{let{editor:o}=n,s=r||[],i=o[t];if(typeof i!="function"){y({type:"error",id:l.id,error:`Unknown editor method: ${t}`});return}let a=i.apply(o,s),d=a.data;a.data instanceof Map&&(d=Object.fromEntries(a.data)),y({type:"editorOperationResult",id:l.id,editorOperationResult:{success:a.success,verified:a.verified,data:d,error:a.error,suggestion:a.suggestion}});}catch(o){console.error(`[LOK Worker] Error in handleEditorOperation (${t}):`,o),y({type:"error",id:l.id,error:o instanceof Error?o.message:String(o)});}}async function Ve(l){let{sessionId:e}=l;if(!e){y({type:"error",id:l.id,error:"Missing sessionId"});return}let t=W.get(e);if(!t){y({type:"error",id:l.id,error:`Session not found: ${e}`});return}try{let{docPtr:r,filePath:n}=t,o;if(E)try{let s=n.split(".").pop()||"docx";c?.documentSaveAs(r,n,s,""),o=E.FS.readFile(n);}catch(s){console.warn("[LOK Worker] Could not save document:",s);}if(c&&r!==0){try{c.unregisterCallback(r);}catch{}try{c.documentDestroy(r);}catch{}}if(E)try{E.FS.unlink(n);}catch{}W.delete(e),console.log(`[LOK Worker] Closed document session: ${e}`),y({type:"documentClosed",id:l.id,data:o});}catch(r){console.error("[LOK Worker] Error in handleCloseDocument:",r),y({type:"error",id:l.id,error:r instanceof Error?r.message:String(r)});}}function ze(l){console.log("handleDestroy");for(let[,e]of W)try{if(c&&e.docPtr!==0){try{c.unregisterCallback(e.docPtr);}catch{}try{c.documentDestroy(e.docPtr);}catch{}}if(E)try{E.FS.unlink(e.filePath);}catch{}}catch{}if(W.clear(),A(),D){try{D.destroy();}catch{}D=null,c=null;}else if(c){try{c.destroy();}catch{}c=null;}if(E&&E.PThread?.terminateAllThreads)try{E.PThread.terminateAllThreads();}catch{}E=null,w=false,y({type:"ready",id:l.id}),setTimeout(()=>self.close(),100);}self.onmessage=async l=>{let e=l.data;switch(e.type){case "init":await Te(e);break;case "convert":await Re(e);break;case "getPageCount":await Ae(e);break;case "renderPreviews":await Ie(e);break;case "renderSinglePage":await xe(e);break;case "renderPageViaConvert":await Fe(e);break;case "renderPageFullQuality":await De(e);break;case "getDocumentInfo":await We(e);break;case "getLokInfo":await Ne(e);break;case "editText":await Ue(e);break;case "renderPageRectangles":await Ke(e);break;case "testLokOperations":await $e(e);break;case "openDocument":await Me(e);break;case "editorOperation":await Be(e);break;case "closeDocument":await Ve(e);break;case "destroy":ze(e);break}};self.postMessage({type:"loaded"}); +})();//# sourceMappingURL=browser.worker.global.js.map +//# sourceMappingURL=browser.worker.global.js.map \ No newline at end of file diff --git a/public/libreoffice-wasm/soffice.data.gz b/public/libreoffice-wasm/soffice.data.gz new file mode 100644 index 0000000..96bd8bc Binary files /dev/null and b/public/libreoffice-wasm/soffice.data.gz differ diff --git a/public/libreoffice-wasm/soffice.js b/public/libreoffice-wasm/soffice.js new file mode 100644 index 0000000..eaf38d4 --- /dev/null +++ b/public/libreoffice-wasm/soffice.js @@ -0,0 +1,2 @@ +if(typeof global!=="undefined"){var Module=global.Module=global.Module||{}} +function GROWABLE_HEAP_I8(){if(wasmMemory.buffer!=HEAP8.buffer){updateMemoryViews()}return HEAP8}function GROWABLE_HEAP_U8(){if(wasmMemory.buffer!=HEAP8.buffer){updateMemoryViews()}return HEAPU8}function GROWABLE_HEAP_I16(){if(wasmMemory.buffer!=HEAP8.buffer){updateMemoryViews()}return HEAP16}function GROWABLE_HEAP_U16(){if(wasmMemory.buffer!=HEAP8.buffer){updateMemoryViews()}return HEAPU16}function GROWABLE_HEAP_I32(){if(wasmMemory.buffer!=HEAP8.buffer){updateMemoryViews()}return HEAP32}function GROWABLE_HEAP_U32(){if(wasmMemory.buffer!=HEAP8.buffer){updateMemoryViews()}return HEAPU32}function GROWABLE_HEAP_F32(){if(wasmMemory.buffer!=HEAP8.buffer){updateMemoryViews()}return HEAPF32}function GROWABLE_HEAP_F64(){if(wasmMemory.buffer!=HEAP8.buffer){updateMemoryViews()}return HEAPF64}var Module=typeof Module!="undefined"?Module:{};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof WorkerGlobalScope!="undefined";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string"&&process.type!="renderer";var ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var ENVIRONMENT_IS_PTHREAD=ENVIRONMENT_IS_WORKER&&self.name?.startsWith("em-pthread");if(ENVIRONMENT_IS_NODE){var worker_threads=require("worker_threads");global.Worker=worker_threads.Worker;ENVIRONMENT_IS_WORKER=!worker_threads.isMainThread;ENVIRONMENT_IS_PTHREAD=ENVIRONMENT_IS_WORKER&&worker_threads["workerData"]=="em-pthread"}if(!("preRun"in Module))Module["preRun"]=[];Module.preRun.push(function(){ENV.SAL_LOG="+WARN"});var Module=typeof Module!="undefined"?Module:{};Module["expectedDataFileDownloads"]??=0;Module["expectedDataFileDownloads"]++;(()=>{var isPthread=typeof ENVIRONMENT_IS_PTHREAD!="undefined"&&ENVIRONMENT_IS_PTHREAD;var isWasmWorker=typeof ENVIRONMENT_IS_WASM_WORKER!="undefined"&&ENVIRONMENT_IS_WASM_WORKER;if(isPthread||isWasmWorker)return;var isNode=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";function loadPackage(metadata){var PACKAGE_PATH="";if(typeof window==="object"){PACKAGE_PATH=window["encodeURIComponent"](window.location.pathname.substring(0,window.location.pathname.lastIndexOf("/"))+"/")}else if(typeof process==="undefined"&&typeof location!=="undefined"){PACKAGE_PATH=encodeURIComponent(location.pathname.substring(0,location.pathname.lastIndexOf("/"))+"/")}var PACKAGE_NAME="soffice.data";var REMOTE_PACKAGE_BASE="soffice.data";var REMOTE_PACKAGE_NAME=Module["locateFile"]?Module["locateFile"](REMOTE_PACKAGE_BASE,""):REMOTE_PACKAGE_BASE;var REMOTE_PACKAGE_SIZE=metadata["remote_package_size"];function fetchRemotePackage(packageName,packageSize,callback,errback){if(isNode){require("fs").readFile(packageName,(err,contents)=>{if(err){errback(err)}else{callback(contents.buffer)}});return}Module["dataFileDownloads"]??={};fetch(packageName).catch(cause=>Promise.reject(new Error(`Network Error: ${packageName}`,{cause}))).then(response=>{if(!response.ok){return Promise.reject(new Error(`${response.status}: ${response.url}`))}if(!response.body&&response.arrayBuffer){return response.arrayBuffer().then(callback)}const reader=response.body.getReader();const iterate=()=>reader.read().then(handleChunk).catch(cause=>Promise.reject(new Error(`Unexpected error while handling : ${response.url} ${cause}`,{cause})));const chunks=[];const headers=response.headers;const total=Number(headers.get("Content-Length")??packageSize);let loaded=0;const handleChunk=({done,value})=>{if(!done){chunks.push(value);loaded+=value.length;Module["dataFileDownloads"][packageName]={loaded,total};let totalLoaded=0;let totalSize=0;for(const download of Object.values(Module["dataFileDownloads"])){totalLoaded+=download.loaded;totalSize+=download.total}Module["setStatus"]?.(`Downloading data... (${totalLoaded}/${totalSize})`);return iterate()}else{const packageData=new Uint8Array(chunks.map(c=>c.length).reduce((a,b)=>a+b,0));let offset=0;for(const chunk of chunks){packageData.set(chunk,offset);offset+=chunk.length}callback(packageData.buffer)}};Module["setStatus"]?.("Downloading data...");return iterate()})}function handleError(error){console.error("package error:",error)}var fetchedCallback=null;var fetched=Module["getPreloadedPackage"]?Module["getPreloadedPackage"](REMOTE_PACKAGE_NAME,REMOTE_PACKAGE_SIZE):null;if(!fetched)fetchRemotePackage(REMOTE_PACKAGE_NAME,REMOTE_PACKAGE_SIZE,data=>{if(fetchedCallback){fetchedCallback(data);fetchedCallback=null}else{fetched=data}},handleError);function runWithFS(Module){function assert(check,msg){if(!check)throw msg+(new Error).stack}Module["FS_createPath"]("/","android",true,true);Module["FS_createPath"]("/android","default-document",true,true);Module["FS_createPath"]("/","instdir",true,true);Module["FS_createPath"]("/instdir","presets",true,true);Module["FS_createPath"]("/instdir/presets","autotext",true,true);Module["FS_createPath"]("/instdir/presets","basic",true,true);Module["FS_createPath"]("/instdir/presets/basic","Standard",true,true);Module["FS_createPath"]("/instdir/presets","config",true,true);Module["FS_createPath"]("/instdir/presets","gallery",true,true);Module["FS_createPath"]("/instdir","program",true,true);Module["FS_createPath"]("/instdir/program","resource",true,true);Module["FS_createPath"]("/instdir/program/resource","common",true,true);Module["FS_createPath"]("/instdir/program/resource/common","fonts",true,true);Module["FS_createPath"]("/instdir/program","services",true,true);Module["FS_createPath"]("/instdir/program","shell",true,true);Module["FS_createPath"]("/instdir/program","types",true,true);Module["FS_createPath"]("/instdir","share",true,true);Module["FS_createPath"]("/instdir/share","config",true,true);Module["FS_createPath"]("/instdir/share/config","soffice.cfg",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","cui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/cui","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","desktop",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/desktop","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","editeng",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/editeng","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","filter",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/filter","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","formula",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/formula","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","fps",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/fps","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","modules",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","StartModule",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/StartModule","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","scalc",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/scalc","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/scalc","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/scalc","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/scalc","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/scalc","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","schart",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/schart","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/schart","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/schart","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/schart","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/schart","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","sdraw",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sdraw","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sdraw","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sdraw","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sdraw","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sdraw","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","sglobal",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sglobal","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sglobal","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sglobal","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sglobal","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","simpress",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/simpress","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/simpress","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/simpress","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/simpress","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/simpress","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","smath",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/smath","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/smath","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/smath","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/smath","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/smath","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","sweb",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sweb","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sweb","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sweb","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/sweb","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","swform",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swform","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swform","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swform","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swform","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","swreport",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swreport","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swreport","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swreport","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swreport","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","swriter",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swriter","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swriter","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swriter","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swriter","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swriter","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules","swxform",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swxform","menubar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swxform","popupmenu",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swxform","statusbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/modules/swxform","toolbar",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","sfx",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/sfx","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","simpress",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","svt",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/svt","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","svx",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/svx","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","uui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/uui","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","vcl",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/vcl","ui",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg","writerperfect",true,true);Module["FS_createPath"]("/instdir/share/config/soffice.cfg/writerperfect","ui",true,true);Module["FS_createPath"]("/instdir/share/config","wizard",true,true);Module["FS_createPath"]("/instdir/share/config/wizard","form",true,true);Module["FS_createPath"]("/instdir/share/config/wizard/form","styles",true,true);Module["FS_createPath"]("/instdir/share","filter",true,true);Module["FS_createPath"]("/instdir/share","fontconfig",true,true);Module["FS_createPath"]("/instdir/share/fontconfig","conf.d",true,true);Module["FS_createPath"]("/instdir/share","fonts",true,true);Module["FS_createPath"]("/instdir/share/fonts","truetype",true,true);Module["FS_createPath"]("/instdir/share","gallery",true,true);Module["FS_createPath"]("/instdir/share","liblangtag",true,true);Module["FS_createPath"]("/instdir/share/liblangtag","common",true,true);Module["FS_createPath"]("/instdir/share/liblangtag/common","bcp47",true,true);Module["FS_createPath"]("/instdir/share/liblangtag/common","supplemental",true,true);Module["FS_createPath"]("/instdir/share","registry",true,true);Module["FS_createPath"]("/instdir/share/registry","res",true,true);function DataRequest(start,end,audio){this.start=start;this.end=end;this.audio=audio}DataRequest.prototype={requests:{},open:function(mode,name){this.name=name;this.requests[name]=this;Module["addRunDependency"](`fp ${this.name}`)},send:function(){},onload:function(){var byteArray=this.byteArray.subarray(this.start,this.end);this.finish(byteArray)},finish:function(byteArray){var that=this;Module["FS_createDataFile"](this.name,null,byteArray,true,true,true);Module["removeRunDependency"](`fp ${that.name}`);this.requests[this.name]=null}};var files=metadata["files"];for(var i=0;i{throw toThrow};var _scriptName=typeof document!="undefined"?document.currentScript?.src:undefined;if(ENVIRONMENT_IS_NODE){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){if(typeof process=="undefined"||!process.release||process.release.name!=="node")throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");var nodeVersion=process.versions.node;var numericVersion=nodeVersion.split(".").slice(0,3);numericVersion=numericVersion[0]*1e4+numericVersion[1]*100+numericVersion[2].split("-")[0]*1;if(numericVersion<16e4){throw new Error("This emscripten-generated code requires node v16.0.0 (detected v"+nodeVersion+")")}var fs=require("fs");var nodePath=require("path");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);assert(Buffer.isBuffer(ret));return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");assert(binary?Buffer.isBuffer(ret):typeof ret=="string");return ret};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);if(typeof module!="undefined"){module["exports"]=Module}quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_SHELL){if(typeof process=="object"&&typeof require==="function"||typeof window=="object"||typeof WorkerGlobalScope!="undefined")throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)")}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}if(!(typeof window=="object"||typeof WorkerGlobalScope!="undefined"))throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");if(!ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{throw new Error("environment detection error")}var defaultPrint=console.log.bind(console);var defaultPrintErr=console.error.bind(console);if(ENVIRONMENT_IS_NODE){defaultPrint=(...args)=>fs.writeSync(1,args.join(" ")+"\n");defaultPrintErr=(...args)=>fs.writeSync(2,args.join(" ")+"\n")}var out=Module["print"]||defaultPrint;var err=Module["printErr"]||defaultPrintErr;Object.assign(Module,moduleOverrides);moduleOverrides=null;checkIncomingModuleAPI();if(Module["arguments"])arguments_=Module["arguments"];legacyModuleProp("arguments","arguments_");if(Module["thisProgram"])thisProgram=Module["thisProgram"];legacyModuleProp("thisProgram","thisProgram");assert(typeof Module["memoryInitializerPrefixURL"]=="undefined","Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead");assert(typeof Module["pthreadMainPrefixURL"]=="undefined","Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead");assert(typeof Module["cdInitializerPrefixURL"]=="undefined","Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead");assert(typeof Module["filePackagePrefixURL"]=="undefined","Module.filePackagePrefixURL option was removed, use Module.locateFile instead");assert(typeof Module["read"]=="undefined","Module.read option was removed");assert(typeof Module["readAsync"]=="undefined","Module.readAsync option was removed (modify readAsync in JS)");assert(typeof Module["readBinary"]=="undefined","Module.readBinary option was removed (modify readBinary in JS)");assert(typeof Module["setWindowTitle"]=="undefined","Module.setWindowTitle option was removed (modify emscripten_set_window_title in JS)");assert(typeof Module["TOTAL_MEMORY"]=="undefined","Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY");legacyModuleProp("asm","wasmExports");legacyModuleProp("readAsync","readAsync");legacyModuleProp("readBinary","readBinary");legacyModuleProp("setWindowTitle","setWindowTitle");assert(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER||ENVIRONMENT_IS_NODE,"Pthreads do not work in this environment yet (need Web Workers, or an alternative to them)");assert(!ENVIRONMENT_IS_SHELL,"shell environment detected but not enabled at build time. Add `shell` to `-sENVIRONMENT` to enable.");var wasmBinary=Module["wasmBinary"];legacyModuleProp("wasmBinary","wasmBinary");if(typeof WebAssembly!="object"){err("no native wasm support detected")}function intArrayFromBase64(s){if(typeof ENVIRONMENT_IS_NODE!="undefined"&&ENVIRONMENT_IS_NODE){var buf=Buffer.from(s,"base64");return new Uint8Array(buf.buffer,buf.byteOffset,buf.length)}var decoded=atob(s);var bytes=new Uint8Array(decoded.length);for(var i=0;ionmessage({data:msg}));Object.assign(globalThis,{self:global,postMessage:msg=>parentPort.postMessage(msg)})}var initializedJS=false;function threadPrintErr(...args){var text=args.join(" ");if(ENVIRONMENT_IS_NODE){fs.writeSync(2,text+"\n");return}console.error(text)}if(!Module["printErr"])err=threadPrintErr;dbg=threadPrintErr;function threadAlert(...args){var text=args.join(" ");postMessage({cmd:"alert",text,threadId:_pthread_self()})}self.alert=threadAlert;self.onunhandledrejection=e=>{throw e.reason||e};function handleMessage(e){try{var msgData=e["data"];var cmd=msgData.cmd;if(cmd==="load"){workerID=msgData.workerID;let messageQueue=[];self.onmessage=e=>messageQueue.push(e);self.startWorker=instance=>{postMessage({cmd:"loaded"});for(let msg of messageQueue){handleMessage(msg)}self.onmessage=handleMessage};for(const handler of msgData.handlers){if(!Module[handler]||Module[handler].proxy){Module[handler]=(...args)=>{postMessage({cmd:"callHandler",handler,args})};if(handler=="print")out=Module[handler];if(handler=="printErr")err=Module[handler]}}wasmMemory=msgData.wasmMemory;updateMemoryViews();wasmModuleReceived(msgData.wasmModule)}else if(cmd==="run"){assert(msgData.pthread_ptr);establishStackSpace(msgData.pthread_ptr);__emscripten_thread_init(msgData.pthread_ptr,0,0,1,0,0);PThread.receiveObjectTransfer(msgData);PThread.threadInitTLS();__emscripten_thread_mailbox_await(msgData.pthread_ptr);if(!initializedJS){__embind_initialize_bindings();initializedJS=true}try{invokeEntryPoint(msgData.start_routine,msgData.arg)}catch(ex){if(ex!="unwind"){throw ex}}}else if(msgData.target==="setimmediate"){}else if(cmd==="checkMailbox"){if(initializedJS){checkMailbox()}}else if(cmd){err(`worker: received unknown command ${cmd}`);err(msgData)}}catch(ex){err(`worker: onmessage() captured an uncaught exception: ${ex}`);if(ex?.stack)err(ex.stack);__emscripten_thread_crashed();throw ex}}self.onmessage=handleMessage}assert(!Module["STACK_SIZE"],"STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time");assert(typeof Int32Array!="undefined"&&typeof Float64Array!=="undefined"&&Int32Array.prototype.subarray!=undefined&&Int32Array.prototype.set!=undefined,"JS engine does not provide full typed array support");if(!ENVIRONMENT_IS_PTHREAD){if(Module["wasmMemory"]){wasmMemory=Module["wasmMemory"]}else{var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||1073741824;legacyModuleProp("INITIAL_MEMORY","INITIAL_MEMORY");assert(INITIAL_MEMORY>=5242880,"INITIAL_MEMORY should be larger than STACK_SIZE, was "+INITIAL_MEMORY+"! (STACK_SIZE="+5242880+")");wasmMemory=new WebAssembly.Memory({initial:INITIAL_MEMORY/65536,maximum:65536,shared:true})}updateMemoryViews()}function writeStackCookie(){var max=_emscripten_stack_get_end();assert((max&3)==0);if(max==0){max+=4}GROWABLE_HEAP_U32()[max>>>2>>>0]=34821223;GROWABLE_HEAP_U32()[max+4>>>2>>>0]=2310721022;GROWABLE_HEAP_U32()[0>>>2>>>0]=1668509029}function checkStackCookie(){if(ABORT)return;var max=_emscripten_stack_get_end();if(max==0){max+=4}var cookie1=GROWABLE_HEAP_U32()[max>>>2>>>0];var cookie2=GROWABLE_HEAP_U32()[max+4>>>2>>>0];if(cookie1!=34821223||cookie2!=2310721022){abort(`Stack overflow! Stack cookie has been overwritten at ${ptrToString(max)}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${ptrToString(cookie2)} ${ptrToString(cookie1)}`)}if(GROWABLE_HEAP_U32()[0>>>2>>>0]!=1668509029){abort("Runtime error: The application has corrupted its heap memory area (address zero)!")}}var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){assert(!ENVIRONMENT_IS_PTHREAD);if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){assert(!runtimeInitialized);runtimeInitialized=true;if(ENVIRONMENT_IS_PTHREAD)return startWorker(Module);checkStackCookie();SOCKFS.root=FS.mount(SOCKFS,{},null);if(!Module["noFSInit"]&&!FS.initialized)FS.init();FS.ignorePermissions=false;TTY.init();PIPEFS.root=FS.mount(PIPEFS,{},null);callRuntimeCallbacks(__ATINIT__)}function preMain(){checkStackCookie();if(ENVIRONMENT_IS_PTHREAD)return;callRuntimeCallbacks(__ATMAIN__)}function postRun(){checkStackCookie();if(ENVIRONMENT_IS_PTHREAD)return;if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPreMain(cb){__ATMAIN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}assert(Math.imul,"This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill");assert(Math.fround,"This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill");assert(Math.clz32,"This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill");assert(Math.trunc,"This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill");var runDependencies=0;var dependenciesFulfilled=null;var runDependencyTracking={};var runDependencyWatcher=null;function getUniqueRunDependency(id){var orig=id;while(1){if(!runDependencyTracking[id])return id;id=orig+Math.random()}}function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies);if(id){assert(!runDependencyTracking[id]);runDependencyTracking[id]=1;if(runDependencyWatcher===null&&typeof setInterval!="undefined"){runDependencyWatcher=setInterval(()=>{if(ABORT){clearInterval(runDependencyWatcher);runDependencyWatcher=null;return}var shown=false;for(var dep in runDependencyTracking){if(!shown){shown=true;err("still waiting on run dependencies:")}err(`dependency: ${dep}`)}if(shown){err("(end of list)")}},1e4)}}else{err("warning: run dependency added without ID")}}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(id){assert(runDependencyTracking[id]);delete runDependencyTracking[id]}else{err("warning: run dependency removed without ID")}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;if(runtimeInitialized){___trap()}var e=new WebAssembly.RuntimeError(what);throw e}var dataURIPrefix="data:application/octet-stream;base64,";var isDataURI=filename=>filename.startsWith(dataURIPrefix);var isFileURI=filename=>filename.startsWith("file://");function createExportWrapper(name,nargs){return(...args)=>{assert(runtimeInitialized,`native function \`${name}\` called before runtime initialization`);var f=wasmExports[name];assert(f,`exported native function \`${name}\` not found`);assert(args.length<=nargs,`native function \`${name}\` called with ${args.length} args but expects ${nargs}`);return f(...args)}}function findWasmBinary(){var f="soffice.wasm";if(!isDataURI(f)){return locateFile(f)}return f}var wasmBinaryFile;function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);if(isFileURI(wasmBinaryFile)){err(`warning: Loading from a file URI (${wasmBinaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`)}abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){assignWasmImports();return{env:wasmImports,wasi_snapshot_preview1:wasmImports}}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;wasmExports=applySignatureConversions(wasmExports);registerTLSInit(wasmExports["_emscripten_tls_init"]);wasmTable=wasmExports["__indirect_function_table"];Module["wasmTable"]=wasmTable;assert(wasmTable,"table not found in wasm exports");addOnInit(wasmExports["__wasm_call_ctors"]);wasmModule=module;removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");var trueModule=Module;function receiveInstantiationResult(result){assert(Module===trueModule,"the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?");trueModule=null;receiveInstance(result["instance"],result["module"])}var info=getWasmImports();if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);return false}}if(ENVIRONMENT_IS_PTHREAD){return new Promise(resolve=>{wasmModuleReceived=module=>{var instance=new WebAssembly.Instance(module,getWasmImports());receiveInstance(instance,module);resolve()}})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);receiveInstantiationResult(result);return result}(()=>{var h16=new Int16Array(1);var h8=new Int8Array(h16.buffer);h16[0]=25459;if(h8[0]!==115||h8[1]!==99)throw"Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)"})();if(Module["ENVIRONMENT"]){throw new Error("Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)")}function legacyModuleProp(prop,newName,incoming=true){if(!Object.getOwnPropertyDescriptor(Module,prop)){Object.defineProperty(Module,prop,{configurable:true,get(){let extra=incoming?" (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)":"";abort(`\`Module.${prop}\` has been replaced by \`${newName}\``+extra)}})}}function ignoredModuleProp(prop){if(Object.getOwnPropertyDescriptor(Module,prop)){abort(`\`Module.${prop}\` was supplied but \`${prop}\` not included in INCOMING_MODULE_JS_API`)}}function isExportedByForceFilesystem(name){return name==="FS_createPath"||name==="FS_createDataFile"||name==="FS_createPreloadedFile"||name==="FS_unlink"||name==="addRunDependency"||name==="FS_createLazyFile"||name==="FS_createDevice"||name==="removeRunDependency"}function hookGlobalSymbolAccess(sym,func){if(typeof globalThis!="undefined"&&!Object.getOwnPropertyDescriptor(globalThis,sym)){Object.defineProperty(globalThis,sym,{configurable:true,get(){func();return undefined}})}}function missingGlobal(sym,msg){hookGlobalSymbolAccess(sym,()=>{warnOnce(`\`${sym}\` is not longer defined by emscripten. ${msg}`)})}missingGlobal("buffer","Please use HEAP8.buffer or wasmMemory.buffer");missingGlobal("asm","Please use wasmExports instead");function missingLibrarySymbol(sym){hookGlobalSymbolAccess(sym,()=>{var msg=`\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`;var librarySymbol=sym;if(!librarySymbol.startsWith("_")){librarySymbol="$"+sym}msg+=` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`;if(isExportedByForceFilesystem(sym)){msg+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you"}warnOnce(msg)});unexportedRuntimeSymbol(sym)}function unexportedRuntimeSymbol(sym){if(ENVIRONMENT_IS_PTHREAD){return}if(!Object.getOwnPropertyDescriptor(Module,sym)){Object.defineProperty(Module,sym,{configurable:true,get(){var msg=`'${sym}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`;if(isExportedByForceFilesystem(sym)){msg+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you"}abort(msg)}})}}function dbg(...args){if(ENVIRONMENT_IS_NODE&&fs){fs.writeSync(2,args.join(" ")+"\n")}else console.warn(...args)}var ASM_CONSTS={52007737:$0=>{window.open(UTF8ToString($0))}};function jsRegisterChar(raw){Module.registerType(raw,{name:"sal_Unicode",fromWireType(ptr){let str=String.fromCharCode(Module.HEAPU16[ptr>>1]);return str},toWireType(destructors,value){if(typeof value!="string"||value.length!==1){Module.throwBindingError("Cannot pass anything but 1-element string to C++ char16_t")}let data=Module._malloc(2);Module.HEAPU16[data>>1]=value.charCodeAt(0);if(destructors!==null){destructors.push(Module._free,data)}return data},argPackAdvance:8,readValueFromPointer(pointer){return this.fromWireType(Module.HEAPU32[pointer>>2])},destructorFunction(ptr){Module._free(ptr)}})}function jsRegisterString(raw){Module.registerType(raw,{name:"rtl::OUString",fromWireType(ptr){let data=Module.HEAPU32[ptr>>2];let length=Module.HEAPU32[(data>>2)+1];let buffer=data+8;let str="";for(let i=0;i>1)+i];str+=String.fromCharCode(c)}Module.rtl_uString_release(data);Module._free(ptr);return str},toWireType(destructors,value){if(typeof value!="string"){Module.throwBindingError("Cannot pass non-string to C++ OUString")}let data=Module._malloc(8+(value.length+1)*2);Module.HEAPU32[data>>2]=1;Module.HEAPU32[(data>>2)+1]=value.length;let buffer=data+8;for(let i=0;i>1)+i]=value.charCodeAt(i)}Module.HEAPU16[(buffer>>1)+value.length]=0;let ptr=Module._malloc(4);Module.HEAPU32[ptr>>2]=data;if(destructors!==null){destructors.push(Module._free,ptr)}return ptr},argPackAdvance:8,readValueFromPointer(pointer){return this.fromWireType(Module.HEAPU32[pointer>>2])},destructorFunction(ptr){Module._free(ptr)}})}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var terminateWorker=worker=>{worker.terminate();worker.onmessage=e=>{var cmd=e["data"].cmd;err(`received "${cmd}" command from terminated worker: ${worker.workerID}`)}};var cleanupThread=pthread_ptr=>{assert(!ENVIRONMENT_IS_PTHREAD,"Internal Error! cleanupThread() can only ever be called from main application thread!");assert(pthread_ptr,"Internal Error! Null pthread_ptr in cleanupThread!");var worker=PThread.pthreads[pthread_ptr];assert(worker);PThread.returnWorkerToPool(worker)};var spawnThread=threadParams=>{assert(!ENVIRONMENT_IS_PTHREAD,"Internal Error! spawnThread() can only ever be called from main application thread!");assert(threadParams.pthread_ptr,"Internal error, no pthread ptr!");var worker=PThread.getNewWorker();if(!worker){return 6}assert(!worker.pthread_ptr,"Internal error!");PThread.runningWorkers.push(worker);PThread.pthreads[threadParams.pthread_ptr]=worker;worker.pthread_ptr=threadParams.pthread_ptr;var msg={cmd:"run",start_routine:threadParams.startRoutine,arg:threadParams.arg,pthread_ptr:threadParams.pthread_ptr};if(ENVIRONMENT_IS_NODE){worker.unref()}worker.postMessage(msg,threadParams.transferList);return 0};var runtimeKeepaliveCounter=0;var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var stackSave=()=>_emscripten_stack_get_current();var stackRestore=val=>__emscripten_stack_restore(val);var stackAlloc=sz=>__emscripten_stack_alloc(sz);var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>numINT53_MAX?NaN:Number(num);var proxyToMainThread=(funcIndex,emAsmAddr,sync,...callArgs)=>{var serializedNumCallArgs=callArgs.length*2;var sp=stackSave();var args=stackAlloc(serializedNumCallArgs*8);var b=args>>>3;for(var i=0;i>>0]=arg}}var rtn=__emscripten_run_on_main_thread_js(funcIndex,emAsmAddr,serializedNumCallArgs,args,sync);stackRestore(sp);return rtn};function _proc_exit(code){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(0,0,1,code);EXITSTATUS=code;if(!keepRuntimeAlive()){PThread.terminateAllThreads();Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))}var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}checkStackCookie();if(e instanceof WebAssembly.RuntimeError){if(_emscripten_stack_get_current()<=0){err("Stack overflow detected. You can try increasing -sSTACK_SIZE (currently set to 5242880)")}}quit_(1,e)};var runtimeKeepalivePop=()=>{assert(runtimeKeepaliveCounter>0);runtimeKeepaliveCounter-=1};function exitOnMainThread(returnCode){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(1,0,0,returnCode);runtimeKeepalivePop();_exit(returnCode)}var exitJS=(status,implicit)=>{EXITSTATUS=status;checkUnflushedContent();if(ENVIRONMENT_IS_PTHREAD){assert(!implicit);exitOnMainThread(status);throw"unwind"}if(keepRuntimeAlive()&&!implicit){var msg=`program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`;err(msg)}_proc_exit(status)};var _exit=exitJS;var ptrToString=ptr=>{assert(typeof ptr==="number");return"0x"+ptr.toString(16).padStart(8,"0")};var PThread={unusedWorkers:[],runningWorkers:[],tlsInitFunctions:[],pthreads:{},nextWorkerID:1,debugInit(){function pthreadLogPrefix(){var t=0;if(runtimeInitialized&&typeof _pthread_self!="undefined"){t=_pthread_self()}return`w:${workerID},t:${ptrToString(t)}: `}var origDbg=dbg;dbg=(...args)=>origDbg(pthreadLogPrefix()+args.join(" "))},init(){PThread.debugInit();if(!ENVIRONMENT_IS_PTHREAD){PThread.initMainThread()}},initMainThread(){var pthreadPoolSize=4;while(pthreadPoolSize--){PThread.allocateUnusedWorker()}addOnPreRun(()=>{addRunDependency("loading-workers");PThread.loadWasmModuleToAllWorkers(()=>removeRunDependency("loading-workers"))})},terminateAllThreads:()=>{assert(!ENVIRONMENT_IS_PTHREAD,"Internal Error! terminateAllThreads() can only ever be called from main application thread!");for(var worker of PThread.runningWorkers){terminateWorker(worker)}for(var worker of PThread.unusedWorkers){terminateWorker(worker)}PThread.unusedWorkers=[];PThread.runningWorkers=[];PThread.pthreads={}},returnWorkerToPool:worker=>{var pthread_ptr=worker.pthread_ptr;delete PThread.pthreads[pthread_ptr];PThread.unusedWorkers.push(worker);PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker),1);worker.pthread_ptr=0;if(ENVIRONMENT_IS_NODE){worker.unref()}__emscripten_thread_free_data(pthread_ptr)},receiveObjectTransfer(data){},threadInitTLS(){PThread.tlsInitFunctions.forEach(f=>f())},loadWasmModuleToWorker:worker=>new Promise(onFinishedLoading=>{worker.onmessage=e=>{var d=e["data"];var cmd=d.cmd;if(d.targetThread&&d.targetThread!=_pthread_self()){var targetWorker=PThread.pthreads[d.targetThread];if(targetWorker){targetWorker.postMessage(d,d.transferList)}else{err(`Internal error! Worker sent a message "${cmd}" to target pthread ${d.targetThread}, but that thread no longer exists!`)}return}if(cmd==="checkMailbox"){checkMailbox()}else if(cmd==="spawnThread"){spawnThread(d)}else if(cmd==="cleanupThread"){cleanupThread(d.thread)}else if(cmd==="loaded"){worker.loaded=true;if(ENVIRONMENT_IS_NODE&&!worker.pthread_ptr){worker.unref()}onFinishedLoading(worker)}else if(cmd==="alert"){alert(`Thread ${d.threadId}: ${d.text}`)}else if(d.target==="setimmediate"){worker.postMessage(d)}else if(cmd==="callHandler"){Module[d.handler](...d.args)}else if(cmd){err(`worker sent an unknown command ${cmd}`)}};worker.onerror=e=>{var message="worker sent an error!";if(worker.pthread_ptr){message=`Pthread ${ptrToString(worker.pthread_ptr)} sent an error!`}err(`${message} ${e.filename}:${e.lineno}: ${e.message}`);throw e};if(ENVIRONMENT_IS_NODE){worker.on("message",data=>worker.onmessage({data}));worker.on("error",e=>worker.onerror(e))}assert(wasmMemory instanceof WebAssembly.Memory,"WebAssembly memory should have been loaded by now!");assert(wasmModule instanceof WebAssembly.Module,"WebAssembly Module should have been loaded by now!");var handlers=[];var knownHandlers=["onExit","onAbort","print","printErr"];for(var handler of knownHandlers){if(Module.propertyIsEnumerable(handler)){handlers.push(handler)}}worker.workerID=PThread.nextWorkerID++;worker.postMessage({cmd:"load",handlers,wasmMemory,wasmModule,workerID:worker.workerID})}),loadWasmModuleToAllWorkers(onMaybeReady){if(ENVIRONMENT_IS_PTHREAD){return onMaybeReady()}let pthreadPoolReady=Promise.all(PThread.unusedWorkers.map(PThread.loadWasmModuleToWorker));pthreadPoolReady.then(onMaybeReady)},allocateUnusedWorker(){var worker;var workerOptions={workerData:"em-pthread",name:"em-pthread-"+PThread.nextWorkerID};var pthreadMainJs=_scriptName;if(Module["mainScriptUrlOrBlob"]){pthreadMainJs=Module["mainScriptUrlOrBlob"];if(typeof pthreadMainJs!="string"){pthreadMainJs=URL.createObjectURL(pthreadMainJs)}}worker=new Worker(pthreadMainJs,workerOptions);PThread.unusedWorkers.push(worker)},getNewWorker(){if(PThread.unusedWorkers.length==0){PThread.allocateUnusedWorker();PThread.loadWasmModuleToWorker(PThread.unusedWorkers[0])}return PThread.unusedWorkers.pop()}};var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var establishStackSpace=pthread_ptr=>{updateMemoryViews();var stackHigh=GROWABLE_HEAP_U32()[pthread_ptr+52>>>2>>>0];var stackSize=GROWABLE_HEAP_U32()[pthread_ptr+56>>>2>>>0];var stackLow=stackHigh-stackSize;assert(stackHigh!=0);assert(stackLow!=0);assert(stackHigh>stackLow,"stackHigh must be higher then stackLow");_emscripten_stack_set_limits(stackHigh,stackLow);stackRestore(stackHigh);writeStackCookie()};var wasmTableMirror=[];var wasmTable;var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}assert(wasmTable.get(funcPtr)==func,"JavaScript-side Wasm function table mirror is out of date!");return func};var invokeEntryPoint=(ptr,arg)=>{runtimeKeepaliveCounter=0;noExitRuntime=0;var result=getWasmTableEntry(ptr)(arg);checkStackCookie();function finish(result){if(keepRuntimeAlive()){EXITSTATUS=result}else{__emscripten_thread_exit(result)}}finish(result)};var noExitRuntime=Module["noExitRuntime"]||true;var registerTLSInit=tlsInitFunc=>PThread.tlsInitFunctions.push(tlsInitFunc);var runtimeKeepalivePush=()=>{runtimeKeepaliveCounter+=1};var warnOnce=text=>{warnOnce.shown||={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;if(ENVIRONMENT_IS_NODE)text="warning: "+text;err(text)}};var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder:undefined;var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead=NaN)=>{idx>>>=0;var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.buffer instanceof ArrayBuffer?heapOrArray.subarray(idx,endPtr):heapOrArray.slice(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead)=>{assert(typeof ptr=="number",`UTF8ToString expects a number (got ${typeof ptr})`);ptr>>>=0;return ptr?UTF8ArrayToString(GROWABLE_HEAP_U8(),ptr,maxBytesToRead):""};function ___assert_fail(condition,filename,line,func){condition>>>=0;filename>>>=0;func>>>=0;return abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"])}function ___call_sighandler(fp,sig){fp>>>=0;return getWasmTableEntry(fp)(sig)}function pthreadCreateProxied(pthread_ptr,attr,startRoutine,arg){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(2,0,1,pthread_ptr,attr,startRoutine,arg);return ___pthread_create_js(pthread_ptr,attr,startRoutine,arg)}var _emscripten_has_threading_support=()=>typeof SharedArrayBuffer!="undefined";function ___pthread_create_js(pthread_ptr,attr,startRoutine,arg){pthread_ptr>>>=0;attr>>>=0;startRoutine>>>=0;arg>>>=0;if(!_emscripten_has_threading_support()){dbg("pthread_create: environment does not support SharedArrayBuffer, pthreads are not available");return 6}var transferList=[];var error=0;if(ENVIRONMENT_IS_PTHREAD&&(transferList.length===0||error)){return pthreadCreateProxied(pthread_ptr,attr,startRoutine,arg)}if(error)return error;var threadParams={startRoutine,pthread_ptr,arg,transferList};if(ENVIRONMENT_IS_PTHREAD){threadParams.cmd="spawnThread";postMessage(threadParams,transferList);return 0}return spawnThread(threadParams)}var initRandomFill=()=>{if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){return view=>(view.set(crypto.getRandomValues(new Uint8Array(view.byteLength))),view)}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");var randomFillSync=crypto_module["randomFillSync"];if(randomFillSync){return view=>crypto_module["randomFillSync"](view)}var randomBytes=crypto_module["randomBytes"];return view=>(view.set(randomBytes(view.byteLength)),view)}catch(e){}}abort("no cryptographic support found for randomDevice. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: (array) => { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };")};var randomFill=view=>(randomFill=initRandomFill())(view);var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{outIdx>>>=0;assert(typeof str==="string",`stringToUTF8Array expects a string (got ${typeof str})`);if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++>>>0]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++>>>0]=192|u>>6;heap[outIdx++>>>0]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++>>>0]=224|u>>12;heap[outIdx++>>>0]=128|u>>6&63;heap[outIdx++>>>0]=128|u&63}else{if(outIdx+3>=endIdx)break;if(u>1114111)warnOnce("Invalid Unicode code point "+ptrToString(u)+" encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF).");heap[outIdx++>>>0]=240|u>>18;heap[outIdx++>>>0]=128|u>>12&63;heap[outIdx++>>>0]=128|u>>6&63;heap[outIdx++>>>0]=128|u&63}}heap[outIdx>>>0]=0;return outIdx-startIdx};function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var zeroMemory=(address,size)=>{GROWABLE_HEAP_U8().fill(0,address,address+size)};var alignMemory=(size,alignment)=>{assert(alignment,"alignment argument is required");return Math.ceil(size/alignment)*alignment};var mmapAlloc=size=>{size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(ptr)zeroMemory(ptr,size);return ptr};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.atime=node.mtime=node.ctime=Date.now();if(parent){parent.contents[name]=node;parent.atime=parent.mtime=parent.ctime=node.atime}return node},getFileDataAsTypedArray(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.atime);attr.mtime=new Date(node.mtime);attr.ctime=new Date(node.ctime);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){for(const key of["mode","atime","mtime","ctime"]){if(attr[key]){node[key]=attr[key]}}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){throw new FS.ErrnoError(44)},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){if(FS.isDir(old_node.mode)){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}FS.hashRemoveNode(new_node)}delete old_node.parent.contents[old_node.name];new_dir.contents[new_name]=old_node;old_node.name=new_name;new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);assert(size>=0);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length>>0)}}return{ptr,allocated}},msync(stream,buffer,offset,length,mmapFlags){MEMFS.stream_ops.write(stream,buffer,0,length,offset,false);return 0}}};var asyncLoad=async url=>{var arrayBuffer=await readAsync(url);assert(arrayBuffer,`Loading data file "${url}" failed (no arrayBuffer).`);return new Uint8Array(arrayBuffer)};var FS_createDataFile=(parent,name,fileData,canRead,canWrite,canOwn)=>{FS.createDataFile(parent,name,fileData,canRead,canWrite,canOwn)};var preloadPlugins=Module["preloadPlugins"]||[];var FS_handledByPreloadPlugin=(byteArray,fullname,finish,onerror)=>{if(typeof Browser!="undefined")Browser.init();var handled=false;preloadPlugins.forEach(plugin=>{if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);function processData(byteArray){function finish(byteArray){preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}onload?.();removeRunDependency(dep)}if(FS_handledByPreloadPlugin(byteArray,fullname,finish,()=>{onerror?.();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url).then(processData,onerror)}else{processData(url)}};var FS_modeStringToFlags=str=>{var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var strError=errno=>UTF8ToString(_strerror(errno));var ERRNO_CODES={EPERM:63,ENOENT:44,ESRCH:71,EINTR:27,EIO:29,ENXIO:60,E2BIG:1,ENOEXEC:45,EBADF:8,ECHILD:12,EAGAIN:6,EWOULDBLOCK:6,ENOMEM:48,EACCES:2,EFAULT:21,ENOTBLK:105,EBUSY:10,EEXIST:20,EXDEV:75,ENODEV:43,ENOTDIR:54,EISDIR:31,EINVAL:28,ENFILE:41,EMFILE:33,ENOTTY:59,ETXTBSY:74,EFBIG:22,ENOSPC:51,ESPIPE:70,EROFS:69,EMLINK:34,EPIPE:64,EDOM:18,ERANGE:68,ENOMSG:49,EIDRM:24,ECHRNG:106,EL2NSYNC:156,EL3HLT:107,EL3RST:108,ELNRNG:109,EUNATCH:110,ENOCSI:111,EL2HLT:112,EDEADLK:16,ENOLCK:46,EBADE:113,EBADR:114,EXFULL:115,ENOANO:104,EBADRQC:103,EBADSLT:102,EDEADLOCK:16,EBFONT:101,ENOSTR:100,ENODATA:116,ETIME:117,ENOSR:118,ENONET:119,ENOPKG:120,EREMOTE:121,ENOLINK:47,EADV:122,ESRMNT:123,ECOMM:124,EPROTO:65,EMULTIHOP:36,EDOTDOT:125,EBADMSG:9,ENOTUNIQ:126,EBADFD:127,EREMCHG:128,ELIBACC:129,ELIBBAD:130,ELIBSCN:131,ELIBMAX:132,ELIBEXEC:133,ENOSYS:52,ENOTEMPTY:55,ENAMETOOLONG:37,ELOOP:32,EOPNOTSUPP:138,EPFNOSUPPORT:139,ECONNRESET:15,ENOBUFS:42,EAFNOSUPPORT:5,EPROTOTYPE:67,ENOTSOCK:57,ENOPROTOOPT:50,ESHUTDOWN:140,ECONNREFUSED:14,EADDRINUSE:3,ECONNABORTED:13,ENETUNREACH:40,ENETDOWN:38,ETIMEDOUT:73,EHOSTDOWN:142,EHOSTUNREACH:23,EINPROGRESS:26,EALREADY:7,EDESTADDRREQ:17,EMSGSIZE:35,EPROTONOSUPPORT:66,ESOCKTNOSUPPORT:137,EADDRNOTAVAIL:4,ENETRESET:39,EISCONN:30,ENOTCONN:53,ETOOMANYREFS:141,EUSERS:136,EDQUOT:19,ESTALE:72,ENOTSUP:138,ENOMEDIUM:148,EILSEQ:25,EOVERFLOW:61,ECANCELED:11,ENOTRECOVERABLE:56,EOWNERDEAD:62,ESTRPIPE:135};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:class extends Error{name="ErrnoError";constructor(errno){super(runtimeInitialized?strError(errno):"");this.errno=errno;for(var key in ERRNO_CODES){if(ERRNO_CODES[key]===errno){this.code=key;break}}}},filesystems:null,syncFSRequests:0,readFiles:{},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=292|73;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.rdev=rdev;this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){if(!path)return{path:"",node:null};opts.follow_mount??=true;if(!PATH.isAbs(path)){path=FS.cwd()+"/"+path}linkloop:for(var nlinks=0;nlinks<40;nlinks++){var parts=path.split("/").filter(p=>!!p&&p!==".");var current=FS.root;var current_path="/";for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){assert(typeof parent=="object");var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){if(!FS.isDir(dir.mode)){return 54}try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){assert(fd>=-1);stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){assert(FS.syncFSRequests>0);FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount(type,opts,mountpoint){if(typeof type=="string"){throw type}var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);assert(idx!==-1);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},statfs(path){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};var parent=FS.lookupPath(path,{follow:true}).node;if(parent?.node_ops.statfs){Object.assign(rtn,parent.node_ops.statfs(parent.mount.opts.root))}return rtn},create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode=511){mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var i=0;i=0);if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write(stream,buffer,offset,length,position,canOwn){assert(offset>=0);if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate(stream,offset,length){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap(stream,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}if(!length){throw new FS.ErrnoError(28)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync(stream,buffer,offset,length,mmapFlags){assert(offset>=0);if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error(`Invalid encoding type "${opts.encoding}"`)}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length,llseek:()=>0});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomLeft=randomFill(randomBuffer).byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);node.stream_ops={llseek:MEMFS.stream_ops.llseek};node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:fd+1};ret.parent=ret;return ret},readdir(){return Array.from(FS.streams.entries()).filter(([k,v])=>v).map(([k,v])=>k.toString())}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input){FS.createDevice("/dev","stdin",input)}else{FS.symlink("/dev/tty","/dev/stdin")}if(output){FS.createDevice("/dev","stdout",null,output)}else{FS.symlink("/dev/tty","/dev/stdout")}if(error){FS.createDevice("/dev","stderr",null,error)}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1);assert(stdin.fd===0,`invalid handle for stdin (${stdin.fd})`);assert(stdout.fd===1,`invalid handle for stdout (${stdout.fd})`);assert(stderr.fd===2,`invalid handle for stderr (${stderr.fd})`)},staticInit(){FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS}},init(input,output,error){assert(!FS.initialized,"FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)");FS.initialized=true;input??=Module["stdin"];output??=Module["stdout"];error??=Module["stderr"];FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;_fflush(0);for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);assert(size>=0);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,GROWABLE_HEAP_I8(),ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node},absolutePath(){abort("FS.absolutePath has been removed; use PATH_FS.resolve instead")},createFolder(){abort("FS.createFolder has been removed; use FS.mkdir instead")},createLink(){abort("FS.createLink has been removed; use FS.symlink instead")},joinPath(){abort("FS.joinPath has been removed; use PATH.join instead")},mmapAlloc(){abort("FS.mmapAlloc has been replaced by the top level function mmapAlloc")},standardizePath(){abort("FS.standardizePath has been removed; use PATH.normalize instead")}};var SOCKFS={websocketArgs:{},callbacks:{},on(event,callback){SOCKFS.callbacks[event]=callback},emit(event,param){SOCKFS.callbacks[event]?.(param)},mount(mount){SOCKFS.websocketArgs=Module["websocket"]||{};(Module["websocket"]??={})["on"]=SOCKFS.on;return FS.createNode(null,"/",16895,0)},createSocket(family,type,protocol){type&=~526336;var streaming=type==1;if(streaming&&protocol&&protocol!=6){throw new FS.ErrnoError(66)}var sock={family,type,protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops};var name=SOCKFS.nextname();var node=FS.createNode(SOCKFS.root,name,49152,0);node.sock=sock;var stream=FS.createStream({path:name,node,flags:2,seekable:false,stream_ops:SOCKFS.stream_ops});sock.stream=stream;return sock},getSocket(fd){var stream=FS.getStream(fd);if(!stream||!FS.isSocket(stream.node.mode)){return null}return stream.node.sock},stream_ops:{poll(stream){var sock=stream.node.sock;return sock.sock_ops.poll(sock)},ioctl(stream,request,varargs){var sock=stream.node.sock;return sock.sock_ops.ioctl(sock,request,varargs)},read(stream,buffer,offset,length,position){var sock=stream.node.sock;var msg=sock.sock_ops.recvmsg(sock,length);if(!msg){return 0}buffer.set(msg.buffer,offset);return msg.buffer.length},write(stream,buffer,offset,length,position){var sock=stream.node.sock;return sock.sock_ops.sendmsg(sock,buffer,offset,length)},close(stream){var sock=stream.node.sock;sock.sock_ops.close(sock)}},nextname(){if(!SOCKFS.nextname.current){SOCKFS.nextname.current=0}return`socket[${SOCKFS.nextname.current++}]`},websocket_sock_ops:{createPeer(sock,addr,port){var ws;if(typeof addr=="object"){ws=addr;addr=null;port=null}if(ws){if(ws._socket){addr=ws._socket.remoteAddress;port=ws._socket.remotePort}else{var result=/ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);if(!result){throw new Error("WebSocket URL must be in the format ws(s)://address:port")}addr=result[1];port=parseInt(result[2],10)}}else{try{var url="ws:#".replace("#","//");var subProtocols="binary";var opts=undefined;if(SOCKFS.websocketArgs["url"]){url=SOCKFS.websocketArgs["url"]}if(SOCKFS.websocketArgs["subprotocol"]){subProtocols=SOCKFS.websocketArgs["subprotocol"]}else if(SOCKFS.websocketArgs["subprotocol"]===null){subProtocols="null"}if(url==="ws://"||url==="wss://"){var parts=addr.split("/");url=url+parts[0]+":"+port+"/"+parts.slice(1).join("/")}if(subProtocols!=="null"){subProtocols=subProtocols.replace(/^ +| +$/g,"").split(/ *, */);opts=subProtocols}var WebSocketConstructor;if(ENVIRONMENT_IS_NODE){WebSocketConstructor=require("ws")}else{WebSocketConstructor=WebSocket}ws=new WebSocketConstructor(url,opts);ws.binaryType="arraybuffer"}catch(e){throw new FS.ErrnoError(23)}}var peer={addr,port,socket:ws,msg_send_queue:[]};SOCKFS.websocket_sock_ops.addPeer(sock,peer);SOCKFS.websocket_sock_ops.handlePeerEvents(sock,peer);if(sock.type===2&&typeof sock.sport!="undefined"){peer.msg_send_queue.push(new Uint8Array([255,255,255,255,"p".charCodeAt(0),"o".charCodeAt(0),"r".charCodeAt(0),"t".charCodeAt(0),(sock.sport&65280)>>8,sock.sport&255]))}return peer},getPeer(sock,addr,port){return sock.peers[addr+":"+port]},addPeer(sock,peer){sock.peers[peer.addr+":"+peer.port]=peer},removePeer(sock,peer){delete sock.peers[peer.addr+":"+peer.port]},handlePeerEvents(sock,peer){var first=true;var handleOpen=function(){sock.connecting=false;SOCKFS.emit("open",sock.stream.fd);try{var queued=peer.msg_send_queue.shift();while(queued){peer.socket.send(queued);queued=peer.msg_send_queue.shift()}}catch(e){peer.socket.close()}};function handleMessage(data){if(typeof data=="string"){var encoder=new TextEncoder;data=encoder.encode(data)}else{assert(data.byteLength!==undefined);if(data.byteLength==0){return}data=new Uint8Array(data)}var wasfirst=first;first=false;if(wasfirst&&data.length===10&&data[0]===255&&data[1]===255&&data[2]===255&&data[3]===255&&data[4]==="p".charCodeAt(0)&&data[5]==="o".charCodeAt(0)&&data[6]==="r".charCodeAt(0)&&data[7]==="t".charCodeAt(0)){var newport=data[8]<<8|data[9];SOCKFS.websocket_sock_ops.removePeer(sock,peer);peer.port=newport;SOCKFS.websocket_sock_ops.addPeer(sock,peer);return}sock.recv_queue.push({addr:peer.addr,port:peer.port,data});SOCKFS.emit("message",sock.stream.fd)}if(ENVIRONMENT_IS_NODE){peer.socket.on("open",handleOpen);peer.socket.on("message",function(data,isBinary){if(!isBinary){return}handleMessage(new Uint8Array(data).buffer)});peer.socket.on("close",function(){SOCKFS.emit("close",sock.stream.fd)});peer.socket.on("error",function(error){sock.error=14;SOCKFS.emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})}else{peer.socket.onopen=handleOpen;peer.socket.onclose=function(){SOCKFS.emit("close",sock.stream.fd)};peer.socket.onmessage=function peer_socket_onmessage(event){handleMessage(event.data)};peer.socket.onerror=function(error){sock.error=14;SOCKFS.emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])}}},poll(sock){if(sock.type===1&&sock.server){return sock.pending.length?64|1:0}var mask=0;var dest=sock.type===1?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null;if(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=64|1}if(!dest||dest&&dest.socket.readyState===dest.socket.OPEN){mask|=4}if(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){if(sock.connecting){mask|=4}else{mask|=16}}return mask},ioctl(sock,request,arg){switch(request){case 21531:var bytes=0;if(sock.recv_queue.length){bytes=sock.recv_queue[0].data.length}GROWABLE_HEAP_I32()[arg>>>2>>>0]=bytes;return 0;default:return 28}},close(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}var peers=Object.keys(sock.peers);for(var i=0;i{var socket=SOCKFS.getSocket(fd);if(!socket)throw new FS.ErrnoError(8);return socket};var inetPton4=str=>{var b=str.split(".");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0};var jstoi_q=str=>parseInt(str);var inetPton6=str=>{var words;var w,offset,z;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str==="::"){return[0,0,0,0,0,0,0,0]}if(str.startsWith("::")){str=str.replace("::","Z:")}else{str=str.replace("::",":Z:")}if(str.indexOf(".")>0){str=str.replace(new RegExp("[.]","g"),":");words=str.split(":");words[words.length-4]=jstoi_q(words[words.length-4])+jstoi_q(words[words.length-3])*256;words[words.length-3]=jstoi_q(words[words.length-2])+jstoi_q(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(":")}offset=0;z=0;for(w=0;w{switch(family){case 2:addr=inetPton4(addr);zeroMemory(sa,16);if(addrlen){GROWABLE_HEAP_I32()[addrlen>>>2>>>0]=16}GROWABLE_HEAP_I16()[sa>>>1>>>0]=family;GROWABLE_HEAP_I32()[sa+4>>>2>>>0]=addr;GROWABLE_HEAP_I16()[sa+2>>>1>>>0]=_htons(port);break;case 10:addr=inetPton6(addr);zeroMemory(sa,28);if(addrlen){GROWABLE_HEAP_I32()[addrlen>>>2>>>0]=28}GROWABLE_HEAP_I32()[sa>>>2>>>0]=family;GROWABLE_HEAP_I32()[sa+8>>>2>>>0]=addr[0];GROWABLE_HEAP_I32()[sa+12>>>2>>>0]=addr[1];GROWABLE_HEAP_I32()[sa+16>>>2>>>0]=addr[2];GROWABLE_HEAP_I32()[sa+20>>>2>>>0]=addr[3];GROWABLE_HEAP_I16()[sa+2>>>1>>>0]=_htons(port);break;default:return 5}return 0};var DNS={address_map:{id:1,addrs:{},names:{}},lookup_name(name){var res=inetPton4(name);if(res!==null){return name}res=inetPton6(name);if(res!==null){return name}var addr;if(DNS.address_map.addrs[name]){addr=DNS.address_map.addrs[name]}else{var id=DNS.address_map.id++;assert(id<65535,"exceeded max address mappings of 65535");addr="172.29."+(id&255)+"."+(id&65280);DNS.address_map.names[addr]=name;DNS.address_map.addrs[name]=addr}return addr},lookup_addr(addr){if(DNS.address_map.names[addr]){return DNS.address_map.names[addr]}return null}};function ___syscall_accept4(fd,addr,addrlen,flags,d1,d2){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(3,0,1,fd,addr,addrlen,flags,d1,d2);addr>>>=0;addrlen>>>=0;try{var sock=getSocketFromFD(fd);var newsock=sock.sock_ops.accept(sock);if(addr){var errno=writeSockaddr(addr,newsock.family,DNS.lookup_name(newsock.daddr),newsock.dport,addrlen);assert(!errno)}return newsock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var inetNtop4=addr=>(addr&255)+"."+(addr>>8&255)+"."+(addr>>16&255)+"."+(addr>>24&255);var inetNtop6=ints=>{var str="";var word=0;var longest=0;var lastzero=0;var zstart=0;var len=0;var i=0;var parts=[ints[0]&65535,ints[0]>>16,ints[1]&65535,ints[1]>>16,ints[2]&65535,ints[2]>>16,ints[3]&65535,ints[3]>>16];var hasipv4=true;var v4part="";for(i=0;i<5;i++){if(parts[i]!==0){hasipv4=false;break}}if(hasipv4){v4part=inetNtop4(parts[6]|parts[7]<<16);if(parts[5]===-1){str="::ffff:";str+=v4part;return str}if(parts[5]===0){str="::";if(v4part==="0.0.0.0")v4part="";if(v4part==="0.0.0.1")v4part="1";str+=v4part;return str}}for(word=0;word<8;word++){if(parts[word]===0){if(word-lastzero>1){len=0}lastzero=word;len++}if(len>longest){longest=len;zstart=word-longest+1}}for(word=0;word<8;word++){if(longest>1){if(parts[word]===0&&word>=zstart&&word{var family=GROWABLE_HEAP_I16()[sa>>>1>>>0];var port=_ntohs(GROWABLE_HEAP_U16()[sa+2>>>1>>>0]);var addr;switch(family){case 2:if(salen!==16){return{errno:28}}addr=GROWABLE_HEAP_I32()[sa+4>>>2>>>0];addr=inetNtop4(addr);break;case 10:if(salen!==28){return{errno:28}}addr=[GROWABLE_HEAP_I32()[sa+8>>>2>>>0],GROWABLE_HEAP_I32()[sa+12>>>2>>>0],GROWABLE_HEAP_I32()[sa+16>>>2>>>0],GROWABLE_HEAP_I32()[sa+20>>>2>>>0]];addr=inetNtop6(addr);break;default:return{errno:5}}return{family,addr,port}};var getSocketAddress=(addrp,addrlen)=>{var info=readSockaddr(addrp,addrlen);if(info.errno)throw new FS.ErrnoError(info.errno);info.addr=DNS.lookup_addr(info.addr)||info.addr;return info};function ___syscall_bind(fd,addr,addrlen,d1,d2,d3){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(4,0,1,fd,addr,addrlen,d1,d2,d3);addr>>>=0;addrlen>>>=0;try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.bind(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return dir+"/"+path},doStat(func,path,buf){var stat=func(path);GROWABLE_HEAP_I32()[buf>>>2>>>0]=stat.dev;GROWABLE_HEAP_I32()[buf+4>>>2>>>0]=stat.mode;GROWABLE_HEAP_U32()[buf+8>>>2>>>0]=stat.nlink;GROWABLE_HEAP_I32()[buf+12>>>2>>>0]=stat.uid;GROWABLE_HEAP_I32()[buf+16>>>2>>>0]=stat.gid;GROWABLE_HEAP_I32()[buf+20>>>2>>>0]=stat.rdev;HEAP64[buf+24>>>3]=BigInt(stat.size);GROWABLE_HEAP_I32()[buf+32>>>2>>>0]=4096;GROWABLE_HEAP_I32()[buf+36>>>2>>>0]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+40>>>3]=BigInt(Math.floor(atime/1e3));GROWABLE_HEAP_U32()[buf+48>>>2>>>0]=atime%1e3*1e3*1e3;HEAP64[buf+56>>>3]=BigInt(Math.floor(mtime/1e3));GROWABLE_HEAP_U32()[buf+64>>>2>>>0]=mtime%1e3*1e3*1e3;HEAP64[buf+72>>>3]=BigInt(Math.floor(ctime/1e3));GROWABLE_HEAP_U32()[buf+80>>>2>>>0]=ctime%1e3*1e3*1e3;HEAP64[buf+88>>>3]=BigInt(stat.ino);return 0},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=GROWABLE_HEAP_U8().slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_chdir(path){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(5,0,1,path);path>>>=0;try{path=SYSCALLS.getStr(path);FS.chdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_chmod(path,mode){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(6,0,1,path,mode);path>>>=0;try{path=SYSCALLS.getStr(path);FS.chmod(path,mode);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_connect(fd,addr,addrlen,d1,d2,d3){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(7,0,1,fd,addr,addrlen,d1,d2,d3);addr>>>=0;addrlen>>>=0;try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.connect(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_dup(fd){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(8,0,1,fd);try{var old=SYSCALLS.getStreamFromFD(fd);return FS.dupStream(old).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_dup3(fd,newfd,flags){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(9,0,1,fd,newfd,flags);try{var old=SYSCALLS.getStreamFromFD(fd);assert(!flags);if(old.fd===newfd)return-28;if(newfd<0||newfd>=FS.MAX_OPEN_FDS)return-8;var existing=FS.getStream(newfd);if(existing)FS.close(existing);return FS.dupStream(old,newfd).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_faccessat(dirfd,path,amode,flags){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(10,0,1,dirfd,path,amode,flags);path>>>=0;try{path=SYSCALLS.getStr(path);assert(flags===0||flags==512);path=SYSCALLS.calculateAt(dirfd,path);if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fchmodat2(dirfd,path,mode,flags){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(11,0,1,dirfd,path,mode,flags);path>>>=0;try{var nofollow=flags&256;path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);FS.chmod(path,mode,nofollow);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fchownat(dirfd,path,owner,group,flags){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(12,0,1,dirfd,path,owner,group,flags);path>>>=0;try{path=SYSCALLS.getStr(path);var nofollow=flags&256;flags=flags&~256;assert(flags===0);path=SYSCALLS.calculateAt(dirfd,path);(nofollow?FS.lchown:FS.chown)(path,owner,group);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var syscallGetVarargI=()=>{assert(SYSCALLS.varargs!=undefined);var ret=GROWABLE_HEAP_I32()[+SYSCALLS.varargs>>>2>>>0];SYSCALLS.varargs+=4;return ret};var syscallGetVarargP=syscallGetVarargI;function ___syscall_fcntl64(fd,cmd,varargs){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(13,0,1,fd,cmd,varargs);varargs>>>=0;SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();stream.flags|=arg;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;GROWABLE_HEAP_I16()[arg+offset>>>1>>>0]=2;return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(14,0,1,fd,buf);buf>>>=0;try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ftruncate64(fd,length){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(15,0,1,fd,length);length=bigintToI53Checked(length);try{if(isNaN(length))return 61;FS.ftruncate(fd,length);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var stringToUTF8=(str,outPtr,maxBytesToWrite)=>{assert(typeof maxBytesToWrite=="number","stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!");return stringToUTF8Array(str,GROWABLE_HEAP_U8(),outPtr,maxBytesToWrite)};function ___syscall_getcwd(buf,size){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(16,0,1,buf,size);buf>>>=0;size>>>=0;try{if(size===0)return-28;var cwd=FS.cwd();var cwdLengthInBytes=lengthBytesUTF8(cwd)+1;if(size>>=0;count>>>=0;try{var stream=SYSCALLS.getStreamFromFD(fd);stream.getdents||=FS.readdir(stream.path);var struct_size=280;var pos=0;var off=FS.llseek(stream,0,1);var startIdx=Math.floor(off/struct_size);var endIdx=Math.min(stream.getdents.length,startIdx+Math.floor(count/struct_size));for(var idx=startIdx;idx>>3]=BigInt(id);HEAP64[dirp+pos+8>>>3]=BigInt((idx+1)*struct_size);GROWABLE_HEAP_I16()[dirp+pos+16>>>1>>>0]=280;GROWABLE_HEAP_I8()[dirp+pos+18>>>0]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_getpeername(fd,addr,addrlen,d1,d2,d3){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(18,0,1,fd,addr,addrlen,d1,d2,d3);addr>>>=0;addrlen>>>=0;try{var sock=getSocketFromFD(fd);if(!sock.daddr){return-53}var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.daddr),sock.dport,addrlen);assert(!errno);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_getsockname(fd,addr,addrlen,d1,d2,d3){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(19,0,1,fd,addr,addrlen,d1,d2,d3);addr>>>=0;addrlen>>>=0;try{var sock=getSocketFromFD(fd);var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.saddr||"0.0.0.0"),sock.sport,addrlen);assert(!errno);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_getsockopt(fd,level,optname,optval,optlen,d1){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(20,0,1,fd,level,optname,optval,optlen,d1);optval>>>=0;optlen>>>=0;try{var sock=getSocketFromFD(fd);if(level===1){if(optname===4){GROWABLE_HEAP_I32()[optval>>>2>>>0]=sock.error;GROWABLE_HEAP_I32()[optlen>>>2>>>0]=4;sock.error=null;return 0}}return-50}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(21,0,1,fd,op,varargs);varargs>>>=0;SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();GROWABLE_HEAP_I32()[argp>>>2>>>0]=termios.c_iflag||0;GROWABLE_HEAP_I32()[argp+4>>>2>>>0]=termios.c_oflag||0;GROWABLE_HEAP_I32()[argp+8>>>2>>>0]=termios.c_cflag||0;GROWABLE_HEAP_I32()[argp+12>>>2>>>0]=termios.c_lflag||0;for(var i=0;i<32;i++){GROWABLE_HEAP_I8()[argp+i+17>>>0]=termios.c_cc[i]||0}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=GROWABLE_HEAP_I32()[argp>>>2>>>0];var c_oflag=GROWABLE_HEAP_I32()[argp+4>>>2>>>0];var c_cflag=GROWABLE_HEAP_I32()[argp+8>>>2>>>0];var c_lflag=GROWABLE_HEAP_I32()[argp+12>>>2>>>0];var c_cc=[];for(var i=0;i<32;i++){c_cc.push(GROWABLE_HEAP_I8()[argp+i+17>>>0])}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag,c_oflag,c_cflag,c_lflag,c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();GROWABLE_HEAP_I32()[argp>>>2>>>0]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();GROWABLE_HEAP_I16()[argp>>>1>>>0]=winsize[0];GROWABLE_HEAP_I16()[argp+2>>>1>>>0]=winsize[1]}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_listen(fd,backlog){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(22,0,1,fd,backlog);try{var sock=getSocketFromFD(fd);sock.sock_ops.listen(sock,backlog);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(23,0,1,path,buf);path>>>=0;buf>>>=0;try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_mkdirat(dirfd,path,mode){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(24,0,1,dirfd,path,mode);path>>>=0;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);FS.mkdir(path,mode,0);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(25,0,1,dirfd,path,buf,flags);path>>>=0;buf>>>=0;try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;assert(!flags,`unknown flags in __syscall_newfstatat: ${flags}`);path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.doStat(nofollow?FS.lstat:FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(26,0,1,dirfd,path,flags,varargs);path>>>=0;varargs>>>=0;SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var PIPEFS={BUCKET_BUFFER_SIZE:8192,mount(mount){return FS.createNode(null,"/",16384|511,0)},createPipe(){var pipe={buckets:[],refcnt:2};pipe.buckets.push({buffer:new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE),offset:0,roffset:0});var rName=PIPEFS.nextname();var wName=PIPEFS.nextname();var rNode=FS.createNode(PIPEFS.root,rName,4096,0);var wNode=FS.createNode(PIPEFS.root,wName,4096,0);rNode.pipe=pipe;wNode.pipe=pipe;var readableStream=FS.createStream({path:rName,node:rNode,flags:0,seekable:false,stream_ops:PIPEFS.stream_ops});rNode.stream=readableStream;var writableStream=FS.createStream({path:wName,node:wNode,flags:1,seekable:false,stream_ops:PIPEFS.stream_ops});wNode.stream=writableStream;return{readable_fd:readableStream.fd,writable_fd:writableStream.fd}},stream_ops:{poll(stream){var pipe=stream.node.pipe;if((stream.flags&2097155)===1){return 256|4}if(pipe.buckets.length>0){for(var i=0;i0){return 64|1}}}return 0},ioctl(stream,request,varargs){return 28},fsync(stream){return 28},read(stream,buffer,offset,length,position){var pipe=stream.node.pipe;var currentLength=0;for(var i=0;i=dataLen){currBucket.buffer.set(data,currBucket.offset);currBucket.offset+=dataLen;return dataLen}else if(freeBytesInCurrBuffer>0){currBucket.buffer.set(data.subarray(0,freeBytesInCurrBuffer),currBucket.offset);currBucket.offset+=freeBytesInCurrBuffer;data=data.subarray(freeBytesInCurrBuffer,data.byteLength)}var numBuckets=data.byteLength/PIPEFS.BUCKET_BUFFER_SIZE|0;var remElements=data.byteLength%PIPEFS.BUCKET_BUFFER_SIZE;for(var i=0;i0){var newBucket={buffer:new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE),offset:data.byteLength,roffset:0};pipe.buckets.push(newBucket);newBucket.buffer.set(data)}return dataLen},close(stream){var pipe=stream.node.pipe;pipe.refcnt--;if(pipe.refcnt===0){pipe.buckets=null}}},nextname(){if(!PIPEFS.nextname.current){PIPEFS.nextname.current=0}return"pipe["+PIPEFS.nextname.current+++"]"}};function ___syscall_pipe(fdPtr){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(27,0,1,fdPtr);fdPtr>>>=0;try{if(fdPtr==0){throw new FS.ErrnoError(21)}var res=PIPEFS.createPipe();GROWABLE_HEAP_I32()[fdPtr>>>2>>>0]=res.readable_fd;GROWABLE_HEAP_I32()[fdPtr+4>>>2>>>0]=res.writable_fd;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_poll(fds,nfds,timeout){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(28,0,1,fds,nfds,timeout);fds>>>=0;try{var nonzero=0;for(var i=0;i>>2>>>0];var events=GROWABLE_HEAP_I16()[pollfd+4>>>1>>>0];var mask=32;var stream=FS.getStream(fd);if(stream){mask=SYSCALLS.DEFAULT_POLLMASK;if(stream.stream_ops.poll){mask=stream.stream_ops.poll(stream,-1)}}mask&=events|8|16;if(mask)nonzero++;GROWABLE_HEAP_I16()[pollfd+6>>>1>>>0]=mask}return nonzero}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_readlinkat(dirfd,path,buf,bufsize){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(29,0,1,dirfd,path,buf,bufsize);path>>>=0;buf>>>=0;bufsize>>>=0;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=GROWABLE_HEAP_I8()[buf+len>>>0];stringToUTF8(ret,buf,bufsize+1);GROWABLE_HEAP_I8()[buf+len>>>0]=endChar;return len}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_recvfrom(fd,buf,len,flags,addr,addrlen){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(30,0,1,fd,buf,len,flags,addr,addrlen);buf>>>=0;len>>>=0;addr>>>=0;addrlen>>>=0;try{var sock=getSocketFromFD(fd);var msg=sock.sock_ops.recvmsg(sock,len);if(!msg)return 0;if(addr){var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(msg.addr),msg.port,addrlen);assert(!errno)}GROWABLE_HEAP_U8().set(msg.buffer,buf>>>0);return msg.buffer.byteLength}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_renameat(olddirfd,oldpath,newdirfd,newpath){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(31,0,1,olddirfd,oldpath,newdirfd,newpath);oldpath>>>=0;newpath>>>=0;try{oldpath=SYSCALLS.getStr(oldpath);newpath=SYSCALLS.getStr(newpath);oldpath=SYSCALLS.calculateAt(olddirfd,oldpath);newpath=SYSCALLS.calculateAt(newdirfd,newpath);FS.rename(oldpath,newpath);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_rmdir(path){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(32,0,1,path);path>>>=0;try{path=SYSCALLS.getStr(path);FS.rmdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_sendto(fd,message,length,flags,addr,addr_len){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(33,0,1,fd,message,length,flags,addr,addr_len);message>>>=0;length>>>=0;addr>>>=0;addr_len>>>=0;try{var sock=getSocketFromFD(fd);if(!addr){return FS.write(sock.stream,GROWABLE_HEAP_I8(),message,length)}var dest=getSocketAddress(addr,addr_len);return sock.sock_ops.sendmsg(sock,GROWABLE_HEAP_I8(),message,length,dest.addr,dest.port)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_socket(domain,type,protocol){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(34,0,1,domain,type,protocol);try{var sock=SOCKFS.createSocket(domain,type,protocol);assert(sock.stream.fd<64);return sock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(35,0,1,path,buf);path>>>=0;buf>>>=0;try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_symlinkat(target,dirfd,linkpath){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(36,0,1,target,dirfd,linkpath);target>>>=0;linkpath>>>=0;try{target=SYSCALLS.getStr(target);linkpath=SYSCALLS.getStr(linkpath);linkpath=SYSCALLS.calculateAt(dirfd,linkpath);FS.symlink(target,linkpath);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_unlinkat(dirfd,path,flags){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(37,0,1,dirfd,path,flags);path>>>=0;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(flags===0){FS.unlink(path)}else if(flags===512){FS.rmdir(path)}else{abort("Invalid flags passed to unlinkat")}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var readI53FromI64=ptr=>GROWABLE_HEAP_U32()[ptr>>>2>>>0]+GROWABLE_HEAP_I32()[ptr+4>>>2>>>0]*4294967296;function ___syscall_utimensat(dirfd,path,times,flags){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(38,0,1,dirfd,path,times,flags);path>>>=0;times>>>=0;try{path=SYSCALLS.getStr(path);assert(flags===0);path=SYSCALLS.calculateAt(dirfd,path,true);var now=Date.now(),atime,mtime;if(!times){atime=now;mtime=now}else{var seconds=readI53FromI64(times);var nanoseconds=GROWABLE_HEAP_I32()[times+8>>>2>>>0];if(nanoseconds==1073741823){atime=now}else if(nanoseconds==1073741822){atime=null}else{atime=seconds*1e3+nanoseconds/(1e3*1e3)}times+=16;seconds=readI53FromI64(times);nanoseconds=GROWABLE_HEAP_I32()[times+8>>>2>>>0];if(nanoseconds==1073741823){mtime=now}else if(nanoseconds==1073741822){mtime=null}else{mtime=seconds*1e3+nanoseconds/(1e3*1e3)}}if((mtime??atime)!==null){FS.utime(path,atime,mtime)}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var getCppExceptionTag=()=>wasmExports["__cpp_exception"];var getCppExceptionThrownObjectFromWebAssemblyException=ex=>{var unwind_header=ex.getArg(getCppExceptionTag(),0);return ___thrown_object_from_unwind_exception(unwind_header)};var getExceptionMessageCommon=ptr=>{var sp=stackSave();var type_addr_addr=stackAlloc(4);var message_addr_addr=stackAlloc(4);___get_exception_message(ptr,type_addr_addr,message_addr_addr);var type_addr=GROWABLE_HEAP_U32()[type_addr_addr>>>2>>>0];var message_addr=GROWABLE_HEAP_U32()[message_addr_addr>>>2>>>0];var type=UTF8ToString(type_addr);_free(type_addr);var message;if(message_addr){message=UTF8ToString(message_addr);_free(message_addr)}stackRestore(sp);return[type,message]};var getExceptionMessage=ex=>{var ptr=getCppExceptionThrownObjectFromWebAssemblyException(ex);return getExceptionMessageCommon(ptr)};Module["getExceptionMessage"]=getExceptionMessage;var ___throw_exception_with_stack_trace=ex=>{var e=new WebAssembly.Exception(getCppExceptionTag(),[ex],{traceStack:true});e.message=getExceptionMessage(e);throw e};var __abort_js=()=>abort("native code called abort()");var createNamedFunction=(name,body)=>Object.defineProperty(body,"name",{value:name});var emval_freelist=[];var emval_handles=[];var BindingError;var throwBindingError=message=>{throw new BindingError(message)};var count_emval_handles=()=>emval_handles.length/2-5-emval_freelist.length;var init_emval=()=>{emval_handles.push(0,1,undefined,1,null,1,true,1,false,1);assert(emval_handles.length===5*2);Module["count_emval_handles"]=count_emval_handles};var Emval={toValue:handle=>{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}assert(handle===2||emval_handles[handle]!==undefined&&handle%2===0,`invalid handle: ${handle}`);return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};var extendError=(baseErrorType,errorName)=>{var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return`${this.name}: ${this.message}`}};return errorClass};var PureVirtualError;var embind_init_charCodes=()=>{var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes};var embind_charCodes;var readLatin1String=ptr=>{var ret="";var c=ptr;while(GROWABLE_HEAP_U8()[c>>>0]){ret+=embind_charCodes[GROWABLE_HEAP_U8()[c++>>>0]]}return ret};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var registeredPointers={};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var InternalError;var throwInternalError=message=>{throw new InternalError(message)};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var attachFinalizer=handle=>{if("undefined"===typeof FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{console.warn(info.leakWarning);releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};var cls=$$.ptrType.registeredClass;var err=new Error(`Embind found a leaked C++ instance ${cls.name} <${ptrToString($$.ptr)}>.\n`+"We'll free it automatically in this case, but this functionality is not reliable across various environments.\n"+"Make sure to invoke .delete() manually once you're done with the instance instead.\n"+"Originally allocated");if("captureStackTrace"in Error){Error.captureStackTrace(err,RegisteredPointer_fromWireType)}info.leakWarning=err.stack.replace(/^Error: /,"");finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};function __embind_create_inheriting_constructor(constructorName,wrapperType,properties){constructorName>>>=0;wrapperType>>>=0;properties>>>=0;constructorName=readLatin1String(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){registeredClass.baseClass.pureVirtualFunctions.forEach(function(name){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}.bind(this));Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)}var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this["fromWireType"](GROWABLE_HEAP_U32()[pointer>>>2>>>0])}var awaitingDependencies={};var typeDependencies={};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=function(structType){structType>>>=0;var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};fieldRecords.forEach((field,i)=>{var fieldName=field.fieldName;var getterReturnType=fieldTypes[i];var getter=field.getter;var getterContext=field.getterContext;var setterArgumentType=fieldTypes[i+fieldRecords.length];var setter=field.setter;var setterContext=field.setterContext;fields[fieldName]={read:ptr=>getterReturnType["fromWireType"](getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}}});return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},argPackAdvance:GenericWireTypeSize,readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){if(registeredInstance.argPackAdvance===undefined){throw new TypeError("registerType registeredInstance requires argPackAdvance")}return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>GROWABLE_HEAP_I8()[pointer>>>0]:pointer=>GROWABLE_HEAP_U8()[pointer>>>0];case 2:return signed?pointer=>GROWABLE_HEAP_I16()[pointer>>>1>>>0]:pointer=>GROWABLE_HEAP_U16()[pointer>>>1>>>0];case 4:return signed?pointer=>GROWABLE_HEAP_I32()[pointer>>>2>>>0]:pointer=>GROWABLE_HEAP_U32()[pointer>>>2>>>0];case 8:return signed?pointer=>HEAP64[pointer>>>3]:pointer=>HEAPU64[pointer>>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function __embind_register_bigint(primitiveType,name,size,minRange,maxRange){primitiveType>>>=0;name>>>=0;size>>>=0;name=readLatin1String(name);var isUnsignedType=name.indexOf("u")!=-1;if(isUnsignedType){maxRange=(1n<<64n)-1n}registerType(primitiveType,{name,fromWireType:value=>value,toWireType:function(destructors,value){if(typeof value!="bigint"&&typeof value!="number"){throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${this.name}`)}if(typeof value=="number"){value=BigInt(value)}if(valuemaxRange){throw new TypeError(`Passing a number "${embindRepr(value)}" from JS side to C/C++ side to an argument of type "${name}", which is outside the valid range [${minRange}, ${maxRange}]!`)}return value},argPackAdvance:GenericWireTypeSize,readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})}var GenericWireTypeSize=8;function __embind_register_bool(rawType,name,trueValue,falseValue){rawType>>>=0;name>>>=0;name=readLatin1String(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},argPackAdvance:GenericWireTypeSize,readValueFromPointer:function(pointer){return this["fromWireType"](GROWABLE_HEAP_U8()[pointer>>>0])},destructorFunction:null})}var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{Object.assign(ClassHandle.prototype,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}})};function ClassHandle(){}var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{assert(typeof name==="string");name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},argPackAdvance:GenericWireTypeSize,readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var dynCall=(sig,ptr,args=[])=>{assert(getWasmTableEntry(ptr),`missing table entry in dynCall: ${ptr}`);var rtn=getWasmTableEntry(ptr)(...args);return sig[0]=="p"?rtn>>>0:rtn};var getDynCaller=(sig,ptr)=>{assert(sig.includes("j")||sig.includes("p"),"getDynCaller should only be called with i64 sigs");return(...args)=>dynCall(sig,ptr,args)};var embind__requireFunction=(signature,rawFunction)=>{signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("p")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};var UnboundTypeError;var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};function __embind_register_class(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor){rawType>>>=0;rawPointerType>>>=0;rawConstPointerType>>>=0;baseClassRawType>>>=0;getActualTypeSignature>>>=0;getActualType>>>=0;upcastSignature>>>=0;upcast>>>=0;downcastSignature>>>=0;downcast>>>=0;name>>>=0;destructorSignature>>>=0;rawDestructor>>>=0;name=readLatin1String(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError("Use 'new' to construct "+name)}if(undefined===registeredClass.constructor_body){throw new BindingError(name+" has no accessible constructor")}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})}function usesDestructorStack(argTypes){for(var i=1;imaxArgs){var argCountMessage=minArgs==maxArgs?minArgs:`${minArgs} to ${maxArgs}`;throwBindingError(`function ${humanName} called with ${numArgs} arguments, expected ${argCountMessage}`)}}function createJsInvoker(argTypes,isClassMethodFunc,returns,isAsync){var needsDestructorStack=usesDestructorStack(argTypes);var argCount=argTypes.length-2;var argsList=[];var argsListWired=["fn"];if(isClassMethodFunc){argsListWired.push("thisWired")}for(var i=0;i=2;--i){if(!argTypes[i].optional){break}requiredArgCount--}return requiredArgCount}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc,isAsync){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}assert(!isAsync,"Async bindings are only supported with JSPI.");var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=usesDestructorStack(argTypes);var returns=argTypes[0].name!=="void";var expectedArgCount=argCount-2;var minArgs=getRequiredArgCount(argTypes);var closureArgs=[humanName,throwBindingError,cppInvokerFunc,cppTargetFunc,runDestructors,argTypes[0],argTypes[1]];for(var i=0;i{var array=[];for(var i=0;i>>2>>>0])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex!==-1){assert(signature[signature.length-1]==")","Parentheses for argument names should match.");return signature.substr(0,argsIndex)}else{return signature}};var __embind_register_class_class_function=function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn){rawClassType>>>=0;methodName>>>=0;rawArgTypesAddr>>>=0;invokerSignature>>>=0;rawInvoker>>>=0;fn>>>=0;var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=function(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){rawClassType>>>=0;rawArgTypesAddr>>>=0;invokerSignature>>>=0;invoker>>>=0;rawConstructor>>>=0;assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn){rawClassType>>>=0;methodName>>>=0;rawArgTypesAddr>>>=0;invokerSignature>>>=0;rawInvoker>>>=0;context>>>=0;var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=function(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){classType>>>=0;fieldName>>>=0;getterReturnType>>>=0;getterSignature>>>=0;getter>>>=0;getterContext>>>=0;setterArgumentType>>>=0;setterSignature>>>=0;setter>>>=0;setterContext>>>=0;fieldName=readLatin1String(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType["fromWireType"](getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};function __emval_decref(handle){handle>>>=0;if(handle>9&&0===--emval_handles[handle+1]){assert(emval_handles[handle]!==undefined,`Decref for unallocated handle.`);emval_handles[handle]=undefined;emval_freelist.push(handle)}}var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),argPackAdvance:GenericWireTypeSize,readValueFromPointer:readPointer,destructorFunction:null};function __embind_register_emval(rawType){rawType>>>=0;return registerType(rawType,EmValType)}var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this["fromWireType"](GROWABLE_HEAP_I8()[pointer>>>0])}:function(pointer){return this["fromWireType"](GROWABLE_HEAP_U8()[pointer>>>0])};case 2:return signed?function(pointer){return this["fromWireType"](GROWABLE_HEAP_I16()[pointer>>>1>>>0])}:function(pointer){return this["fromWireType"](GROWABLE_HEAP_U16()[pointer>>>1>>>0])};case 4:return signed?function(pointer){return this["fromWireType"](GROWABLE_HEAP_I32()[pointer>>>2>>>0])}:function(pointer){return this["fromWireType"](GROWABLE_HEAP_U32()[pointer>>>2>>>0])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function __embind_register_enum(rawType,name,size,isSigned){rawType>>>=0;name>>>=0;size>>>=0;name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,argPackAdvance:GenericWireTypeSize,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function __embind_register_enum_value(rawEnumType,name,enumValue){rawEnumType>>>=0;name>>>=0;var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this["fromWireType"](GROWABLE_HEAP_F32()[pointer>>>2>>>0])};case 8:return function(pointer){return this["fromWireType"](GROWABLE_HEAP_F64()[pointer>>>3>>>0])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=function(rawType,name,size){rawType>>>=0;name>>>=0;size>>>=0;name=readLatin1String(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>{if(typeof value!="number"&&typeof value!="boolean"){throw new TypeError(`Cannot convert ${embindRepr(value)} to ${this.name}`)}return value},argPackAdvance:GenericWireTypeSize,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};function __embind_register_function(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn){name>>>=0;rawArgTypesAddr>>>=0;signature>>>=0;rawInvoker>>>=0;fn>>>=0;var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){primitiveType>>>=0;name>>>=0;size>>>=0;name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{if(typeof value!="number"&&typeof value!="boolean"){throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${toTypeName}`)}if(valuemaxRange){throw new TypeError(`Passing a number "${embindRepr(value)}" from JS side to C/C++ side to an argument of type "${name}", which is outside the valid range [${minRange}, ${maxRange}]!`)}};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name,fromWireType,toWireType,argPackAdvance:GenericWireTypeSize,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){rawType>>>=0;name>>>=0;var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=GROWABLE_HEAP_U32()[handle>>>2>>>0];var data=GROWABLE_HEAP_U32()[handle+4>>>2>>>0];return new TA(GROWABLE_HEAP_I8().buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name,fromWireType:decodeMemoryView,argPackAdvance:GenericWireTypeSize,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})}var __embind_register_smart_ptr=function(rawType,rawPointeeType,name,sharingPolicy,getPointeeSignature,rawGetPointee,constructorSignature,rawConstructor,shareSignature,rawShare,destructorSignature,rawDestructor){rawType>>>=0;rawPointeeType>>>=0;name>>>=0;getPointeeSignature>>>=0;rawGetPointee>>>=0;constructorSignature>>>=0;rawConstructor>>>=0;shareSignature>>>=0;rawShare>>>=0;destructorSignature>>>=0;rawDestructor>>>=0;name=readLatin1String(name);rawGetPointee=embind__requireFunction(getPointeeSignature,rawGetPointee);rawConstructor=embind__requireFunction(constructorSignature,rawConstructor);rawShare=embind__requireFunction(shareSignature,rawShare);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);whenDependentTypesAreResolved([rawType],[rawPointeeType],pointeeType=>{pointeeType=pointeeType[0];var registeredPointer=new RegisteredPointer(name,pointeeType.registeredClass,false,false,true,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor);return[registeredPointer]})};function __embind_register_std_string(rawType,name){rawType>>>=0;name>>>=0;name=readLatin1String(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=GROWABLE_HEAP_U32()[value>>>2>>>0];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+i;if(i==length||GROWABLE_HEAP_U8()[currentBytePtr>>>0]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>>0])}str=a.join("")}_free(value);return str},toWireType(destructors,value){if(value instanceof ArrayBuffer){value=new Uint8Array(value)}var length;var valueIsOfTypeString=typeof value=="string";if(!(valueIsOfTypeString||value instanceof Uint8Array||value instanceof Uint8ClampedArray||value instanceof Int8Array)){throwBindingError("Cannot pass non-string to std::string")}if(stdStringIsUTF8&&valueIsOfTypeString){length=lengthBytesUTF8(value)}else{length=value.length}var base=_malloc(4+length+1);var ptr=base+4;GROWABLE_HEAP_U32()[base>>>2>>>0]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}GROWABLE_HEAP_U8()[ptr+i>>>0]=charCode}}else{for(var i=0;i>>0]=value[i]}}}if(destructors!==null){destructors.push(_free,base)}return base},argPackAdvance:GenericWireTypeSize,readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})}var UTF16Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead)=>{assert(ptr%2==0,"Pointer passed to UTF16ToString must be aligned to two bytes!");var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&GROWABLE_HEAP_U16()[idx>>>0])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder)return UTF16Decoder.decode(GROWABLE_HEAP_U8().slice(ptr,endPtr));var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=GROWABLE_HEAP_I16()[ptr+i*2>>>1>>>0];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str};var stringToUTF16=(str,outPtr,maxBytesToWrite)=>{assert(outPtr%2==0,"Pointer passed to stringToUTF16 must be aligned to two bytes!");assert(typeof maxBytesToWrite=="number","stringToUTF16(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!");maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>>1>>>0]=codeUnit;outPtr+=2}GROWABLE_HEAP_I16()[outPtr>>>1>>>0]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead)=>{assert(ptr%4==0,"Pointer passed to UTF32ToString must be aligned to four bytes!");var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=GROWABLE_HEAP_I32()[ptr+i*4>>>2>>>0];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{outPtr>>>=0;assert(outPtr%4==0,"Pointer passed to stringToUTF32 must be aligned to four bytes!");assert(typeof maxBytesToWrite=="number","stringToUTF32(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!");maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}GROWABLE_HEAP_I32()[outPtr>>>2>>>0]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}GROWABLE_HEAP_I32()[outPtr>>>2>>>0]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len};var __embind_register_std_wstring=function(rawType,charSize,name){rawType>>>=0;charSize>>>=0;name>>>=0;name=readLatin1String(name);var decodeString,encodeString,readCharAt,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16;readCharAt=pointer=>GROWABLE_HEAP_U16()[pointer>>>1>>>0]}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;readCharAt=pointer=>GROWABLE_HEAP_U32()[pointer>>>2>>>0]}registerType(rawType,{name,fromWireType:value=>{var length=GROWABLE_HEAP_U32()[value>>>2>>>0];var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||readCharAt(currentBytePtr)==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);GROWABLE_HEAP_U32()[ptr>>>2>>>0]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},argPackAdvance:GenericWireTypeSize,readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){rawType>>>=0;name>>>=0;constructorSignature>>>=0;rawConstructor>>>=0;destructorSignature>>>=0;rawDestructor>>>=0;structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structType>>>=0;fieldName>>>=0;getterReturnType>>>=0;getterSignature>>>=0;getter>>>=0;getterContext>>>=0;setterArgumentType>>>=0;setterSignature>>>=0;setter>>>=0;setterContext>>>=0;structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})}var __embind_register_void=function(rawType,name){rawType>>>=0;name>>>=0;name=readLatin1String(name);registerType(rawType,{isVoid:true,name,argPackAdvance:0,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};function __emscripten_init_main_thread_js(tb){tb>>>=0;__emscripten_thread_init(tb,!ENVIRONMENT_IS_WORKER,1,!ENVIRONMENT_IS_WEB,2097152,false);PThread.threadInitTLS()}var maybeExit=()=>{if(!keepRuntimeAlive()){try{if(ENVIRONMENT_IS_PTHREAD)__emscripten_thread_exit(EXITSTATUS);else _exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){err("user callback triggered after runtime exited or application aborted. Ignoring.");return}try{func();maybeExit()}catch(e){handleException(e)}};function __emscripten_thread_mailbox_await(pthread_ptr){pthread_ptr>>>=0;if(typeof Atomics.waitAsync==="function"){var wait=Atomics.waitAsync(GROWABLE_HEAP_I32(),pthread_ptr>>>2,pthread_ptr);assert(wait.async);wait.value.then(checkMailbox);var waitingAsync=pthread_ptr+128;Atomics.store(GROWABLE_HEAP_I32(),waitingAsync>>>2,1)}}var checkMailbox=()=>{var pthread_ptr=_pthread_self();if(pthread_ptr){__emscripten_thread_mailbox_await(pthread_ptr);callUserCallback(__emscripten_check_mailbox)}};function __emscripten_notify_mailbox_postmessage(targetThread,currThreadId){targetThread>>>=0;currThreadId>>>=0;if(targetThread==currThreadId){setTimeout(checkMailbox)}else if(ENVIRONMENT_IS_PTHREAD){postMessage({targetThread,cmd:"checkMailbox"})}else{var worker=PThread.pthreads[targetThread];if(!worker){err(`Cannot send message to thread with ID ${targetThread}, unknown thread ID!`);return}worker.postMessage({cmd:"checkMailbox"})}}var proxiedJSCallArgs=[];function __emscripten_receive_on_main_thread_js(funcIndex,emAsmAddr,callingThread,numCallArgs,args){emAsmAddr>>>=0;callingThread>>>=0;args>>>=0;numCallArgs/=2;proxiedJSCallArgs.length=numCallArgs;var b=args>>>3;for(var i=0;i>>0]}}var func=emAsmAddr?ASM_CONSTS[emAsmAddr]:proxiedFunctionTable[funcIndex];assert(!(funcIndex&&emAsmAddr));assert(func.length==numCallArgs,"Call args mismatch in _emscripten_receive_on_main_thread_js");PThread.currentProxiedOperationCallerThread=callingThread;var rtn=func(...proxiedJSCallArgs);PThread.currentProxiedOperationCallerThread=0;assert(typeof rtn!="bigint");return rtn}var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};function __emscripten_thread_cleanup(thread){thread>>>=0;if(!ENVIRONMENT_IS_PTHREAD)cleanupThread(thread);else postMessage({cmd:"cleanupThread",thread})}function __emscripten_thread_set_strongref(thread){thread>>>=0;if(ENVIRONMENT_IS_NODE){PThread.pthreads[thread].ref()}}var emval_returnValue=(returnType,destructorsRef,handle)=>{var destructors=[];var result=returnType["toWireType"](destructors,handle);if(destructors.length){GROWABLE_HEAP_U32()[destructorsRef>>>2>>>0]=Emval.toHandle(destructors)}return result};function __emval_as(handle,returnType,destructorsRef){handle>>>=0;returnType>>>=0;destructorsRef>>>=0;handle=Emval.toValue(handle);returnType=requireRegisteredType(returnType,"emval::as");return emval_returnValue(returnType,destructorsRef,handle)}function __emval_as_int64(handle,returnType){handle>>>=0;returnType>>>=0;handle=Emval.toValue(handle);returnType=requireRegisteredType(returnType,"emval::as");return returnType["toWireType"](null,handle)}function __emval_as_uint64(handle,returnType){handle>>>=0;returnType>>>=0;handle=Emval.toValue(handle);returnType=requireRegisteredType(returnType,"emval::as");return returnType["toWireType"](null,handle)}var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}return symbol};var emval_methodCallers=[];function __emval_call_method(caller,objHandle,methodName,destructorsRef,args){caller>>>=0;objHandle>>>=0;methodName>>>=0;destructorsRef>>>=0;args>>>=0;caller=emval_methodCallers[caller];objHandle=Emval.toValue(objHandle);methodName=getStringOrSymbol(methodName);return caller(objHandle,objHandle[methodName],destructorsRef,args)}var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>>2>>>0],"parameter "+i)}return a};var reflectConstruct=Reflect.construct;function __emval_get_method_caller(argCount,argTypes,kind){argTypes>>>=0;var types=emval_lookupTypes(argCount,argTypes);var retType=types.shift();argCount--;var functionBody=`return function (obj, func, destructorsRef, args) {\n`;var offset=0;var argsList=[];if(kind===0){argsList.push("obj")}var params=["retType"];var args=[retType];for(var i=0;it.name).join(", ")}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))}function __emval_get_property(handle,key){handle>>>=0;key>>>=0;handle=Emval.toValue(handle);key=Emval.toValue(key);return Emval.toHandle(handle[key])}function __emval_new_cstring(v){v>>>=0;return Emval.toHandle(getStringOrSymbol(v))}function __emval_run_destructors(handle){handle>>>=0;var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)}function __emval_take_value(type,arg){type>>>=0;arg>>>=0;type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](arg);return Emval.toHandle(v)}function __gmtime_js(time,tmPtr){time=bigintToI53Checked(time);tmPtr>>>=0;var date=new Date(time*1e3);GROWABLE_HEAP_I32()[tmPtr>>>2>>>0]=date.getUTCSeconds();GROWABLE_HEAP_I32()[tmPtr+4>>>2>>>0]=date.getUTCMinutes();GROWABLE_HEAP_I32()[tmPtr+8>>>2>>>0]=date.getUTCHours();GROWABLE_HEAP_I32()[tmPtr+12>>>2>>>0]=date.getUTCDate();GROWABLE_HEAP_I32()[tmPtr+16>>>2>>>0]=date.getUTCMonth();GROWABLE_HEAP_I32()[tmPtr+20>>>2>>>0]=date.getUTCFullYear()-1900;GROWABLE_HEAP_I32()[tmPtr+24>>>2>>>0]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;GROWABLE_HEAP_I32()[tmPtr+28>>>2>>>0]=yday}var isLeapYear=year=>year%4===0&&(year%100!==0||year%400===0);var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];var ydayFromDate=date=>{var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday};function __localtime_js(time,tmPtr){time=bigintToI53Checked(time);tmPtr>>>=0;var date=new Date(time*1e3);GROWABLE_HEAP_I32()[tmPtr>>>2>>>0]=date.getSeconds();GROWABLE_HEAP_I32()[tmPtr+4>>>2>>>0]=date.getMinutes();GROWABLE_HEAP_I32()[tmPtr+8>>>2>>>0]=date.getHours();GROWABLE_HEAP_I32()[tmPtr+12>>>2>>>0]=date.getDate();GROWABLE_HEAP_I32()[tmPtr+16>>>2>>>0]=date.getMonth();GROWABLE_HEAP_I32()[tmPtr+20>>>2>>>0]=date.getFullYear()-1900;GROWABLE_HEAP_I32()[tmPtr+24>>>2>>>0]=date.getDay();var yday=ydayFromDate(date)|0;GROWABLE_HEAP_I32()[tmPtr+28>>>2>>>0]=yday;GROWABLE_HEAP_I32()[tmPtr+36>>>2>>>0]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;GROWABLE_HEAP_I32()[tmPtr+32>>>2>>>0]=dst}var __mktime_js=function(tmPtr){tmPtr>>>=0;var ret=(()=>{var date=new Date(GROWABLE_HEAP_I32()[tmPtr+20>>>2>>>0]+1900,GROWABLE_HEAP_I32()[tmPtr+16>>>2>>>0],GROWABLE_HEAP_I32()[tmPtr+12>>>2>>>0],GROWABLE_HEAP_I32()[tmPtr+8>>>2>>>0],GROWABLE_HEAP_I32()[tmPtr+4>>>2>>>0],GROWABLE_HEAP_I32()[tmPtr>>>2>>>0],0);var dst=GROWABLE_HEAP_I32()[tmPtr+32>>>2>>>0];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){GROWABLE_HEAP_I32()[tmPtr+32>>>2>>>0]=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}GROWABLE_HEAP_I32()[tmPtr+24>>>2>>>0]=date.getDay();var yday=ydayFromDate(date)|0;GROWABLE_HEAP_I32()[tmPtr+28>>>2>>>0]=yday;GROWABLE_HEAP_I32()[tmPtr>>>2>>>0]=date.getSeconds();GROWABLE_HEAP_I32()[tmPtr+4>>>2>>>0]=date.getMinutes();GROWABLE_HEAP_I32()[tmPtr+8>>>2>>>0]=date.getHours();GROWABLE_HEAP_I32()[tmPtr+12>>>2>>>0]=date.getDate();GROWABLE_HEAP_I32()[tmPtr+16>>>2>>>0]=date.getMonth();GROWABLE_HEAP_I32()[tmPtr+20>>>2>>>0]=date.getYear();var timeMs=date.getTime();if(isNaN(timeMs)){return-1}return timeMs/1e3})();return BigInt(ret)};function __mmap_js(len,prot,flags,fd,offset,allocated,addr){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(39,0,1,len,prot,flags,fd,offset,allocated,addr);len>>>=0;offset=bigintToI53Checked(offset);allocated>>>=0;addr>>>=0;try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);var res=FS.mmap(stream,len,offset,prot,flags);var ptr=res.ptr;GROWABLE_HEAP_I32()[allocated>>>2>>>0]=res.allocated;GROWABLE_HEAP_U32()[addr>>>2>>>0]=ptr;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function __munmap_js(addr,len,prot,flags,fd,offset){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(40,0,1,addr,len,prot,flags,fd,offset);addr>>>=0;len>>>=0;offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __timegm_js=function(tmPtr){tmPtr>>>=0;var ret=(()=>{var time=Date.UTC(GROWABLE_HEAP_I32()[tmPtr+20>>>2>>>0]+1900,GROWABLE_HEAP_I32()[tmPtr+16>>>2>>>0],GROWABLE_HEAP_I32()[tmPtr+12>>>2>>>0],GROWABLE_HEAP_I32()[tmPtr+8>>>2>>>0],GROWABLE_HEAP_I32()[tmPtr+4>>>2>>>0],GROWABLE_HEAP_I32()[tmPtr>>>2>>>0],0);var date=new Date(time);GROWABLE_HEAP_I32()[tmPtr+24>>>2>>>0]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;GROWABLE_HEAP_I32()[tmPtr+28>>>2>>>0]=yday;return date.getTime()/1e3})();return BigInt(ret)};var __tzset_js=function(timezone,daylight,std_name,dst_name){timezone>>>=0;daylight>>>=0;std_name>>>=0;dst_name>>>=0;var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);GROWABLE_HEAP_U32()[timezone>>>2>>>0]=stdTimezoneOffset*60;GROWABLE_HEAP_I32()[daylight>>>2>>>0]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);assert(winterName);assert(summerName);assert(lengthBytesUTF8(winterName)<=16,`timezone name truncated to fit in TZNAME_MAX (${winterName})`);assert(lengthBytesUTF8(summerName)<=16,`timezone name truncated to fit in TZNAME_MAX (${summerName})`);if(summerOffsetperformance.timeOrigin+performance.now();var _emscripten_date_now=()=>Date.now();var nowIsMonotonic=1;var checkWasiClock=clock_id=>clock_id>=0&&clock_id<=3;function _clock_time_get(clk_id,ignored_precision,ptime){ignored_precision=bigintToI53Checked(ignored_precision);ptime>>>=0;if(!checkWasiClock(clk_id)){return 28}var now;if(clk_id===0){now=_emscripten_date_now()}else if(nowIsMonotonic){now=_emscripten_get_now()}else{return 52}var nsec=Math.round(now*1e3*1e3);HEAP64[ptime>>>3]=BigInt(nsec);return 0}var readEmAsmArgsArray=[];var readEmAsmArgs=(sigPtr,buf)=>{assert(Array.isArray(readEmAsmArgsArray));assert(buf%16==0);readEmAsmArgsArray.length=0;var ch;while(ch=GROWABLE_HEAP_U8()[sigPtr++>>>0]){var chr=String.fromCharCode(ch);var validChars=["d","f","i","p"];validChars.push("j");assert(validChars.includes(chr),`Invalid character ${ch}("${chr}") in readEmAsmArgs! Use only [${validChars}], and do not specify "v" for void return argument.`);var wide=ch!=105;wide&=ch!=112;buf+=wide&&buf%8?4:0;readEmAsmArgsArray.push(ch==112?GROWABLE_HEAP_U32()[buf>>>2>>>0]:ch==106?HEAP64[buf>>>3]:ch==105?GROWABLE_HEAP_I32()[buf>>>2>>>0]:GROWABLE_HEAP_F64()[buf>>>3>>>0]);buf+=wide?8:4}return readEmAsmArgsArray};var runEmAsmFunction=(code,sigPtr,argbuf)=>{var args=readEmAsmArgs(sigPtr,argbuf);assert(ASM_CONSTS.hasOwnProperty(code),`No EM_ASM constant found at address ${code}. The loaded WebAssembly file is likely out of sync with the generated JavaScript.`);return ASM_CONSTS[code](...args)};function _emscripten_asm_const_int(code,sigPtr,argbuf){code>>>=0;sigPtr>>>=0;argbuf>>>=0;return runEmAsmFunction(code,sigPtr,argbuf)}var _emscripten_check_blocking_allowed=()=>{if(ENVIRONMENT_IS_NODE)return;if(ENVIRONMENT_IS_WORKER)return;warnOnce("Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread")};function _emscripten_err(str){str>>>=0;return err(UTF8ToString(str))}var _emscripten_exit_with_live_runtime=()=>{runtimeKeepalivePush();throw"unwind"};var getHeapMax=()=>4294901760;function _emscripten_get_heap_max(){return getHeapMax()}var _emscripten_num_logical_cores=()=>ENVIRONMENT_IS_NODE?require("os").cpus().length:navigator["hardwareConcurrency"];var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){err(`growMemory: Attempted to grow heap from ${b.byteLength} bytes to ${size} bytes, but got error: ${e}`)}};function _emscripten_resize_heap(requestedSize){requestedSize>>>=0;var oldSize=GROWABLE_HEAP_U8().length;if(requestedSize<=oldSize){return false}var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){err(`Cannot enlarge memory, requested ${requestedSize} bytes, but the limit is ${maxHeapSize} bytes!`);return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}err(`Failed to grow the heap from ${oldSize} bytes to ${newSize} bytes, not enough memory!`);return false}var _emscripten_runtime_keepalive_check=keepRuntimeAlive;var _emscripten_unwind_to_js_event_loop=()=>{throw"unwind"};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var stringToAscii=(str,buffer)=>{for(var i=0;i>>0]=str.charCodeAt(i)}GROWABLE_HEAP_I8()[buffer>>>0]=0};var _environ_get=function(__environ,environ_buf){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(41,0,1,__environ,environ_buf);__environ>>>=0;environ_buf>>>=0;var bufSize=0;getEnvStrings().forEach((string,i)=>{var ptr=environ_buf+bufSize;GROWABLE_HEAP_U32()[__environ+i*4>>>2>>>0]=ptr;stringToAscii(string,ptr);bufSize+=string.length+1});return 0};var _environ_sizes_get=function(penviron_count,penviron_buf_size){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(42,0,1,penviron_count,penviron_buf_size);penviron_count>>>=0;penviron_buf_size>>>=0;var strings=getEnvStrings();GROWABLE_HEAP_U32()[penviron_count>>>2>>>0]=strings.length;var bufSize=0;strings.forEach(string=>bufSize+=string.length+1);GROWABLE_HEAP_U32()[penviron_buf_size>>>2>>>0]=bufSize;return 0};function _fd_close(fd){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(43,0,1,fd);try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_fdstat_get(fd,pbuf){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(44,0,1,fd,pbuf);pbuf>>>=0;try{var rightsBase=0;var rightsInheriting=0;var flags=0;{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4}GROWABLE_HEAP_I8()[pbuf>>>0]=type;GROWABLE_HEAP_I16()[pbuf+2>>>1>>>0]=flags;HEAP64[pbuf+8>>>3]=BigInt(rightsBase);HEAP64[pbuf+16>>>3]=BigInt(rightsInheriting);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>>2>>>0];var len=GROWABLE_HEAP_U32()[iov+4>>>2>>>0];iov+=8;var curr=FS.read(stream,GROWABLE_HEAP_I8(),ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>>=0;iovcnt>>>=0;offset=bigintToI53Checked(offset);pnum>>>=0;try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);var num=doReadv(stream,iov,iovcnt,offset);GROWABLE_HEAP_U32()[pnum>>>2>>>0]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>>2>>>0];var len=GROWABLE_HEAP_U32()[iov+4>>>2>>>0];iov+=8;var curr=FS.write(stream,GROWABLE_HEAP_I8(),ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>>=0;iovcnt>>>=0;offset=bigintToI53Checked(offset);pnum>>>=0;try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt,offset);GROWABLE_HEAP_U32()[pnum>>>2>>>0]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(47,0,1,fd,iov,iovcnt,pnum);iov>>>=0;iovcnt>>>=0;pnum>>>=0;try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doReadv(stream,iov,iovcnt);GROWABLE_HEAP_U32()[pnum>>>2>>>0]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_seek(fd,offset,whence,newOffset){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(48,0,1,fd,offset,whence,newOffset);offset=bigintToI53Checked(offset);newOffset>>>=0;try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_sync(fd){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(49,0,1,fd);try{var stream=SYSCALLS.getStreamFromFD(fd);if(stream.stream_ops?.fsync){return stream.stream_ops.fsync(stream)}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_write(fd,iov,iovcnt,pnum){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(50,0,1,fd,iov,iovcnt,pnum);iov>>>=0;iovcnt>>>=0;pnum>>>=0;try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);GROWABLE_HEAP_U32()[pnum>>>2>>>0]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _getaddrinfo(node,service,hint,out){if(ENVIRONMENT_IS_PTHREAD)return proxyToMainThread(51,0,1,node,service,hint,out);node>>>=0;service>>>=0;hint>>>=0;out>>>=0;var addr=0;var port=0;var flags=0;var family=0;var type=0;var proto=0;var ai;function allocaddrinfo(family,type,proto,canon,addr,port){var sa,salen,ai;var errno;salen=family===10?28:16;addr=family===10?inetNtop6(addr):inetNtop4(addr);sa=_malloc(salen);errno=writeSockaddr(sa,family,addr,port);assert(!errno);ai=_malloc(32);GROWABLE_HEAP_I32()[ai+4>>>2>>>0]=family;GROWABLE_HEAP_I32()[ai+8>>>2>>>0]=type;GROWABLE_HEAP_I32()[ai+12>>>2>>>0]=proto;GROWABLE_HEAP_U32()[ai+24>>>2>>>0]=canon;GROWABLE_HEAP_U32()[ai+20>>>2>>>0]=sa;if(family===10){GROWABLE_HEAP_I32()[ai+16>>>2>>>0]=28}else{GROWABLE_HEAP_I32()[ai+16>>>2>>>0]=16}GROWABLE_HEAP_I32()[ai+28>>>2>>>0]=0;return ai}if(hint){flags=GROWABLE_HEAP_I32()[hint>>>2>>>0];family=GROWABLE_HEAP_I32()[hint+4>>>2>>>0];type=GROWABLE_HEAP_I32()[hint+8>>>2>>>0];proto=GROWABLE_HEAP_I32()[hint+12>>>2>>>0]}if(type&&!proto){proto=type===2?17:6}if(!type&&proto){type=proto===17?2:1}if(proto===0){proto=6}if(type===0){type=1}if(!node&&!service){return-2}if(flags&~(1|2|4|1024|8|16|32)){return-1}if(hint!==0&&GROWABLE_HEAP_I32()[hint>>>2>>>0]&2&&!node){return-1}if(flags&32){return-2}if(type!==0&&type!==1&&type!==2){return-7}if(family!==0&&family!==2&&family!==10){return-6}if(service){service=UTF8ToString(service);port=parseInt(service,10);if(isNaN(port)){if(flags&1024){return-2}return-8}}if(!node){if(family===0){family=2}if((flags&1)===0){if(family===2){addr=_htonl(2130706433)}else{addr=[0,0,0,_htonl(1)]}}ai=allocaddrinfo(family,type,proto,null,addr,port);GROWABLE_HEAP_U32()[out>>>2>>>0]=ai;return 0}node=UTF8ToString(node);addr=inetPton4(node);if(addr!==null){if(family===0||family===2){family=2}else if(family===10&&flags&8){addr=[0,0,_htonl(65535),addr];family=10}else{return-2}}else{addr=inetPton6(node);if(addr!==null){if(family===0||family===10){family=10}else{return-2}}}if(addr!=null){ai=allocaddrinfo(family,type,proto,node,addr,port);GROWABLE_HEAP_U32()[out>>>2>>>0]=ai;return 0}if(flags&4){return-2}node=DNS.lookup_name(node);addr=inetPton4(node);if(family===0){family=2}else if(family===10){addr=[0,0,_htonl(65535),addr]}ai=allocaddrinfo(family,type,proto,null,addr,port);GROWABLE_HEAP_U32()[out>>>2>>>0]=ai;return 0}function _getnameinfo(sa,salen,node,nodelen,serv,servlen,flags){sa>>>=0;node>>>=0;serv>>>=0;var info=readSockaddr(sa,salen);if(info.errno){return-6}var port=info.port;var addr=info.addr;var overflowed=false;if(node&&nodelen){var lookup;if(flags&1||!(lookup=DNS.lookup_addr(addr))){if(flags&8){return-2}}else{addr=lookup}var numBytesWrittenExclNull=stringToUTF8(addr,node,nodelen);if(numBytesWrittenExclNull+1>=nodelen){overflowed=true}}if(serv&&servlen){port=""+port;var numBytesWrittenExclNull=stringToUTF8(port,serv,servlen);if(numBytesWrittenExclNull+1>=servlen){overflowed=true}}if(overflowed){return-12}return 0}function _random_get(buffer,size){buffer>>>=0;size>>>=0;try{randomFill(GROWABLE_HEAP_U8().subarray(buffer>>>0,buffer+size>>>0));return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var getCFunc=ident=>{var func=Module["_"+ident];assert(func,"Cannot call unknown function "+ident+", make sure it is exported");return func};var writeArrayToMemory=(array,buffer)=>{assert(array.length>=0,"writeArrayToMemory array must have a length (should be an array or typed array)");GROWABLE_HEAP_I8().set(array,buffer>>>0)};var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;assert(returnType!=="array",'Return type should not be "array".');if(args){for(var i=0;i(...args)=>ccall(ident,returnType,argTypes,args,opts);var FS_createPath=FS.createPath;var FS_unlink=path=>FS.unlink(path);var FS_createLazyFile=FS.createLazyFile;var FS_createDevice=FS.createDevice;var incrementExceptionRefcount=ex=>{var ptr=getCppExceptionThrownObjectFromWebAssemblyException(ex);___cxa_increment_exception_refcount(ptr)};Module["incrementExceptionRefcount"]=incrementExceptionRefcount;var decrementExceptionRefcount=ex=>{var ptr=getCppExceptionThrownObjectFromWebAssemblyException(ex);___cxa_decrement_exception_refcount(ptr)};Module["decrementExceptionRefcount"]=decrementExceptionRefcount;PThread.init();FS.createPreloadedFile=FS_createPreloadedFile;FS.staticInit();Module["FS_createPath"]=FS.createPath;Module["FS_createDataFile"]=FS.createDataFile;Module["FS_createPreloadedFile"]=FS.createPreloadedFile;Module["FS_unlink"]=FS.unlink;Module["FS_createLazyFile"]=FS.createLazyFile;Module["FS_createDevice"]=FS.createDevice;BindingError=Module["BindingError"]=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};init_emval();PureVirtualError=Module["PureVirtualError"]=extendError(Error,"PureVirtualError");embind_init_charCodes();InternalError=Module["InternalError"]=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};init_ClassHandle();init_RegisteredPointer();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");var proxiedFunctionTable=[_proc_exit,exitOnMainThread,pthreadCreateProxied,___syscall_accept4,___syscall_bind,___syscall_chdir,___syscall_chmod,___syscall_connect,___syscall_dup,___syscall_dup3,___syscall_faccessat,___syscall_fchmodat2,___syscall_fchownat,___syscall_fcntl64,___syscall_fstat64,___syscall_ftruncate64,___syscall_getcwd,___syscall_getdents64,___syscall_getpeername,___syscall_getsockname,___syscall_getsockopt,___syscall_ioctl,___syscall_listen,___syscall_lstat64,___syscall_mkdirat,___syscall_newfstatat,___syscall_openat,___syscall_pipe,___syscall_poll,___syscall_readlinkat,___syscall_recvfrom,___syscall_renameat,___syscall_rmdir,___syscall_sendto,___syscall_socket,___syscall_stat64,___syscall_symlinkat,___syscall_unlinkat,___syscall_utimensat,__mmap_js,__munmap_js,_environ_get,_environ_sizes_get,_fd_close,_fd_fdstat_get,_fd_pread,_fd_pwrite,_fd_read,_fd_seek,_fd_sync,_fd_write,_getaddrinfo];function checkIncomingModuleAPI(){ignoredModuleProp("fetchSettings")}var wasmImports;function assignWasmImports(){wasmImports={__assert_fail:___assert_fail,__call_sighandler:___call_sighandler,__pthread_create_js:___pthread_create_js,__syscall_accept4:___syscall_accept4,__syscall_bind:___syscall_bind,__syscall_chdir:___syscall_chdir,__syscall_chmod:___syscall_chmod,__syscall_connect:___syscall_connect,__syscall_dup:___syscall_dup,__syscall_dup3:___syscall_dup3,__syscall_faccessat:___syscall_faccessat,__syscall_fchmodat2:___syscall_fchmodat2,__syscall_fchownat:___syscall_fchownat,__syscall_fcntl64:___syscall_fcntl64,__syscall_fstat64:___syscall_fstat64,__syscall_ftruncate64:___syscall_ftruncate64,__syscall_getcwd:___syscall_getcwd,__syscall_getdents64:___syscall_getdents64,__syscall_getpeername:___syscall_getpeername,__syscall_getsockname:___syscall_getsockname,__syscall_getsockopt:___syscall_getsockopt,__syscall_ioctl:___syscall_ioctl,__syscall_listen:___syscall_listen,__syscall_lstat64:___syscall_lstat64,__syscall_mkdirat:___syscall_mkdirat,__syscall_newfstatat:___syscall_newfstatat,__syscall_openat:___syscall_openat,__syscall_pipe:___syscall_pipe,__syscall_poll:___syscall_poll,__syscall_readlinkat:___syscall_readlinkat,__syscall_recvfrom:___syscall_recvfrom,__syscall_renameat:___syscall_renameat,__syscall_rmdir:___syscall_rmdir,__syscall_sendto:___syscall_sendto,__syscall_socket:___syscall_socket,__syscall_stat64:___syscall_stat64,__syscall_symlinkat:___syscall_symlinkat,__syscall_unlinkat:___syscall_unlinkat,__syscall_utimensat:___syscall_utimensat,__throw_exception_with_stack_trace:___throw_exception_with_stack_trace,_abort_js:__abort_js,_embind_create_inheriting_constructor:__embind_create_inheriting_constructor,_embind_finalize_value_object:__embind_finalize_value_object,_embind_register_bigint:__embind_register_bigint,_embind_register_bool:__embind_register_bool,_embind_register_class:__embind_register_class,_embind_register_class_class_function:__embind_register_class_class_function,_embind_register_class_constructor:__embind_register_class_constructor,_embind_register_class_function:__embind_register_class_function,_embind_register_class_property:__embind_register_class_property,_embind_register_emval:__embind_register_emval,_embind_register_enum:__embind_register_enum,_embind_register_enum_value:__embind_register_enum_value,_embind_register_float:__embind_register_float,_embind_register_function:__embind_register_function,_embind_register_integer:__embind_register_integer,_embind_register_memory_view:__embind_register_memory_view,_embind_register_smart_ptr:__embind_register_smart_ptr,_embind_register_std_string:__embind_register_std_string,_embind_register_std_wstring:__embind_register_std_wstring,_embind_register_value_object:__embind_register_value_object,_embind_register_value_object_field:__embind_register_value_object_field,_embind_register_void:__embind_register_void,_emscripten_init_main_thread_js:__emscripten_init_main_thread_js,_emscripten_notify_mailbox_postmessage:__emscripten_notify_mailbox_postmessage,_emscripten_receive_on_main_thread_js:__emscripten_receive_on_main_thread_js,_emscripten_runtime_keepalive_clear:__emscripten_runtime_keepalive_clear,_emscripten_thread_cleanup:__emscripten_thread_cleanup,_emscripten_thread_mailbox_await:__emscripten_thread_mailbox_await,_emscripten_thread_set_strongref:__emscripten_thread_set_strongref,_emval_as:__emval_as,_emval_as_int64:__emval_as_int64,_emval_as_uint64:__emval_as_uint64,_emval_call_method:__emval_call_method,_emval_decref:__emval_decref,_emval_get_method_caller:__emval_get_method_caller,_emval_get_property:__emval_get_property,_emval_new_cstring:__emval_new_cstring,_emval_run_destructors:__emval_run_destructors,_emval_take_value:__emval_take_value,_gmtime_js:__gmtime_js,_localtime_js:__localtime_js,_mktime_js:__mktime_js,_mmap_js:__mmap_js,_munmap_js:__munmap_js,_timegm_js:__timegm_js,_tzset_js:__tzset_js,clock_time_get:_clock_time_get,emscripten_asm_const_int:_emscripten_asm_const_int,emscripten_check_blocking_allowed:_emscripten_check_blocking_allowed,emscripten_date_now:_emscripten_date_now,emscripten_err:_emscripten_err,emscripten_exit_with_live_runtime:_emscripten_exit_with_live_runtime,emscripten_get_heap_max:_emscripten_get_heap_max,emscripten_get_now:_emscripten_get_now,emscripten_num_logical_cores:_emscripten_num_logical_cores,emscripten_resize_heap:_emscripten_resize_heap,emscripten_runtime_keepalive_check:_emscripten_runtime_keepalive_check,emscripten_unwind_to_js_event_loop:_emscripten_unwind_to_js_event_loop,environ_get:_environ_get,environ_sizes_get:_environ_sizes_get,exit:_exit,fd_close:_fd_close,fd_fdstat_get:_fd_fdstat_get,fd_pread:_fd_pread,fd_pwrite:_fd_pwrite,fd_read:_fd_read,fd_seek:_fd_seek,fd_sync:_fd_sync,fd_write:_fd_write,getaddrinfo:_getaddrinfo,getnameinfo:_getnameinfo,jsRegisterChar,jsRegisterString,memory:wasmMemory,proc_exit:_proc_exit,random_get:_random_get}}var wasmExports;createWasm();var ___wasm_call_ctors=createExportWrapper("__wasm_call_ctors",0);var ___getTypeName=createExportWrapper("__getTypeName",1);var __embind_initialize_bindings=createExportWrapper("_embind_initialize_bindings",0);var _malloc=Module["_malloc"]=createExportWrapper("malloc",1);var _pthread_self=()=>(_pthread_self=wasmExports["pthread_self"])();var _main=Module["_main"]=createExportWrapper("__main_argc_argv",2);var _fflush=createExportWrapper("fflush",1);var _free=Module["_free"]=createExportWrapper("free",1);var _htonl=createExportWrapper("htonl",1);var _htons=createExportWrapper("htons",1);var _ntohs=createExportWrapper("ntohs",1);var _strerror=createExportWrapper("strerror",1);var _libreofficekit_hook_2=Module["_libreofficekit_hook_2"]=createExportWrapper("libreofficekit_hook_2",2);var _libreofficekit_hook=Module["_libreofficekit_hook"]=createExportWrapper("libreofficekit_hook",1);var _lok_preinit=Module["_lok_preinit"]=createExportWrapper("lok_preinit",2);var _lok_preinit_2=Module["_lok_preinit_2"]=createExportWrapper("lok_preinit_2",3);var _lok_documentLoad=Module["_lok_documentLoad"]=createExportWrapper("lok_documentLoad",2);var _lok_documentLoadWithOptions=Module["_lok_documentLoadWithOptions"]=createExportWrapper("lok_documentLoadWithOptions",3);var _lok_documentSaveAs=Module["_lok_documentSaveAs"]=createExportWrapper("lok_documentSaveAs",4);var _lok_documentDestroy=Module["_lok_documentDestroy"]=createExportWrapper("lok_documentDestroy",1);var _lok_getError=Module["_lok_getError"]=createExportWrapper("lok_getError",1);var _lok_destroy=Module["_lok_destroy"]=createExportWrapper("lok_destroy",1);var _lok_documentGetParts=Module["_lok_documentGetParts"]=createExportWrapper("lok_documentGetParts",1);var _lok_documentGetPart=Module["_lok_documentGetPart"]=createExportWrapper("lok_documentGetPart",1);var _lok_documentSetPart=Module["_lok_documentSetPart"]=createExportWrapper("lok_documentSetPart",2);var _lok_documentGetDocumentType=Module["_lok_documentGetDocumentType"]=createExportWrapper("lok_documentGetDocumentType",1);var _lok_documentGetDocumentSize=Module["_lok_documentGetDocumentSize"]=createExportWrapper("lok_documentGetDocumentSize",3);var _lok_documentInitializeForRendering=Module["_lok_documentInitializeForRendering"]=createExportWrapper("lok_documentInitializeForRendering",2);var _lok_documentPaintTile=Module["_lok_documentPaintTile"]=createExportWrapper("lok_documentPaintTile",8);var _lok_documentGetTileMode=Module["_lok_documentGetTileMode"]=createExportWrapper("lok_documentGetTileMode",1);var _lok_documentGetTextSelection=Module["_lok_documentGetTextSelection"]=createExportWrapper("lok_documentGetTextSelection",3);var _lok_documentSetTextSelection=Module["_lok_documentSetTextSelection"]=createExportWrapper("lok_documentSetTextSelection",4);var _lok_documentGetSelectionType=Module["_lok_documentGetSelectionType"]=createExportWrapper("lok_documentGetSelectionType",1);var _lok_documentResetSelection=Module["_lok_documentResetSelection"]=createExportWrapper("lok_documentResetSelection",1);var _lok_documentPostMouseEvent=Module["_lok_documentPostMouseEvent"]=createExportWrapper("lok_documentPostMouseEvent",7);var _lok_documentPostKeyEvent=Module["_lok_documentPostKeyEvent"]=createExportWrapper("lok_documentPostKeyEvent",4);var _lok_documentPostUnoCommand=Module["_lok_documentPostUnoCommand"]=createExportWrapper("lok_documentPostUnoCommand",4);var _lok_documentGetCommandValues=Module["_lok_documentGetCommandValues"]=createExportWrapper("lok_documentGetCommandValues",2);var _lok_documentGetPartPageRectangles=Module["_lok_documentGetPartPageRectangles"]=createExportWrapper("lok_documentGetPartPageRectangles",1);var _lok_documentGetPartInfo=Module["_lok_documentGetPartInfo"]=createExportWrapper("lok_documentGetPartInfo",2);var _lok_documentGetPartName=Module["_lok_documentGetPartName"]=createExportWrapper("lok_documentGetPartName",2);var _lok_documentPaste=Module["_lok_documentPaste"]=createExportWrapper("lok_documentPaste",4);var _lok_documentSetClientZoom=Module["_lok_documentSetClientZoom"]=createExportWrapper("lok_documentSetClientZoom",5);var _lok_documentSetClientVisibleArea=Module["_lok_documentSetClientVisibleArea"]=createExportWrapper("lok_documentSetClientVisibleArea",5);var _lok_documentGetA11yFocusedParagraph=Module["_lok_documentGetA11yFocusedParagraph"]=createExportWrapper("lok_documentGetA11yFocusedParagraph",1);var _lok_documentGetA11yCaretPosition=Module["_lok_documentGetA11yCaretPosition"]=createExportWrapper("lok_documentGetA11yCaretPosition",1);var _lok_documentSetAccessibilityState=Module["_lok_documentSetAccessibilityState"]=createExportWrapper("lok_documentSetAccessibilityState",3);var _lok_documentGetDataArea=Module["_lok_documentGetDataArea"]=createExportWrapper("lok_documentGetDataArea",4);var _lok_documentGetEditMode=Module["_lok_documentGetEditMode"]=createExportWrapper("lok_documentGetEditMode",1);var _lok_documentSetEditMode=Module["_lok_documentSetEditMode"]=createExportWrapper("lok_documentSetEditMode",2);var _lok_documentCreateView=Module["_lok_documentCreateView"]=createExportWrapper("lok_documentCreateView",1);var _lok_documentCreateViewWithOptions=Module["_lok_documentCreateViewWithOptions"]=createExportWrapper("lok_documentCreateViewWithOptions",2);var _lok_documentDestroyView=Module["_lok_documentDestroyView"]=createExportWrapper("lok_documentDestroyView",2);var _lok_documentSetView=Module["_lok_documentSetView"]=createExportWrapper("lok_documentSetView",2);var _lok_documentGetView=Module["_lok_documentGetView"]=createExportWrapper("lok_documentGetView",1);var _lok_documentGetViewsCount=Module["_lok_documentGetViewsCount"]=createExportWrapper("lok_documentGetViewsCount",1);var _lok_enableSyncEvents=Module["_lok_enableSyncEvents"]=createExportWrapper("lok_enableSyncEvents",0);var _lok_disableSyncEvents=Module["_lok_disableSyncEvents"]=createExportWrapper("lok_disableSyncEvents",0);var _lok_runLoop=Module["_lok_runLoop"]=createExportWrapper("lok_runLoop",4);var _lok_documentRegisterCallback=Module["_lok_documentRegisterCallback"]=createExportWrapper("lok_documentRegisterCallback",1);var _lok_documentUnregisterCallback=Module["_lok_documentUnregisterCallback"]=createExportWrapper("lok_documentUnregisterCallback",1);var _lok_hasCallbackEvents=Module["_lok_hasCallbackEvents"]=createExportWrapper("lok_hasCallbackEvents",0);var _lok_getCallbackEventCount=Module["_lok_getCallbackEventCount"]=createExportWrapper("lok_getCallbackEventCount",0);var _lok_pollCallback=Module["_lok_pollCallback"]=createExportWrapper("lok_pollCallback",3);var _lok_clearCallbackQueue=Module["_lok_clearCallbackQueue"]=createExportWrapper("lok_clearCallbackQueue",0);var _lok_flushCallbacks=Module["_lok_flushCallbacks"]=createExportWrapper("lok_flushCallbacks",1);var __emscripten_tls_init=createExportWrapper("_emscripten_tls_init",0);var _emscripten_builtin_memalign=createExportWrapper("emscripten_builtin_memalign",2);var __emscripten_proxy_main=Module["__emscripten_proxy_main"]=createExportWrapper("_emscripten_proxy_main",2);var _emscripten_stack_get_base=()=>(_emscripten_stack_get_base=wasmExports["emscripten_stack_get_base"])();var _emscripten_stack_get_end=()=>(_emscripten_stack_get_end=wasmExports["emscripten_stack_get_end"])();var __emscripten_thread_init=createExportWrapper("_emscripten_thread_init",6);var __emscripten_thread_crashed=createExportWrapper("_emscripten_thread_crashed",0);var __emscripten_run_on_main_thread_js=createExportWrapper("_emscripten_run_on_main_thread_js",5);var __emscripten_thread_free_data=createExportWrapper("_emscripten_thread_free_data",1);var __emscripten_thread_exit=createExportWrapper("_emscripten_thread_exit",1);var __emscripten_check_mailbox=createExportWrapper("_emscripten_check_mailbox",0);var ___trap=()=>(___trap=wasmExports["__trap"])();var _emscripten_stack_init=()=>(_emscripten_stack_init=wasmExports["emscripten_stack_init"])();var _emscripten_stack_set_limits=(a0,a1)=>(_emscripten_stack_set_limits=wasmExports["emscripten_stack_set_limits"])(a0,a1);var _emscripten_stack_get_free=()=>(_emscripten_stack_get_free=wasmExports["emscripten_stack_get_free"])();var __emscripten_stack_restore=a0=>(__emscripten_stack_restore=wasmExports["_emscripten_stack_restore"])(a0);var __emscripten_stack_alloc=a0=>(__emscripten_stack_alloc=wasmExports["_emscripten_stack_alloc"])(a0);var _emscripten_stack_get_current=()=>(_emscripten_stack_get_current=wasmExports["emscripten_stack_get_current"])();var ___cxa_decrement_exception_refcount=createExportWrapper("__cxa_decrement_exception_refcount",1);var ___cxa_increment_exception_refcount=createExportWrapper("__cxa_increment_exception_refcount",1);var ___thrown_object_from_unwind_exception=createExportWrapper("__thrown_object_from_unwind_exception",1);var ___get_exception_message=createExportWrapper("__get_exception_message",3);function applySignatureConversions(wasmExports){wasmExports=Object.assign({},wasmExports);var makeWrapper_pp=f=>a0=>f(a0)>>>0;var makeWrapper_p=f=>()=>f()>>>0;var makeWrapper_p_=f=>a0=>f(a0)>>>0;var makeWrapper_ppp=f=>(a0,a1)=>f(a0,a1)>>>0;wasmExports["__getTypeName"]=makeWrapper_pp(wasmExports["__getTypeName"]);wasmExports["malloc"]=makeWrapper_pp(wasmExports["malloc"]);wasmExports["pthread_self"]=makeWrapper_p(wasmExports["pthread_self"]);wasmExports["strerror"]=makeWrapper_p_(wasmExports["strerror"]);wasmExports["emscripten_builtin_memalign"]=makeWrapper_ppp(wasmExports["emscripten_builtin_memalign"]);wasmExports["emscripten_stack_get_base"]=makeWrapper_p(wasmExports["emscripten_stack_get_base"]);wasmExports["emscripten_stack_get_end"]=makeWrapper_p(wasmExports["emscripten_stack_get_end"]);wasmExports["_emscripten_stack_alloc"]=makeWrapper_pp(wasmExports["_emscripten_stack_alloc"]);wasmExports["emscripten_stack_get_current"]=makeWrapper_p(wasmExports["emscripten_stack_get_current"]);return wasmExports}Module["addOnPreMain"]=addOnPreMain;Module["addOnPostRun"]=addOnPostRun;Module["addRunDependency"]=addRunDependency;Module["removeRunDependency"]=removeRunDependency;Module["ENV"]=ENV;Module["wasmTable"]=wasmTable;Module["ccall"]=ccall;Module["cwrap"]=cwrap;Module["UTF8ToString"]=UTF8ToString;Module["UTF16ToString"]=UTF16ToString;Module["stringToUTF16"]=stringToUTF16;Module["FS_createPreloadedFile"]=FS_createPreloadedFile;Module["FS_unlink"]=FS_unlink;Module["FS_createPath"]=FS_createPath;Module["FS_createDevice"]=FS_createDevice;Module["FS"]=FS;Module["FS_createDataFile"]=FS_createDataFile;Module["FS_createLazyFile"]=FS_createLazyFile;Module["throwBindingError"]=throwBindingError;Module["registerType"]=registerType;var missingLibrarySymbols=["writeI53ToI64","writeI53ToI64Clamped","writeI53ToI64Signaling","writeI53ToU64Clamped","writeI53ToU64Signaling","readI53FromU64","convertI32PairToI53","convertI32PairToI53Checked","convertU32PairToI53","getTempRet0","setTempRet0","emscriptenLog","runMainThreadEmAsm","listenOnce","autoResumeAudioContext","asmjsMangle","HandleAllocator","getNativeTypeSize","STACK_SIZE","STACK_ALIGN","POINTER_SIZE","ASSERTIONS","uleb128Encode","sigToWasmTypes","generateFuncType","convertJsFunctionToWasm","getEmptyTableSlot","updateTableMap","getFunctionAddress","addFunction","removeFunction","reallyNegative","unSign","strLen","reSign","formatString","intArrayToString","AsciiToString","stringToNewUTF8","registerKeyEventCallback","maybeCStringToJsString","findEventTarget","getBoundingClientRect","fillMouseEventData","registerMouseEventCallback","registerWheelEventCallback","registerUiEventCallback","registerFocusEventCallback","fillDeviceOrientationEventData","registerDeviceOrientationEventCallback","fillDeviceMotionEventData","registerDeviceMotionEventCallback","screenOrientation","fillOrientationChangeEventData","registerOrientationChangeEventCallback","fillFullscreenChangeEventData","registerFullscreenChangeEventCallback","JSEvents_requestFullscreen","JSEvents_resizeCanvasForFullscreen","registerRestoreOldStyle","hideEverythingExceptGivenElement","restoreHiddenElements","setLetterbox","softFullscreenResizeWebGLRenderTarget","doRequestFullscreen","fillPointerlockChangeEventData","registerPointerlockChangeEventCallback","registerPointerlockErrorEventCallback","requestPointerLock","fillVisibilityChangeEventData","registerVisibilityChangeEventCallback","registerTouchEventCallback","fillGamepadEventData","registerGamepadEventCallback","registerBeforeUnloadEventCallback","fillBatteryEventData","battery","registerBatteryEventCallback","setCanvasElementSizeCallingThread","setCanvasElementSizeMainThread","setCanvasElementSize","getCanvasSizeCallingThread","getCanvasSizeMainThread","getCanvasElementSize","jsStackTrace","getCallstack","convertPCtoSourceLocation","wasiRightsToMuslOFlags","wasiOFlagsToMuslOFlags","safeSetTimeout","setImmediateWrapped","safeRequestAnimationFrame","clearImmediateWrapped","polyfillSetImmediate","registerPostMainLoop","registerPreMainLoop","getPromise","makePromise","idsToPromises","makePromiseCallback","Browser_asyncPrepareDataCounter","arraySum","addDays","FS_mkdirTree","_setNetworkCallback","heapObjectForWebGLType","toTypedArrayIndex","webgl_enable_ANGLE_instanced_arrays","webgl_enable_OES_vertex_array_object","webgl_enable_WEBGL_draw_buffers","webgl_enable_WEBGL_multi_draw","webgl_enable_EXT_polygon_offset_clamp","webgl_enable_EXT_clip_control","webgl_enable_WEBGL_polygon_mode","emscriptenWebGLGet","computeUnpackAlignedImageSize","colorChannelsInGlTextureFormat","emscriptenWebGLGetTexPixelData","emscriptenWebGLGetUniform","webglGetUniformLocation","webglPrepareUniformLocationsBeforeFirstUse","webglGetLeftBracePos","emscriptenWebGLGetVertexAttrib","__glGetActiveAttribOrUniform","writeGLArray","emscripten_webgl_destroy_context_before_on_calling_thread","registerWebGlEventCallback","runAndAbortIfError","ALLOC_NORMAL","ALLOC_STACK","allocate","writeStringToMemory","writeAsciiToMemory","setErrNo","demangle","stackTrace","getFunctionArgsName","createJsInvokerSignature","getInheritedInstanceCount","getLiveInheritedInstances","setDelayFunction","emval_get_global","fetchDeleteCachedData","fetchLoadCachedData","fetchCacheData","fetchXHR"];missingLibrarySymbols.forEach(missingLibrarySymbol);var unexportedSymbols=["run","addOnPreRun","addOnInit","addOnExit","out","err","callMain","abort","wasmMemory","wasmExports","GROWABLE_HEAP_I8","GROWABLE_HEAP_U8","GROWABLE_HEAP_I16","GROWABLE_HEAP_U16","GROWABLE_HEAP_I32","GROWABLE_HEAP_U32","GROWABLE_HEAP_F32","GROWABLE_HEAP_F64","writeStackCookie","checkStackCookie","readI53FromI64","INT53_MAX","INT53_MIN","bigintToI53Checked","stackSave","stackRestore","stackAlloc","ptrToString","zeroMemory","exitJS","getHeapMax","growMemory","ERRNO_CODES","strError","inetPton4","inetNtop4","inetPton6","inetNtop6","readSockaddr","writeSockaddr","DNS","Protocols","Sockets","timers","warnOnce","readEmAsmArgsArray","readEmAsmArgs","runEmAsmFunction","jstoi_q","jstoi_s","getExecutableName","getDynCaller","dynCall","handleException","keepRuntimeAlive","runtimeKeepalivePush","runtimeKeepalivePop","callUserCallback","maybeExit","asyncLoad","alignMemory","mmapAlloc","noExitRuntime","getCFunc","freeTableIndexes","functionsInTableMap","setValue","getValue","PATH","PATH_FS","UTF8Decoder","UTF8ArrayToString","stringToUTF8Array","stringToUTF8","lengthBytesUTF8","intArrayFromString","stringToAscii","UTF16Decoder","lengthBytesUTF16","UTF32ToString","stringToUTF32","lengthBytesUTF32","stringToUTF8OnStack","writeArrayToMemory","JSEvents","specialHTMLTargets","findCanvasEventTarget","currentFullscreenStrategy","restoreOldWindowedStyle","UNWIND_CACHE","ExitStatus","getEnvStrings","checkWasiClock","doReadv","doWritev","initRandomFill","randomFill","promiseMap","getExceptionMessageCommon","getCppExceptionTag","getCppExceptionThrownObjectFromWebAssemblyException","incrementExceptionRefcount","decrementExceptionRefcount","getExceptionMessage","Browser","getPreloadedImageData__data","wget","MONTH_DAYS_REGULAR","MONTH_DAYS_LEAP","MONTH_DAYS_REGULAR_CUMULATIVE","MONTH_DAYS_LEAP_CUMULATIVE","isLeapYear","ydayFromDate","SYSCALLS","getSocketFromFD","getSocketAddress","preloadPlugins","FS_modeStringToFlags","FS_getMode","FS_stdin_getChar_buffer","FS_stdin_getChar","FS_readFile","MEMFS","TTY","PIPEFS","SOCKFS","tempFixedLengthArray","miniTempWebGLFloatBuffers","miniTempWebGLIntBuffers","GL","AL","GLUT","EGL","GLEW","IDBStore","SDL","SDL_gfx","allocateUTF8","allocateUTF8OnStack","print","printErr","PThread","terminateWorker","cleanupThread","registerTLSInit","spawnThread","exitOnMainThread","proxyToMainThread","proxiedJSCallArgs","invokeEntryPoint","checkMailbox","InternalError","BindingError","throwInternalError","registeredTypes","awaitingDependencies","typeDependencies","tupleRegistrations","structRegistrations","sharedRegisterType","whenDependentTypesAreResolved","embind_charCodes","embind_init_charCodes","readLatin1String","getTypeName","getFunctionName","heap32VectorToArray","requireRegisteredType","usesDestructorStack","checkArgCount","getRequiredArgCount","createJsInvoker","UnboundTypeError","PureVirtualError","GenericWireTypeSize","EmValType","EmValOptionalType","throwUnboundTypeError","ensureOverloadTable","exposePublicSymbol","replacePublicSymbol","extendError","createNamedFunction","embindRepr","registeredInstances","getBasestPointer","registerInheritedInstance","unregisterInheritedInstance","getInheritedInstance","registeredPointers","integerReadValueFromPointer","enumReadValueFromPointer","floatReadValueFromPointer","readPointer","runDestructors","newFunc","craftInvokerFunction","embind__requireFunction","genericPointerToWireType","constNoSmartPtrRawPointerToWireType","nonConstNoSmartPtrRawPointerToWireType","init_RegisteredPointer","RegisteredPointer","RegisteredPointer_fromWireType","runDestructor","releaseClassHandle","finalizationRegistry","detachFinalizer_deps","detachFinalizer","attachFinalizer","makeClassHandle","init_ClassHandle","ClassHandle","throwInstanceAlreadyDeleted","deletionQueue","flushPendingDeletes","delayFunction","RegisteredClass","shallowCopyInternalPointer","downcastPointer","upcastPointer","validateThis","char_0","char_9","makeLegalFunctionName","emval_freelist","emval_handles","emval_symbols","init_emval","count_emval_handles","getStringOrSymbol","Emval","emval_returnValue","emval_lookupTypes","emval_methodCallers","emval_addMethodCaller","reflectConstruct","Fetch"];unexportedSymbols.forEach(unexportedRuntimeSymbol);var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function callMain(args=[]){assert(runDependencies==0,'cannot call main when async dependencies remain! (listen on Module["onRuntimeInitialized"])');assert(__ATPRERUN__.length==0,"cannot call main when preRun functions remain to be called");var entryFunction=__emscripten_proxy_main;runtimeKeepalivePush();args.unshift(thisProgram);var argc=args.length;var argv=stackAlloc((argc+1)*4);var argv_ptr=argv;args.forEach(arg=>{GROWABLE_HEAP_U32()[argv_ptr>>>2>>>0]=stringToUTF8OnStack(arg);argv_ptr+=4});GROWABLE_HEAP_U32()[argv_ptr>>>2>>>0]=0;try{var ret=entryFunction(argc,argv);exitJS(ret,true);return ret}catch(e){return handleException(e)}}function stackCheckInit(){assert(!ENVIRONMENT_IS_PTHREAD);_emscripten_stack_init();writeStackCookie()}function run(args=arguments_){if(runDependencies>0){return}if(ENVIRONMENT_IS_PTHREAD){initRuntime();return}stackCheckInit();preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();Module["onRuntimeInitialized"]?.();if(shouldRunNow)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}checkStackCookie()}function checkUnflushedContent(){var oldOut=out;var oldErr=err;var has=false;out=err=x=>{has=true};try{_fflush(0);["stdout","stderr"].forEach(name=>{var info=FS.analyzePath("/dev/"+name);if(!info)return;var stream=info.object;var rdev=stream.rdev;var tty=TTY.ttys[rdev];if(tty?.output?.length){has=true}})}catch(e){}out=oldOut;err=oldErr;if(has){warnOnce("stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the Emscripten FAQ), or make sure to emit a newline when you printf etc.")}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=true;if(Module["noInitialRun"])shouldRunNow=false;run(); diff --git a/public/libreoffice-wasm/soffice.wasm.gz b/public/libreoffice-wasm/soffice.wasm.gz new file mode 100755 index 0000000..8a93838 Binary files /dev/null and b/public/libreoffice-wasm/soffice.wasm.gz differ diff --git a/public/libreoffice-wasm/soffice.worker.js b/public/libreoffice-wasm/soffice.worker.js new file mode 100644 index 0000000..0bd99a5 --- /dev/null +++ b/public/libreoffice-wasm/soffice.worker.js @@ -0,0 +1 @@ +"use strict";var Module={};var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",data=>onmessage({data:data}));var fs=require("fs");var vm=require("vm");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:f=>vm.runInThisContext(fs.readFileSync(f,"utf8"),{filename:f}),postMessage:msg=>parentPort.postMessage(msg),performance:global.performance||{now:Date.now}})}var initializedJS=false;function assert(condition,text){if(!condition)abort("Assertion failed: "+text)}function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");if(ENVIRONMENT_IS_NODE){fs.writeSync(2,text+"\n");return}console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var out=()=>{throw"out() is not defined in worker.js."};var err=threadPrintErr;self.alert=threadAlert;var dbg=threadPrintErr;Module["instantiateWasm"]=(info,receiveInstance)=>{var module=Module["wasmModule"];Module["wasmModule"]=null;var instance=new WebAssembly.Instance(module,info);return receiveInstance(instance)};self.onunhandledrejection=e=>{throw e.reason||e};function handleMessage(e){try{if(e.data.cmd==="load"){let messageQueue=[];self.onmessage=e=>messageQueue.push(e);self.startWorker=instance=>{postMessage({"cmd":"loaded"});for(let msg of messageQueue){handleMessage(msg)}self.onmessage=handleMessage};Module["wasmModule"]=e.data.wasmModule;for(const handler of e.data.handlers){Module[handler]=(...args)=>{postMessage({cmd:"callHandler",handler:handler,args:args})}}Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["workerID"]=e.data.workerID;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob=="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}}else if(e.data.cmd==="run"){Module["__emscripten_thread_init"](e.data.pthread_ptr,0,0,1);Module["__emscripten_thread_mailbox_await"](e.data.pthread_ptr);assert(e.data.pthread_ptr);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInitTLS();if(!initializedJS){Module["__embind_initialize_bindings"]();initializedJS=true}try{Module["invokeEntryPoint"](e.data.start_routine,e.data.arg)}catch(ex){if(ex!="unwind"){throw ex}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="checkMailbox"){if(initializedJS){Module["checkMailbox"]()}}else if(e.data.cmd){err(`worker.js received unknown command ${e.data.cmd}`);err(e.data)}}catch(ex){err(`worker.js onmessage() captured an uncaught exception: ${ex}`);if(ex?.stack){var lines=ex.stack.split("\n").slice(0,10);err(lines.join("\n"))};Module["__emscripten_thread_crashed"]?.();throw ex}}self.onmessage=handleMessage; diff --git a/public/locales/ar/common.json b/public/locales/ar/common.json new file mode 100644 index 0000000..133e022 --- /dev/null +++ b/public/locales/ar/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "الرئيسية", + "about": "حول", + "contact": "اتصل بنا", + "licensing": "الترخيص", + "allTools": "جميع الأدوات", + "openMainMenu": "فتح القائمة الرئيسية", + "language": "اللغة" + }, + "donation": { + "message": "أعجبك BentoPDF؟ ساعدنا في إبقائه مجانيًا ومفتوح المصدر!", + "button": "تبرّع" + }, + "hero": { + "title": "مجموعة", + "pdfToolkit": "أدوات PDF", + "builtForPrivacy": "مصمّمة للخصوصية", + "noSignups": "بدون تسجيل", + "unlimitedUse": "استخدام غير محدود", + "worksOffline": "يعمل بدون إنترنت", + "startUsing": "ابدأ الاستخدام الآن" + }, + "usedBy": { + "title": "يستخدمه شركات وأشخاص يعملون في" + }, + "features": { + "title": "لماذا تختار", + "bentoPdf": "BentoPDF؟", + "noSignup": { + "title": "بدون تسجيل", + "description": "ابدأ فورًا، بدون حسابات أو بريد إلكتروني." + }, + "noUploads": { + "title": "بدون رفع ملفات", + "description": "معالجة كاملة على جهازك، ملفاتك لا تغادر جهازك أبدًا." + }, + "foreverFree": { + "title": "مجاني للأبد", + "description": "جميع الأدوات، بدون فترات تجريبية، بدون حواجز دفع." + }, + "noLimits": { + "title": "بدون حدود", + "description": "استخدم بقدر ما تريد، بدون قيود مخفية." + }, + "batchProcessing": { + "title": "معالجة دفعية", + "description": "عالج عددًا غير محدود من ملفات PDF دفعة واحدة." + }, + "lightningFast": { + "title": "سريع كالبرق", + "description": "عالج ملفات PDF فورًا، بدون انتظار أو تأخير." + } + }, + "tools": { + "title": "ابدأ مع", + "toolsLabel": "الأدوات", + "subtitle": "انقر على أداة لفتح رافع الملفات", + "searchPlaceholder": "ابحث عن أداة (مثلاً، 'تقسيم'، 'ترتيب'...)", + "backToTools": "العودة إلى الأدوات", + "firstLoadNotice": "يستغرق التحميل الأول لحظة أثناء تنزيل محرك التحويل. بعد ذلك، ستكون جميع التحميلات فورية." + }, + "upload": { + "clickToSelect": "انقر لاختيار ملف", + "orDragAndDrop": "أو اسحب وأفلت", + "pdfOrImages": "ملفات PDF أو صور", + "filesNeverLeave": "ملفاتك لا تغادر جهازك أبدًا.", + "addMore": "إضافة المزيد من الملفات", + "clearAll": "مسح الكل", + "clearFiles": "مسح الملفات", + "hints": { + "singlePdf": "ملف PDF واحد", + "pdfFile": "ملف PDF", + "multiplePdfs2": "عدة ملفات PDF (اثنان على الأقل)", + "bmpImages": "صور BMP", + "oneOrMorePdfs": "ملف PDF واحد أو أكثر", + "pdfDocuments": "مستندات PDF", + "oneOrMoreCsv": "ملف CSV واحد أو أكثر", + "multiplePdfsSupported": "يدعم عدة ملفات PDF", + "singleOrMultiplePdfs": "يدعم ملف PDF واحد أو أكثر", + "singlePdfFile": "ملف PDF واحد", + "pdfWithForms": "ملف PDF يحتوي على حقول نماذج", + "heicImages": "صور HEIC/HEIF", + "jpgImages": "صور JPG، JPEG، JP2، JPX", + "pdfsOrImages": "ملفات PDF أو صور", + "oneOrMoreOdt": "ملف ODT واحد أو أكثر", + "singlePdfOnly": "ملف PDF واحد فقط", + "pdfFiles": "ملفات PDF", + "multiplePdfs": "عدة ملفات PDF", + "pngImages": "صور PNG", + "pdfFilesOneOrMore": "ملفات PDF (واحد أو أكثر)", + "oneOrMoreRtf": "ملف RTF واحد أو أكثر", + "svgGraphics": "رسومات SVG", + "tiffImages": "صور TIFF", + "webpImages": "صور WebP" + } + }, + "howItWorks": { + "title": "كيف يعمل", + "step1": "انقر أو اسحب وأفلت ملفك للبدء", + "step2": "انقر زر المعالجة للبدء", + "step3": "احفظ ملفك المعالج فورًا" + }, + "relatedTools": { + "title": "أدوات PDF ذات صلة" + }, + "loader": { + "processing": "جارٍ المعالجة..." + }, + "alert": { + "title": "تنبيه", + "ok": "حسنًا" + }, + "preview": { + "title": "معاينة المستند", + "downloadAsPdf": "تنزيل كـ PDF", + "close": "إغلاق" + }, + "settings": { + "title": "الإعدادات", + "shortcuts": "اختصارات لوحة المفاتيح", + "preferences": "التفضيلات", + "displayPreferences": "تفضيلات العرض", + "searchShortcuts": "البحث في الاختصارات...", + "shortcutsInfo": "اضغط مع الاستمرار على المفاتيح لتعيين اختصار. يتم الحفظ تلقائيًا.", + "shortcutsWarning": "⚠️ تجنب اختصارات المتصفح الشائعة (Cmd/Ctrl+W، Cmd/Ctrl+T، Cmd/Ctrl+N إلخ) لأنها قد لا تعمل بشكل موثوق.", + "import": "استيراد", + "export": "تصدير", + "resetToDefaults": "إعادة التعيين إلى الافتراضي", + "fullWidthMode": "وضع العرض الكامل", + "fullWidthDescription": "استخدم عرض الشاشة الكامل لجميع الأدوات بدلاً من حاوية مركزية", + "settingsAutoSaved": "يتم حفظ الإعدادات تلقائيًا", + "clickToSet": "انقر للتعيين", + "pressKeys": "اضغط على المفاتيح...", + "warnings": { + "alreadyInUse": "الاختصار مستخدم بالفعل", + "assignedTo": "مخصص بالفعل لـ:", + "chooseDifferent": "يرجى اختيار اختصار مختلف.", + "reserved": "تحذير اختصار محجوز", + "commonlyUsed": "يُستخدم عادةً لـ:", + "unreliable": "قد لا يعمل هذا الاختصار بشكل موثوق أو قد يتعارض مع سلوك المتصفح/النظام.", + "useAnyway": "هل تريد استخدامه على أي حال؟", + "resetTitle": "إعادة تعيين الاختصارات", + "resetMessage": "هل أنت متأكد من إعادة تعيين جميع الاختصارات إلى الافتراضي؟

لا يمكن التراجع عن هذا الإجراء.", + "importSuccessTitle": "تم الاستيراد بنجاح", + "importSuccessMessage": "تم استيراد الاختصارات بنجاح!", + "importFailTitle": "فشل الاستيراد", + "importFailMessage": "فشل استيراد الاختصارات. تنسيق الملف غير صالح." + } + }, + "warning": { + "title": "تحذير", + "cancel": "إلغاء", + "proceed": "متابعة" + }, + "compliance": { + "title": "بياناتك لا تغادر جهازك أبدًا", + "weKeep": "نحافظ على", + "yourInfoSafe": "أمان معلوماتك", + "byFollowingStandards": "باتباع معايير الأمان العالمية.", + "processingLocal": "تتم جميع المعالجة محليًا على جهازك.", + "gdpr": { + "title": "توافق GDPR", + "description": "يحمي البيانات الشخصية وخصوصية الأفراد داخل الاتحاد الأوروبي." + }, + "ccpa": { + "title": "توافق CCPA", + "description": "يمنح سكان كاليفورنيا حقوقًا حول كيفية جمع واستخدام ومشاركة معلوماتهم الشخصية." + }, + "hipaa": { + "title": "توافق HIPAA", + "description": "يضع ضمانات للتعامل مع المعلومات الصحية الحساسة في نظام الرعاية الصحية الأمريكي." + } + }, + "faq": { + "title": "الأسئلة", + "questions": "الشائعة", + "sectionTitle": "الأسئلة الشائعة", + "isFree": { + "question": "هل BentoPDF مجاني حقًا؟", + "answer": "نعم، بالتأكيد. جميع أدوات BentoPDF مجانية 100% بدون حدود للملفات، بدون تسجيل، وبدون علامات مائية. نؤمن بأن الجميع يستحق الوصول إلى أدوات PDF بسيطة وقوية بدون حواجز دفع." + }, + "areFilesSecure": { + "question": "هل ملفاتي آمنة؟ أين تتم معالجتها؟", + "answer": "ملفاتك آمنة قدر الإمكان لأنها لا تغادر جهازك أبدًا. تتم جميع المعالجة مباشرة في متصفحك (من جانب العميل). لا نقوم أبدًا برفع ملفاتك إلى خادم، لذا تحافظ على خصوصيتك الكاملة والتحكم في مستنداتك." + }, + "platforms": { + "question": "هل يعمل على Mac وWindows والأجهزة المحمولة؟", + "answer": "نعم! بما أن BentoPDF يعمل بالكامل في متصفحك، فهو يعمل على أي نظام تشغيل بمتصفح حديث، بما في ذلك Windows وmacOS وLinux وiOS وAndroid." + }, + "gdprCompliant": { + "question": "هل BentoPDF متوافق مع GDPR؟", + "answer": "نعم. BentoPDF متوافق تمامًا مع GDPR. بما أن جميع معالجة الملفات تتم محليًا في متصفحك ولا نجمع أو ننقل ملفاتك إلى أي خادم، فليس لدينا وصول إلى بياناتك. هذا يضمن أنك دائمًا تتحكم في مستنداتك." + }, + "dataStorage": { + "question": "هل تخزنون أو تتتبعون أيًا من ملفاتي؟", + "answer": "لا. لا نقوم أبدًا بتخزين أو تتبع أو تسجيل ملفاتك. كل ما تفعله على BentoPDF يحدث في ذاكرة متصفحك ويختفي بمجرد إغلاق الصفحة. لا يوجد رفع، لا سجلات، ولا خوادم معنية." + }, + "different": { + "question": "ما الذي يميز BentoPDF عن أدوات PDF الأخرى؟", + "answer": "معظم أدوات PDF ترفع ملفاتك إلى خادم للمعالجة. BentoPDF لا يفعل ذلك أبدًا. نستخدم تقنيات ويب حديثة وآمنة لمعالجة ملفاتك مباشرة في متصفحك. هذا يعني أداءً أسرع، خصوصية أقوى، وراحة بال كاملة." + }, + "browserBased": { + "question": "كيف تحافظ المعالجة عبر المتصفح على أمانك؟", + "answer": "بالعمل بالكامل داخل متصفحك، يضمن BentoPDF أن ملفاتك لا تغادر جهازك أبدًا. هذا يلغي مخاطر اختراق الخوادم أو تسريب البيانات أو الوصول غير المصرح به. ملفاتك تبقى ملكك — دائمًا." + }, + "analytics": { + "question": "هل تستخدمون ملفات تعريف الارتباط أو التحليلات لتتبعي؟", + "answer": "نحن نهتم بخصوصيتك. BentoPDF لا يتتبع المعلومات الشخصية. نستخدم Simple Analytics فقط لرؤية عدد الزيارات المجهولة. هذا يعني أننا نعرف عدد المستخدمين الذين يزورون موقعنا، لكننا لا نعرف أبدًا من أنت. Simple Analytics متوافق تمامًا مع GDPR ويحترم خصوصيتك." + } + }, + "testimonials": { + "title": "ماذا يقول", + "users": "مستخدمونا", + "say": "" + }, + "support": { + "title": "أعجبك عملنا؟", + "description": "BentoPDF مشروع شغف، صُمم لتوفير مجموعة أدوات PDF مجانية وخاصة وقوية للجميع. إذا وجدته مفيدًا، فكّر في دعم تطويره. كل قهوة تساعد!", + "buyMeCoffee": "اشترِ لي قهوة" + }, + "footer": { + "copyright": "© 2026 BentoPDF. جميع الحقوق محفوظة.", + "version": "الإصدار", + "company": "الشركة", + "aboutUs": "من نحن", + "faqLink": "الأسئلة الشائعة", + "contactUs": "اتصل بنا", + "legal": "قانوني", + "termsAndConditions": "الشروط والأحكام", + "privacyPolicy": "سياسة الخصوصية", + "followUs": "تابعنا" + }, + "merge": { + "title": "دمج ملفات PDF", + "description": "ادمج ملفات كاملة، أو حدد صفحات معينة لدمجها في مستند جديد.", + "fileMode": "وضع الملفات", + "pageMode": "وضع الصفحات", + "howItWorks": "كيف يعمل:", + "fileModeInstructions": [ + "انقر واسحب الأيقونة لتغيير ترتيب الملفات.", + "في حقل \"الصفحات\" لكل ملف، يمكنك تحديد نطاقات (مثلاً، \"1-3, 5\") لدمج تلك الصفحات فقط.", + "اترك حقل \"الصفحات\" فارغًا لتضمين جميع صفحات ذلك الملف." + ], + "pageModeInstructions": [ + "جميع الصفحات من ملفات PDF المرفوعة تظهر أدناه.", + "ما عليك سوى سحب وإفلات الصور المصغرة للصفحات لإنشاء الترتيب الذي تريده لملفك الجديد." + ], + "mergePdfs": "دمج ملفات PDF" + }, + "common": { + "page": "صفحة", + "pages": "صفحات", + "of": "من", + "download": "تنزيل", + "cancel": "إلغاء", + "save": "حفظ", + "delete": "حذف", + "edit": "تعديل", + "add": "إضافة", + "remove": "إزالة", + "loading": "جارٍ التحميل...", + "error": "خطأ", + "success": "تم بنجاح", + "file": "ملف", + "files": "ملفات", + "close": "إغلاق" + }, + "about": { + "hero": { + "title": "نؤمن بأن أدوات PDF يجب أن تكون", + "subtitle": "سريعة، خاصة، ومجانية.", + "noCompromises": "بدون تنازلات." + }, + "mission": { + "title": "مهمتنا", + "description": "تقديم أشمل مجموعة أدوات PDF تحترم خصوصيتك ولا تطلب أي مقابل. نؤمن بأن أدوات المستندات الأساسية يجب أن تكون متاحة للجميع، في كل مكان، بدون عوائق." + }, + "philosophy": { + "label": "فلسفتنا الأساسية", + "title": "الخصوصية أولاً. دائمًا.", + "description": "في عصر أصبحت فيه البيانات سلعة، نتبع نهجًا مختلفًا. تتم جميع معالجة أدوات Bentopdf محليًا في متصفحك. هذا يعني أن ملفاتك لا تلمس خوادمنا أبدًا، ولا نرى مستنداتك، ولا نتتبع ما تفعله. مستنداتك تبقى خاصة تمامًا وبشكل قاطع. إنها ليست مجرد ميزة؛ إنها أساسنا." + }, + "whyBentopdf": { + "title": "لماذا", + "speed": { + "title": "مصمّم للسرعة", + "description": "لا انتظار لرفع أو تنزيل من خادم. بمعالجة الملفات مباشرة في متصفحك باستخدام تقنيات ويب حديثة مثل WebAssembly، نقدم سرعة لا مثيل لها لجميع أدواتنا." + }, + "free": { + "title": "مجاني بالكامل", + "description": "لا فترات تجريبية، لا اشتراكات، لا رسوم مخفية، ولا ميزات \"مميزة\" محتجزة. نؤمن بأن أدوات PDF القوية يجب أن تكون خدمة عامة، وليست مركز ربح." + }, + "noAccount": { + "title": "لا حاجة لحساب", + "description": "ابدأ باستخدام أي أداة فورًا. لا نحتاج بريدك الإلكتروني أو كلمة مرور أو أي معلومات شخصية. سير عملك يجب أن يكون سلسًا ومجهولاً." + }, + "openSource": { + "title": "روح المصدر المفتوح", + "description": "مبني بشفافية. نستفيد من مكتبات مفتوحة المصدر رائعة مثل PDF-lib وPDF.js، ونؤمن بالجهد المجتمعي لجعل الأدوات القوية متاحة للجميع." + } + }, + "cta": { + "title": "مستعد للبدء؟", + "description": "انضم إلى آلاف المستخدمين الذين يثقون بـ Bentopdf لاحتياجات مستنداتهم اليومية. اختبر الفرق الذي يمكن أن تحدثه الخصوصية والأداء.", + "button": "استكشف جميع الأدوات" + } + }, + "contact": { + "title": "تواصل معنا", + "subtitle": "يسعدنا سماعك. سواء كان لديك سؤال أو ملاحظة أو طلب ميزة، لا تتردد في التواصل.", + "email": "يمكنك التواصل معنا مباشرة عبر البريد الإلكتروني على:" + }, + "licensing": { + "title": "الترخيص لـ", + "subtitle": "اختر الترخيص المناسب لاحتياجاتك." + }, + "multiTool": { + "uploadPdfs": "رفع ملفات PDF", + "upload": "رفع", + "addBlankPage": "إضافة صفحة فارغة", + "edit": "تعديل:", + "undo": "تراجع", + "redo": "إعادة", + "reset": "إعادة تعيين", + "selection": "التحديد:", + "selectAll": "تحديد الكل", + "deselectAll": "إلغاء تحديد الكل", + "rotate": "تدوير:", + "rotateLeft": "يسار", + "rotateRight": "يمين", + "transform": "تحويل:", + "duplicate": "تكرار", + "split": "تقسيم", + "clear": "مسح:", + "delete": "حذف", + "download": "تنزيل:", + "downloadSelected": "تنزيل المحدد", + "exportPdf": "تصدير PDF", + "uploadPdfFiles": "اختر ملفات PDF", + "dragAndDrop": "اسحب وأفلت ملفات PDF هنا، أو انقر للاختيار", + "selectFiles": "اختر الملفات", + "renderingPages": "جارٍ عرض الصفحات...", + "actions": { + "duplicatePage": "تكرار هذه الصفحة", + "deletePage": "حذف هذه الصفحة", + "insertPdf": "إدراج PDF بعد هذه الصفحة", + "toggleSplit": "تبديل التقسيم بعد هذه الصفحة" + }, + "pleaseWait": "يرجى الانتظار", + "pagesRendering": "لا تزال الصفحات قيد العرض. يرجى الانتظار...", + "noPagesSelected": "لم يتم تحديد صفحات", + "selectOnePage": "يرجى تحديد صفحة واحدة على الأقل للتنزيل.", + "noPages": "لا توجد صفحات", + "noPagesToExport": "لا توجد صفحات للتصدير.", + "renderingTitle": "جارٍ عرض معاينات الصفحات", + "errorRendering": "فشل عرض الصور المصغرة للصفحات", + "error": "خطأ", + "failedToLoad": "فشل التحميل" + }, + "simpleMode": { + "title": "أدوات PDF", + "subtitle": "اختر أداة للبدء" + } +} diff --git a/public/locales/ar/tools.json b/public/locales/ar/tools.json new file mode 100644 index 0000000..ae5082c --- /dev/null +++ b/public/locales/ar/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "الأدوات الشائعة", + "editAnnotate": "تعديل وتعليق", + "convertToPdf": "تحويل إلى PDF", + "convertFromPdf": "تحويل من PDF", + "organizeManage": "تنظيم وإدارة", + "optimizeRepair": "تحسين وإصلاح", + "securePdf": "تأمين PDF" + }, + "pdfMultiTool": { + "name": "أداة PDF المتعددة", + "subtitle": "دمج، تقسيم، تنظيم، حذف، تدوير، إضافة صفحات فارغة، استخراج وتكرار في واجهة موحدة." + }, + "mergePdf": { + "name": "دمج PDF", + "subtitle": "دمج عدة ملفات PDF في ملف واحد. يحافظ على الإشارات المرجعية." + }, + "splitPdf": { + "name": "تقسيم PDF", + "subtitle": "استخراج نطاق من الصفحات في ملف PDF جديد." + }, + "compressPdf": { + "name": "ضغط PDF", + "subtitle": "تقليل حجم ملف PDF الخاص بك.", + "algorithmLabel": "خوارزمية الضغط", + "condense": "تكثيف (موصى به)", + "photon": "فوتون (لملفات PDF كثيرة الصور)", + "condenseInfo": "يستخدم التكثيف ضغطًا متقدمًا: يزيل البيانات الزائدة، يحسّن الصور، يقلّص الخطوط. الأفضل لمعظم ملفات PDF.", + "photonInfo": "يحول فوتون الصفحات إلى صور. استخدمه لملفات PDF كثيرة الصور/الممسوحة ضوئيًا.", + "photonWarning": "تحذير: سيصبح النص غير قابل للتحديد وستتوقف الروابط عن العمل.", + "levelLabel": "مستوى الضغط", + "light": "خفيف (الحفاظ على الجودة)", + "balanced": "متوازن (موصى به)", + "aggressive": "عدواني (ملفات أصغر)", + "extreme": "أقصى (ضغط أقصى)", + "grayscale": "تحويل إلى تدرج الرمادي", + "grayscaleHint": "يقلل حجم الملف بإزالة معلومات الألوان", + "customSettings": "إعدادات مخصصة", + "customSettingsHint": "ضبط دقيق لمعلمات الضغط:", + "outputQuality": "جودة المخرجات", + "resizeImagesTo": "تغيير حجم الصور إلى", + "onlyProcessAbove": "معالجة فقط أعلى من", + "removeMetadata": "إزالة البيانات الوصفية", + "subsetFonts": "تقليص الخطوط (إزالة الحروف غير المستخدمة)", + "removeThumbnails": "إزالة الصور المصغرة المضمنة", + "compressButton": "ضغط PDF" + }, + "pdfEditor": { + "name": "محرر PDF", + "subtitle": "تعليق، تمييز، تنقيح، تعليقات، إضافة أشكال/صور، بحث وعرض ملفات PDF." + }, + "jpgToPdf": { + "name": "JPG إلى PDF", + "subtitle": "إنشاء PDF من صور JPG وJPEG وJPEG2000 (JP2/JPX)." + }, + "signPdf": { + "name": "توقيع PDF", + "subtitle": "ارسم أو اكتب أو ارفع توقيعك." + }, + "cropPdf": { + "name": "قص PDF", + "subtitle": "قص هوامش كل صفحة في ملف PDF الخاص بك." + }, + "extractPages": { + "name": "استخراج الصفحات", + "subtitle": "حفظ مجموعة من الصفحات كملفات جديدة." + }, + "duplicateOrganize": { + "name": "تكرار وتنظيم", + "subtitle": "تكرار وإعادة ترتيب وحذف الصفحات." + }, + "deletePages": { + "name": "حذف الصفحات", + "subtitle": "إزالة صفحات محددة من مستندك." + }, + "editBookmarks": { + "name": "تعديل الإشارات المرجعية", + "subtitle": "إضافة وتعديل واستيراد وحذف واستخراج الإشارات المرجعية في PDF." + }, + "tableOfContents": { + "name": "جدول المحتويات", + "subtitle": "إنشاء صفحة جدول محتويات من إشارات PDF المرجعية." + }, + "pageNumbers": { + "name": "أرقام الصفحات", + "subtitle": "إدراج أرقام الصفحات في مستندك." + }, + "batesNumbering": { + "name": "ترقيم بيتس", + "subtitle": "إضافة أرقام بيتس التسلسلية عبر ملف PDF واحد أو أكثر." + }, + "addWatermark": { + "name": "إضافة علامة مائية", + "subtitle": "ختم نص أو صورة على صفحات PDF الخاصة بك.", + "applyToAllPages": "تطبيق على جميع الصفحات" + }, + "headerFooter": { + "name": "رأس وتذييل", + "subtitle": "إضافة نص في أعلى وأسفل الصفحات." + }, + "invertColors": { + "name": "عكس الألوان", + "subtitle": "إنشاء نسخة \"الوضع الداكن\" من PDF الخاص بك." + }, + "scannerEffect": { + "name": "تأثير الماسح الضوئي", + "subtitle": "اجعل PDF يبدو كمستند ممسوح ضوئيًا.", + "scanSettings": "إعدادات المسح", + "colorspace": "فضاء الألوان", + "gray": "رمادي", + "border": "حد", + "rotate": "تدوير", + "rotateVariance": "تباين التدوير", + "brightness": "السطوع", + "contrast": "التباين", + "blur": "ضبابية", + "noise": "تشويش", + "yellowish": "اصفرار", + "resolution": "الدقة", + "processButton": "تطبيق تأثير الماسح الضوئي" + }, + "adjustColors": { + "name": "ضبط الألوان", + "subtitle": "ضبط دقيق للسطوع والتباين والتشبع والمزيد في PDF.", + "colorSettings": "إعدادات الألوان", + "brightness": "السطوع", + "contrast": "التباين", + "saturation": "التشبع", + "hueShift": "تحويل درجة اللون", + "temperature": "درجة الحرارة", + "tint": "صبغة", + "gamma": "جاما", + "sepia": "بني داكن", + "processButton": "تطبيق تعديلات الألوان" + }, + "backgroundColor": { + "name": "لون الخلفية", + "subtitle": "تغيير لون خلفية PDF الخاص بك." + }, + "changeTextColor": { + "name": "تغيير لون النص", + "subtitle": "تغيير لون النص في PDF الخاص بك." + }, + "addStamps": { + "name": "إضافة أختام", + "subtitle": "إضافة أختام صور إلى PDF باستخدام شريط أدوات التعليقات.", + "usernameLabel": "اسم مستخدم الختم", + "usernamePlaceholder": "أدخل اسمك (للأختام)", + "usernameHint": "سيظهر هذا الاسم على الأختام التي تنشئها." + }, + "removeAnnotations": { + "name": "إزالة التعليقات", + "subtitle": "حذف التعليقات والتمييز والروابط." + }, + "pdfFormFiller": { + "name": "ملء نماذج PDF", + "subtitle": "ملء النماذج مباشرة في المتصفح. يدعم أيضًا نماذج XFA." + }, + "createPdfForm": { + "name": "إنشاء نموذج PDF", + "subtitle": "إنشاء نماذج PDF قابلة للتعبئة مع حقول نص بالسحب والإفلات." + }, + "removeBlankPages": { + "name": "إزالة الصفحات الفارغة", + "subtitle": "اكتشاف وحذف الصفحات الفارغة تلقائيًا.", + "sensitivityHint": "أعلى = أكثر صرامة، فقط الصفحات الفارغة تمامًا. أقل = يسمح بصفحات تحتوي على بعض المحتوى." + }, + "imageToPdf": { + "name": "صور إلى PDF", + "subtitle": "تحويل JPG وPNG وBMP وGIF وTIFF وPNM وPGM وPBM وPPM وPAM وJXR وJPX وJP2 وPSD وSVG وHEIC وWebP إلى PDF." + }, + "pngToPdf": { + "name": "PNG إلى PDF", + "subtitle": "إنشاء PDF من صورة PNG واحدة أو أكثر." + }, + "webpToPdf": { + "name": "WebP إلى PDF", + "subtitle": "إنشاء PDF من صورة WebP واحدة أو أكثر." + }, + "svgToPdf": { + "name": "SVG إلى PDF", + "subtitle": "إنشاء PDF من صورة SVG واحدة أو أكثر." + }, + "bmpToPdf": { + "name": "BMP إلى PDF", + "subtitle": "إنشاء PDF من صورة BMP واحدة أو أكثر." + }, + "heicToPdf": { + "name": "HEIC إلى PDF", + "subtitle": "إنشاء PDF من صورة HEIC واحدة أو أكثر." + }, + "tiffToPdf": { + "name": "TIFF إلى PDF", + "subtitle": "إنشاء PDF من صورة TIFF واحدة أو أكثر." + }, + "textToPdf": { + "name": "نص إلى PDF", + "subtitle": "تحويل ملف نص عادي إلى PDF." + }, + "jsonToPdf": { + "name": "JSON إلى PDF", + "subtitle": "تحويل ملفات JSON إلى تنسيق PDF." + }, + "pdfToJpg": { + "name": "PDF إلى JPG", + "subtitle": "تحويل كل صفحة PDF إلى صورة JPG." + }, + "pdfToPng": { + "name": "PDF إلى PNG", + "subtitle": "تحويل كل صفحة PDF إلى صورة PNG." + }, + "pdfToWebp": { + "name": "PDF إلى WebP", + "subtitle": "تحويل كل صفحة PDF إلى صورة WebP." + }, + "pdfToBmp": { + "name": "PDF إلى BMP", + "subtitle": "تحويل كل صفحة PDF إلى صورة BMP." + }, + "pdfToTiff": { + "name": "PDF إلى TIFF", + "subtitle": "تحويل كل صفحة PDF إلى صورة TIFF." + }, + "pdfToGreyscale": { + "name": "PDF إلى تدرج الرمادي", + "subtitle": "تحويل جميع الألوان إلى أبيض وأسود." + }, + "pdfToJson": { + "name": "PDF إلى JSON", + "subtitle": "تحويل ملفات PDF إلى تنسيق JSON." + }, + "ocrPdf": { + "name": "التعرف الضوئي على PDF", + "subtitle": "اجعل PDF قابلاً للبحث والنسخ." + }, + "alternateMix": { + "name": "تبديل ومزج الصفحات", + "subtitle": "دمج ملفات PDF بتبديل الصفحات من كل ملف. يحافظ على الإشارات المرجعية." + }, + "addAttachments": { + "name": "إضافة مرفقات", + "subtitle": "تضمين ملف واحد أو أكثر في PDF الخاص بك." + }, + "extractAttachments": { + "name": "استخراج المرفقات", + "subtitle": "استخراج جميع الملفات المضمنة من PDF كملف ZIP." + }, + "editAttachments": { + "name": "تعديل المرفقات", + "subtitle": "عرض أو إزالة المرفقات في PDF الخاص بك." + }, + "dividePages": { + "name": "تقسيم الصفحات", + "subtitle": "تقسيم الصفحات أفقيًا أو عموديًا." + }, + "addBlankPage": { + "name": "إضافة صفحة فارغة", + "subtitle": "إدراج صفحة فارغة في أي مكان في PDF الخاص بك." + }, + "reversePages": { + "name": "عكس الصفحات", + "subtitle": "عكس ترتيب جميع الصفحات في مستندك." + }, + "rotatePdf": { + "name": "تدوير PDF", + "subtitle": "تدوير الصفحات بزيادات 90 درجة." + }, + "rotateCustom": { + "name": "تدوير بزاوية مخصصة", + "subtitle": "تدوير الصفحات بأي زاوية مخصصة." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "ترتيب عدة صفحات على ورقة واحدة." + }, + "combineToSinglePage": { + "name": "دمج في صفحة واحدة", + "subtitle": "دمج جميع الصفحات في تمرير مستمر واحد." + }, + "viewMetadata": { + "name": "عرض البيانات الوصفية", + "subtitle": "فحص الخصائص المخفية لملف PDF الخاص بك." + }, + "editMetadata": { + "name": "تعديل البيانات الوصفية", + "subtitle": "تغيير المؤلف والعنوان والخصائص الأخرى." + }, + "pdfsToZip": { + "name": "PDF إلى ZIP", + "subtitle": "تجميع عدة ملفات PDF في أرشيف ZIP." + }, + "comparePdfs": { + "name": "مقارنة ملفات PDF", + "subtitle": "مقارنة ملفي PDF جنبًا إلى جنب." + }, + "posterizePdf": { + "name": "تقسيم PDF إلى ملصقات", + "subtitle": "تقسيم صفحة كبيرة إلى عدة صفحات أصغر." + }, + "fixPageSize": { + "name": "توحيد حجم الصفحة", + "subtitle": "توحيد جميع الصفحات إلى حجم موحد." + }, + "linearizePdf": { + "name": "تحسين PDF للويب", + "subtitle": "تحسين PDF للعرض السريع على الويب." + }, + "pageDimensions": { + "name": "أبعاد الصفحة", + "subtitle": "تحليل حجم الصفحة والاتجاه والوحدات." + }, + "removeRestrictions": { + "name": "إزالة القيود", + "subtitle": "إزالة حماية كلمة المرور وقيود الأمان المرتبطة بملفات PDF الموقعة رقميًا." + }, + "repairPdf": { + "name": "إصلاح PDF", + "subtitle": "استرداد البيانات من ملفات PDF التالفة أو المعطوبة." + }, + "encryptPdf": { + "name": "تشفير PDF", + "subtitle": "قفل PDF بإضافة كلمة مرور." + }, + "sanitizePdf": { + "name": "تنظيف PDF", + "subtitle": "إزالة البيانات الوصفية والتعليقات والبرامج النصية والمزيد." + }, + "decryptPdf": { + "name": "فك تشفير PDF", + "subtitle": "فتح PDF بإزالة حماية كلمة المرور." + }, + "flattenPdf": { + "name": "تسطيح PDF", + "subtitle": "جعل حقول النماذج والتعليقات غير قابلة للتعديل." + }, + "removeMetadata": { + "name": "إزالة البيانات الوصفية", + "subtitle": "حذف البيانات المخفية من PDF الخاص بك." + }, + "changePermissions": { + "name": "تغيير الأذونات", + "subtitle": "تعيين أو تغيير أذونات المستخدم على PDF." + }, + "odtToPdf": { + "name": "ODT إلى PDF", + "subtitle": "تحويل ملفات OpenDocument النصية إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات ODT", + "convertButton": "تحويل إلى PDF" + }, + "csvToPdf": { + "name": "CSV إلى PDF", + "subtitle": "تحويل ملفات جداول CSV إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات CSV", + "convertButton": "تحويل إلى PDF" + }, + "rtfToPdf": { + "name": "RTF إلى PDF", + "subtitle": "تحويل مستندات Rich Text إلى PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات RTF", + "convertButton": "تحويل إلى PDF" + }, + "wordToPdf": { + "name": "Word إلى PDF", + "subtitle": "تحويل مستندات Word (DOCX، DOC، ODT، RTF) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات DOCX، DOC، ODT، RTF", + "convertButton": "تحويل إلى PDF" + }, + "excelToPdf": { + "name": "Excel إلى PDF", + "subtitle": "تحويل جداول Excel (XLSX، XLS، ODS، CSV) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات XLSX، XLS، ODS، CSV", + "convertButton": "تحويل إلى PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint إلى PDF", + "subtitle": "تحويل عروض PowerPoint (PPTX، PPT، ODP) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات PPTX، PPT، ODP", + "convertButton": "تحويل إلى PDF" + }, + "markdownToPdf": { + "name": "Markdown إلى PDF", + "subtitle": "اكتب أو الصق Markdown وصدّره كـ PDF منسق بشكل جميل.", + "paneMarkdown": "Markdown", + "panePreview": "معاينة", + "btnUpload": "رفع", + "btnSyncScroll": "مزامنة التمرير", + "btnSettings": "الإعدادات", + "btnExportPdf": "تصدير PDF", + "settingsTitle": "إعدادات Markdown", + "settingsPreset": "إعداد مسبق", + "presetDefault": "افتراضي (شبيه بـ GFM)", + "presetCommonmark": "CommonMark (صارم)", + "presetZero": "أدنى (بدون ميزات)", + "settingsOptions": "خيارات Markdown", + "optAllowHtml": "السماح بوسوم HTML", + "optBreaks": "تحويل أسطر جديدة إلى
", + "optLinkify": "تحويل الروابط تلقائيًا", + "optTypographer": "المطبعي (علامات اقتباس ذكية، إلخ)" + }, + "pdfBooklet": { + "name": "كتيّب PDF", + "subtitle": "إعادة ترتيب الصفحات لطباعة كتيّب مزدوج الوجه. اطوِ ودبّس لإنشاء كتيّب.", + "howItWorks": "كيف يعمل:", + "step1": "ارفع ملف PDF.", + "step2": "سيتم إعادة ترتيب الصفحات بترتيب الكتيّب.", + "step3": "اطبع مزدوج الوجه، اقلب على الحافة القصيرة، اطوِ ودبّس.", + "paperSize": "حجم الورق", + "orientation": "الاتجاه", + "portrait": "عمودي", + "landscape": "أفقي", + "pagesPerSheet": "صفحات لكل ورقة", + "createBooklet": "إنشاء كتيّب", + "processing": "جارٍ المعالجة...", + "pageCount": "سيتم تعبئة عدد الصفحات إلى مضاعف 4 إذا لزم الأمر." + }, + "xpsToPdf": { + "name": "XPS إلى PDF", + "subtitle": "تحويل مستندات XPS/OXPS إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات XPS، OXPS", + "convertButton": "تحويل إلى PDF" + }, + "mobiToPdf": { + "name": "MOBI إلى PDF", + "subtitle": "تحويل كتب MOBI الإلكترونية إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات MOBI", + "convertButton": "تحويل إلى PDF" + }, + "epubToPdf": { + "name": "EPUB إلى PDF", + "subtitle": "تحويل كتب EPUB الإلكترونية إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات EPUB", + "convertButton": "تحويل إلى PDF" + }, + "fb2ToPdf": { + "name": "FB2 إلى PDF", + "subtitle": "تحويل كتب FictionBook (FB2) الإلكترونية إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات FB2", + "convertButton": "تحويل إلى PDF" + }, + "cbzToPdf": { + "name": "CBZ إلى PDF", + "subtitle": "تحويل أرشيفات القصص المصورة (CBZ/CBR) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات CBZ، CBR", + "convertButton": "تحويل إلى PDF" + }, + "wpdToPdf": { + "name": "WPD إلى PDF", + "subtitle": "تحويل مستندات WordPerfect (WPD) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات WPD", + "convertButton": "تحويل إلى PDF" + }, + "wpsToPdf": { + "name": "WPS إلى PDF", + "subtitle": "تحويل مستندات WPS Office إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات WPS", + "convertButton": "تحويل إلى PDF" + }, + "xmlToPdf": { + "name": "XML إلى PDF", + "subtitle": "تحويل مستندات XML إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات XML", + "convertButton": "تحويل إلى PDF" + }, + "pagesToPdf": { + "name": "Pages إلى PDF", + "subtitle": "تحويل مستندات Apple Pages إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات Pages", + "convertButton": "تحويل إلى PDF" + }, + "odgToPdf": { + "name": "ODG إلى PDF", + "subtitle": "تحويل ملفات OpenDocument Graphics (ODG) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات ODG", + "convertButton": "تحويل إلى PDF" + }, + "odsToPdf": { + "name": "ODS إلى PDF", + "subtitle": "تحويل جداول OpenDocument (ODS) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات ODS", + "convertButton": "تحويل إلى PDF" + }, + "odpToPdf": { + "name": "ODP إلى PDF", + "subtitle": "تحويل عروض OpenDocument (ODP) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات ODP", + "convertButton": "تحويل إلى PDF" + }, + "pubToPdf": { + "name": "PUB إلى PDF", + "subtitle": "تحويل ملفات Microsoft Publisher (PUB) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات PUB", + "convertButton": "تحويل إلى PDF" + }, + "vsdToPdf": { + "name": "VSD إلى PDF", + "subtitle": "تحويل ملفات Microsoft Visio (VSD، VSDX) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات VSD، VSDX", + "convertButton": "تحويل إلى PDF" + }, + "psdToPdf": { + "name": "PSD إلى PDF", + "subtitle": "تحويل ملفات Adobe Photoshop (PSD) إلى تنسيق PDF. يدعم عدة ملفات.", + "acceptedFormats": "ملفات PSD", + "convertButton": "تحويل إلى PDF" + }, + "pdfToSvg": { + "name": "PDF إلى SVG", + "subtitle": "تحويل كل صفحة من ملف PDF إلى رسومات متجهة قابلة للتوسع (SVG) بجودة مثالية بأي حجم." + }, + "extractTables": { + "name": "استخراج جداول PDF", + "subtitle": "استخراج الجداول من ملفات PDF وتصديرها كـ CSV أو JSON أو Markdown." + }, + "pdfToCsv": { + "name": "PDF إلى CSV", + "subtitle": "استخراج الجداول من PDF وتحويلها إلى تنسيق CSV." + }, + "pdfToExcel": { + "name": "PDF إلى Excel", + "subtitle": "استخراج الجداول من PDF وتحويلها إلى تنسيق Excel (XLSX)." + }, + "pdfToText": { + "name": "PDF إلى نص", + "subtitle": "استخراج النص من ملفات PDF وحفظه كنص عادي (.txt). يدعم عدة ملفات.", + "note": "تعمل هذه الأداة فقط مع ملفات PDF المنشأة رقميًا. للمستندات الممسوحة ضوئيًا أو ملفات PDF المبنية على الصور، استخدم أداة التعرف الضوئي.", + "convertButton": "استخراج النص" + }, + "digitalSignPdf": { + "name": "توقيع رقمي لـ PDF", + "pageTitle": "توقيع رقمي لـ PDF - إضافة توقيع تشفيري | BentoPDF", + "subtitle": "إضافة توقيع رقمي تشفيري إلى PDF باستخدام شهادات X.509. يدعم تنسيقات PKCS#12 (.pfx، .p12) وPEM. مفتاحك الخاص لا يغادر متصفحك أبدًا.", + "certificateSection": "الشهادة", + "uploadCert": "رفع شهادة (.pfx، .p12)", + "certPassword": "كلمة مرور الشهادة", + "certPasswordPlaceholder": "أدخل كلمة مرور الشهادة", + "certInfo": "معلومات الشهادة", + "certSubject": "الموضوع", + "certIssuer": "المُصدر", + "certValidity": "صالحة", + "signatureDetails": "تفاصيل التوقيع (اختياري)", + "reason": "السبب", + "reasonPlaceholder": "مثلاً، أوافق على هذا المستند", + "location": "الموقع", + "locationPlaceholder": "مثلاً، نيويورك، الولايات المتحدة", + "contactInfo": "معلومات الاتصال", + "contactPlaceholder": "مثلاً، email@example.com", + "applySignature": "تطبيق التوقيع الرقمي", + "successMessage": "تم توقيع PDF بنجاح! يمكن التحقق من التوقيع في أي قارئ PDF." + }, + "validateSignaturePdf": { + "name": "التحقق من توقيع PDF", + "pageTitle": "التحقق من توقيع PDF - التحقق من التوقيعات الرقمية | BentoPDF", + "subtitle": "التحقق من التوقيعات الرقمية في ملفات PDF. تحقق من صلاحية الشهادة، واعرض تفاصيل الموقّع، وتأكد من سلامة المستند. تتم جميع المعالجة في متصفحك." + }, + "emailToPdf": { + "name": "بريد إلكتروني إلى PDF", + "subtitle": "تحويل ملفات البريد الإلكتروني (EML، MSG) إلى تنسيق PDF. يدعم تصديرات Outlook وتنسيقات البريد القياسية.", + "acceptedFormats": "ملفات EML، MSG", + "convertButton": "تحويل إلى PDF" + }, + "fontToOutline": { + "name": "تحويل الخطوط إلى مخططات", + "subtitle": "تحويل جميع الخطوط إلى مخططات متجهة لعرض متسق على جميع الأجهزة." + }, + "deskewPdf": { + "name": "تصحيح انحراف PDF", + "subtitle": "تقويم الصفحات الممسوحة ضوئيًا المائلة تلقائيًا باستخدام OpenCV." + }, + "pdfToWord": { + "name": "PDF إلى Word", + "subtitle": "تحويل ملفات PDF إلى مستندات Word قابلة للتعديل." + }, + "extractImages": { + "name": "استخراج الصور", + "subtitle": "استخراج جميع الصور المضمنة من ملفات PDF." + }, + "pdfToMarkdown": { + "name": "PDF إلى Markdown", + "subtitle": "تحويل نصوص وجداول PDF إلى تنسيق Markdown." + }, + "preparePdfForAi": { + "name": "تحضير PDF للذكاء الاصطناعي", + "subtitle": "استخراج محتوى PDF كـ JSON بتنسيق LlamaIndex لخطوط RAG/LLM." + }, + "pdfOcg": { + "name": "طبقات PDF OCG", + "subtitle": "عرض وتبديل وإضافة وحذف طبقات OCG في PDF." + }, + "pdfToPdfa": { + "name": "PDF إلى PDF/A", + "subtitle": "تحويل PDF إلى PDF/A للأرشفة طويلة المدى." + }, + "rasterizePdf": { + "name": "تحويل PDF إلى صور نقطية", + "subtitle": "تحويل PDF إلى PDF قائم على الصور. تسطيح الطبقات وإزالة النص القابل للتحديد." + }, + "pdfWorkflow": { + "name": "منشئ سير عمل PDF", + "subtitle": "بناء خطوط معالجة PDF مخصصة باستخدام محرر عقد مرئي.", + "nodes": "العُقد", + "searchNodes": "البحث في العُقد...", + "run": "تشغيل", + "clear": "مسح", + "save": "حفظ", + "load": "تحميل", + "export": "تصدير", + "import": "استيراد", + "ready": "جاهز", + "settings": "الإعدادات", + "processing": "جارٍ المعالجة...", + "saveTemplate": "حفظ القالب", + "templateName": "اسم القالب", + "templatePlaceholder": "مثلاً سير عمل الفواتير", + "cancel": "إلغاء", + "loadTemplate": "تحميل القالب", + "noTemplates": "لا توجد قوالب محفوظة بعد.", + "ok": "حسنًا", + "workflowCompleted": "اكتمل سير العمل", + "errorDuringExecution": "خطأ أثناء التنفيذ", + "addNodeError": "أضف عقدة واحدة على الأقل لتشغيل سير العمل.", + "needInputOutput": "يحتاج سير العمل إلى عقدة إدخال واحدة وعقدة إخراج واحدة على الأقل للتشغيل.", + "enterName": "يرجى إدخال اسم.", + "templateExists": "يوجد قالب بهذا الاسم بالفعل.", + "templateSaved": "تم حفظ القالب \"{{name}}\".", + "templateLoaded": "تم تحميل القالب \"{{name}}\".", + "failedLoadTemplate": "فشل تحميل القالب.", + "noSettings": "لا توجد إعدادات قابلة للتخصيص لهذه العقدة.", + "advancedSettings": "إعدادات متقدمة" + } +} diff --git a/public/locales/be/common.json b/public/locales/be/common.json new file mode 100644 index 0000000..3161429 --- /dev/null +++ b/public/locales/be/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "Галоўная", + "about": "Пра нас", + "contact": "Кантакты", + "licensing": "Ліцэнзія", + "allTools": "Усе інструменты", + "openMainMenu": "Адкрыць галоўнае меню", + "language": "Мова" + }, + "donation": { + "message": "Падабаецца BentoPDF? Падтрымайце, каб ён заставаўся бясплатным і з адкрытым зыходным кодам!", + "button": "Ахвяраваць" + }, + "hero": { + "title": "Набор", + "pdfToolkit": "інструментаў PDF", + "builtForPrivacy": "для максімальнай прыватнасці", + "noSignups": "Без рэгістрацыі", + "unlimitedUse": "Без абмежаванняў", + "worksOffline": "Працуе па-за сеткай", + "startUsing": "Пачаць" + }, + "usedBy": { + "title": "Выкарыстоўваецца кампаніямі і людзьмі, якія працуюць у" + }, + "features": { + "title": "Чаму выбіраюць", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "Без рэгістрацыі", + "description": "Пачынайце адразу, без уліковага запісу і электроннай пошты." + }, + "noUploads": { + "title": "Без запампоўванняў", + "description": "100% на баку кліента, вашы файлы ніколі не пакідаюць прыладу." + }, + "foreverFree": { + "title": "Заўсёды бясплатна", + "description": "Усе інструменты, ніякіх пробных перыядаў і платных бар'ераў." + }, + "noLimits": { + "title": "Без лімітаў", + "description": "Карыстайцеся колькі заўгодна, без схаваных абмежаванняў." + }, + "batchProcessing": { + "title": "Пакетная апрацоўка", + "description": "Апрацоўвайце неабмежаваную колькасць PDF за адзін раз." + }, + "lightningFast": { + "title": "З хуткасцю маланкі", + "description": "Апрацоўвайце PDF імгненна, без чакання і затрымак." + } + }, + "tools": { + "title": "Пачніце працу з", + "toolsLabel": "інструментамі", + "subtitle": "Націсніце на інструмент, каб адкрыць запампоўшчык", + "searchPlaceholder": "Пошук (напр., \"выдаліць\", \"сціснуць\"...)", + "backToTools": "Назад да інструментаў", + "firstLoadNotice": "Першае адкрыццё займае крыху часу, пакуль загружаецца рухавік канвертацыі. Потым усё будзе адкрывацца імгненна." + }, + "upload": { + "clickToSelect": "Націсніце, каб выбраць файл,", + "orDragAndDrop": "або перацягніце сюды", + "pdfOrImages": "PDF або відарысы", + "filesNeverLeave": "Вашы файлы ніколі не пакідаюць прыладу.", + "addMore": "Дадаць больш файлаў", + "clearAll": "Ачысціць усё", + "clearFiles": "Ачысціць файлы", + "hints": { + "singlePdf": "Адзін файл PDF", + "pdfFile": "Файл PDF", + "multiplePdfs2": "Некалькі файлаў PDF (мінімум 2)", + "bmpImages": "Відарысы BMP", + "oneOrMorePdfs": "Адзін або некалькі файлаў PDF", + "pdfDocuments": "Дакументы PDF", + "oneOrMoreCsv": "Адзін або некалькі файлаў CSV", + "multiplePdfsSupported": "Падтрымліваецца некалькі файлаў PDF", + "singleOrMultiplePdfs": "Падтрымліваецца адзін або некалькі файлаў PDF", + "singlePdfFile": "Адзін файл PDF", + "pdfWithForms": "Файл PDF з палямі формы", + "heicImages": "Відарысы HEIC/HEIF", + "jpgImages": "Відарысы JPG, JPEG, JP2, JPX", + "pdfsOrImages": "PDF або відарысы", + "oneOrMoreOdt": "Адзін або некалькі файлаў ODT", + "singlePdfOnly": "Толькі адзін файл PDF", + "pdfFiles": "Файлы PDF", + "multiplePdfs": "Некалькі файлаў PDF", + "pngImages": "Відарысы PNG", + "pdfFilesOneOrMore": "Файлы PDF (адзін або некалькі)", + "oneOrMoreRtf": "Адзін або некалькі файлаў RTF", + "svgGraphics": "SVG-графіка", + "tiffImages": "Відарысы TIFF", + "webpImages": "Відарысы WebP" + } + }, + "loader": { + "processing": "Апрацоўка..." + }, + "alert": { + "title": "Апавяшчэнне", + "ok": "ОК" + }, + "preview": { + "title": "Папярэдні прагляд дакумента", + "downloadAsPdf": "Спампаваць як PDF", + "close": "Закрыць" + }, + "settings": { + "title": "Налады", + "shortcuts": "Спалучэнні клавіш", + "preferences": "Параметры", + "displayPreferences": "Параметры адлюстравання", + "searchShortcuts": "Пошук спалучэнняў...", + "shortcutsInfo": "Утрымлівайце клавішы, каб задаць спалучэнне. Змены захоўваюцца аўтаматычна.", + "shortcutsWarning": "⚠️ Пазбягайце стандартных спалучэнняў браўзера (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N і інш.), бо яны могуць працаваць ненадзейна.", + "import": "Імпарт", + "export": "Экспарт", + "resetToDefaults": "Скінуць да прадвызначаных", + "fullWidthMode": "На ўсю шырыню", + "fullWidthDescription": "Выкарыстоўваць усю шырыню экрана для ўсіх інструментаў замест цэнтраванага кантэйнера", + "settingsAutoSaved": "Налады захоўваюцца аўтаматычна", + "clickToSet": "Задаць", + "pressKeys": "Націсніце...", + "warnings": { + "alreadyInUse": "Спалучэнне клавіш ужо выкарыстоўваецца", + "assignedTo": "ужо прызначана для:", + "chooseDifferent": "Выберыце іншае спалучэнне.", + "reserved": "Папярэджанне аб зарэзерваваным спалучэнні клавіш", + "commonlyUsed": "часта выкарыстоўваецца для:", + "unreliable": "Гэта спалучэнне клавіш можа працаваць ненадзейна або канфліктаваць з паводзінамі браўзера/сістэмы.", + "useAnyway": "Усё роўна выкарыстоўваць?", + "resetTitle": "Скінуць спалучэнні", + "resetMessage": "Вы ўпэўнены, што хочаце скінуць усе спалучэнні да прадвызначаных значэнняў?

Гэта дзеянне нельга адрабіць.", + "importSuccessTitle": "Імпарт паспяховы", + "importSuccessMessage": "Спалучэнні клавіш паспяхова імпартаваныя!", + "importFailTitle": "Не ўдалося імпартаваць", + "importFailMessage": "Не атрымалася імпартаваць спалучэнні клавіш. Памылковы фармат файла." + } + }, + "warning": { + "title": "Папярэджанне", + "cancel": "Скасаваць", + "proceed": "Працягнуць" + }, + "compliance": { + "title": "Вашы даныя ніколі не пакідаюць прыладу", + "weKeep": "Мы захоўваем", + "yourInfoSafe": "вашу інфармацыю ў бяспецы", + "byFollowingStandards": "паводле глабальных стандартаў.", + "processingLocal": "Уся апрацоўка адбываецца лакальна на вашай прыладзе.", + "gdpr": { + "title": "Адпаведнасць GDPR", + "description": "Ахоўвае персанальныя даныя і прыватнасць людзей у Еўрапейскім Саюзе." + }, + "ccpa": { + "title": "Адпаведнасць CCPA", + "description": "Дае жыхарам Каліфорніі правы ведаць, як збіраецца, выкарыстоўваецца і перадаецца іх персанальная інфармацыя." + }, + "hipaa": { + "title": "Адпаведнасць HIPAA", + "description": "Усталёўвае меры бяспекі для апрацоўкі канфідэнцыйнай медыцынскай інфармацыі ў сістэме аховы здароўя ЗША." + } + }, + "faq": { + "title": "Частыя", + "questions": "пытанні", + "isFree": { + "question": "BentoPDF сапраўды бясплатны?", + "answer": "Так, абсалютна. Усе інструменты BentoPDF на 100% бясплатныя для выкарыстання, без лімітаў файлаў, без рэгістрацыі і без вадзяных знакаў. Мы верым, што кожны мае права доступу да простых і магутных інструментаў PDF без платных бар'ераў." + }, + "areFilesSecure": { + "question": "Ці ў бяспецы мае файлы? Дзе яны апрацоўваюцца?", + "answer": "Вашы файлы ў бяспецы настолькі, наколькі гэта магчыма, бо яны ніколі не пакідаюць ваш камп'ютар. Уся апрацоўка адбываецца непасрэдна ў вашым браўзеры (на баку кліента). Мы ніколі не запампоўваем вашы файлы на сервер, таму вы захоўваеце поўную прыватнасць і кантроль над сваімі дакументамі." + }, + "platforms": { + "question": "Ці працуе BentoPDF на Mac, Windows і мабільных прыладах?", + "answer": "Так! Паколькі BentoPDF працуе выключна ў браўзеры, ён працуе на любой аперацыйнай сістэме з сучасным вэб-браўзерам, уключаючы Windows, macOS, Linux, iOS і Android." + }, + "gdprCompliant": { + "question": "Ці адпавядае BentoPDF GDPR?", + "answer": "Так. BentoPDF цалкам адпавядае GDPR. Паколькі ўся апрацоўка файлаў адбываецца лакальна ў браўзеры і мы ніколі не збіраем і не перадаём вашы файлы на сервер, мы не маем доступу да вашых даных. Гэта гарантуе, што вы заўсёды кантралюеце свае дакументы." + }, + "dataStorage": { + "question": "Вы захоўваеце або адсочваеце мае файлы?", + "answer": "Не. Мы ніколі не захоўваем, не адсочваем і не запісваем звесткі пра вашы файлы. Усё, што вы робіце ў BentoPDF, адбываецца ў памяці вашага браўзера і знікае пасля закрыцця старонкі. Няма запампоўванняў, няма гісторыі і няма сервераў." + }, + "different": { + "question": "Чым BentoPDF адрозніваецца ад іншых інструментаў PDF?", + "answer": "Звычайна інструменты PDF запампоўваюць вашы файлы на сервер для апрацоўкі. BentoPDF ніколі так не робіць. Мы выкарыстоўваем бяспечныя сучасныя вэб-тэхналогіі, каб апрацоўваць вашы файлы непасрэдна ў браўзеры. Гэта азначае большую хуткасць, лепшую прыватнасць і поўны спакой." + }, + "browserBased": { + "question": "Чаму апрацоўка ў браўзеры - гэта бяспечна?", + "answer": "Працуючы цалкам у браўзеры, BentoPDF гарантуе, што вашы файлы ніколі не пакінуць вашу прыладу. Гэта ліквідуе рызыкі ўзлому сервераў, уцечак даных або несанкцыянаванага доступу. Вашы файлы застаюцца вашымі — заўсёды." + }, + "analytics": { + "question": "Вы выкарыстоўваеце cookies або аналітыку, каб сачыць за мной?", + "answer": "Мы дбаем пра вашу прыватнасць. BentoPDF не адсочвае персанальную інфармацыю. Мы выкарыстоўваем Simple Analytics, толькі каб бачыць ананімную статыстыку наведванняў. Гэта значыць, мы ведаем, колькі карыстальнікаў наведвае наш сайт, але ніколі не ведаем, хто вы. Simple Analytics цалкам адпавядае GDPR і шануе вашу прыватнасць." + }, + "sectionTitle": "Частыя пытанні" + }, + "testimonials": { + "title": "Што кажуць", + "users": "нашы карыстальнікі", + "say": " " + }, + "support": { + "title": "Спадабалася мая праца?", + "description": "BentoPDF — гэта праект, створаны на энтузіязме з мэтай, каб кожны меў бясплатны, прыватны і магутны набор інструментаў PDF. Калі ён прыносіць вам карысць, падтрымайце распрацоўку. Дапамагае кожная кава!", + "buyMeCoffee": "Пачастуйце мяне кавай" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Усе правы абаронены.", + "version": "Версія", + "company": "Кампанія", + "aboutUs": "Пра нас", + "faqLink": "Частыя пытанні", + "contactUs": "Звязацца з намі", + "legal": "Прававая інфармацыя", + "termsAndConditions": "Умовы выкарыстання", + "privacyPolicy": "Палітыка прыватнасці", + "followUs": "Сачыце за намі" + }, + "merge": { + "title": "Аб'яднаць PDF", + "description": "Аб'ядноўвайце цэлыя файлы або выбірайце пэўныя старонкі, з якіх будзе складацца новы дакумент.", + "fileMode": "Рэжым файлаў", + "pageMode": "Рэжым старонак", + "howItWorks": "Як гэта працуе:", + "fileModeInstructions": [ + "Націсніце і перацягніце значок, каб змяніць парадак файлаў.", + "У полі \"Старонкі\" для кожнага файла можна задаць дыяпазоны (напр., \"1-3, 5\"), каб аб'яднаць толькі гэтыя старонкі.", + "Пакіньце поле \"Старонкі\" пустым, каб уключыць усе старонкі гэтага файла." + ], + "pageModeInstructions": [ + "Усе старонкі з запампаваных PDF паказаны ніжэй.", + "Проста перацягніце мініяцюры старонак, каб задаць патрэбны парадак для новага файла." + ], + "mergePdfs": "Аб'яднаць PDF" + }, + "common": { + "page": "Старонка", + "pages": "Старонкі", + "of": "з", + "download": "Спампаваць", + "cancel": "Скасаваць", + "save": "Захаваць", + "delete": "Выдаліць", + "edit": "Рэдагаваць", + "add": "Дадаць", + "remove": "Выдаліць", + "loading": "Загрузка...", + "error": "Памылка", + "success": "Поспех", + "file": "Файл", + "files": "Файлы", + "close": "Зачыніць" + }, + "about": { + "hero": { + "title": "Мы лічым, што інструменты PDF павінны быць", + "subtitle": "хуткімі, прыватнымі і бясплатнымі.", + "noCompromises": "Без кампрамісаў." + }, + "mission": { + "title": "Наша місія", + "description": "Даць найбольш поўны набор інструментаў PDF, які шануе вашу прыватнасць і ніколі не патрабуе аплаты. Мы лічым, што неабходныя інструменты для дакументаў павінны быць даступнымі ўсім, паўсюль, без бар'ераў." + }, + "philosophy": { + "label": "Наша асноўная філасофія", + "title": "Прыватнасць на першым месцы. Заўсёды.", + "description": "У эпоху, калі даныя — гэта тавар, мы выбіраем іншы шлях. Уся апрацоўка інструментаў BentoPDF адбываецца лакальна ў вашым браўзеры. Гэта значыць, што вашы файлы ніколі не трапляюць на нашы серверы, мы не бачым вашых дакументаў і не адсочваем, што вы робіце. Вашы дакументы застаюцца цалкам і безумоўна прыватнымі. Гэта не проста функцыя — гэта наш падмурак." + }, + "whyBentopdf": { + "title": "Чаму", + "speed": { + "title": "Створаны для хуткасці", + "description": "Не трэба чакаць запампоўвання на сервер. Апрацоўваючы файлы непасрэдна ў вашым браўзеры з дапамогай сучасных вэб-тэхналогій, такіх як WebAssembly, мы прапануем непараўнальную хуткасць для ўсіх нашых інструментаў." + }, + "free": { + "title": "Цалкам бясплатна", + "description": "Без пробных версій, падпісак, схаваных плацяжоў і без \"прэміум\" функцый. Мы верым, што магутныя інструменты PDF павінны быць грамадскай карысцю, а не цэнтрам прыбытку." + }, + "noAccount": { + "title": "Не патрэбны ўліковы запіс", + "description": "Пачынайце выкарыстоўваць любы інструмент адразу. Нам не патрэбныя ваша электронная пошта, пароль або асабістыя даныя. Працоўны працэс павінен быць гладкім і ананімным." + }, + "openSource": { + "title": "Дух адкрытага кода", + "description": "Задуманы і створаны празрыстым. Мы выкарыстоўваем выдатныя бібліятэкі з адкрытым зыходным кодам, такія як PDF-lib і PDF.js, і верым у намаганні супольнасці зрабіць магутныя інструменты даступнымі ўсім." + } + }, + "cta": { + "title": "Гатовы пачаць?", + "description": "Далучайцеся да тысяч карыстальнікаў, якія давяраюць BentoPDF свае штодзённыя патрэбы з дакументамі. Адчуйце розніцу, якую даюць прыватнасць і прадукцыйнасць.", + "button": "Паглядзець усе інструменты" + } + }, + "contact": { + "title": "Звязацца з намі", + "subtitle": "Мы будзем рады пачуць ваша меркаванне. Калі ў вас ёсць пытанне, водгук або прапанова функцыі, не саромейцеся звяртацца.", + "email": "Вы можаце напісаць нам на email:" + }, + "licensing": { + "title": "Ліцэнзія на", + "subtitle": "Выберыце ліцэнзію, якая падыходзіць вам." + }, + "multiTool": { + "uploadPdfs": "Запампаваць PDF", + "upload": "Запампаваць", + "addBlankPage": "Дадаць пустую старонку", + "edit": "Рэдагаваць:", + "undo": "Адрабіць", + "redo": "Узнавіць", + "reset": "Скінуць", + "selection": "Вылучэнне:", + "selectAll": "Вылучыць усё", + "deselectAll": "Зняць вылучэнне", + "rotate": "Паварот:", + "rotateLeft": "Налева", + "rotateRight": "Направа", + "transform": "Пераўтварэнне:", + "duplicate": "Дубляваць", + "split": "Падзяліць", + "clear": "Ачысціць:", + "delete": "Выдаліць", + "download": "Спампаваць:", + "downloadSelected": "Спампаваць выбранае", + "exportPdf": "Экспартаваць PDF", + "uploadPdfFiles": "Выбраць PDF файлы", + "dragAndDrop": "Перацягніце PDF файлы сюды або націсніце, каб выбраць", + "selectFiles": "Выбраць файлы", + "renderingPages": "Апрацоўка старонак...", + "actions": { + "duplicatePage": "Дубляваць гэту старонку", + "deletePage": "Выдаліць гэту старонку", + "insertPdf": "Уставіць PDF пасля гэтай старонкі", + "toggleSplit": "Пераключыць падзел пасля гэтай старонкі" + }, + "pleaseWait": "Пачакайце", + "pagesRendering": "Старонкі яшчэ апрацоўваюцца. Пачакайце...", + "noPagesSelected": "Старонкі не выбраны", + "selectOnePage": "Выберыце хаця б адну старонку для спампоўвання.", + "noPages": "Няма старонак", + "noPagesToExport": "Няма старонак для экспарту.", + "renderingTitle": "Апрацоўка перадпраглядаў старонак", + "errorRendering": "Не ўдалося апрацаваць мініяцюры старонак", + "error": "Памылка", + "failedToLoad": "Не ўдалося загрузіць" + }, + "howItWorks": { + "title": "Як гэта працуе", + "step1": "Націсніце або перацягніце файл, каб пачаць", + "step2": "Націсніце кнопку апрацоўкі", + "step3": "Праз імгненне захавайце апрацаваны файл" + }, + "relatedTools": { + "title": "Звязаныя інструменты PDF" + }, + "simpleMode": { + "title": "Інструменты PDF", + "subtitle": "Выберыце інструмент, каб пачаць" + } +} diff --git a/public/locales/be/tools.json b/public/locales/be/tools.json new file mode 100644 index 0000000..cd6f23a --- /dev/null +++ b/public/locales/be/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "Папулярныя інструменты", + "editAnnotate": "Рэдагаванне і анатацыі", + "convertToPdf": "Канвертацыя ў PDF", + "convertFromPdf": "Канвертацыя з PDF", + "organizeManage": "Арганізацыя і кіраванне", + "optimizeRepair": "Аптымізацыя і аднаўленне", + "securePdf": "Бяспека PDF" + }, + "pdfMultiTool": { + "name": "Мультыінструмент PDF", + "subtitle": "Аб'яднаць, Падзяліць, Арганізаваць, Выдаліць, Павярнуць, Дадаць пустыя старонкі, Выняць і Дубляваць у адзіным інтэрфейсе." + }, + "mergePdf": { + "name": "Аб'яднаць PDF", + "subtitle": "Аб'яднаць некалькі PDF у адзін файл. Закладкі захоўваюцца." + }, + "splitPdf": { + "name": "Падзяліць PDF", + "subtitle": "Выняць дыяпазон старонак у новы PDF." + }, + "compressPdf": { + "name": "Сціснуць PDF", + "subtitle": "Зменшыць памер файла PDF.", + "algorithmLabel": "Алгарытм сціскання", + "condense": "Condense (Рэкамендуецца)", + "photon": "Photon (Для PDF з вялікай колькасцю фота)", + "condenseInfo": "Condense выкарыстоўвае прасунутае сцісканне: выдаляе лішняе, аптымізуе відарысы, паднаборы шрыфтоў. Найлепш пасуе для большасці PDF.", + "photonInfo": "Photon ператварае старонкі ў відарысы. Для PDF з вялікай колькасцю фота або сканіраваных PDF.", + "photonWarning": "Папярэджанне: стане немагчыма вылучыць тэкст, і перастануць працаваць спасылкі.", + "levelLabel": "Узровень сціскання", + "light": "Лёгкі (Захаванне якасці)", + "balanced": "Збалансаваны (Рэкамендуецца)", + "aggressive": "Агрэсіўны (Меншыя файлы)", + "extreme": "Экстрэмальны (Максімальнае сцісканне)", + "grayscale": "Канвертаваць у градацыі шэрага", + "grayscaleHint": "Памяншае памер файла, выдаляючы інфармацыю пра колер", + "customSettings": "Карыстальніцкія налады", + "customSettingsHint": "Дакладная настройка параметраў сціскання:", + "outputQuality": "Якасць вываду", + "resizeImagesTo": "Змяніць памер відарысаў да", + "onlyProcessAbove": "Апрацоўваць толькі большыя за", + "removeMetadata": "Выдаліць метаданыя", + "subsetFonts": "Паднабор шрыфтоў (выдаліць сімвалы, якія не выкарыстоўваюцца)", + "removeThumbnails": "Выдаліць убудаваныя мініяцюры", + "compressButton": "Сціснуць PDF" + }, + "pdfEditor": { + "name": "Рэдактар PDF", + "subtitle": "Анатаваць, вылучыць, зацямніць, дадаць фігуры/відарысы, каментарыі, пошук і прагляд PDF." + }, + "jpgToPdf": { + "name": "JPG у PDF", + "subtitle": "Стварыць PDF з відарысаў JPG, JPEG і JPEG2000 (JP2/JPX)." + }, + "signPdf": { + "name": "Падпісаць PDF", + "subtitle": "Нарысаваць, набраць або запампаваць свой подпіс." + }, + "cropPdf": { + "name": "Абрэзаць PDF", + "subtitle": "Абрэзаць палі кожнай старонкі PDF." + }, + "extractPages": { + "name": "Выняць старонкі", + "subtitle": "Захаваць выбраныя старонкі ў новым файле." + }, + "duplicateOrganize": { + "name": "Дубляваць і арганізаваць", + "subtitle": "Дубляваць, змяніць парадак і выдаліць старонкі." + }, + "deletePages": { + "name": "Выдаліць старонкі", + "subtitle": "Выдаліць з дакумента пэўныя старонкі." + }, + "editBookmarks": { + "name": "Рэдагаваць закладкі", + "subtitle": "Дадаць, рэдагаваць, імпартаваць, выдаліць і выняць закладкі PDF." + }, + "tableOfContents": { + "name": "Змест", + "subtitle": "Стварыць з закладак PDF старонку зместу." + }, + "pageNumbers": { + "name": "Нумары старонак", + "subtitle": "Уставіць у дакумент нумары старонак." + }, + "batesNumbering": { + "name": "Нумарацыя Бейтса", + "subtitle": "Дадаць паслядоўныя нумары Бейтса да аднаго або некалькіх файлаў PDF." + }, + "addWatermark": { + "name": "Дадаць вадзяны знак", + "subtitle": "Накласці на старонкі PDF тэкст або відарыс.", + "applyToAllPages": "Прымяніць да ўсіх старонак" + }, + "headerFooter": { + "name": "Верхні і ніжні калантытул", + "subtitle": "Дадаць тэкст уверсе і ўнізе старонак." + }, + "invertColors": { + "name": "Інвертаваць колеры", + "subtitle": "Стварыць версію PDF у \"цёмнай тэме\"." + }, + "scannerEffect": { + "name": "Эфект сканера", + "subtitle": "Зрабіць PDF падобным да адсканаванага дакумента.", + "scanSettings": "Налады сканіравання", + "colorspace": "Каляровая прастора", + "gray": "Градацыі шэрага", + "border": "Рамка", + "rotate": "Паварот", + "rotateVariance": "Варыяцыя павароту", + "brightness": "Яркасць", + "contrast": "Кантраст", + "blur": "Размытасць", + "noise": "Шум", + "yellowish": "Жаўтаватасць", + "resolution": "Раздзяляльнасць", + "processButton": "Ужыць эфект сканера" + }, + "adjustColors": { + "name": "Наладзіць колеры", + "subtitle": "Наладзіць яркасць, кантраст, насычанасць і іншае ў PDF.", + "colorSettings": "Налады колеру", + "brightness": "Яркасць", + "contrast": "Кантраст", + "saturation": "Насычанасць", + "hueShift": "Зрух адцення", + "temperature": "Тэмпература", + "tint": "Адценне", + "gamma": "Гама", + "sepia": "Сэпія", + "processButton": "Ужыць рэгуліроўкі колеру" + }, + "backgroundColor": { + "name": "Колер фону", + "subtitle": "Змяніць колер фону PDF." + }, + "changeTextColor": { + "name": "Змяніць колер тэксту", + "subtitle": "Змяніць колер тэксту ў PDF." + }, + "addStamps": { + "name": "Дадаць штампы", + "subtitle": "Дадаць у PDF штампы-відарысы праз панэль анатацый.", + "usernameLabel": "Імя для штампа", + "usernamePlaceholder": "Увядзіце сваё імя (для штампаў)", + "usernameHint": "Гэта імя з'явіцца на створаных вамі штампах." + }, + "removeAnnotations": { + "name": "Выдаліць анатацыі", + "subtitle": "Выдаліць каментарыі, вылучэнні і спасылкі." + }, + "pdfFormFiller": { + "name": "Запоўніць форму PDF", + "subtitle": "Запоўніць форму непасрэдна ў браўзеры. Таксама падтрымліваюцца формы XFA." + }, + "createPdfForm": { + "name": "Стварыць форму PDF", + "subtitle": "Стварыць запаўняльныя формы PDF з тэкставымі палямі, якія можна перацягваць." + }, + "removeBlankPages": { + "name": "Выдаліць пустыя старонкі", + "subtitle": "Аўтаматычна выявіць і выдаліць пустыя старонкі.", + "sensitivityHint": "Вышэй = больш строга, толькі цалкам пустыя старонкі. Ніжэй = дапускае старонкі з нейкім змесцівам." + }, + "imageToPdf": { + "name": "Відарысы ў PDF", + "subtitle": "Канвертаваць JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP у PDF." + }, + "pngToPdf": { + "name": "PNG у PDF", + "subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў PNG." + }, + "webpToPdf": { + "name": "WebP у PDF", + "subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў WebP." + }, + "svgToPdf": { + "name": "SVG у PDF", + "subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў SVG." + }, + "bmpToPdf": { + "name": "BMP у PDF", + "subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў BMP." + }, + "heicToPdf": { + "name": "HEIC у PDF", + "subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў HEIC." + }, + "tiffToPdf": { + "name": "TIFF у PDF", + "subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў TIFF." + }, + "textToPdf": { + "name": "Тэкст у PDF", + "subtitle": "Канвертаваць звычайны тэкставы файл у PDF." + }, + "jsonToPdf": { + "name": "JSON у PDF", + "subtitle": "Канвертаваць файлы JSON у фармат PDF." + }, + "pdfToJpg": { + "name": "PDF у JPG", + "subtitle": "Канвертаваць кожную старонку PDF у відарыс JPG." + }, + "pdfToPng": { + "name": "PDF у PNG", + "subtitle": "Канвертаваць кожную старонку PDF у відарыс PNG." + }, + "pdfToWebp": { + "name": "PDF у WebP", + "subtitle": "Канвертаваць кожную старонку PDF у відарыс WebP." + }, + "pdfToBmp": { + "name": "PDF у BMP", + "subtitle": "Канвертаваць кожную старонку PDF у відарыс BMP." + }, + "pdfToTiff": { + "name": "PDF у TIFF", + "subtitle": "Канвертаваць кожную старонку PDF у відарыс TIFF." + }, + "pdfToGreyscale": { + "name": "PDF у градацыі шэрага", + "subtitle": "Канвертаваць усе колеры ў чорна-белыя." + }, + "pdfToJson": { + "name": "PDF у JSON", + "subtitle": "Канвертаваць файлы PDF у фармат JSON." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Дадаць у PDF магчымасці пошуку і капіравання." + }, + "alternateMix": { + "name": "Чаргаваць і змяшаць старонкі", + "subtitle": "Аб'яднаць PDF, чаргуючы старонкі з асобных PDF. Закладкі захоўваюцца." + }, + "addAttachments": { + "name": "Дадаць далучэнні", + "subtitle": "Убудаваць адзін або некалькі файлаў у PDF." + }, + "extractAttachments": { + "name": "Выняць далучэнні", + "subtitle": "Выняць усе ўбудаваныя файлы з PDF як ZIP." + }, + "editAttachments": { + "name": "Рэдагаваць далучэнні", + "subtitle": "Прагледзець або выдаліць далучэнні ў PDF." + }, + "dividePages": { + "name": "Падзяліць старонкі", + "subtitle": "Падзяліць старонкі гарызантальна або вертыкальна." + }, + "addBlankPage": { + "name": "Дадаць пустую старонку", + "subtitle": "Уставіць пустую старонку ў любым месцы PDF." + }, + "reversePages": { + "name": "Адваротны парадак старонак", + "subtitle": "Змяніць парадак усіх старонак у дакуменце на адваротны." + }, + "rotatePdf": { + "name": "Павярнуць PDF", + "subtitle": "Павярнуць старонкі на 90 градусаў." + }, + "rotateCustom": { + "name": "Павярнуць на зададзены вугал", + "subtitle": "Павярнуць старонкі на любы вугал." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "Размясціць некалькі старонак на адным аркушы." + }, + "combineToSinglePage": { + "name": "Аб'яднаць у адну старонку", + "subtitle": "Сшыць усе старонкі ў адну бесперапынную прагортку." + }, + "viewMetadata": { + "name": "Праглядзець метаданыя", + "subtitle": "Праглядзець схаваныя ўласцівасці PDF." + }, + "editMetadata": { + "name": "Рэдагаваць метаданыя", + "subtitle": "Змяніць аўтара, назву і іншыя ўласцівасці." + }, + "pdfsToZip": { + "name": "PDF у ZIP", + "subtitle": "Запакаваць некалькі файлаў PDF у ZIP-архіў." + }, + "comparePdfs": { + "name": "Параўнаць PDF", + "subtitle": "Параўнаць два PDF побач." + }, + "posterizePdf": { + "name": "Пераўтварыць у постэр", + "subtitle": "Разбіць вялікую старонку на некалькі меншых." + }, + "fixPageSize": { + "name": "Уніфікаваць памер старонак", + "subtitle": "Уніфікаваць памер усіх старонак." + }, + "linearizePdf": { + "name": "Зрабіць лінейны PDF", + "subtitle": "Аптымізаваць PDF для хуткага прагляду ў інтэрнэце." + }, + "pageDimensions": { + "name": "Памеры старонкі", + "subtitle": "Аналізаваць памер старонкі, арыентацыю і адзінкі." + }, + "removeRestrictions": { + "name": "Выдаліць абмежаванні", + "subtitle": "Выдаліць ахову паролем і абмежаванні бяспекі, звязаныя з лічбавымі подпісамі PDF." + }, + "repairPdf": { + "name": "Аднавіць PDF", + "subtitle": "Аднавіць даныя з пашкоджаных файлаў PDF." + }, + "encryptPdf": { + "name": "Зашыфраваць PDF", + "subtitle": "Заблакіраваць PDF, дадаўшы пароль." + }, + "sanitizePdf": { + "name": "Ачысціць PDF", + "subtitle": "Выдаліць метаданыя, анатацыі, скрыпты і іншае." + }, + "decryptPdf": { + "name": "Расшыфраваць PDF", + "subtitle": "Разблакіраваць PDF, выдаліўшы пароль." + }, + "flattenPdf": { + "name": "Звесці PDF", + "subtitle": "Зрабіць палі формы і анатацыі толькі для чытання." + }, + "removeMetadata": { + "name": "Выдаліць метаданыя", + "subtitle": "Выдаліць схаваныя даныя з PDF." + }, + "changePermissions": { + "name": "Змяніць дазволы", + "subtitle": "Задаць або змяніць правы карыстальнікаў для PDF." + }, + "odtToPdf": { + "name": "ODT у PDF", + "subtitle": "Канвертаваць файлы OpenDocument Text у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы ODT", + "convertButton": "Канвертаваць у PDF" + }, + "csvToPdf": { + "name": "CSV у PDF", + "subtitle": "Канвертаваць табліцы CSV у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы CSV", + "convertButton": "Канвертаваць у PDF" + }, + "rtfToPdf": { + "name": "RTF у PDF", + "subtitle": "Канвертаваць дакументы Rich Text Format у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы RTF", + "convertButton": "Канвертаваць у PDF" + }, + "wordToPdf": { + "name": "Word у PDF", + "subtitle": "Канвертаваць дакументы Word (DOCX, DOC, ODT, RTF) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы DOCX, DOC, ODT, RTF", + "convertButton": "Канвертаваць у PDF" + }, + "excelToPdf": { + "name": "Excel у PDF", + "subtitle": "Канвертаваць табліцы Excel (XLSX, XLS, ODS, CSV) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы XLSX, XLS, ODS, CSV", + "convertButton": "Канвертаваць у PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint у PDF", + "subtitle": "Канвертаваць прэзентацыі PowerPoint (PPTX, PPT, ODP) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы PPTX, PPT, ODP", + "convertButton": "Канвертаваць у PDF" + }, + "markdownToPdf": { + "name": "Markdown у PDF", + "subtitle": "Напісаць або ўставіць Markdown і экспартаваць яго ў прыгожа аформлены PDF.", + "paneMarkdown": "Markdown", + "panePreview": "Папярэдні прагляд", + "btnUpload": "Запампаваць", + "btnSyncScroll": "Сінхранізацыя пракруткі", + "btnSettings": "Налады", + "btnExportPdf": "Экспартаваць PDF", + "settingsTitle": "Налады Markdown", + "settingsPreset": "Набор налад", + "presetDefault": "Прадвызначаны (як GFM)", + "presetCommonmark": "CommonMark (строгі)", + "presetZero": "Мінімальны (без функцый)", + "settingsOptions": "Параметры Markdown", + "optAllowHtml": "Дазволіць HTML-тэгі", + "optBreaks": "Пераўтвараць пераносы радкоў у
", + "optLinkify": "Аўтаматычна пераўтвараць URL-адрасы ў спасылкі", + "optTypographer": "Тыпограф (разумныя двукоссі і інш.)" + }, + "pdfBooklet": { + "name": "Буклет PDF", + "subtitle": "Змяніць парадак старонак для друку двухбаковага буклета. Складзіце і счапіце аркушы, каб атрымаўся буклет.", + "howItWorks": "Як гэта працуе:", + "step1": "Запампуйце файл PDF.", + "step2": "Парадак старонак будзе зменены для буклета.", + "step3": "Надрукуйце з двух бакоў, перавярніце па кароткім краі, складзіце і сшыйце.", + "paperSize": "Памер паперы", + "orientation": "Арыентацыя", + "portrait": "Кніжная", + "landscape": "Альбомная", + "pagesPerSheet": "Старонак на аркуш", + "createBooklet": "Стварыць буклет", + "processing": "Апрацоўка...", + "pageCount": "Колькасць старонак будзе дапоўнена да кратнай 4 пры неабходнасці." + }, + "xpsToPdf": { + "name": "XPS у PDF", + "subtitle": "Канвертаваць дакументы XPS/OXPS у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы XPS, OXPS", + "convertButton": "Канвертаваць у PDF" + }, + "mobiToPdf": { + "name": "MOBI у PDF", + "subtitle": "Канвертаваць электронныя кнігі MOBI у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы MOBI", + "convertButton": "Канвертаваць у PDF" + }, + "epubToPdf": { + "name": "EPUB у PDF", + "subtitle": "Канвертаваць электронныя кнігі EPUB у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы EPUB", + "convertButton": "Канвертаваць у PDF" + }, + "fb2ToPdf": { + "name": "FB2 у PDF", + "subtitle": "Канвертаваць электронныя кнігі FictionBook (FB2) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы FB2", + "convertButton": "Канвертаваць у PDF" + }, + "cbzToPdf": { + "name": "CBZ у PDF", + "subtitle": "Канвертаваць архівы коміксаў (CBZ/CBR) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы CBZ, CBR", + "convertButton": "Канвертаваць у PDF" + }, + "wpdToPdf": { + "name": "WPD у PDF", + "subtitle": "Канвертаваць дакументы WordPerfect (WPD) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы WPD", + "convertButton": "Канвертаваць у PDF" + }, + "wpsToPdf": { + "name": "WPS у PDF", + "subtitle": "Канвертаваць дакументы WPS Office у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы WPS", + "convertButton": "Канвертаваць у PDF" + }, + "xmlToPdf": { + "name": "XML у PDF", + "subtitle": "Канвертаваць дакументы XML у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы XML", + "convertButton": "Канвертаваць у PDF" + }, + "pagesToPdf": { + "name": "Pages у PDF", + "subtitle": "Канвертаваць дакументы Apple Pages у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы Pages", + "convertButton": "Канвертаваць у PDF" + }, + "odgToPdf": { + "name": "ODG у PDF", + "subtitle": "Канвертаваць файлы OpenDocument Graphics (ODG) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы ODG", + "convertButton": "Канвертаваць у PDF" + }, + "odsToPdf": { + "name": "ODS у PDF", + "subtitle": "Канвертаваць файлы OpenDocument Spreadsheet (ODS) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы ODS", + "convertButton": "Канвертаваць у PDF" + }, + "odpToPdf": { + "name": "ODP у PDF", + "subtitle": "Канвертаваць файлы OpenDocument Presentation (ODP) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы ODP", + "convertButton": "Канвертаваць у PDF" + }, + "pubToPdf": { + "name": "PUB у PDF", + "subtitle": "Канвертаваць файлы Microsoft Publisher (PUB) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы PUB", + "convertButton": "Канвертаваць у PDF" + }, + "vsdToPdf": { + "name": "VSD у PDF", + "subtitle": "Канвертаваць файлы Microsoft Visio (VSD, VSDX) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы VSD, VSDX", + "convertButton": "Канвертаваць у PDF" + }, + "psdToPdf": { + "name": "PSD у PDF", + "subtitle": "Канвертаваць файлы Adobe Photoshop (PSD) у PDF. Падтрымлівае некалькі файлаў.", + "acceptedFormats": "файлы PSD", + "convertButton": "Канвертаваць у PDF" + }, + "pdfToSvg": { + "name": "PDF у SVG", + "subtitle": "Канвертаваць кожную старонку PDF у маштабаваную вектарную графіку (SVG) для ідэальнай якасці на любым памеры." + }, + "extractTables": { + "name": "Выняць табліцы з PDF", + "subtitle": "Выняць табліцы з PDF і экспартаваць у CSV, JSON або Markdown." + }, + "pdfToCsv": { + "name": "PDF у CSV", + "subtitle": "Выняць табліцы з PDF і канвертаваць у CSV." + }, + "pdfToExcel": { + "name": "PDF у Excel", + "subtitle": "Выняць табліцы з PDF і канвертаваць у Excel (XLSX)." + }, + "pdfToText": { + "name": "PDF у тэкст", + "subtitle": "Выняць тэкст з файлаў PDF і захаваць як тэкставы файл (.txt). Падтрымлівае некалькі файлаў.", + "note": "Гэты інструмент працуе ТОЛЬКІ з PDF, створанымі лічбавым спосабам. Для сканіраваных дакументаў або PDF на аснове відарысаў выкарыстоўвайце інструмент OCR PDF.", + "convertButton": "Выняць тэкст" + }, + "digitalSignPdf": { + "name": "Лічбавы подпіс PDF", + "pageTitle": "Лічбавы подпіс PDF - Дадаць крыптаграфічны подпіс | BentoPDF", + "subtitle": "Дадаць крыптаграфічны лічбавы подпіс да PDF з выкарыстаннем сертыфікатаў X.509. Падтрымлівае фарматы PKCS#12 (.pfx, .p12) і PEM. Ваш прыватны ключ ніколі не пакідае ваш браўзер.", + "certificateSection": "Сертыфікат", + "uploadCert": "Запампаваць сертыфікат (.pfx, .p12)", + "certPassword": "Пароль сертыфіката", + "certPasswordPlaceholder": "Увядзіце пароль сертыфіката", + "certInfo": "Інфармацыя пра сертыфікат", + "certSubject": "Суб'ект", + "certIssuer": "Выдавец", + "certValidity": "Дзейсны", + "signatureDetails": "Дэталі подпісу (неабавязкова)", + "reason": "Прычына", + "reasonPlaceholder": "напрыклад, я ўхваляю гэты дакумент", + "location": "Месцазнаходжанне", + "locationPlaceholder": "напрыклад, Мінск, Беларусь", + "contactInfo": "Кантактная інфармацыя", + "contactPlaceholder": "напрыклад, email@example.com", + "applySignature": "Ужыць лічбавы подпіс", + "successMessage": "PDF паспяхова падпісаны! Подпіс можна праверыць у любым PDF-чытальніку." + }, + "validateSignaturePdf": { + "name": "Праверыць подпіс PDF", + "pageTitle": "Праверыць подпіс PDF - Верыфікацыя лічбавых подпісаў | BentoPDF", + "subtitle": "Праверыць лічбавыя подпісы ў PDF. Праверыць сапраўднасць сертыфіката, паглядзець дэталі падпісанта і пацвердзіць надзейнасць дакумента. Уся апрацоўка адбываецца ў вашым браўзеры." + }, + "emailToPdf": { + "name": "Email у PDF", + "subtitle": "Канвертаваць файлы email (EML, MSG) у PDF. Падтрымлівае экспарты Outlook і стандартныя email-фарматы.", + "acceptedFormats": "файлы EML, MSG", + "convertButton": "Канвертаваць у PDF" + }, + "fontToOutline": { + "name": "Шрыфт у контуры", + "subtitle": "Ператварыць усе шрыфты ў вектарныя контуры для стабільнага адлюстравання на ўсіх прыладах." + }, + "deskewPdf": { + "name": "Выпрастаць PDF", + "subtitle": "Аўтаматычна выраўняць нахіленыя адсканіраваныя старонкі з дапамогай OpenCV." + }, + "pdfToWord": { + "name": "PDF у Word", + "subtitle": "Канвертаваць файлы PDF у дакументы Word, якія можна рэдагаваць." + }, + "extractImages": { + "name": "Выняць відарысы", + "subtitle": "Выняць усе ўбудаваныя відарысы з файлаў PDF." + }, + "pdfToMarkdown": { + "name": "PDF у Markdown", + "subtitle": "Канвертаваць тэкст і табліцы PDF у фармат Markdown." + }, + "preparePdfForAi": { + "name": "Падрыхтаваць PDF для ШI", + "subtitle": "Выняць змесціва PDF як JSON LlamaIndex для канвеераў RAG/LLM." + }, + "pdfOcg": { + "name": "PDF OCG", + "subtitle": "Праглядзець, пераключыць, дадаць і выдаліць слаі OCG у PDF." + }, + "pdfToPdfa": { + "name": "PDF у PDF/A", + "subtitle": "Канвертаваць PDF у PDF/A для доўгатэрміновага архівавання." + }, + "rasterizePdf": { + "name": "Растарызаваць PDF", + "subtitle": "Канвертаваць PDF у PDF на аснове відарысаў. Звесці слаі і выдаліць тэкст, які можна вылучыць." + }, + "pdfWorkflow": { + "name": "Канструктар рабочага працэсу PDF", + "subtitle": "Стварыць уласныя канвееры апрацоўкі PDF з дапамогай візуальнага рэдактара.", + "nodes": "Вузлы", + "searchNodes": "Пошук вузлоў...", + "run": "Запусціць", + "clear": "Ачысціць", + "save": "Захаваць", + "load": "Загрузіць", + "export": "Экспартаваць", + "import": "Імпартаваць", + "ready": "Гатова", + "settings": "Налады", + "processing": "Апрацоўка...", + "saveTemplate": "Захаваць шаблон", + "templateName": "Назва шаблону", + "templatePlaceholder": "напрыклад, Праца з накладнымі", + "cancel": "Скасаваць", + "loadTemplate": "Загрузіць шаблон", + "noTemplates": "Пакуль няма захаваных шаблонаў.", + "ok": "OK", + "workflowCompleted": "Рабочы працэс завершаны", + "errorDuringExecution": "Памылка падчас выканання", + "addNodeError": "Дадайце хаця б адзін вузел для запуску рабочага працэсу.", + "needInputOutput": "Для працы рабочага працэсу патрэбен хаця б адзін вузел уводу і адзін вузел вываду.", + "enterName": "Увядзіце назву.", + "templateExists": "Шаблон з такой назвай ужо існуе.", + "templateSaved": "Шаблон \"{{name}}\" захаваны.", + "templateLoaded": "Шаблон \"{{name}}\" загружаны.", + "failedLoadTemplate": "Не ўдалося загрузіць шаблон.", + "noSettings": "Няма налад для гэтага вузла.", + "advancedSettings": "Пашыраныя налады" + } +} diff --git a/public/locales/da/common.json b/public/locales/da/common.json new file mode 100644 index 0000000..6710815 --- /dev/null +++ b/public/locales/da/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "Hjem", + "about": "Om", + "contact": "Kontakt", + "licensing": "licensing", + "allTools": "Alle værktøjer", + "openMainMenu": "Åbn hovedmenu", + "language": "Sprog" + }, + "donation": { + "message": "Elsker du BentoPDF? Hjælp os med at holde det gratis og open source!", + "button": "Donér" + }, + "hero": { + "title": "Den", + "pdfToolkit": "PDF-værktøjskasse", + "builtForPrivacy": "bygget til privatliv", + "noSignups": "Ingen tilmeldinger", + "unlimitedUse": "Ubegrænset brug", + "worksOffline": "Virker offline", + "startUsing": "Start med at bruge det nu" + }, + "usedBy": { + "title": "Bruges af virksomheder og personer der arbejder hos" + }, + "features": { + "title": "Hvorfor vælge", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "Ingen tilmelding", + "description": "Start med det samme, ingen konti eller e-mails." + }, + "noUploads": { + "title": "Ingen uploads", + "description": "100% klientside – dine filer forlader aldrig din enhed." + }, + "foreverFree": { + "title": "Altid gratis", + "description": "Alle værktøjer, ingen prøveperioder, ingen betalingsmure." + }, + "noLimits": { + "title": "Ingen begrænsninger", + "description": "Brug det så meget du vil, ingen skjulte grænser." + }, + "batchProcessing": { + "title": "Batchbehandling", + "description": "Håndter ubegrænsede PDF'er på én gang." + }, + "lightningFast": { + "title": "Lynhurtigt", + "description": "Behandl PDF’er øjeblikkeligt, uden ventetid eller forsinkelser." + } + }, + "tools": { + "title": "Kom i gang med", + "toolsLabel": "Værktøjer", + "subtitle": "Klik på et værktøj for at åbne fil-upload", + "searchPlaceholder": "Søg efter et værktøj (f.eks. 'split', 'organiser'...)", + "backToTools": "Tilbage til værktøjer", + "firstLoadNotice": "Første indlæsning tager et øjeblik, mens vi downloader konverteringsmotoren. Derefter vil alt indlæses øjeblikkeligt." + }, + "upload": { + "clickToSelect": "Klik for at vælge en fil", + "orDragAndDrop": "eller træk og slip", + "pdfOrImages": "PDF’er eller billeder", + "filesNeverLeave": "Dine filer forlader aldrig din enhed.", + "addMore": "Tilføj flere filer", + "clearAll": "Ryd alle", + "clearFiles": "Ryd filer", + "hints": { + "singlePdf": "En enkelt PDF-fil", + "pdfFile": "PDF-fil", + "multiplePdfs2": "Flere PDF-filer (mindst 2)", + "bmpImages": "BMP-billeder", + "oneOrMorePdfs": "En eller flere PDF-filer", + "pdfDocuments": "PDF-dokumenter", + "oneOrMoreCsv": "En eller flere CSV-filer", + "multiplePdfsSupported": "Flere PDF-filer understøttet", + "singleOrMultiplePdfs": "Enkelt eller flere PDF-filer understøttet", + "singlePdfFile": "Enkelt PDF-fil", + "pdfWithForms": "PDF-fil med formularfelter", + "heicImages": "HEIC/HEIF-billeder", + "jpgImages": "JPG, JPEG, JP2, JPX-billeder", + "pdfsOrImages": "PDF’er eller billeder", + "oneOrMoreOdt": "En eller flere ODT-filer", + "singlePdfOnly": "Kun én PDF-fil", + "pdfFiles": "PDF-filer", + "multiplePdfs": "Flere PDF-filer", + "pngImages": "PNG-billeder", + "pdfFilesOneOrMore": "PDF-filer (en eller flere)", + "oneOrMoreRtf": "En eller flere RTF-filer", + "svgGraphics": "SVG-grafik", + "tiffImages": "TIFF-billeder", + "webpImages": "WebP-billeder" + } + }, + "howItWorks": { + "title": "Sådan fungerer det", + "step1": "Klik eller træk og slip din fil for at starte", + "step2": "Klik på behandl-knappen for at starte", + "step3": "Gem din behandlede fil med det samme" + }, + "relatedTools": { + "title": "Relaterede PDF-værktøjer" + }, + "loader": { + "processing": "Behandler..." + }, + "alert": { + "title": "Advarsel", + "ok": "OK" + }, + "preview": { + "title": "Dokumentforhåndsvisning", + "downloadAsPdf": "Download som PDF", + "close": "Luk" + }, + "settings": { + "title": "Indstillinger", + "shortcuts": "Genveje", + "preferences": "Præferencer", + "displayPreferences": "Visningspræferencer", + "searchShortcuts": "Søg efter genveje...", + "shortcutsInfo": "Tryk og hold taster nede for at sætte en genvej. Ændringer gemmes automatisk.", + "shortcutsWarning": "⚠️ Undgå almindelige browsergenveje (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N osv.), da de muligvis ikke fungerer stabilt.", + "import": "Importér", + "export": "Eksportér", + "resetToDefaults": "Nulstil til standard", + "fullWidthMode": "Fuld bredde-tilstand", + "fullWidthDescription": "Brug hele skærmbredden til alle værktøjer i stedet for en centreret container", + "settingsAutoSaved": "Indstillinger gemmes automatisk", + "clickToSet": "Klik for at vælge", + "pressKeys": "Tryk på taster...", + "warnings": { + "alreadyInUse": "Genvej bruges allerede", + "assignedTo": "er allerede tildelt:", + "chooseDifferent": "Vælg venligst en anden genvej.", + "reserved": "Advarsel om reserveret genvej", + "commonlyUsed": "bruges ofte til:", + "unreliable": "Denne genvej fungerer muligvis ikke stabilt eller kan konfliktere med browser/system.", + "useAnyway": "Vil du bruge den alligevel?", + "resetTitle": "Nulstil genveje", + "resetMessage": "Er du sikker på, at du vil nulstille alle genveje til standard?

Denne handling kan ikke fortrydes.", + "importSuccessTitle": "Import gennemført", + "importSuccessMessage": "Genveje importeret!", + "importFailTitle": "Import mislykkedes", + "importFailMessage": "Kunne ikke importere genveje. Ugyldigt filformat." + } + }, + "warning": { + "title": "Advarsel", + "cancel": "Annuller", + "proceed": "Fortsæt" + }, + "compliance": { + "title": "Dine data forlader aldrig din enhed", + "weKeep": "Vi holder", + "yourInfoSafe": "dine oplysninger sikre", + "byFollowingStandards": "ved at følge globale sikkerhedsstandarder.", + "processingLocal": "Al behandling foregår lokalt på din enhed.", + "gdpr": { + "title": "GDPR-overholdelse", + "description": "Beskytter persondata og privatliv for personer i EU." + }, + "ccpa": { + "title": "CCPA-overholdelse", + "description": "Giver Californiens borgere rettigheder over deres personlige oplysninger." + }, + "hipaa": { + "title": "HIPAA-overholdelse", + "description": "Fastlægger krav til håndtering af følsomme sundhedsoplysninger i USA." + } + }, + "faq": { + "title": "Ofte stillede", + "questions": "Spørgsmål", + "sectionTitle": "Ofte stillede spørgsmål", + "isFree": { + "question": "Er BentoPDF virkelig gratis?", + "answer": "Ja, absolut. Alle værktøjer er 100% gratis at bruge, uden filgrænser, uden tilmeldinger og uden vandmærker." + }, + "areFilesSecure": { + "question": "Er mine filer sikre? Hvor bliver de behandlet?", + "answer": "Dine filer er så sikre som muligt, fordi de aldrig forlader din computer. Alt behandles direkte i din browser." + }, + "platforms": { + "question": "Virker det på Mac, Windows og mobil?", + "answer": "Ja! BentoPDF virker på alle moderne browsere, uanset styresystem." + }, + "gdprCompliant": { + "question": "Er BentoPDF GDPR-kompatibel?", + "answer": "Ja. Da vi ikke indsamler eller behandler dine filer på vores servere, er dine data altid under din kontrol." + }, + "dataStorage": { + "question": "Gemmer eller sporer I mine filer?", + "answer": "Nej. Vi gemmer eller sporer aldrig dine filer. Alt foregår i din browser." + }, + "different": { + "question": "Hvad gør BentoPDF anderledes?", + "answer": "De fleste PDF-værktøjer uploader dine filer til en server. BentoPDF gør det hele lokalt i din browser." + }, + "browserBased": { + "question": "Hvordan gør browserbaseret behandling mig sikker?", + "answer": "Dine filer forlader aldrig enheden, hvilket fjerner risikoen for datalæk, hacks eller uautoriseret adgang." + }, + "analytics": { + "question": "Bruger I cookies eller analyseværktøjer?", + "answer": "Vi bruger kun Simple Analytics til anonyme besøgsdata. Ingen personlige oplysninger indsamles." + } + }, + "testimonials": { + "title": "Hvad vores", + "users": "Brugere", + "say": "Siger" + }, + "support": { + "title": "Kan du lide mit arbejde?", + "description": "BentoPDF er et passioneret projekt, bygget for at tilbyde et gratis og privat PDF-værktøj til alle.", + "buyMeCoffee": "Køb en kaffe til mig" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Alle rettigheder forbeholdes.", + "version": "Version", + "company": "Virksomhed", + "aboutUs": "Om os", + "faqLink": "FAQ", + "contactUs": "Kontakt os", + "legal": "Juridisk", + "termsAndConditions": "Terms and Conditions", + "privacyPolicy": "Privacy Policy", + "followUs": "Følg os" + }, + "merge": { + "title": "Flet PDF'er", + "description": "Kombinér hele filer eller vælg specifikke sider til et nyt dokument.", + "fileMode": "Filtilstand", + "pageMode": "Sidetilstand", + "howItWorks": "Sådan fungerer det:", + "fileModeInstructions": [ + "Klik og træk ikonet for at ændre rækkefølge.", + "I \"Sider\"-feltet kan du angive intervaller (fx \"1-3, 5\").", + "Lad feltet stå tomt for at inkludere alle sider." + ], + "pageModeInstructions": [ + "Alle sider fra dine PDF’er vises nedenfor.", + "Træk og slip siderne for at lave den ønskede rækkefølge." + ], + "mergePdfs": "Flet PDF'er" + }, + "common": { + "page": "Side", + "pages": "Sider", + "of": "af", + "download": "Download", + "cancel": "Annuller", + "save": "Gem", + "delete": "Slet", + "edit": "Rediger", + "add": "Tilføj", + "remove": "Fjern", + "loading": "Indlæser...", + "error": "Fejl", + "success": "Succes", + "file": "Fil", + "files": "Filer", + "close": "Luk" + }, + "about": { + "hero": { + "title": "Vi mener PDF-værktøjer bør være", + "subtitle": "hurtige, private og gratis.", + "noCompromises": "Ingen kompromiser." + }, + "mission": { + "title": "Vores mission", + "description": "At give den mest komplette PDF-værktøjskasse uden betaling og med fuldt fokus på privatliv." + }, + "philosophy": { + "label": "Vores kernefilosofi", + "title": "Privatliv først. Altid.", + "description": "Alt sker lokalt i din browser. Dine dokumenter er 100% private." + }, + "whyBentopdf": { + "title": "Hvorfor", + "speed": { + "title": "Bygget til hastighed", + "description": "Ingen ventetid på uploads eller downloads — alt behandles lokalt." + }, + "free": { + "title": "Fuldstændig gratis", + "description": "Ingen abonnementer, ingen skjulte gebyrer, ingen premiumlås." + }, + "noAccount": { + "title": "Ingen konto nødvendig", + "description": "Brug værktøjerne med det samme — helt uden login." + }, + "openSource": { + "title": "Open source-ånd", + "description": "Bygget med gennemsigtighed og baseret på stærke open source-biblioteker." + } + }, + "cta": { + "title": "Klar til at komme i gang?", + "description": "Prøv selv den hurtige og private PDF-oplevelse.", + "button": "Udforsk alle værktøjer" + } + }, + "contact": { + "title": "Kontakt os", + "subtitle": "Vi vil gerne høre fra dig — spørgsmål, feedback eller ønsker er velkomne.", + "email": "Du kan kontakte os direkte på:" + }, + "licensing": { + "title": "licensing til", + "subtitle": "Vælg den licens der passer til dine behov." + }, + "multiTool": { + "uploadPdfs": "Upload PDF'er", + "upload": "Upload", + "addBlankPage": "Tilføj tom side", + "edit": "Rediger:", + "undo": "Fortryd", + "redo": "Gentag", + "reset": "Nulstil", + "selection": "Markering:", + "selectAll": "Vælg alle", + "deselectAll": "Fravælg alle", + "rotate": "Rotér:", + "rotateLeft": "Venstre", + "rotateRight": "Højre", + "transform": "Transformér:", + "duplicate": "Duplikér", + "split": "Opdel", + "clear": "Ryd:", + "delete": "Slet", + "download": "Download:", + "downloadSelected": "Download valgte", + "exportPdf": "Eksportér PDF", + "uploadPdfFiles": "Vælg PDF-filer", + "dragAndDrop": "Træk og slip PDF-filer her, eller klik for at vælge", + "selectFiles": "Vælg filer", + "renderingPages": "Renderer sider...", + "actions": { + "duplicatePage": "Duplikér denne side", + "deletePage": "Slet denne side", + "insertPdf": "Indsæt PDF efter denne side", + "toggleSplit": "Slå opdeling til/fra efter denne side" + }, + "pleaseWait": "Vent venligst", + "pagesRendering": "Siderne bliver stadig renderet. Vent venligst...", + "noPagesSelected": "Ingen sider valgt", + "selectOnePage": "Vælg mindst én side for at downloade.", + "noPages": "Ingen sider", + "noPagesToExport": "Der er ingen sider at eksportere.", + "renderingTitle": "Renderer side-forhåndsvisninger", + "errorRendering": "Kunne ikke rendere side-miniaturer", + "error": "Fejl", + "failedToLoad": "Kunne ikke indlæses" + }, + "simpleMode": { + "title": "PDF-værktøjer", + "subtitle": "Vælg et værktøj for at komme i gang" + } +} diff --git a/public/locales/da/tools.json b/public/locales/da/tools.json new file mode 100644 index 0000000..2524b97 --- /dev/null +++ b/public/locales/da/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "Populære værktøjer", + "editAnnotate": "Rediger og annotér", + "convertToPdf": "Konverter til PDF", + "convertFromPdf": "Konverter fra PDF", + "organizeManage": "Organisér og administrér", + "optimizeRepair": "Optimer og reparér", + "securePdf": "Sikre PDF" + }, + "pdfMultiTool": { + "name": "PDF Multi-værktøj", + "subtitle": "Flet, opdel, organisér, slet, roter, tilføj tomme sider, udtræk og duplikér i én samlet grænseflade." + }, + "mergePdf": { + "name": "Flet PDF", + "subtitle": "Kombinér flere PDF’er til én fil. Bevarer bogmærker." + }, + "splitPdf": { + "name": "Opdel PDF", + "subtitle": "Udtræk et sideinterval til en ny PDF." + }, + "compressPdf": { + "name": "Komprimér PDF", + "subtitle": "Reducer filstørrelsen på din PDF.", + "algorithmLabel": "Komprimeringsalgoritme", + "condense": "Kondenser (anbefalet)", + "photon": "Photon (til billedtunge PDF’er)", + "condenseInfo": "Kondenser bruger avanceret komprimering: fjerner overflødigt indhold, optimerer billeder, udvælger skrifttyper. Bedst til de fleste PDF’er.", + "photonInfo": "Photon konverterer sider til billeder. Bruges til billedtunge eller scannede PDF’er.", + "photonWarning": "Advarsel: Tekst bliver ikke valgbar, og links holder op med at virke.", + "levelLabel": "Komprimeringsniveau", + "light": "Let (bevar kvalitet)", + "balanced": "Balanceret (anbefalet)", + "aggressive": "Aggressiv (mindre filer)", + "extreme": "Ekstrem (maksimal komprimering)", + "grayscale": "Konverter til gråtoner", + "grayscaleHint": "Reducerer filstørrelsen ved at fjerne farveinformation", + "customSettings": "Brugerdefinerede indstillinger", + "customSettingsHint": "Finjustér komprimeringsparametre:", + "outputQuality": "Outputkvalitet", + "resizeImagesTo": "Skalér billeder til", + "onlyProcessAbove": "Behandl kun over", + "removeMetadata": "Fjern metadata", + "subsetFonts": "Delvis skrifttypeindlæsning (fjern ubrugte tegn)", + "removeThumbnails": "Fjern indlejrede miniaturer", + "compressButton": "Komprimér PDF" + }, + "pdfEditor": { + "name": "PDF-editor", + "subtitle": "Annotér, fremhæv, redigér, kommentér, tilføj former/billeder, søg og vis PDF’er." + }, + "jpgToPdf": { + "name": "JPG til PDF", + "subtitle": "Opret en PDF fra JPG, JPEG og JPEG2000 (JP2/JPX) billeder." + }, + "signPdf": { + "name": "Underskriv PDF", + "subtitle": "Tegn, skriv eller upload din signatur." + }, + "cropPdf": { + "name": "Beskær PDF", + "subtitle": "Trim margenerne på alle sider i din PDF." + }, + "extractPages": { + "name": "Udtræk sider", + "subtitle": "Gem et udvalg af sider som nye filer." + }, + "duplicateOrganize": { + "name": "Duplikér og organisér", + "subtitle": "Duplikér, omorganisér og slet sider." + }, + "deletePages": { + "name": "Slet sider", + "subtitle": "Fjern specifikke sider fra dokumentet." + }, + "editBookmarks": { + "name": "Redigér bogmærker", + "subtitle": "Tilføj, redigér, importér, slet og udtræk PDF-bogmærker." + }, + "tableOfContents": { + "name": "Indholdsfortegnelse", + "subtitle": "Generér en indholdsfortegnelse ud fra PDF-bogmærker." + }, + "pageNumbers": { + "name": "Sidetal", + "subtitle": "Indsæt sidetal i dokumentet." + }, + "batesNumbering": { + "name": "Bates-nummerering", + "subtitle": "Tilføj sekventielle Bates-numre på tværs af en eller flere PDF-filer." + }, + "addWatermark": { + "name": "Tilføj vandmærke", + "subtitle": "Placer tekst eller et billede oven på dine PDF-sider.", + "applyToAllPages": "Anvend på alle sider" + }, + "headerFooter": { + "name": "Sidehoved og sidefod", + "subtitle": "Tilføj tekst øverst og nederst på siderne." + }, + "invertColors": { + "name": "Invertér farver", + "subtitle": "Lav en slags “dark mode”-version af din PDF." + }, + "scannerEffect": { + "name": "Scannereffekt", + "subtitle": "Få din PDF til at ligne et scannet dokument.", + "scanSettings": "Scanneindstillinger", + "colorspace": "Farverum", + "gray": "Grå", + "border": "Kant", + "rotate": "Rotér", + "rotateVariance": "Rotationsvariation", + "brightness": "Lysstyrke", + "contrast": "Kontrast", + "blur": "Sløring", + "noise": "Støj", + "yellowish": "Gulskær", + "resolution": "Opløsning", + "processButton": "Anvend scannereffekt" + }, + "adjustColors": { + "name": "Justér farver", + "subtitle": "Finjustér lysstyrke, kontrast, mætning og mere i din PDF.", + "colorSettings": "Farveindstillinger", + "brightness": "Lysstyrke", + "contrast": "Kontrast", + "saturation": "Mætning", + "hueShift": "Farvetonejustering", + "temperature": "Temperatur", + "tint": "Farvetone", + "gamma": "Gamma", + "sepia": "Sepia", + "processButton": "Anvend farvejusteringer" + }, + "backgroundColor": { + "name": "Baggrundsfarve", + "subtitle": "Skift baggrundsfarven på din PDF." + }, + "changeTextColor": { + "name": "Skift tekstfarve", + "subtitle": "Ændr tekstfarven i din PDF." + }, + "addStamps": { + "name": "Tilføj stempler", + "subtitle": "Tilføj billedstempler til din PDF via annoteringsværktøjet.", + "usernameLabel": "Stempelbrugernavn", + "usernamePlaceholder": "Indtast dit navn (til stempler)", + "usernameHint": "Dette navn vises på de stempler du opretter." + }, + "removeAnnotations": { + "name": "Fjern annotationer", + "subtitle": "Fjern kommentarer, markeringer og links." + }, + "pdfFormFiller": { + "name": "PDF-formularudfylder", + "subtitle": "Udfyld formularer direkte i browseren. Understøtter også XFA-formularer." + }, + "createPdfForm": { + "name": "Opret PDF-formular", + "subtitle": "Lav udfyldelige PDF-formularer med træk-og-slip tekstfelter." + }, + "removeBlankPages": { + "name": "Fjern tomme sider", + "subtitle": "Find og fjern automatisk tomme sider.", + "sensitivityHint": "Højere = strengere, kun helt tomme sider. Lavere = tillader sider med noget indhold." + }, + "imageToPdf": { + "name": "Billeder til PDF", + "subtitle": "Konverter JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC og WebP til PDF." + }, + "pngToPdf": { + "name": "PNG til PDF", + "subtitle": "Opret en PDF fra en eller flere PNG-billeder." + }, + "webpToPdf": { + "name": "WebP til PDF", + "subtitle": "Opret en PDF fra en eller flere WebP-billeder." + }, + "svgToPdf": { + "name": "SVG til PDF", + "subtitle": "Opret en PDF fra en eller flere SVG-billeder." + }, + "bmpToPdf": { + "name": "BMP til PDF", + "subtitle": "Opret en PDF fra en eller flere BMP-billeder." + }, + "heicToPdf": { + "name": "HEIC til PDF", + "subtitle": "Opret en PDF fra en eller flere HEIC-billeder." + }, + "tiffToPdf": { + "name": "TIFF til PDF", + "subtitle": "Opret en PDF fra en eller flere TIFF-billeder." + }, + "textToPdf": { + "name": "Tekst til PDF", + "subtitle": "Konverter en almindelig tekstfil til PDF." + }, + "jsonToPdf": { + "name": "JSON til PDF", + "subtitle": "Konverter JSON-filer til PDF." + }, + "pdfToJpg": { + "name": "PDF til JPG", + "subtitle": "Konverter hver PDF-side til en JPG-billedfil." + }, + "pdfToPng": { + "name": "PDF til PNG", + "subtitle": "Konverter hver PDF-side til en PNG-billedfil." + }, + "pdfToWebp": { + "name": "PDF til WebP", + "subtitle": "Konverter hver PDF-side til en WebP-billedfil." + }, + "pdfToBmp": { + "name": "PDF til BMP", + "subtitle": "Konverter hver PDF-side til en BMP-billedfil." + }, + "pdfToTiff": { + "name": "PDF til TIFF", + "subtitle": "Konverter hver PDF-side til en TIFF-billedfil." + }, + "pdfToGreyscale": { + "name": "PDF til gråtoner", + "subtitle": "Konverter alle farver til sort/hvid." + }, + "pdfToJson": { + "name": "PDF til JSON", + "subtitle": "Konverter PDF-filer til JSON-format." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Gør PDF’en søgbar og kopierbar." + }, + "alternateMix": { + "name": "Alternér og miks sider", + "subtitle": "Flet PDF’er ved at skifte mellem sider fra hver fil. Bevarer bogmærker." + }, + "addAttachments": { + "name": "Tilføj vedhæftninger", + "subtitle": "Indlejr en eller flere filer i din PDF." + }, + "extractAttachments": { + "name": "Udtræk vedhæftninger", + "subtitle": "Udtræk alle indlejrede filer som en ZIP." + }, + "editAttachments": { + "name": "Redigér vedhæftninger", + "subtitle": "Se eller fjern vedhæftninger i din PDF." + }, + "dividePages": { + "name": "Opdel sider", + "subtitle": "Opdel sider vandret eller lodret." + }, + "addBlankPage": { + "name": "Tilføj tom side", + "subtitle": "Indsæt en tom side hvor som helst i din PDF." + }, + "reversePages": { + "name": "Vend sider", + "subtitle": "Vend rækkefølgen på alle sider." + }, + "rotatePdf": { + "name": "Roter PDF", + "subtitle": "Drej sider i intervaller på 90 grader." + }, + "rotateCustom": { + "name": "Roter med brugerdefineret vinkel", + "subtitle": "Roter sider med en valgfri vinkel." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "Arrangér flere sider på ét ark." + }, + "combineToSinglePage": { + "name": "Kombinér til én side", + "subtitle": "Sy alle sider sammen til én lang rulle." + }, + "viewMetadata": { + "name": "Vis metadata", + "subtitle": "Inspektér skjulte PDF-egenskaber." + }, + "editMetadata": { + "name": "Redigér metadata", + "subtitle": "Redigér forfatter, titel og andre egenskaber." + }, + "pdfsToZip": { + "name": "PDF’er til ZIP", + "subtitle": "Pak flere PDF-filer i et ZIP-arkiv." + }, + "comparePdfs": { + "name": "Sammenlign PDF’er", + "subtitle": "Sammenlign to PDF’er side om side." + }, + "posterizePdf": { + "name": "Posterisér PDF", + "subtitle": "Opdel en stor side i flere mindre sider." + }, + "fixPageSize": { + "name": "Ret sidestørrelse", + "subtitle": "Standardisér alle sider til samme størrelse." + }, + "linearizePdf": { + "name": "Lineariser PDF", + "subtitle": "Optimer PDF til hurtig visning på nettet." + }, + "pageDimensions": { + "name": "Sidestørrelser", + "subtitle": "Analysér sidestørrelse, orientering og enheder." + }, + "removeRestrictions": { + "name": "Fjern begrænsninger", + "subtitle": "Fjern adgangskoder og sikkerhedsbegrænsninger fra digitalt signerede PDF’er." + }, + "repairPdf": { + "name": "Reparér PDF", + "subtitle": "Gendan data fra beskadigede eller korrupte PDF’er." + }, + "encryptPdf": { + "name": "Kryptér PDF", + "subtitle": "Lås din PDF med en adgangskode." + }, + "sanitizePdf": { + "name": "Rens PDF", + "subtitle": "Fjern metadata, annotationer, scripts og mere." + }, + "decryptPdf": { + "name": "Dekryptér PDF", + "subtitle": "Fjern adgangskodebeskyttelse." + }, + "flattenPdf": { + "name": "Flatten PDF", + "subtitle": "Gør formularfelter og annotationer ikke-redigerbare." + }, + "removeMetadata": { + "name": "Fjern metadata", + "subtitle": "Fjern skjulte data fra PDF’en." + }, + "changePermissions": { + "name": "Skift tilladelser", + "subtitle": "Konfigurer brugerrettigheder i PDF’en." + }, + "odtToPdf": { + "name": "ODT til PDF", + "subtitle": "Konverter ODT-dokumenter til PDF. Understøtter flere filer.", + "acceptedFormats": "ODT-filer", + "convertButton": "Konverter til PDF" + }, + "csvToPdf": { + "name": "CSV til PDF", + "subtitle": "Konverter CSV-regneark til PDF. Understøtter flere filer.", + "acceptedFormats": "CSV-filer", + "convertButton": "Konverter til PDF" + }, + "rtfToPdf": { + "name": "RTF til PDF", + "subtitle": "Konverter RTF-dokumenter til PDF. Understøtter flere filer.", + "acceptedFormats": "RTF-filer", + "convertButton": "Konverter til PDF" + }, + "wordToPdf": { + "name": "Word til PDF", + "subtitle": "Konverter Word-dokumenter (DOCX, DOC, ODT, RTF) til PDF.", + "acceptedFormats": "DOCX, DOC, ODT, RTF-filer", + "convertButton": "Konverter til PDF" + }, + "excelToPdf": { + "name": "Excel til PDF", + "subtitle": "Konverter Excel-filer (XLSX, XLS, ODS, CSV) til PDF.", + "acceptedFormats": "XLSX, XLS, ODS, CSV-filer", + "convertButton": "Konverter til PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint til PDF", + "subtitle": "Konverter PowerPoint-presentationer (PPTX, PPT, ODP) til PDF.", + "acceptedFormats": "PPTX, PPT, ODP-filer", + "convertButton": "Konverter til PDF" + }, + "markdownToPdf": { + "name": "Markdown til PDF", + "subtitle": "Skriv eller indsæt Markdown og eksportér som en flot formateret PDF.", + "paneMarkdown": "Markdown", + "panePreview": "Forhåndsvisning", + "btnUpload": "Upload", + "btnSyncScroll": "Synkron rulning", + "btnSettings": "Indstillinger", + "btnExportPdf": "Eksportér PDF", + "settingsTitle": "Markdown-indstillinger", + "settingsPreset": "Forudindstilling", + "presetDefault": "Standard (GFM-lignende)", + "presetCommonmark": "CommonMark (striks)", + "presetZero": "Minimal (ingen funktioner)", + "settingsOptions": "Markdown-muligheder", + "optAllowHtml": "Tillad HTML-tags", + "optBreaks": "Konverter linjeskift til
", + "optLinkify": "Lav automatisk URL’er om til links", + "optTypographer": "Typograf (smart anførselstegn m.m.)" + }, + "pdfBooklet": { + "name": "PDF-hæfte", + "subtitle": "Arrangér sider til dobbeltsidet hæfteudskrivning. Fold og hæft for at skabe et hæfte.", + "howItWorks": "Sådan fungerer det:", + "step1": "Upload en PDF-fil.", + "step2": "Siderne bliver omarrangeret i hæfterækkefølge.", + "step3": "Udskriv dobbeltsidet, vend på kort kant, fold og hæft.", + "paperSize": "Papirstørrelse", + "orientation": "Retning", + "portrait": "Portræt", + "landscape": "Landskab", + "pagesPerSheet": "Sider pr. ark", + "createBooklet": "Opret hæfte", + "processing": "Behandler...", + "pageCount": "Sidetal bliver justeret til et multiplum af 4 om nødvendigt." + }, + "xpsToPdf": { + "name": "XPS til PDF", + "subtitle": "Konverter XPS/OXPS-dokumenter til PDF. Understøtter flere filer.", + "acceptedFormats": "XPS, OXPS-filer", + "convertButton": "Konverter til PDF" + }, + "mobiToPdf": { + "name": "MOBI til PDF", + "subtitle": "Konverter MOBI e-bøger til PDF. Understøtter flere filer.", + "acceptedFormats": "MOBI-filer", + "convertButton": "Konverter til PDF" + }, + "epubToPdf": { + "name": "EPUB til PDF", + "subtitle": "Konverter EPUB e-bøger til PDF. Understøtter flere filer.", + "acceptedFormats": "EPUB-filer", + "convertButton": "Konverter til PDF" + }, + "fb2ToPdf": { + "name": "FB2 til PDF", + "subtitle": "Konverter FictionBook (FB2) e-bøger til PDF. Understøtter flere filer.", + "acceptedFormats": "FB2-filer", + "convertButton": "Konverter til PDF" + }, + "cbzToPdf": { + "name": "CBZ til PDF", + "subtitle": "Konverter tegneseriearkiver (CBZ/CBR) til PDF. Understøtter flere filer.", + "acceptedFormats": "CBZ, CBR-filer", + "convertButton": "Konverter til PDF" + }, + "wpdToPdf": { + "name": "WPD til PDF", + "subtitle": "Konverter WordPerfect-dokumenter (WPD) til PDF.", + "acceptedFormats": "WPD-filer", + "convertButton": "Konverter til PDF" + }, + "wpsToPdf": { + "name": "WPS til PDF", + "subtitle": "Konverter WPS Office-dokumenter til PDF.", + "acceptedFormats": "WPS-filer", + "convertButton": "Konverter til PDF" + }, + "xmlToPdf": { + "name": "XML til PDF", + "subtitle": "Konverter XML-dokumenter til PDF. Understøtter flere filer.", + "acceptedFormats": "XML-filer", + "convertButton": "Konverter til PDF" + }, + "pagesToPdf": { + "name": "Pages til PDF", + "subtitle": "Konverter Apple Pages-dokumenter til PDF.", + "acceptedFormats": "Pages-filer", + "convertButton": "Konverter til PDF" + }, + "odgToPdf": { + "name": "ODG til PDF", + "subtitle": "Konverter OpenDocument Graphics (ODG) filer til PDF.", + "acceptedFormats": "ODG-filer", + "convertButton": "Konverter til PDF" + }, + "odsToPdf": { + "name": "ODS til PDF", + "subtitle": "Konverter OpenDocument Spreadsheet (ODS) filer til PDF.", + "acceptedFormats": "ODS-filer", + "convertButton": "Konverter til PDF" + }, + "odpToPdf": { + "name": "ODP til PDF", + "subtitle": "Konverter OpenDocument Presentation (ODP) filer til PDF.", + "acceptedFormats": "ODP-filer", + "convertButton": "Konverter til PDF" + }, + "pubToPdf": { + "name": "PUB til PDF", + "subtitle": "Konverter Microsoft Publisher (PUB) filer til PDF.", + "acceptedFormats": "PUB-filer", + "convertButton": "Konverter til PDF" + }, + "vsdToPdf": { + "name": "VSD til PDF", + "subtitle": "Konverter Microsoft Visio (VSD, VSDX) filer til PDF.", + "acceptedFormats": "VSD, VSDX-filer", + "convertButton": "Konverter til PDF" + }, + "psdToPdf": { + "name": "PSD til PDF", + "subtitle": "Konverter Adobe Photoshop (PSD) filer til PDF.", + "acceptedFormats": "PSD-filer", + "convertButton": "Konverter til PDF" + }, + "pdfToSvg": { + "name": "PDF til SVG", + "subtitle": "Konverter hver side i en PDF til en skalerbar vektorgrafik (SVG) med perfekt kvalitet i alle størrelser." + }, + "extractTables": { + "name": "Udtræk PDF-tabeller", + "subtitle": "Udtræk tabeller fra PDF og eksportér som CSV, JSON eller Markdown." + }, + "pdfToCsv": { + "name": "PDF til CSV", + "subtitle": "Udtræk tabeller fra PDF og konverter til CSV." + }, + "pdfToExcel": { + "name": "PDF til Excel", + "subtitle": "Udtræk tabeller fra PDF og konverter til Excel (XLSX)." + }, + "pdfToText": { + "name": "PDF til tekst", + "subtitle": "Udtræk tekst fra PDF og gem som almindelig tekst (.txt). Understøtter flere filer.", + "note": "Dette værktøj virker KUN med digitalt oprettede PDF’er. Brug OCR PDF til scannede dokumenter.", + "convertButton": "Udtræk tekst" + }, + "digitalSignPdf": { + "name": "Digital signatur PDF", + "pageTitle": "Digital signatur PDF - Tilføj kryptografisk signatur | BentoPDF", + "subtitle": "Tilføj en digital signatur til din PDF med X.509-certifikater. Understøtter PKCS#12 (.pfx, .p12) og PEM. Din private nøgle forlader aldrig browseren.", + "certificateSection": "Certifikat", + "uploadCert": "Upload certifikat (.pfx, .p12)", + "certPassword": "Certifikatkodeord", + "certPasswordPlaceholder": "Indtast kodeord til certifikat", + "certInfo": "Certifikatinformation", + "certSubject": "Emne", + "certIssuer": "Udsteder", + "certValidity": "Gyldighed", + "signatureDetails": "Signaturdetaljer (valgfrit)", + "reason": "Årsag", + "reasonPlaceholder": "Fx: Jeg godkender dette dokument", + "location": "Lokation", + "locationPlaceholder": "Fx: København, Danmark", + "contactInfo": "Kontaktinfo", + "contactPlaceholder": "Fx: email@example.com", + "applySignature": "Anvend digital signatur", + "successMessage": "PDF signeret! Signaturen kan verificeres i enhver PDF-læser." + }, + "validateSignaturePdf": { + "name": "Validér PDF-signatur", + "pageTitle": "Validér PDF-signatur - Verificér digitale signaturer | BentoPDF", + "subtitle": "Tjek digitale signaturer i dine PDF’er. Verificér certifikater, se underskriverdetaljer og bekræft dokumentintegritet." + }, + "emailToPdf": { + "name": "Email til PDF", + "subtitle": "Konverter e-mailfiler (EML, MSG) til PDF. Understøtter Outlook-formater.", + "acceptedFormats": "EML, MSG-filer", + "convertButton": "Konverter til PDF" + }, + "fontToOutline": { + "name": "Skrifttype til kontur", + "subtitle": "Konverter alle skrifttyper til vektorkonturer for ensartet visning." + }, + "deskewPdf": { + "name": "Ret skæve sider", + "subtitle": "Ret automatisk skæve scannede sider med OpenCV." + }, + "pdfToWord": { + "name": "PDF til Word", + "subtitle": "Konverter PDF-filer til redigerbare Word-dokumenter." + }, + "extractImages": { + "name": "Udtræk billeder", + "subtitle": "Udtræk alle indlejrede billeder fra PDF-filer." + }, + "pdfToMarkdown": { + "name": "PDF til Markdown", + "subtitle": "Konverter PDF-tekst og tabeller til Markdown." + }, + "preparePdfForAi": { + "name": "Forbered PDF til AI", + "subtitle": "Udtræk PDF-indhold som LlamaIndex JSON til RAG/LLM workflows." + }, + "pdfOcg": { + "name": "PDF OCG", + "subtitle": "Se, skift, tilføj og slet OCG-lag i din PDF." + }, + "pdfToPdfa": { + "name": "PDF til PDF/A", + "subtitle": "Konverter PDF til PDF/A til langtidsarkivering." + }, + "rasterizePdf": { + "name": "Rasterisér PDF", + "subtitle": "Konverter PDF til en billedbaseret PDF. Flatten lag og fjern valgbare tekster." + }, + "pdfWorkflow": { + "name": "PDF Workflow-bygger", + "subtitle": "Byg tilpassede PDF-behandlingspipelines med en visuel nodeeditor.", + "nodes": "Noder", + "searchNodes": "Søg noder...", + "run": "Kør", + "clear": "Ryd", + "save": "Gem", + "load": "Indlæs", + "export": "Eksportér", + "import": "Importér", + "ready": "Klar", + "settings": "Indstillinger", + "processing": "Behandler...", + "saveTemplate": "Gem skabelon", + "templateName": "Skabelonnavn", + "templatePlaceholder": "f.eks. Faktura-workflow", + "cancel": "Annuller", + "loadTemplate": "Indlæs skabelon", + "noTemplates": "Ingen gemte skabeloner endnu.", + "ok": "OK", + "workflowCompleted": "Workflow fuldført", + "errorDuringExecution": "Fejl under udførelse", + "addNodeError": "Tilføj mindst én node for at køre workflowet.", + "needInputOutput": "Dit workflow skal have mindst én inputnode og én outputnode for at kunne køre.", + "enterName": "Indtast venligst et navn.", + "templateExists": "En skabelon med dette navn findes allerede.", + "templateSaved": "Skabelon \"{{name}}\" gemt.", + "templateLoaded": "Skabelon \"{{name}}\" indlæst.", + "failedLoadTemplate": "Kunne ikke indlæse skabelon.", + "noSettings": "Ingen konfigurerbare indstillinger for denne node.", + "advancedSettings": "Avancerede indstillinger" + } +} diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 61d7a42..70ec209 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -1,318 +1,365 @@ { - "nav": { - "home": "Startseite", - "about": "Über uns", - "contact": "Kontakt", - "licensing": "Lizenzierung", - "allTools": "Alle Werkzeuge", - "openMainMenu": "Hauptmenü öffnen", - "language": "Sprache" + "nav": { + "home": "Startseite", + "about": "Über uns", + "contact": "Kontakt", + "licensing": "Lizenzierung", + "allTools": "Alle Werkzeuge", + "openMainMenu": "Hauptmenü öffnen", + "language": "Sprache" + }, + "donation": { + "message": "Lieben Sie BentoPDF? Helfen Sie uns, es kostenlos und Open Source zu halten!", + "button": "Spenden" + }, + "hero": { + "title": "Das", + "pdfToolkit": "PDF-Toolkit", + "builtForPrivacy": "für maximale Privatsphäre", + "noSignups": "Keine Anmeldung", + "unlimitedUse": "Unbegrenzte Nutzung", + "worksOffline": "Funktioniert offline", + "startUsing": "Jetzt starten" + }, + "usedBy": { + "title": "Verwendet von Unternehmen und Mitarbeitern bei" + }, + "features": { + "title": "Warum", + "bentoPdf": "BentoPDF wählen?", + "noSignup": { + "title": "Keine Anmeldung", + "description": "Sofort starten, keine Konten oder E-Mails erforderlich." }, - "hero": { - "title": "Das", - "pdfToolkit": "PDF-Toolkit", - "builtForPrivacy": "für maximale Privatsphäre", - "noSignups": "Keine Anmeldung", - "unlimitedUse": "Unbegrenzte Nutzung", - "worksOffline": "Funktioniert offline", - "startUsing": "Jetzt starten" + "noUploads": { + "title": "Kein Upload", + "description": "100% clientseitig, Ihre Dateien verlassen nie Ihr Gerät." }, - "usedBy": { - "title": "Verwendet von Unternehmen und Mitarbeitern bei" + "foreverFree": { + "title": "Für immer kostenlos", + "description": "Alle Werkzeuge, keine Testversionen, keine Bezahlschranken." }, - "features": { - "title": "Warum", - "bentoPdf": "BentoPDF wählen?", - "noSignup": { - "title": "Keine Anmeldung", - "description": "Sofort starten, keine Konten oder E-Mails erforderlich." - }, - "noUploads": { - "title": "Kein Upload", - "description": "100% clientseitig, Ihre Dateien verlassen nie Ihr Gerät." - }, - "foreverFree": { - "title": "Für immer kostenlos", - "description": "Alle Werkzeuge, keine Testversionen, keine Bezahlschranken." - }, - "noLimits": { - "title": "Keine Limits", - "description": "Nutzen Sie so viel Sie wollen, ohne versteckte Grenzen." - }, - "batchProcessing": { - "title": "Stapelverarbeitung", - "description": "Verarbeiten Sie unbegrenzt viele PDFs auf einmal." - }, - "lightningFast": { - "title": "Blitzschnell", - "description": "PDFs sofort verarbeiten, ohne Wartezeiten." - } + "noLimits": { + "title": "Keine Limits", + "description": "Nutzen Sie so viel Sie wollen, ohne versteckte Grenzen." }, - "tools": { - "title": "Starten Sie mit", - "toolsLabel": "Werkzeugen", - "subtitle": "Klicken Sie auf ein Werkzeug, um den Datei-Uploader zu öffnen", - "searchPlaceholder": "Werkzeug suchen (z.B. 'teilen', 'organisieren'...)", - "backToTools": "Zurück zu den Werkzeugen" + "batchProcessing": { + "title": "Stapelverarbeitung", + "description": "Verarbeiten Sie unbegrenzt viele PDFs auf einmal." }, - "upload": { - "clickToSelect": "Klicken Sie, um eine Datei auszuwählen", - "orDragAndDrop": "oder per Drag & Drop", - "pdfOrImages": "PDFs oder Bilder", - "filesNeverLeave": "Ihre Dateien verlassen nie Ihr Gerät.", - "addMore": "Weitere Dateien hinzufügen", - "clearAll": "Alle löschen" - }, - "loader": { - "processing": "Verarbeitung..." - }, - "alert": { - "title": "Hinweis", - "ok": "OK" - }, - "preview": { - "title": "Dokumentvorschau", - "downloadAsPdf": "Als PDF herunterladen", - "close": "Schließen" - }, - "settings": { - "title": "Einstellungen", - "shortcuts": "Tastenkürzel", - "preferences": "Voreinstellungen", - "displayPreferences": "Anzeige-Einstellungen", - "searchShortcuts": "Tastenkürzel suchen...", - "shortcutsInfo": "Halten Sie Tasten gedrückt, um ein Kürzel festzulegen. Änderungen werden automatisch gespeichert.", - "shortcutsWarning": "⚠️ Vermeiden Sie gängige Browser-Tastenkürzel (Strg+W, Strg+T, Strg+N usw.), da diese möglicherweise nicht zuverlässig funktionieren.", - "import": "Importieren", - "export": "Exportieren", - "resetToDefaults": "Auf Standard zurücksetzen", - "fullWidthMode": "Vollbreite-Modus", - "fullWidthDescription": "Verwenden Sie die volle Bildschirmbreite für alle Werkzeuge anstelle eines zentrierten Containers", - "settingsAutoSaved": "Einstellungen werden automatisch gespeichert", - "clickToSet": "Klicken zum Festlegen", - "pressKeys": "Tasten drücken...", - "warnings": { - "alreadyInUse": "Tastenkombination bereits vergeben", - "assignedTo": "ist bereits zugewiesen an:", - "chooseDifferent": "Bitte wählen Sie eine andere Tastenkombination.", - "reserved": "Warnung: Reservierte Tastenkombination", - "commonlyUsed": "wird häufig verwendet für:", - "unreliable": "Diese Tastenkombination funktioniert möglicherweise nicht zuverlässig oder könnte mit Browser-/Systemfunktionen in Konflikt geraten.", - "useAnyway": "Möchten Sie sie trotzdem verwenden?", - "resetTitle": "Tastenkombinationen zurücksetzen", - "resetMessage": "Sind Sie sicher, dass Sie alle Tastenkombinationen auf die Standardwerte zurücksetzen möchten?

Diese Aktion kann nicht rückgängig gemacht werden.", - "importSuccessTitle": "Import erfolgreich", - "importSuccessMessage": "Tastenkombinationen erfolgreich importiert!", - "importFailTitle": "Import fehlgeschlagen", - "importFailMessage": "Fehler beim Importieren der Tastenkombinationen. Ungültiges Dateiformat." - } - }, - "warning": { - "title": "Warnung", - "cancel": "Abbrechen", - "proceed": "Fortfahren" - }, - "compliance": { - "title": "Ihre Daten verlassen nie Ihr Gerät", - "weKeep": "Wir schützen", - "yourInfoSafe": "Ihre Informationen", - "byFollowingStandards": "nach globalen Sicherheitsstandards.", - "processingLocal": "Die gesamte Verarbeitung erfolgt lokal auf Ihrem Gerät.", - "gdpr": { - "title": "DSGVO-konform", - "description": "Schützt die personenbezogenen Daten und die Privatsphäre von Personen innerhalb der Europäischen Union." - }, - "ccpa": { - "title": "CCPA-konform", - "description": "Gibt Einwohnern Kaliforniens Rechte darüber, wie ihre persönlichen Daten gesammelt, verwendet und weitergegeben werden." - }, - "hipaa": { - "title": "HIPAA-konform", - "description": "Legt Schutzmaßnahmen für den Umgang mit sensiblen Gesundheitsinformationen im US-Gesundheitssystem fest." - } - }, - "faq": { - "title": "Häufig gestellte", - "questions": "Fragen", - "isFree": { - "question": "Ist BentoPDF wirklich kostenlos?", - "answer": "Ja, absolut. Alle Werkzeuge auf BentoPDF sind zu 100% kostenlos nutzbar, ohne Dateilimits, ohne Anmeldung und ohne Wasserzeichen. Wir glauben, dass jeder Zugang zu einfachen, leistungsstarken PDF-Werkzeugen verdient, ohne Bezahlschranke." - }, - "areFilesSecure": { - "question": "Sind meine Dateien sicher? Wo werden sie verarbeitet?", - "answer": "Ihre Dateien sind so sicher wie möglich, da sie nie Ihren Computer verlassen. Die gesamte Verarbeitung erfolgt direkt in Ihrem Webbrowser (clientseitig). Wir laden Ihre Dateien nie auf einen Server hoch, sodass Sie die vollständige Privatsphäre und Kontrolle über Ihre Dokumente behalten." - }, - "platforms": { - "question": "Funktioniert es auf Mac, Windows und Mobilgeräten?", - "answer": "Ja! Da BentoPDF vollständig in Ihrem Browser läuft, funktioniert es auf jedem Betriebssystem mit einem modernen Webbrowser, einschließlich Windows, macOS, Linux, iOS und Android." - }, - "gdprCompliant": { - "question": "Ist BentoPDF DSGVO-konform?", - "answer": "Ja. BentoPDF ist vollständig DSGVO-konform. Da die gesamte Dateiverarbeitung lokal in Ihrem Browser erfolgt und wir Ihre Dateien nie sammeln oder übertragen, haben wir keinen Zugang zu Ihren Daten. Dies stellt sicher, dass Sie immer die Kontrolle über Ihre Dokumente haben." - }, - "dataStorage": { - "question": "Speichern oder verfolgen Sie meine Dateien?", - "answer": "Nein. Wir speichern, verfolgen oder protokollieren Ihre Dateien niemals. Alles, was Sie auf BentoPDF tun, geschieht im Speicher Ihres Browsers und verschwindet, sobald Sie die Seite schließen. Es gibt keine Uploads, keine Verlaufsprotokolle und keine Server." - }, - "different": { - "question": "Was unterscheidet BentoPDF von anderen PDF-Werkzeugen?", - "answer": "Die meisten PDF-Werkzeuge laden Ihre Dateien zur Verarbeitung auf einen Server hoch. BentoPDF tut das nie. Wir verwenden sichere, moderne Webtechnologie, um Ihre Dateien direkt in Ihrem Browser zu verarbeiten. Das bedeutet schnellere Leistung, stärkere Privatsphäre und vollständige Sicherheit." - }, - "browserBased": { - "question": "Wie schützt mich die browserbasierte Verarbeitung?", - "answer": "Durch die vollständige Ausführung in Ihrem Browser stellt BentoPDF sicher, dass Ihre Dateien nie Ihr Gerät verlassen. Dies eliminiert die Risiken von Server-Hacks, Datenschutzverletzungen oder unbefugtem Zugriff. Ihre Dateien bleiben Ihre — immer." - }, - "analytics": { - "question": "Verwenden Sie Cookies oder Analysen, um mich zu verfolgen?", - "answer": "Uns liegt Ihre Privatsphäre am Herzen. BentoPDF verfolgt keine persönlichen Informationen. Wir verwenden Simple Analytics ausschließlich, um anonyme Besucherzahlen zu sehen. Das bedeutet, wir können wissen, wie viele Benutzer unsere Seite besuchen, aber wir wissen nie, wer Sie sind. Simple Analytics ist vollständig DSGVO-konform und respektiert Ihre Privatsphäre." - } - }, - "testimonials": { - "title": "Was unsere", - "users": "Nutzer", - "say": "sagen" - }, - "support": { - "title": "Gefällt Ihnen meine Arbeit?", - "description": "BentoPDF ist ein Leidenschaftsprojekt, entwickelt um ein kostenloses, privates und leistungsstarkes PDF-Toolkit für alle bereitzustellen. Wenn Sie es nützlich finden, erwägen Sie, die Entwicklung zu unterstützen. Jeder Kaffee hilft!", - "buyMeCoffee": "Kauf mir einen Kaffee" - }, - "footer": { - "copyright": "© 2025 BentoPDF. Alle Rechte vorbehalten.", - "version": "Version", - "company": "Unternehmen", - "aboutUs": "Über uns", - "faqLink": "FAQ", - "contactUs": "Kontakt", - "legal": "Rechtliches", - "termsAndConditions": "Nutzungsbedingungen", - "privacyPolicy": "Datenschutzrichtlinie", - "followUs": "Folgen Sie uns" - }, - "merge": { - "title": "PDFs zusammenführen", - "description": "Kombinieren Sie ganze Dateien oder wählen Sie bestimmte Seiten zum Zusammenführen in ein neues Dokument.", - "fileMode": "Datei-Modus", - "pageMode": "Seiten-Modus", - "howItWorks": "So funktioniert es:", - "fileModeInstructions": [ - "Klicken und ziehen Sie das Symbol, um die Reihenfolge der Dateien zu ändern.", - "Im Feld \"Seiten\" für jede Datei können Sie Bereiche angeben (z.B. \"1-3, 5\"), um nur diese Seiten zusammenzuführen.", - "Lassen Sie das Feld \"Seiten\" leer, um alle Seiten dieser Datei einzuschließen." - ], - "pageModeInstructions": [ - "Alle Seiten Ihrer hochgeladenen PDFs werden unten angezeigt.", - "Ziehen Sie einfach die einzelnen Seitenvorschauen per Drag & Drop, um die gewünschte Reihenfolge für Ihre neue Datei zu erstellen." - ], - "mergePdfs": "PDFs zusammenführen" - }, - "common": { - "page": "Seite", - "pages": "Seiten", - "of": "von", - "download": "Herunterladen", - "cancel": "Abbrechen", - "save": "Speichern", - "delete": "Löschen", - "edit": "Bearbeiten", - "add": "Hinzufügen", - "remove": "Entfernen", - "loading": "Laden...", - "error": "Fehler", - "success": "Erfolg", - "file": "Datei", - "files": "Dateien" - }, - "about": { - "hero": { - "title": "Wir glauben PDF-Werkzeuge sollten", - "subtitle": "schnell, privat und kostenlos sein.", - "noCompromises": "Ohne Kompromisse." - }, - "mission": { - "title": "Unsere Mission", - "description": "Die umfassendste PDF-Toolbox bereitzustellen, die Ihre Privatsphäre respektiert und niemals eine Bezahlung verlangt. Wir glauben, dass wichtige Dokumentenwerkzeuge für jeden, überall und ohne Barrieren zugänglich sein sollten." - }, - "philosophy": { - "label": "Unsere Kernphilosophie", - "title": "Privatsphäre zuerst. Immer.", - "description": "In einer Zeit, in der Daten eine Ware sind, gehen wir einen anderen Weg. Die gesamte Verarbeitung für Bentopdf-Werkzeuge erfolgt lokal in Ihrem Browser. Das bedeutet, Ihre Dateien berühren niemals unsere Server, wir sehen Ihre Dokumente nie und verfolgen nicht, was Sie tun. Ihre Dokumente bleiben vollständig und unmissverständlich privat. Es ist nicht nur eine Funktion; es ist unser Fundament." - }, - "whyBentopdf": { - "title": "Warum", - "speed": { - "title": "Für Geschwindigkeit gebaut", - "description": "Kein Warten auf Uploads oder Downloads zu einem Server. Durch die Verarbeitung von Dateien direkt in Ihrem Browser mit modernen Webtechnologien wie WebAssembly bieten wir unvergleichliche Geschwindigkeit für alle unsere Werkzeuge." - }, - "free": { - "title": "Komplett kostenlos", - "description": "Keine Testversionen, keine Abonnements, keine versteckten Gebühren und keine \"Premium\"-Funktionen als Geiseln. Wir glauben, leistungsstarke PDF-Werkzeuge sollten ein öffentliches Gut sein, kein Profitcenter." - }, - "noAccount": { - "title": "Kein Konto erforderlich", - "description": "Beginnen Sie sofort mit der Nutzung eines beliebigen Werkzeugs. Wir brauchen weder Ihre E-Mail noch ein Passwort oder persönliche Informationen. Ihr Workflow sollte reibungslos und anonym sein." - }, - "openSource": { - "title": "Open-Source-Geist", - "description": "Mit Transparenz im Blick entwickelt. Wir nutzen großartige Open-Source-Bibliotheken wie PDF-lib und PDF.js und glauben an die gemeinschaftsgetriebene Bemühung, leistungsstarke Werkzeuge für alle zugänglich zu machen." - } - }, - "cta": { - "title": "Bereit loszulegen?", - "description": "Schließen Sie sich Tausenden von Benutzern an, die Bentopdf für ihre täglichen Dokumentenbedürfnisse vertrauen. Erleben Sie den Unterschied, den Privatsphäre und Leistung machen können.", - "button": "Alle Werkzeuge erkunden" - } - }, - "contact": { - "title": "Kontakt aufnehmen", - "subtitle": "Wir freuen uns, von Ihnen zu hören. Ob Sie eine Frage, Feedback oder eine Funktionsanfrage haben, zögern Sie nicht, uns zu kontaktieren.", - "email": "Sie können uns direkt per E-Mail erreichen unter:" - }, - "licensing": { - "title": "Lizenzierung für", - "subtitle": "Wählen Sie die Lizenz, die Ihren Anforderungen entspricht." - }, - "multiTool": { - "uploadPdfs": "PDFs hochladen", - "upload": "Hochladen", - "addBlankPage": "Leere Seite hinzufügen", - "edit": "Bearbeiten:", - "undo": "Rückgängig", - "redo": "Wiederholen", - "reset": "Zurücksetzen", - "selection": "Auswahl:", - "selectAll": "Alles auswählen", - "deselectAll": "Auswahl aufheben", - "rotate": "Drehen:", - "rotateLeft": "Links", - "rotateRight": "Rechts", - "transform": "Transformieren:", - "duplicate": "Duplizieren", - "split": "Teilen", - "clear": "Löschen:", - "delete": "Entfernen", - "download": "Download:", - "downloadSelected": "Auswahl herunterladen", - "exportPdf": "PDF exportieren", - "uploadPdfFiles": "PDF-Dateien auswählen", - "dragAndDrop": "PDF-Dateien hierhin ziehen oder klicken zum Auswählen", - "selectFiles": "Dateien auswählen", - "renderingPages": "Seiten werden gerendert...", - "actions": { - "duplicatePage": "Diese Seite duplizieren", - "deletePage": "Diese Seite löschen", - "insertPdf": "PDF nach dieser Seite einfügen", - "toggleSplit": "Trennung nach dieser Seite umschalten" - }, - "pleaseWait": "Bitte warten", - "pagesRendering": "Seiten werden noch gerendert. Bitte warten...", - "noPagesSelected": "Keine Seiten ausgewählt", - "selectOnePage": "Bitte wählen Sie mindestens eine Seite zum Herunterladen aus.", - "noPages": "Keine Seiten", - "noPagesToExport": "Keine Seiten zum Exportieren vorhanden.", - "renderingTitle": "Seiten-Vorschau wird gerendert", - "errorRendering": "Fehler beim Rendern der Seitenvorschau", - "error": "Fehler", - "failedToLoad": "Laden fehlgeschlagen" + "lightningFast": { + "title": "Blitzschnell", + "description": "PDFs sofort verarbeiten, ohne Wartezeiten." } -} \ No newline at end of file + }, + "tools": { + "title": "Starten Sie mit", + "toolsLabel": "Werkzeugen", + "subtitle": "Klicken Sie auf ein Werkzeug, um den Datei-Uploader zu öffnen", + "searchPlaceholder": "Werkzeug suchen (z.B. 'teilen', 'organisieren'...)", + "backToTools": "Zurück zu den Werkzeugen", + "firstLoadNotice": "Der erste Ladevorgang dauert einen Moment, da wir unsere Konvertierungs-Engine herunterladen. Danach erfolgt jedes Laden sofort." + }, + "upload": { + "clickToSelect": "Klicken Sie, um eine Datei auszuwählen", + "orDragAndDrop": "oder per Drag & Drop", + "pdfOrImages": "PDFs oder Bilder", + "filesNeverLeave": "Ihre Dateien verlassen nie Ihr Gerät.", + "addMore": "Weitere Dateien hinzufügen", + "clearAll": "Alle löschen", + "clearFiles": "Dateien löschen", + "hints": { + "singlePdf": "Eine einzelne PDF-Datei", + "pdfFile": "PDF-Datei", + "multiplePdfs2": "Mehrere PDF-Dateien (mindestens 2)", + "bmpImages": "BMP-Bilder", + "oneOrMorePdfs": "Eine oder mehrere PDF-Dateien", + "pdfDocuments": "PDF-Dokumente", + "oneOrMoreCsv": "Eine oder mehrere CSV-Dateien", + "multiplePdfsSupported": "Mehrere PDF-Dateien unterstützt", + "singleOrMultiplePdfs": "Einzelne oder mehrere PDF-Dateien unterstützt", + "singlePdfFile": "Einzelne PDF-Datei", + "pdfWithForms": "PDF-Datei mit Formularfeldern", + "heicImages": "HEIC/HEIF-Bilder", + "jpgImages": "JPG, JPEG, JP2, JPX Bilder", + "pdfsOrImages": "PDFs oder Bilder", + "oneOrMoreOdt": "Eine oder mehrere ODT-Dateien", + "singlePdfOnly": "Nur eine einzelne PDF-Datei", + "pdfFiles": "PDF-Dateien", + "multiplePdfs": "Mehrere PDF-Dateien", + "pngImages": "PNG-Bilder", + "pdfFilesOneOrMore": "PDF-Dateien (eine oder mehrere)", + "oneOrMoreRtf": "Eine oder mehrere RTF-Dateien", + "svgGraphics": "SVG-Grafiken", + "tiffImages": "TIFF-Bilder", + "webpImages": "WebP-Bilder" + } + }, + "loader": { + "processing": "Verarbeitung..." + }, + "alert": { + "title": "Hinweis", + "ok": "OK" + }, + "preview": { + "title": "Dokumentvorschau", + "downloadAsPdf": "Als PDF herunterladen", + "close": "Schließen" + }, + "settings": { + "title": "Einstellungen", + "shortcuts": "Tastenkürzel", + "preferences": "Voreinstellungen", + "displayPreferences": "Anzeige-Einstellungen", + "searchShortcuts": "Tastenkürzel suchen...", + "shortcutsInfo": "Halten Sie Tasten gedrückt, um ein Kürzel festzulegen. Änderungen werden automatisch gespeichert.", + "shortcutsWarning": "⚠️ Vermeiden Sie gängige Browser-Tastenkürzel (Strg+W, Strg+T, Strg+N usw.), da diese möglicherweise nicht zuverlässig funktionieren.", + "import": "Importieren", + "export": "Exportieren", + "resetToDefaults": "Auf Standard zurücksetzen", + "fullWidthMode": "Vollbreite-Modus", + "fullWidthDescription": "Verwenden Sie die volle Bildschirmbreite für alle Werkzeuge anstelle eines zentrierten Containers", + "settingsAutoSaved": "Einstellungen werden automatisch gespeichert", + "clickToSet": "Klicken zum Festlegen", + "pressKeys": "Tasten drücken...", + "warnings": { + "alreadyInUse": "Tastenkombination bereits vergeben", + "assignedTo": "ist bereits zugewiesen an:", + "chooseDifferent": "Bitte wählen Sie eine andere Tastenkombination.", + "reserved": "Warnung: Reservierte Tastenkombination", + "commonlyUsed": "wird häufig verwendet für:", + "unreliable": "Diese Tastenkombination funktioniert möglicherweise nicht zuverlässig oder könnte mit Browser-/Systemfunktionen in Konflikt geraten.", + "useAnyway": "Möchten Sie sie trotzdem verwenden?", + "resetTitle": "Tastenkombinationen zurücksetzen", + "resetMessage": "Sind Sie sicher, dass Sie alle Tastenkombinationen auf die Standardwerte zurücksetzen möchten?

Diese Aktion kann nicht rückgängig gemacht werden.", + "importSuccessTitle": "Import erfolgreich", + "importSuccessMessage": "Tastenkombinationen erfolgreich importiert!", + "importFailTitle": "Import fehlgeschlagen", + "importFailMessage": "Fehler beim Importieren der Tastenkombinationen. Ungültiges Dateiformat." + } + }, + "warning": { + "title": "Warnung", + "cancel": "Abbrechen", + "proceed": "Fortfahren" + }, + "compliance": { + "title": "Ihre Daten verlassen nie Ihr Gerät", + "weKeep": "Wir schützen", + "yourInfoSafe": "Ihre Informationen", + "byFollowingStandards": "nach globalen Sicherheitsstandards.", + "processingLocal": "Die gesamte Verarbeitung erfolgt lokal auf Ihrem Gerät.", + "gdpr": { + "title": "DSGVO-konform", + "description": "Schützt die personenbezogenen Daten und die Privatsphäre von Personen innerhalb der Europäischen Union." + }, + "ccpa": { + "title": "CCPA-konform", + "description": "Gibt Einwohnern Kaliforniens Rechte darüber, wie ihre persönlichen Daten gesammelt, verwendet und weitergegeben werden." + }, + "hipaa": { + "title": "HIPAA-konform", + "description": "Legt Schutzmaßnahmen für den Umgang mit sensiblen Gesundheitsinformationen im US-Gesundheitssystem fest." + } + }, + "faq": { + "title": "Häufig gestellte", + "questions": "Fragen", + "isFree": { + "question": "Ist BentoPDF wirklich kostenlos?", + "answer": "Ja, absolut. Alle Werkzeuge auf BentoPDF sind zu 100% kostenlos nutzbar, ohne Dateilimits, ohne Anmeldung und ohne Wasserzeichen. Wir glauben, dass jeder Zugang zu einfachen, leistungsstarken PDF-Werkzeugen verdient, ohne Bezahlschranke." + }, + "areFilesSecure": { + "question": "Sind meine Dateien sicher? Wo werden sie verarbeitet?", + "answer": "Ihre Dateien sind so sicher wie möglich, da sie nie Ihren Computer verlassen. Die gesamte Verarbeitung erfolgt direkt in Ihrem Webbrowser (clientseitig). Wir laden Ihre Dateien nie auf einen Server hoch, sodass Sie die vollständige Privatsphäre und Kontrolle über Ihre Dokumente behalten." + }, + "platforms": { + "question": "Funktioniert es auf Mac, Windows und Mobilgeräten?", + "answer": "Ja! Da BentoPDF vollständig in Ihrem Browser läuft, funktioniert es auf jedem Betriebssystem mit einem modernen Webbrowser, einschließlich Windows, macOS, Linux, iOS und Android." + }, + "gdprCompliant": { + "question": "Ist BentoPDF DSGVO-konform?", + "answer": "Ja. BentoPDF ist vollständig DSGVO-konform. Da die gesamte Dateiverarbeitung lokal in Ihrem Browser erfolgt und wir Ihre Dateien nie sammeln oder übertragen, haben wir keinen Zugang zu Ihren Daten. Dies stellt sicher, dass Sie immer die Kontrolle über Ihre Dokumente haben." + }, + "dataStorage": { + "question": "Speichern oder verfolgen Sie meine Dateien?", + "answer": "Nein. Wir speichern, verfolgen oder protokollieren Ihre Dateien niemals. Alles, was Sie auf BentoPDF tun, geschieht im Speicher Ihres Browsers und verschwindet, sobald Sie die Seite schließen. Es gibt keine Uploads, keine Verlaufsprotokolle und keine Server." + }, + "different": { + "question": "Was unterscheidet BentoPDF von anderen PDF-Werkzeugen?", + "answer": "Die meisten PDF-Werkzeuge laden Ihre Dateien zur Verarbeitung auf einen Server hoch. BentoPDF tut das nie. Wir verwenden sichere, moderne Webtechnologie, um Ihre Dateien direkt in Ihrem Browser zu verarbeiten. Das bedeutet schnellere Leistung, stärkere Privatsphäre und vollständige Sicherheit." + }, + "browserBased": { + "question": "Wie schützt mich die browserbasierte Verarbeitung?", + "answer": "Durch die vollständige Ausführung in Ihrem Browser stellt BentoPDF sicher, dass Ihre Dateien nie Ihr Gerät verlassen. Dies eliminiert die Risiken von Server-Hacks, Datenschutzverletzungen oder unbefugtem Zugriff. Ihre Dateien bleiben Ihre — immer." + }, + "analytics": { + "question": "Verwenden Sie Cookies oder Analysen, um mich zu verfolgen?", + "answer": "Uns liegt Ihre Privatsphäre am Herzen. BentoPDF verfolgt keine persönlichen Informationen. Wir verwenden Simple Analytics ausschließlich, um anonyme Besucherzahlen zu sehen. Das bedeutet, wir können wissen, wie viele Benutzer unsere Seite besuchen, aber wir wissen nie, wer Sie sind. Simple Analytics ist vollständig DSGVO-konform und respektiert Ihre Privatsphäre." + }, + "sectionTitle": "Häufig gestellte Fragen" + }, + "testimonials": { + "title": "Was unsere", + "users": "Nutzer", + "say": "sagen" + }, + "support": { + "title": "Gefällt Ihnen meine Arbeit?", + "description": "BentoPDF ist ein Leidenschaftsprojekt, entwickelt um ein kostenloses, privates und leistungsstarkes PDF-Toolkit für alle bereitzustellen. Wenn Sie es nützlich finden, erwägen Sie, die Entwicklung zu unterstützen. Jeder Kaffee hilft!", + "buyMeCoffee": "Kauf mir einen Kaffee" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Alle Rechte vorbehalten.", + "version": "Version", + "company": "Unternehmen", + "aboutUs": "Über uns", + "faqLink": "FAQ", + "contactUs": "Kontakt", + "legal": "Rechtliches", + "termsAndConditions": "Nutzungsbedingungen", + "privacyPolicy": "Datenschutzrichtlinie", + "followUs": "Folgen Sie uns" + }, + "merge": { + "title": "PDFs zusammenführen", + "description": "Kombinieren Sie ganze Dateien oder wählen Sie bestimmte Seiten zum Zusammenführen in ein neues Dokument.", + "fileMode": "Datei-Modus", + "pageMode": "Seiten-Modus", + "howItWorks": "So funktioniert es:", + "fileModeInstructions": [ + "Klicken und ziehen Sie das Symbol, um die Reihenfolge der Dateien zu ändern.", + "Im Feld \"Seiten\" für jede Datei können Sie Bereiche angeben (z.B. \"1-3, 5\"), um nur diese Seiten zusammenzuführen.", + "Lassen Sie das Feld \"Seiten\" leer, um alle Seiten dieser Datei einzuschließen." + ], + "pageModeInstructions": [ + "Alle Seiten Ihrer hochgeladenen PDFs werden unten angezeigt.", + "Ziehen Sie einfach die einzelnen Seitenvorschauen per Drag & Drop, um die gewünschte Reihenfolge für Ihre neue Datei zu erstellen." + ], + "mergePdfs": "PDFs zusammenführen" + }, + "common": { + "page": "Seite", + "pages": "Seiten", + "of": "von", + "download": "Herunterladen", + "cancel": "Abbrechen", + "save": "Speichern", + "delete": "Löschen", + "edit": "Bearbeiten", + "add": "Hinzufügen", + "remove": "Entfernen", + "loading": "Laden...", + "error": "Fehler", + "success": "Erfolg", + "file": "Datei", + "files": "Dateien", + "close": "Schließen" + }, + "about": { + "hero": { + "title": "Wir glauben PDF-Werkzeuge sollten", + "subtitle": "schnell, privat und kostenlos sein.", + "noCompromises": "Ohne Kompromisse." + }, + "mission": { + "title": "Unsere Mission", + "description": "Die umfassendste PDF-Toolbox bereitzustellen, die Ihre Privatsphäre respektiert und niemals eine Bezahlung verlangt. Wir glauben, dass wichtige Dokumentenwerkzeuge für jeden, überall und ohne Barrieren zugänglich sein sollten." + }, + "philosophy": { + "label": "Unsere Kernphilosophie", + "title": "Privatsphäre zuerst. Immer.", + "description": "In einer Zeit, in der Daten eine Ware sind, gehen wir einen anderen Weg. Die gesamte Verarbeitung für Bentopdf-Werkzeuge erfolgt lokal in Ihrem Browser. Das bedeutet, Ihre Dateien berühren niemals unsere Server, wir sehen Ihre Dokumente nie und verfolgen nicht, was Sie tun. Ihre Dokumente bleiben vollständig und unmissverständlich privat. Es ist nicht nur eine Funktion; es ist unser Fundament." + }, + "whyBentopdf": { + "title": "Warum", + "speed": { + "title": "Für Geschwindigkeit gebaut", + "description": "Kein Warten auf Uploads oder Downloads zu einem Server. Durch die Verarbeitung von Dateien direkt in Ihrem Browser mit modernen Webtechnologien wie WebAssembly bieten wir unvergleichliche Geschwindigkeit für alle unsere Werkzeuge." + }, + "free": { + "title": "Komplett kostenlos", + "description": "Keine Testversionen, keine Abonnements, keine versteckten Gebühren und keine \"Premium\"-Funktionen als Geiseln. Wir glauben, leistungsstarke PDF-Werkzeuge sollten ein öffentliches Gut sein, kein Profitcenter." + }, + "noAccount": { + "title": "Kein Konto erforderlich", + "description": "Beginnen Sie sofort mit der Nutzung eines beliebigen Werkzeugs. Wir brauchen weder Ihre E-Mail noch ein Passwort oder persönliche Informationen. Ihr Workflow sollte reibungslos und anonym sein." + }, + "openSource": { + "title": "Open-Source-Geist", + "description": "Mit Transparenz im Blick entwickelt. Wir nutzen großartige Open-Source-Bibliotheken wie PDF-lib und PDF.js und glauben an die gemeinschaftsgetriebene Bemühung, leistungsstarke Werkzeuge für alle zugänglich zu machen." + } + }, + "cta": { + "title": "Bereit loszulegen?", + "description": "Schließen Sie sich Tausenden von Benutzern an, die Bentopdf für ihre täglichen Dokumentenbedürfnisse vertrauen. Erleben Sie den Unterschied, den Privatsphäre und Leistung machen können.", + "button": "Alle Werkzeuge erkunden" + } + }, + "contact": { + "title": "Kontakt aufnehmen", + "subtitle": "Wir freuen uns, von Ihnen zu hören. Ob Sie eine Frage, Feedback oder eine Funktionsanfrage haben, zögern Sie nicht, uns zu kontaktieren.", + "email": "Sie können uns direkt per E-Mail erreichen unter:" + }, + "licensing": { + "title": "Lizenzierung für", + "subtitle": "Wählen Sie die Lizenz, die Ihren Anforderungen entspricht." + }, + "multiTool": { + "uploadPdfs": "PDFs hochladen", + "upload": "Hochladen", + "addBlankPage": "Leere Seite hinzufügen", + "edit": "Bearbeiten:", + "undo": "Rückgängig", + "redo": "Wiederholen", + "reset": "Zurücksetzen", + "selection": "Auswahl:", + "selectAll": "Alles auswählen", + "deselectAll": "Auswahl aufheben", + "rotate": "Drehen:", + "rotateLeft": "Links", + "rotateRight": "Rechts", + "transform": "Transformieren:", + "duplicate": "Duplizieren", + "split": "Teilen", + "clear": "Löschen:", + "delete": "Entfernen", + "download": "Download:", + "downloadSelected": "Auswahl herunterladen", + "exportPdf": "PDF exportieren", + "uploadPdfFiles": "PDF-Dateien auswählen", + "dragAndDrop": "PDF-Dateien hierhin ziehen oder klicken zum Auswählen", + "selectFiles": "Dateien auswählen", + "renderingPages": "Seiten werden gerendert...", + "actions": { + "duplicatePage": "Diese Seite duplizieren", + "deletePage": "Diese Seite löschen", + "insertPdf": "PDF nach dieser Seite einfügen", + "toggleSplit": "Trennung nach dieser Seite umschalten" + }, + "pleaseWait": "Bitte warten", + "pagesRendering": "Seiten werden noch gerendert. Bitte warten...", + "noPagesSelected": "Keine Seiten ausgewählt", + "selectOnePage": "Bitte wählen Sie mindestens eine Seite zum Herunterladen aus.", + "noPages": "Keine Seiten", + "noPagesToExport": "Keine Seiten zum Exportieren vorhanden.", + "renderingTitle": "Seiten-Vorschau wird gerendert", + "errorRendering": "Fehler beim Rendern der Seitenvorschau", + "error": "Fehler", + "failedToLoad": "Laden fehlgeschlagen" + }, + "howItWorks": { + "title": "So funktioniert's", + "step1": "Klicken oder Datei hierher ziehen", + "step2": "Klicken Sie auf die Verarbeitungsschaltfläche", + "step3": "Speichern Sie Ihre verarbeitete Datei sofort" + }, + "relatedTools": { + "title": "Verwandte PDF-Tools" + }, + "simpleMode": { + "title": "PDF-Werkzeuge", + "subtitle": "Wählen Sie ein Werkzeug aus, um zu beginnen" + } +} diff --git a/public/locales/de/tools.json b/public/locales/de/tools.json index 9aed2ce..ea93f3a 100644 --- a/public/locales/de/tools.json +++ b/public/locales/de/tools.json @@ -1,282 +1,631 @@ { - "categories": { - "popularTools": "Beliebte Werkzeuge", - "editAnnotate": "Bearbeiten & Annotieren", - "convertToPdf": "In PDF konvertieren", - "convertFromPdf": "Aus PDF konvertieren", - "organizeManage": "Organisieren & Verwalten", - "optimizeRepair": "Optimieren & Reparieren", - "securePdf": "PDF sichern" - }, - "pdfMultiTool": { - "name": "PDF Multi-Werkzeug", - "subtitle": "Zusammenführen, Teilen, Organisieren, Löschen, Drehen, Leere Seiten hinzufügen, Extrahieren und Duplizieren in einer einheitlichen Oberfläche." - }, - "mergePdf": { - "name": "PDF zusammenführen", - "subtitle": "Mehrere PDFs zu einer Datei kombinieren. Lesezeichen werden beibehalten." - }, - "splitPdf": { - "name": "PDF teilen", - "subtitle": "Einen Seitenbereich in eine neue PDF extrahieren." - }, - "compressPdf": { - "name": "PDF komprimieren", - "subtitle": "Die Dateigröße Ihrer PDF reduzieren." - }, - "pdfEditor": { - "name": "PDF-Editor", - "subtitle": "Annotieren, hervorheben, schwärzen, kommentieren, Formen/Bilder hinzufügen, suchen und PDFs anzeigen." - }, - "jpgToPdf": { - "name": "JPG zu PDF", - "subtitle": "Eine PDF aus einem oder mehreren JPG-Bildern erstellen." - }, - "signPdf": { - "name": "PDF unterschreiben", - "subtitle": "Zeichnen, tippen oder laden Sie Ihre Unterschrift hoch." - }, - "cropPdf": { - "name": "PDF zuschneiden", - "subtitle": "Die Ränder jeder Seite in Ihrer PDF beschneiden." - }, - "extractPages": { - "name": "Seiten extrahieren", - "subtitle": "Eine Auswahl von Seiten als neue Dateien speichern." - }, - "duplicateOrganize": { - "name": "Duplizieren & Organisieren", - "subtitle": "Seiten duplizieren, neu anordnen und löschen." - }, - "deletePages": { - "name": "Seiten löschen", - "subtitle": "Bestimmte Seiten aus Ihrem Dokument entfernen." - }, - "editBookmarks": { - "name": "Lesezeichen bearbeiten", - "subtitle": "PDF-Lesezeichen hinzufügen, bearbeiten, importieren, löschen und extrahieren." - }, - "tableOfContents": { - "name": "Inhaltsverzeichnis", - "subtitle": "Ein Inhaltsverzeichnis aus PDF-Lesezeichen generieren." - }, - "pageNumbers": { - "name": "Seitenzahlen", - "subtitle": "Seitenzahlen in Ihr Dokument einfügen." - }, - "addWatermark": { - "name": "Wasserzeichen hinzufügen", - "subtitle": "Text oder ein Bild über Ihre PDF-Seiten stempeln." - }, - "headerFooter": { - "name": "Kopf- & Fußzeile", - "subtitle": "Text oben und unten auf Seiten hinzufügen." - }, - "invertColors": { - "name": "Farben invertieren", - "subtitle": "Eine \"Dunkelmodus\"-Version Ihrer PDF erstellen." - }, - "backgroundColor": { - "name": "Hintergrundfarbe", - "subtitle": "Die Hintergrundfarbe Ihrer PDF ändern." - }, - "changeTextColor": { - "name": "Textfarbe ändern", - "subtitle": "Die Farbe des Textes in Ihrer PDF ändern." - }, - "addStamps": { - "name": "Stempel hinzufügen", - "subtitle": "Bildstempel zu Ihrer PDF über die Annotations-Symbolleiste hinzufügen.", - "usernameLabel": "Stempel-Benutzername", - "usernamePlaceholder": "Geben Sie Ihren Namen ein (für Stempel)", - "usernameHint": "Dieser Name erscheint auf von Ihnen erstellten Stempeln." - }, - "removeAnnotations": { - "name": "Annotationen entfernen", - "subtitle": "Kommentare, Hervorhebungen und Links entfernen." - }, - "pdfFormFiller": { - "name": "PDF-Formular ausfüllen", - "subtitle": "Formulare direkt im Browser ausfüllen. Unterstützt auch XFA-Formulare." - }, - "createPdfForm": { - "name": "PDF-Formular erstellen", - "subtitle": "Ausfüllbare PDF-Formulare mit Drag-and-Drop-Textfeldern erstellen." - }, - "removeBlankPages": { - "name": "Leere Seiten entfernen", - "subtitle": "Leere Seiten automatisch erkennen und löschen." - }, - "imageToPdf": { - "name": "Bild zu PDF", - "subtitle": "JPG, PNG, WebP, BMP, TIFF, SVG, HEIC in PDF konvertieren." - }, - "pngToPdf": { - "name": "PNG zu PDF", - "subtitle": "Eine PDF aus einem oder mehreren PNG-Bildern erstellen." - }, - "webpToPdf": { - "name": "WebP zu PDF", - "subtitle": "Eine PDF aus einem oder mehreren WebP-Bildern erstellen." - }, - "svgToPdf": { - "name": "SVG zu PDF", - "subtitle": "Eine PDF aus einem oder mehreren SVG-Bildern erstellen." - }, - "bmpToPdf": { - "name": "BMP zu PDF", - "subtitle": "Eine PDF aus einem oder mehreren BMP-Bildern erstellen." - }, - "heicToPdf": { - "name": "HEIC zu PDF", - "subtitle": "Eine PDF aus einem oder mehreren HEIC-Bildern erstellen." - }, - "tiffToPdf": { - "name": "TIFF zu PDF", - "subtitle": "Eine PDF aus einem oder mehreren TIFF-Bildern erstellen." - }, - "textToPdf": { - "name": "Text zu PDF", - "subtitle": "Eine Textdatei in eine PDF konvertieren." - }, - "jsonToPdf": { - "name": "JSON zu PDF", - "subtitle": "JSON-Dateien in PDF-Format konvertieren." - }, - "pdfToJpg": { - "name": "PDF zu JPG", - "subtitle": "Jede PDF-Seite in ein JPG-Bild konvertieren." - }, - "pdfToPng": { - "name": "PDF zu PNG", - "subtitle": "Jede PDF-Seite in ein PNG-Bild konvertieren." - }, - "pdfToWebp": { - "name": "PDF zu WebP", - "subtitle": "Jede PDF-Seite in ein WebP-Bild konvertieren." - }, - "pdfToBmp": { - "name": "PDF zu BMP", - "subtitle": "Jede PDF-Seite in ein BMP-Bild konvertieren." - }, - "pdfToTiff": { - "name": "PDF zu TIFF", - "subtitle": "Jede PDF-Seite in ein TIFF-Bild konvertieren." - }, - "pdfToGreyscale": { - "name": "PDF zu Graustufen", - "subtitle": "Alle Farben in Schwarz-Weiß konvertieren." - }, - "pdfToJson": { - "name": "PDF zu JSON", - "subtitle": "PDF-Dateien in JSON-Format konvertieren." - }, - "ocrPdf": { - "name": "OCR PDF", - "subtitle": "Eine PDF durchsuchbar und kopierbar machen." - }, - "alternateMix": { - "name": "Seiten abwechselnd mischen", - "subtitle": "PDFs durch abwechselnde Seiten aus jedem PDF zusammenführen. Lesezeichen werden beibehalten." - }, - "addAttachments": { - "name": "Anhänge hinzufügen", - "subtitle": "Eine oder mehrere Dateien in Ihre PDF einbetten." - }, - "extractAttachments": { - "name": "Anhänge extrahieren", - "subtitle": "Alle eingebetteten Dateien aus PDF(s) als ZIP extrahieren." - }, - "editAttachments": { - "name": "Anhänge bearbeiten", - "subtitle": "Anhänge in Ihrer PDF anzeigen oder entfernen." - }, - "dividePages": { - "name": "Seiten teilen", - "subtitle": "Seiten horizontal oder vertikal teilen." - }, - "addBlankPage": { - "name": "Leere Seite hinzufügen", - "subtitle": "Eine leere Seite an beliebiger Stelle in Ihre PDF einfügen." - }, - "reversePages": { - "name": "Seiten umkehren", - "subtitle": "Die Reihenfolge aller Seiten in Ihrem Dokument umkehren." - }, - "rotatePdf": { - "name": "PDF drehen", - "subtitle": "Seiten in 90-Grad-Schritten drehen." - }, - "nUpPdf": { - "name": "N-Up PDF", - "subtitle": "Mehrere Seiten auf einem einzigen Blatt anordnen." - }, - "combineToSinglePage": { - "name": "Zu einer Seite kombinieren", - "subtitle": "Alle Seiten zu einem fortlaufenden Dokument zusammenfügen." - }, - "viewMetadata": { - "name": "Metadaten anzeigen", - "subtitle": "Die versteckten Eigenschaften Ihrer PDF inspizieren." - }, - "editMetadata": { - "name": "Metadaten bearbeiten", - "subtitle": "Autor, Titel und andere Eigenschaften ändern." - }, - "pdfsToZip": { - "name": "PDFs zu ZIP", - "subtitle": "Mehrere PDF-Dateien in ein ZIP-Archiv packen." - }, - "comparePdfs": { - "name": "PDFs vergleichen", - "subtitle": "Zwei PDFs nebeneinander vergleichen." - }, - "posterizePdf": { - "name": "PDF posterisieren", - "subtitle": "Eine große Seite in mehrere kleinere Seiten aufteilen." - }, - "fixPageSize": { - "name": "Seitengröße reparieren", - "subtitle": "Alle Seiten auf eine einheitliche Größe standardisieren." - }, - "linearizePdf": { - "name": "PDF linearisieren", - "subtitle": "PDF für schnelle Web-Anzeige optimieren." - }, - "pageDimensions": { - "name": "Seitenmaße", - "subtitle": "Seitengröße, Ausrichtung und Einheiten analysieren." - }, - "removeRestrictions": { - "name": "Beschränkungen entfernen", - "subtitle": "Passwortschutz und Sicherheitsbeschränkungen von digital signierten PDF-Dateien entfernen." - }, - "repairPdf": { - "name": "PDF reparieren", - "subtitle": "Daten aus beschädigten PDF-Dateien wiederherstellen." - }, - "encryptPdf": { - "name": "PDF verschlüsseln", - "subtitle": "Ihre PDF durch Hinzufügen eines Passworts sperren." - }, - "sanitizePdf": { - "name": "PDF bereinigen", - "subtitle": "Metadaten, Annotationen, Skripte und mehr entfernen." - }, - "decryptPdf": { - "name": "PDF entschlüsseln", - "subtitle": "PDF durch Entfernen des Passwortschutzes entsperren." - }, - "flattenPdf": { - "name": "PDF reduzieren", - "subtitle": "Formularfelder und Annotationen nicht editierbar machen." - }, - "removeMetadata": { - "name": "Metadaten entfernen", - "subtitle": "Versteckte Daten aus Ihrer PDF entfernen." - }, - "changePermissions": { - "name": "Berechtigungen ändern", - "subtitle": "Benutzerberechtigungen für eine PDF festlegen oder ändern." - } -} \ No newline at end of file + "categories": { + "popularTools": "Beliebte Werkzeuge", + "editAnnotate": "Bearbeiten & Annotieren", + "convertToPdf": "In PDF konvertieren", + "convertFromPdf": "Aus PDF konvertieren", + "organizeManage": "Organisieren & Verwalten", + "optimizeRepair": "Optimieren & Reparieren", + "securePdf": "PDF sichern" + }, + "pdfMultiTool": { + "name": "PDF Multi-Werkzeug", + "subtitle": "Zusammenführen, Teilen, Organisieren, Löschen, Drehen, Leere Seiten hinzufügen, Extrahieren und Duplizieren in einer einheitlichen Oberfläche." + }, + "mergePdf": { + "name": "PDF zusammenführen", + "subtitle": "Mehrere PDFs zu einer Datei kombinieren. Lesezeichen werden beibehalten." + }, + "splitPdf": { + "name": "PDF teilen", + "subtitle": "Einen Seitenbereich in eine neue PDF extrahieren." + }, + "compressPdf": { + "name": "PDF komprimieren", + "subtitle": "Die Dateigröße Ihrer PDF reduzieren.", + "algorithmLabel": "Komprimierungsalgorithmus", + "condense": "Condense (Empfohlen)", + "photon": "Photon (Für bildlastige PDFs)", + "condenseInfo": "Condense nutzt fortschrittliche Komprimierung: entfernt unnötige Daten, optimiert Bilder, reduziert Schriftarten. Optimal für die meisten PDFs.", + "photonInfo": "Photon wandelt Seiten in Bilder um. Für bildlastige/gescannte PDFs.", + "photonWarning": "Warnung: Text wird nicht mehr auswählbar und Links funktionieren nicht mehr.", + "levelLabel": "Komprimierungsstufe", + "light": "Leicht (Qualität erhalten)", + "balanced": "Ausgewogen (Empfohlen)", + "aggressive": "Aggressiv (Kleinere Dateien)", + "extreme": "Extrem (Maximale Komprimierung)", + "grayscale": "In Graustufen umwandeln", + "grayscaleHint": "Reduziert Dateigröße durch Entfernen von Farbinformationen", + "customSettings": "Erweiterte Einstellungen", + "customSettingsHint": "Komprimierungsparameter anpassen:", + "outputQuality": "Ausgabequalität", + "resizeImagesTo": "Bilder anpassen auf", + "onlyProcessAbove": "Nur verarbeiten über", + "removeMetadata": "Metadaten entfernen", + "subsetFonts": "Schriftarten optimieren (ungenutzte Zeichen entfernen)", + "removeThumbnails": "Eingebettete Miniaturansichten entfernen", + "compressButton": "PDF komprimieren" + }, + "pdfEditor": { + "name": "PDF-Editor", + "subtitle": "Annotieren, hervorheben, schwärzen, kommentieren, Formen/Bilder hinzufügen, suchen und PDFs anzeigen." + }, + "jpgToPdf": { + "name": "JPG zu PDF", + "subtitle": "Eine PDF aus JPG, JPEG und JPEG2000 (JP2/JPX) Bildern erstellen." + }, + "signPdf": { + "name": "PDF unterschreiben", + "subtitle": "Zeichnen, tippen oder laden Sie Ihre Unterschrift hoch." + }, + "cropPdf": { + "name": "PDF zuschneiden", + "subtitle": "Die Ränder jeder Seite in Ihrer PDF beschneiden." + }, + "extractPages": { + "name": "Seiten extrahieren", + "subtitle": "Eine Auswahl von Seiten als neue Dateien speichern." + }, + "duplicateOrganize": { + "name": "Duplizieren & Organisieren", + "subtitle": "Seiten duplizieren, neu anordnen und löschen." + }, + "deletePages": { + "name": "Seiten löschen", + "subtitle": "Bestimmte Seiten aus Ihrem Dokument entfernen." + }, + "editBookmarks": { + "name": "Lesezeichen bearbeiten", + "subtitle": "PDF-Lesezeichen hinzufügen, bearbeiten, importieren, löschen und extrahieren." + }, + "tableOfContents": { + "name": "Inhaltsverzeichnis", + "subtitle": "Ein Inhaltsverzeichnis aus PDF-Lesezeichen generieren." + }, + "pageNumbers": { + "name": "Seitenzahlen", + "subtitle": "Seitenzahlen in Ihr Dokument einfügen." + }, + "batesNumbering": { + "name": "Bates-Nummerierung", + "subtitle": "Fortlaufende Bates-Nummern über eine oder mehrere PDF-Dateien hinzufügen." + }, + "addWatermark": { + "name": "Wasserzeichen hinzufügen", + "subtitle": "Text oder ein Bild über Ihre PDF-Seiten stempeln.", + "applyToAllPages": "Auf alle Seiten anwenden" + }, + "headerFooter": { + "name": "Kopf- & Fußzeile", + "subtitle": "Text oben und unten auf Seiten hinzufügen." + }, + "invertColors": { + "name": "Farben invertieren", + "subtitle": "Eine \"Dunkelmodus\"-Version Ihrer PDF erstellen." + }, + "scannerEffect": { + "name": "Scanner-Effekt", + "subtitle": "Lassen Sie Ihr PDF wie ein gescanntes Dokument aussehen.", + "scanSettings": "Scan-Einstellungen", + "colorspace": "Farbraum", + "gray": "Grau", + "border": "Rand", + "rotate": "Drehen", + "rotateVariance": "Drehvarianz", + "brightness": "Helligkeit", + "contrast": "Kontrast", + "blur": "Unschärfe", + "noise": "Rauschen", + "yellowish": "Gelbstich", + "resolution": "Auflösung", + "processButton": "Scanner-Effekt anwenden" + }, + "adjustColors": { + "name": "Farben anpassen", + "subtitle": "Helligkeit, Kontrast, Sättigung und mehr in Ihrem PDF feinabstimmen.", + "colorSettings": "Farbeinstellungen", + "brightness": "Helligkeit", + "contrast": "Kontrast", + "saturation": "Sättigung", + "hueShift": "Farbton", + "temperature": "Temperatur", + "tint": "Tönung", + "gamma": "Gamma", + "sepia": "Sepia", + "processButton": "Farbanpassungen anwenden" + }, + "backgroundColor": { + "name": "Hintergrundfarbe", + "subtitle": "Die Hintergrundfarbe Ihrer PDF ändern." + }, + "changeTextColor": { + "name": "Textfarbe ändern", + "subtitle": "Die Farbe des Textes in Ihrer PDF ändern." + }, + "addStamps": { + "name": "Stempel hinzufügen", + "subtitle": "Bildstempel zu Ihrer PDF über die Annotations-Symbolleiste hinzufügen.", + "usernameLabel": "Stempel-Benutzername", + "usernamePlaceholder": "Geben Sie Ihren Namen ein (für Stempel)", + "usernameHint": "Dieser Name erscheint auf von Ihnen erstellten Stempeln." + }, + "removeAnnotations": { + "name": "Annotationen entfernen", + "subtitle": "Kommentare, Hervorhebungen und Links entfernen." + }, + "pdfFormFiller": { + "name": "PDF-Formular ausfüllen", + "subtitle": "Formulare direkt im Browser ausfüllen. Unterstützt auch XFA-Formulare." + }, + "createPdfForm": { + "name": "PDF-Formular erstellen", + "subtitle": "Ausfüllbare PDF-Formulare mit Drag-and-Drop-Textfeldern erstellen." + }, + "removeBlankPages": { + "name": "Leere Seiten entfernen", + "subtitle": "Leere Seiten automatisch erkennen und löschen.", + "sensitivityHint": "Höher = strenger, nur rein leere Seiten. Niedriger = erlaubt Seiten mit etwas Inhalt." + }, + "imageToPdf": { + "name": "Bilder zu PDF", + "subtitle": "JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP in PDF konvertieren." + }, + "pngToPdf": { + "name": "PNG zu PDF", + "subtitle": "Eine PDF aus einem oder mehreren PNG-Bildern erstellen." + }, + "webpToPdf": { + "name": "WebP zu PDF", + "subtitle": "Eine PDF aus einem oder mehreren WebP-Bildern erstellen." + }, + "svgToPdf": { + "name": "SVG zu PDF", + "subtitle": "Eine PDF aus einem oder mehreren SVG-Bildern erstellen." + }, + "bmpToPdf": { + "name": "BMP zu PDF", + "subtitle": "Eine PDF aus einem oder mehreren BMP-Bildern erstellen." + }, + "heicToPdf": { + "name": "HEIC zu PDF", + "subtitle": "Eine PDF aus einem oder mehreren HEIC-Bildern erstellen." + }, + "tiffToPdf": { + "name": "TIFF zu PDF", + "subtitle": "Eine PDF aus einem oder mehreren TIFF-Bildern erstellen." + }, + "textToPdf": { + "name": "Text zu PDF", + "subtitle": "Eine Textdatei in eine PDF konvertieren." + }, + "jsonToPdf": { + "name": "JSON zu PDF", + "subtitle": "JSON-Dateien in PDF-Format konvertieren." + }, + "pdfToJpg": { + "name": "PDF zu JPG", + "subtitle": "Jede PDF-Seite in ein JPG-Bild konvertieren." + }, + "pdfToPng": { + "name": "PDF zu PNG", + "subtitle": "Jede PDF-Seite in ein PNG-Bild konvertieren." + }, + "pdfToWebp": { + "name": "PDF zu WebP", + "subtitle": "Jede PDF-Seite in ein WebP-Bild konvertieren." + }, + "pdfToBmp": { + "name": "PDF zu BMP", + "subtitle": "Jede PDF-Seite in ein BMP-Bild konvertieren." + }, + "pdfToTiff": { + "name": "PDF zu TIFF", + "subtitle": "Jede PDF-Seite in ein TIFF-Bild konvertieren." + }, + "pdfToGreyscale": { + "name": "PDF zu Graustufen", + "subtitle": "Alle Farben in Schwarz-Weiß konvertieren." + }, + "pdfToJson": { + "name": "PDF zu JSON", + "subtitle": "PDF-Dateien in JSON-Format konvertieren." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Eine PDF durchsuchbar und kopierbar machen." + }, + "alternateMix": { + "name": "Seiten abwechselnd mischen", + "subtitle": "PDFs durch abwechselnde Seiten aus jedem PDF zusammenführen. Lesezeichen werden beibehalten." + }, + "addAttachments": { + "name": "Anhänge hinzufügen", + "subtitle": "Eine oder mehrere Dateien in Ihre PDF einbetten." + }, + "extractAttachments": { + "name": "Anhänge extrahieren", + "subtitle": "Alle eingebetteten Dateien aus PDF(s) als ZIP extrahieren." + }, + "editAttachments": { + "name": "Anhänge bearbeiten", + "subtitle": "Anhänge in Ihrer PDF anzeigen oder entfernen." + }, + "dividePages": { + "name": "Seiten teilen", + "subtitle": "Seiten horizontal oder vertikal teilen." + }, + "addBlankPage": { + "name": "Leere Seite hinzufügen", + "subtitle": "Eine leere Seite an beliebiger Stelle in Ihre PDF einfügen." + }, + "reversePages": { + "name": "Seiten umkehren", + "subtitle": "Die Reihenfolge aller Seiten in Ihrem Dokument umkehren." + }, + "rotatePdf": { + "name": "PDF drehen", + "subtitle": "Seiten in 90-Grad-Schritten drehen." + }, + "rotateCustom": { + "name": "Um benutzerdefinierte Grad drehen", + "subtitle": "Seiten um einen beliebigen Winkel drehen." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "Mehrere Seiten auf einem einzigen Blatt anordnen." + }, + "combineToSinglePage": { + "name": "Zu einer Seite kombinieren", + "subtitle": "Alle Seiten zu einem fortlaufenden Dokument zusammenfügen." + }, + "viewMetadata": { + "name": "Metadaten anzeigen", + "subtitle": "Die versteckten Eigenschaften Ihrer PDF inspizieren." + }, + "editMetadata": { + "name": "Metadaten bearbeiten", + "subtitle": "Autor, Titel und andere Eigenschaften ändern." + }, + "pdfsToZip": { + "name": "PDFs zu ZIP", + "subtitle": "Mehrere PDF-Dateien in ein ZIP-Archiv packen." + }, + "comparePdfs": { + "name": "PDFs vergleichen", + "subtitle": "Zwei PDFs nebeneinander vergleichen." + }, + "posterizePdf": { + "name": "PDF posterisieren", + "subtitle": "Eine große Seite in mehrere kleinere Seiten aufteilen." + }, + "fixPageSize": { + "name": "Seitengröße reparieren", + "subtitle": "Alle Seiten auf eine einheitliche Größe standardisieren." + }, + "linearizePdf": { + "name": "PDF linearisieren", + "subtitle": "PDF für schnelle Web-Anzeige optimieren." + }, + "pageDimensions": { + "name": "Seitenmaße", + "subtitle": "Seitengröße, Ausrichtung und Einheiten analysieren." + }, + "removeRestrictions": { + "name": "Beschränkungen entfernen", + "subtitle": "Passwortschutz und Sicherheitsbeschränkungen von digital signierten PDF-Dateien entfernen." + }, + "repairPdf": { + "name": "PDF reparieren", + "subtitle": "Daten aus beschädigten PDF-Dateien wiederherstellen." + }, + "encryptPdf": { + "name": "PDF verschlüsseln", + "subtitle": "Ihre PDF durch Hinzufügen eines Passworts sperren." + }, + "sanitizePdf": { + "name": "PDF bereinigen", + "subtitle": "Metadaten, Annotationen, Skripte und mehr entfernen." + }, + "decryptPdf": { + "name": "PDF entschlüsseln", + "subtitle": "PDF durch Entfernen des Passwortschutzes entsperren." + }, + "flattenPdf": { + "name": "PDF reduzieren", + "subtitle": "Formularfelder und Annotationen nicht editierbar machen." + }, + "removeMetadata": { + "name": "Metadaten entfernen", + "subtitle": "Versteckte Daten aus Ihrer PDF entfernen." + }, + "changePermissions": { + "name": "Berechtigungen ändern", + "subtitle": "Benutzerberechtigungen für eine PDF festlegen oder ändern." + }, + "odtToPdf": { + "name": "ODT zu PDF", + "subtitle": "OpenDocument Text-Dateien in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "ODT-Dateien", + "convertButton": "In PDF konvertieren" + }, + "csvToPdf": { + "name": "CSV zu PDF", + "subtitle": "CSV-Tabellendateien in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "CSV-Dateien", + "convertButton": "In PDF konvertieren" + }, + "rtfToPdf": { + "name": "RTF zu PDF", + "subtitle": "Rich Text Format-Dokumente in PDF konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "RTF-Dateien", + "convertButton": "In PDF konvertieren" + }, + "wordToPdf": { + "name": "Word zu PDF", + "subtitle": "Word-Dokumente (DOCX, DOC, ODT, RTF) in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "DOCX, DOC, ODT, RTF-Dateien", + "convertButton": "In PDF konvertieren" + }, + "excelToPdf": { + "name": "Excel zu PDF", + "subtitle": "Excel-Tabellen (XLSX, XLS, ODS, CSV) in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "XLSX, XLS, ODS, CSV-Dateien", + "convertButton": "In PDF konvertieren" + }, + "powerpointToPdf": { + "name": "PowerPoint zu PDF", + "subtitle": "PowerPoint-Präsentationen (PPTX, PPT, ODP) in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "PPTX, PPT, ODP-Dateien", + "convertButton": "In PDF konvertieren" + }, + "markdownToPdf": { + "name": "Markdown zu PDF", + "subtitle": "Schreiben oder fügen Sie Markdown ein und exportieren Sie es als schön formatiertes PDF.", + "paneMarkdown": "Markdown", + "panePreview": "Vorschau", + "btnUpload": "Hochladen", + "btnSyncScroll": "Sync-Scrollen", + "btnSettings": "Einstellungen", + "btnExportPdf": "PDF exportieren", + "settingsTitle": "Markdown-Einstellungen", + "settingsPreset": "Voreinstellung", + "presetDefault": "Standard (GFM-ähnlich)", + "presetCommonmark": "CommonMark (strikt)", + "presetZero": "Minimal (keine Funktionen)", + "settingsOptions": "Markdown-Optionen", + "optAllowHtml": "HTML-Tags erlauben", + "optBreaks": "Zeilenumbrüche in
umwandeln", + "optLinkify": "URLs automatisch in Links umwandeln", + "optTypographer": "Typograf (intelligente Anführungszeichen usw.)" + }, + "pdfBooklet": { + "name": "PDF-Broschüre", + "subtitle": "Seiten für beidseitigen Broschürendruck neu anordnen. Falten und heften zum Erstellen einer Broschüre.", + "howItWorks": "So funktioniert es:", + "step1": "Eine PDF-Datei hochladen.", + "step2": "Die Seiten werden in Broschürenreihenfolge neu angeordnet.", + "step3": "Beidseitig drucken, an der kurzen Kante wenden, falten und heften.", + "paperSize": "Papiergröße", + "orientation": "Ausrichtung", + "portrait": "Hochformat", + "landscape": "Querformat", + "pagesPerSheet": "Seiten pro Blatt", + "createBooklet": "Broschüre erstellen", + "processing": "Verarbeitung...", + "pageCount": "Die Seitenzahl wird bei Bedarf auf ein Vielfaches von 4 aufgerundet." + }, + "xpsToPdf": { + "name": "XPS zu PDF", + "subtitle": "XPS/OXPS-Dokumente in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "XPS, OXPS-Dateien", + "convertButton": "In PDF konvertieren" + }, + "mobiToPdf": { + "name": "MOBI zu PDF", + "subtitle": "MOBI-E-Books in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "MOBI-Dateien", + "convertButton": "In PDF konvertieren" + }, + "epubToPdf": { + "name": "EPUB zu PDF", + "subtitle": "EPUB-E-Books in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "EPUB-Dateien", + "convertButton": "In PDF konvertieren" + }, + "fb2ToPdf": { + "name": "FB2 zu PDF", + "subtitle": "FictionBook (FB2) E-Books in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "FB2-Dateien", + "convertButton": "In PDF konvertieren" + }, + "cbzToPdf": { + "name": "CBZ zu PDF", + "subtitle": "Comic-Archive (CBZ/CBR) in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "CBZ, CBR-Dateien", + "convertButton": "In PDF konvertieren" + }, + "wpdToPdf": { + "name": "WPD zu PDF", + "subtitle": "WordPerfect-Dokumente (WPD) in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "WPD-Dateien", + "convertButton": "In PDF konvertieren" + }, + "wpsToPdf": { + "name": "WPS zu PDF", + "subtitle": "WPS Office-Dokumente in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "WPS-Dateien", + "convertButton": "In PDF konvertieren" + }, + "xmlToPdf": { + "name": "XML zu PDF", + "subtitle": "XML-Dokumente in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "XML-Dateien", + "convertButton": "In PDF konvertieren" + }, + "pagesToPdf": { + "name": "Pages zu PDF", + "subtitle": "Apple Pages-Dokumente in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "Pages-Dateien", + "convertButton": "In PDF konvertieren" + }, + "odgToPdf": { + "name": "ODG zu PDF", + "subtitle": "OpenDocument Graphics (ODG) Dateien in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "ODG-Dateien", + "convertButton": "In PDF konvertieren" + }, + "odsToPdf": { + "name": "ODS zu PDF", + "subtitle": "OpenDocument Spreadsheet (ODS) Dateien in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "ODS-Dateien", + "convertButton": "In PDF konvertieren" + }, + "odpToPdf": { + "name": "ODP zu PDF", + "subtitle": "OpenDocument Presentation (ODP) Dateien in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "ODP-Dateien", + "convertButton": "In PDF konvertieren" + }, + "pubToPdf": { + "name": "PUB zu PDF", + "subtitle": "Microsoft Publisher (PUB) Dateien in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "PUB-Dateien", + "convertButton": "In PDF konvertieren" + }, + "vsdToPdf": { + "name": "VSD zu PDF", + "subtitle": "Microsoft Visio (VSD, VSDX) Dateien in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "VSD, VSDX-Dateien", + "convertButton": "In PDF konvertieren" + }, + "psdToPdf": { + "name": "PSD zu PDF", + "subtitle": "Adobe Photoshop (PSD) Dateien in PDF-Format konvertieren. Unterstützt mehrere Dateien.", + "acceptedFormats": "PSD-Dateien", + "convertButton": "In PDF konvertieren" + }, + "pdfToSvg": { + "name": "PDF zu SVG", + "subtitle": "Jede Seite einer PDF-Datei in eine skalierbare Vektorgrafik (SVG) konvertieren für perfekte Qualität in jeder Größe." + }, + "extractTables": { + "name": "PDF-Tabellen extrahieren", + "subtitle": "Tabellen aus PDF-Dateien extrahieren und als CSV, JSON oder Markdown exportieren." + }, + "pdfToCsv": { + "name": "PDF zu CSV", + "subtitle": "Tabellen aus PDF extrahieren und in CSV-Format konvertieren." + }, + "pdfToExcel": { + "name": "PDF zu Excel", + "subtitle": "Tabellen aus PDF extrahieren und in Excel (XLSX) Format konvertieren." + }, + "pdfToText": { + "name": "PDF zu Text", + "subtitle": "Text aus PDF-Dateien extrahieren und als Textdatei (.txt) speichern. Unterstützt mehrere Dateien.", + "note": "Dieses Tool funktioniert NUR mit digital erstellten PDFs. Für gescannte Dokumente oder bildbasierte PDFs verwenden Sie stattdessen unser OCR PDF-Tool.", + "convertButton": "Text extrahieren" + }, + "digitalSignPdf": { + "name": "Digitale PDF-Signatur", + "pageTitle": "Digitale PDF-Signatur - Kryptografische Signatur hinzufügen | BentoPDF", + "subtitle": "Fügen Sie eine kryptografische digitale Signatur mit X.509-Zertifikaten zu Ihrer PDF hinzu. Unterstützt PKCS#12 (.pfx, .p12) und PEM-Formate. Ihr privater Schlüssel verlässt niemals Ihren Browser.", + "certificateSection": "Zertifikat", + "uploadCert": "Zertifikat hochladen (.pfx, .p12)", + "certPassword": "Zertifikat-Passwort", + "certPasswordPlaceholder": "Zertifikat-Passwort eingeben", + "certInfo": "Zertifikat-Informationen", + "certSubject": "Betreff", + "certIssuer": "Aussteller", + "certValidity": "Gültig", + "signatureDetails": "Signatur-Details (Optional)", + "reason": "Grund", + "reasonPlaceholder": "z.B. Ich genehmige dieses Dokument", + "location": "Ort", + "locationPlaceholder": "z.B. Berlin, Deutschland", + "contactInfo": "Kontaktdaten", + "contactPlaceholder": "z.B. email@beispiel.de", + "applySignature": "Digitale Signatur anwenden", + "successMessage": "PDF erfolgreich signiert! Die Signatur kann in jedem PDF-Reader überprüft werden." + }, + "validateSignaturePdf": { + "name": "PDF-Signatur überprüfen", + "pageTitle": "PDF-Signatur überprüfen - Digitale Signaturen verifizieren | BentoPDF", + "subtitle": "Überprüfen Sie digitale Signaturen in Ihren PDF-Dateien. Prüfen Sie die Zertifikatsgültigkeit, sehen Sie Unterzeichnerdetails und bestätigen Sie die Dokumentenintegrität. Die gesamte Verarbeitung erfolgt in Ihrem Browser." + }, + "emailToPdf": { + "name": "E-Mail zu PDF", + "subtitle": "E-Mail-Dateien (EML, MSG) in PDF-Format konvertieren. Unterstützt Outlook-Exporte und Standard-E-Mail-Formate.", + "acceptedFormats": "EML, MSG-Dateien", + "convertButton": "In PDF konvertieren" + }, + "fontToOutline": { + "name": "Schriftart zu Umriss", + "subtitle": "Alle Schriftarten in Vektorumrisse für konsistente Darstellung auf allen Geräten konvertieren." + }, + "deskewPdf": { + "name": "PDF entzerren", + "subtitle": "Automatisch schiefe gescannte Seiten mit OpenCV begradigen." + }, + "pdfToWord": { + "name": "PDF zu Word", + "subtitle": "PDF-Dateien in bearbeitbare Word-Dokumente umwandeln." + }, + "extractImages": { + "name": "Bilder extrahieren", + "subtitle": "Alle eingebetteten Bilder aus Ihren PDF-Dateien extrahieren." + }, + "pdfToMarkdown": { + "name": "PDF zu Markdown", + "subtitle": "PDF-Text und Tabellen in das Markdown-Format umwandeln." + }, + "preparePdfForAi": { + "name": "PDF für KI vorbereiten", + "subtitle": "PDF-Inhalte als LlamaIndex JSON für RAG/LLM-Pipelines extrahieren." + }, + "pdfOcg": { + "name": "PDF-Ebenen (OCG)", + "subtitle": "OCG-Ebenen in Ihrem PDF anzeigen, umschalten, hinzufügen und löschen." + }, + "pdfToPdfa": { + "name": "PDF zu PDF/A", + "subtitle": "PDF in PDF/A für Langzeitarchivierung umwandeln." + }, + "rasterizePdf": { + "name": "PDF rastern", + "subtitle": "PDF in bildbasiertes PDF umwandeln. Ebenen glätten und auswählbaren Text entfernen." + }, + "pdfWorkflow": { + "name": "PDF-Workflow-Builder", + "subtitle": "Erstellen Sie individuelle PDF-Verarbeitungspipelines mit einem visuellen Node-Editor.", + "nodes": "Nodes", + "searchNodes": "Nodes suchen...", + "run": "Ausführen", + "clear": "Leeren", + "save": "Speichern", + "load": "Laden", + "export": "Exportieren", + "import": "Importieren", + "ready": "Bereit", + "settings": "Einstellungen", + "processing": "Verarbeitung...", + "saveTemplate": "Vorlage speichern", + "templateName": "Vorlagenname", + "templatePlaceholder": "z. B. Rechnungs-Workflow", + "cancel": "Abbrechen", + "loadTemplate": "Vorlage laden", + "noTemplates": "Noch keine gespeicherten Vorlagen.", + "ok": "OK", + "workflowCompleted": "Workflow abgeschlossen", + "errorDuringExecution": "Fehler bei der Ausführung", + "addNodeError": "Fügen Sie mindestens einen Node hinzu, um den Workflow auszuführen.", + "needInputOutput": "Ihr Workflow benötigt mindestens einen Eingabe-Node und einen Ausgabe-Node.", + "enterName": "Bitte geben Sie einen Namen ein.", + "templateExists": "Eine Vorlage mit diesem Namen existiert bereits.", + "templateSaved": "Vorlage \"{{name}}\" gespeichert.", + "templateLoaded": "Vorlage \"{{name}}\" geladen.", + "failedLoadTemplate": "Vorlage konnte nicht geladen werden.", + "noSettings": "Keine konfigurierbaren Einstellungen für diesen Node.", + "advancedSettings": "Erweiterte Einstellungen" + } +} diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 2ffd41b..475ee64 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1,318 +1,365 @@ { - "nav": { - "home": "Home", - "about": "About", - "contact": "Contact", - "licensing": "Licensing", - "allTools": "All Tools", - "openMainMenu": "Open main menu", - "language": "Language" + "nav": { + "home": "Home", + "about": "About", + "contact": "Contact", + "licensing": "Licensing", + "allTools": "All Tools", + "openMainMenu": "Open main menu", + "language": "Language" + }, + "donation": { + "message": "Love BentoPDF? Help us keep it free and open source!", + "button": "Donate" + }, + "hero": { + "title": "The", + "pdfToolkit": "PDF Toolkit", + "builtForPrivacy": "built for privacy", + "noSignups": "No Signups", + "unlimitedUse": "Unlimited Use", + "worksOffline": "Works Offline", + "startUsing": "Start Using Now" + }, + "usedBy": { + "title": "Used by companies and people working at" + }, + "features": { + "title": "Why choose", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "No Signup", + "description": "Start instantly, no accounts or emails." }, - "hero": { - "title": "The", - "pdfToolkit": "PDF Toolkit", - "builtForPrivacy": "built for privacy", - "noSignups": "No Signups", - "unlimitedUse": "Unlimited Use", - "worksOffline": "Works Offline", - "startUsing": "Start Using Now" + "noUploads": { + "title": "No Uploads", + "description": "100% client-side, your files never leave your device." }, - "usedBy": { - "title": "Used by companies and people working at" + "foreverFree": { + "title": "Forever Free", + "description": "All tools, no trials, no paywalls." }, - "features": { - "title": "Why choose", - "bentoPdf": "BentoPDF?", - "noSignup": { - "title": "No Signup", - "description": "Start instantly, no accounts or emails." - }, - "noUploads": { - "title": "No Uploads", - "description": "100% client-side, your files never leave your device." - }, - "foreverFree": { - "title": "Forever Free", - "description": "All tools, no trials, no paywalls." - }, - "noLimits": { - "title": "No Limits", - "description": "Use as much as you want, no hidden caps." - }, - "batchProcessing": { - "title": "Batch Processing", - "description": "Handle unlimited PDFs in one go." - }, - "lightningFast": { - "title": "Lightning Fast", - "description": "Process PDFs instantly, without waiting or delays." - } + "noLimits": { + "title": "No Limits", + "description": "Use as much as you want, no hidden caps." }, - "tools": { - "title": "Get Started with", - "toolsLabel": "Tools", - "subtitle": "Click a tool to open the file uploader", - "searchPlaceholder": "Search for a tool (e.g., 'split', 'organize'...)", - "backToTools": "Back to Tools" + "batchProcessing": { + "title": "Batch Processing", + "description": "Handle unlimited PDFs in one go." }, - "upload": { - "clickToSelect": "Click to select a file", - "orDragAndDrop": "or drag and drop", - "pdfOrImages": "PDFs or Images", - "filesNeverLeave": "Your files never leave your device.", - "addMore": "Add More Files", - "clearAll": "Clear All" - }, - "loader": { - "processing": "Processing..." - }, - "alert": { - "title": "Alert", - "ok": "OK" - }, - "preview": { - "title": "Document Preview", - "downloadAsPdf": "Download as PDF", - "close": "Close" - }, - "settings": { - "title": "Settings", - "shortcuts": "Shortcuts", - "preferences": "Preferences", - "displayPreferences": "Display Preferences", - "searchShortcuts": "Search shortcuts...", - "shortcutsInfo": "Press and hold keys to set a shortcut. Changes are auto-saved.", - "shortcutsWarning": "⚠️ Avoid common browser shortcuts (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N etc.) as they may not work reliably.", - "import": "Import", - "export": "Export", - "resetToDefaults": "Reset to Defaults", - "fullWidthMode": "Full Width Mode", - "fullWidthDescription": "Use the full screen width for all tools instead of a centered container", - "settingsAutoSaved": "Settings are automatically saved", - "clickToSet": "Click to set", - "pressKeys": "Press keys...", - "warnings": { - "alreadyInUse": "Shortcut Already in Use", - "assignedTo": "is already assigned to:", - "chooseDifferent": "Please choose a different shortcut.", - "reserved": "Reserved Shortcut Warning", - "commonlyUsed": "is commonly used for:", - "unreliable": "This shortcut may not work reliably or might conflict with browser/system behavior.", - "useAnyway": "Do you want to use it anyway?", - "resetTitle": "Reset Shortcuts", - "resetMessage": "Are you sure you want to reset all shortcuts to default?

This action cannot be undone.", - "importSuccessTitle": "Import Successful", - "importSuccessMessage": "Shortcuts imported successfully!", - "importFailTitle": "Import Failed", - "importFailMessage": "Failed to import shortcuts. Invalid file format." - } - }, - "warning": { - "title": "Warning", - "cancel": "Cancel", - "proceed": "Proceed" - }, - "compliance": { - "title": "Your data never leaves your device", - "weKeep": "We keep", - "yourInfoSafe": "your information safe", - "byFollowingStandards": "by following global security standards.", - "processingLocal": "All the processing happens locally on your device.", - "gdpr": { - "title": "GDPR compliance", - "description": "Protects the personal data and privacy of individuals within the European Union." - }, - "ccpa": { - "title": "CCPA compliance", - "description": "Gives California residents rights over how their personal information is collected, used, and shared." - }, - "hipaa": { - "title": "HIPAA compliance", - "description": "Sets safeguards for handling sensitive health information in the United States healthcare system." - } - }, - "faq": { - "title": "Frequently Asked", - "questions": "Questions", - "isFree": { - "question": "Is BentoPDF really free?", - "answer": "Yes, absolutely. All tools on BentoPDF are 100% free to use, with no file limits, no sign-ups, and no watermarks. We believe everyone deserves access to simple, powerful PDF tools without a paywall." - }, - "areFilesSecure": { - "question": "Are my files secure? Where are they processed?", - "answer": "Your files are as secure as possible because they never leave your computer. All processing happens directly in your web browser (client-side). We never upload your files to a server, so you maintain complete privacy and control over your documents." - }, - "platforms": { - "question": "Does it work on Mac, Windows, and Mobile?", - "answer": "Yes! Since BentoPDF runs entirely in your browser, it works on any operating system with a modern web browser, including Windows, macOS, Linux, iOS, and Android." - }, - "gdprCompliant": { - "question": "Is BentoPDF GDPR compliant?", - "answer": "Yes. BentoPDF is fully GDPR compliant. Since all file processing happens locally in your browser and we never collect or transmit your files to any server, we have no access to your data. This ensures you are always in control of your documents." - }, - "dataStorage": { - "question": "Do you store or track any of my files?", - "answer": "No. We never store, track, or log your files. Everything you do on BentoPDF happens in your browser memory and disappears once you close the page. There are no uploads, no history logs, and no servers involved." - }, - "different": { - "question": "What makes BentoPDF different from other PDF tools?", - "answer": "Most PDF tools upload your files to a server for processing. BentoPDF never does that. We use secure, modern web technology to process your files directly in your browser. This means faster performance, stronger privacy, and complete peace of mind." - }, - "browserBased": { - "question": "How does browser-based processing keep me safe?", - "answer": "By running entirely inside your browser, BentoPDF ensures that your files never leave your device. This eliminates the risks of server hacks, data breaches, or unauthorized access. Your files remain yours—always." - }, - "analytics": { - "question": "Do you use cookies or analytics to track me?", - "answer": "We care about your privacy. BentoPDF does not track personal information. We use Simple Analytics solely to see anonymous visit counts. This means we can know how many users visit our site, but we never know who you are. Simple Analytics is fully GDPR-compliant and respects your privacy." - } - }, - "testimonials": { - "title": "What Our", - "users": "Users", - "say": "Say" - }, - "support": { - "title": "Like My Work?", - "description": "BentoPDF is a passion project, built to provide a free, private, and powerful PDF toolkit for everyone. If you find it useful, consider supporting its development. Every coffee helps!", - "buyMeCoffee": "Buy Me a Coffee" - }, - "footer": { - "copyright": "© 2025 BentoPDF. All rights reserved.", - "version": "Version", - "company": "Company", - "aboutUs": "About Us", - "faqLink": "FAQ", - "contactUs": "Contact Us", - "legal": "Legal", - "termsAndConditions": "Terms and Conditions", - "privacyPolicy": "Privacy Policy", - "followUs": "Follow Us" - }, - "merge": { - "title": "Merge PDFs", - "description": "Combine whole files, or select specific pages to merge into a new document.", - "fileMode": "File Mode", - "pageMode": "Page Mode", - "howItWorks": "How it works:", - "fileModeInstructions": [ - "Click and drag the icon to change the order of the files.", - "In the \"Pages\" box for each file, you can specify ranges (e.g., \"1-3, 5\") to merge only those pages.", - "Leave the \"Pages\" box blank to include all pages from that file." - ], - "pageModeInstructions": [ - "All pages from your uploaded PDFs are shown below.", - "Simply drag and drop the individual page thumbnails to create the exact order you want for your new file." - ], - "mergePdfs": "Merge PDFs" - }, - "common": { - "page": "Page", - "pages": "Pages", - "of": "of", - "download": "Download", - "cancel": "Cancel", - "save": "Save", - "delete": "Delete", - "edit": "Edit", - "add": "Add", - "remove": "Remove", - "loading": "Loading...", - "error": "Error", - "success": "Success", - "file": "File", - "files": "Files" - }, - "about": { - "hero": { - "title": "We believe PDF tools should be", - "subtitle": "fast, private, and free.", - "noCompromises": "No compromises." - }, - "mission": { - "title": "Our Mission", - "description": "To provide the most comprehensive PDF toolbox that respects your privacy and never asks for payment. We believe essential document tools should be accessible to everyone, everywhere, without barriers." - }, - "philosophy": { - "label": "Our Core Philosophy", - "title": "Privacy First. Always.", - "description": "In an era where data is a commodity, we take a different approach. All processing for Bentopdf tools happens locally in your browser. This means your files never touch our servers, we never see your documents, and we don't track what you do. Your documents remain completely and unequivocally private. It's not just a feature; it's our foundation." - }, - "whyBentopdf": { - "title": "Why", - "speed": { - "title": "Built for Speed", - "description": "No waiting for uploads or downloads to a server. By processing files directly in your browser using modern web technologies like WebAssembly, we offer unparalleled speed for all our tools." - }, - "free": { - "title": "Completely Free", - "description": "No trials, no subscriptions, no hidden fees, and no \"premium\" features held hostage. We believe powerful PDF tools should be a public utility, not a profit center." - }, - "noAccount": { - "title": "No Account Required", - "description": "Start using any tool immediately. We don't need your email, a password, or any personal information. Your workflow should be frictionless and anonymous." - }, - "openSource": { - "title": "Open Source Spirit", - "description": "Built with transparency in mind. We leverage incredible open-source libraries like PDF-lib and PDF.js, and believe in the community-driven effort to make powerful tools accessible to everyone." - } - }, - "cta": { - "title": "Ready to get started?", - "description": "Join thousands of users who trust Bentopdf for their daily document needs. Experience the difference that privacy and performance can make.", - "button": "Explore All Tools" - } - }, - "contact": { - "title": "Get in Touch", - "subtitle": "We'd love to hear from you. Whether you have a question, feedback, or a feature request, please don't hesitate to reach out.", - "email": "You can reach us directly by email at:" - }, - "licensing": { - "title": "Licensing for", - "subtitle": "Choose the license that fits your needs." - }, - "multiTool": { - "uploadPdfs": "Upload PDFs", - "upload": "Upload", - "addBlankPage": "Add Blank Page", - "edit": "Edit:", - "undo": "Undo", - "redo": "Redo", - "reset": "Reset", - "selection": "Selection:", - "selectAll": "Select All", - "deselectAll": "Deselect All", - "rotate": "Rotate:", - "rotateLeft": "Left", - "rotateRight": "Right", - "transform": "Transform:", - "duplicate": "Duplicate", - "split": "Split", - "clear": "Clear:", - "delete": "Delete", - "download": "Download:", - "downloadSelected": "Download Selected", - "exportPdf": "Export PDF", - "uploadPdfFiles": "Select PDF Files", - "dragAndDrop": "Drag and drop PDF files here, or click to select", - "selectFiles": "Select Files", - "renderingPages": "Rendering pages...", - "actions": { - "duplicatePage": "Duplicate this page", - "deletePage": "Delete this page", - "insertPdf": "Insert PDF after this page", - "toggleSplit": "Toggle split after this page" - }, - "pleaseWait": "Please Wait", - "pagesRendering": "Pages are still being rendered. Please wait...", - "noPagesSelected": "No Pages Selected", - "selectOnePage": "Please select at least one page to download.", - "noPages": "No Pages", - "noPagesToExport": "There are no pages to export.", - "renderingTitle": "Rendering page previews", - "errorRendering": "Failed to render page thumbnails", - "error": "Error", - "failedToLoad": "Failed to load" + "lightningFast": { + "title": "Lightning Fast", + "description": "Process PDFs instantly, without waiting or delays." } -} \ No newline at end of file + }, + "tools": { + "title": "Get Started with", + "toolsLabel": "Tools", + "subtitle": "Click a tool to open the file uploader", + "searchPlaceholder": "Search for a tool (e.g., 'split', 'organize'...)", + "backToTools": "Back to Tools", + "firstLoadNotice": "First load takes a moment as we download our conversion engine. After that, all loads will be instant." + }, + "upload": { + "clickToSelect": "Click to select a file", + "orDragAndDrop": "or drag and drop", + "pdfOrImages": "PDFs or Images", + "filesNeverLeave": "Your files never leave your device.", + "addMore": "Add More Files", + "clearAll": "Clear All", + "clearFiles": "Clear Files", + "hints": { + "singlePdf": "A single PDF file", + "pdfFile": "PDF file", + "multiplePdfs2": "Multiple PDF files (at least 2)", + "bmpImages": "BMP images", + "oneOrMorePdfs": "One or more PDF files", + "pdfDocuments": "PDF Documents", + "oneOrMoreCsv": "One or more CSV files", + "multiplePdfsSupported": "Multiple PDF files supported", + "singleOrMultiplePdfs": "Single or multiple PDF files supported", + "singlePdfFile": "Single PDF file", + "pdfWithForms": "PDF file with form fields", + "heicImages": "HEIC/HEIF images", + "jpgImages": "JPG, JPEG, JP2, JPX Images", + "pdfsOrImages": "PDFs or Images", + "oneOrMoreOdt": "One or more ODT files", + "singlePdfOnly": "Single PDF file only", + "pdfFiles": "PDF files", + "multiplePdfs": "Multiple PDF files", + "pngImages": "PNG Images", + "pdfFilesOneOrMore": "PDF files (one or more)", + "oneOrMoreRtf": "One or more RTF files", + "svgGraphics": "SVG Graphics", + "tiffImages": "TIFF images", + "webpImages": "WebP Images" + } + }, + "howItWorks": { + "title": "How It Works", + "step1": "Click or drag and drop your file to begin", + "step2": "Click the process button to start", + "step3": "Save your processed file instantly" + }, + "relatedTools": { + "title": "Related PDF Tools" + }, + "loader": { + "processing": "Processing..." + }, + "alert": { + "title": "Alert", + "ok": "OK" + }, + "preview": { + "title": "Document Preview", + "downloadAsPdf": "Download as PDF", + "close": "Close" + }, + "settings": { + "title": "Settings", + "shortcuts": "Shortcuts", + "preferences": "Preferences", + "displayPreferences": "Display Preferences", + "searchShortcuts": "Search shortcuts...", + "shortcutsInfo": "Press and hold keys to set a shortcut. Changes are auto-saved.", + "shortcutsWarning": "⚠️ Avoid common browser shortcuts (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N etc.) as they may not work reliably.", + "import": "Import", + "export": "Export", + "resetToDefaults": "Reset to Defaults", + "fullWidthMode": "Full Width Mode", + "fullWidthDescription": "Use the full screen width for all tools instead of a centered container", + "settingsAutoSaved": "Settings are automatically saved", + "clickToSet": "Click to set", + "pressKeys": "Press keys...", + "warnings": { + "alreadyInUse": "Shortcut Already in Use", + "assignedTo": "is already assigned to:", + "chooseDifferent": "Please choose a different shortcut.", + "reserved": "Reserved Shortcut Warning", + "commonlyUsed": "is commonly used for:", + "unreliable": "This shortcut may not work reliably or might conflict with browser/system behavior.", + "useAnyway": "Do you want to use it anyway?", + "resetTitle": "Reset Shortcuts", + "resetMessage": "Are you sure you want to reset all shortcuts to default?

This action cannot be undone.", + "importSuccessTitle": "Import Successful", + "importSuccessMessage": "Shortcuts imported successfully!", + "importFailTitle": "Import Failed", + "importFailMessage": "Failed to import shortcuts. Invalid file format." + } + }, + "warning": { + "title": "Warning", + "cancel": "Cancel", + "proceed": "Proceed" + }, + "compliance": { + "title": "Your data never leaves your device", + "weKeep": "We keep", + "yourInfoSafe": "your information safe", + "byFollowingStandards": "by following global security standards.", + "processingLocal": "All the processing happens locally on your device.", + "gdpr": { + "title": "GDPR compliance", + "description": "Protects the personal data and privacy of individuals within the European Union." + }, + "ccpa": { + "title": "CCPA compliance", + "description": "Gives California residents rights over how their personal information is collected, used, and shared." + }, + "hipaa": { + "title": "HIPAA compliance", + "description": "Sets safeguards for handling sensitive health information in the United States healthcare system." + } + }, + "faq": { + "title": "Frequently Asked", + "questions": "Questions", + "sectionTitle": "Frequently Asked Questions", + "isFree": { + "question": "Is BentoPDF really free?", + "answer": "Yes, absolutely. All tools on BentoPDF are 100% free to use, with no file limits, no sign-ups, and no watermarks. We believe everyone deserves access to simple, powerful PDF tools without a paywall." + }, + "areFilesSecure": { + "question": "Are my files secure? Where are they processed?", + "answer": "Your files are as secure as possible because they never leave your computer. All processing happens directly in your web browser (client-side). We never upload your files to a server, so you maintain complete privacy and control over your documents." + }, + "platforms": { + "question": "Does it work on Mac, Windows, and Mobile?", + "answer": "Yes! Since BentoPDF runs entirely in your browser, it works on any operating system with a modern web browser, including Windows, macOS, Linux, iOS, and Android." + }, + "gdprCompliant": { + "question": "Is BentoPDF GDPR compliant?", + "answer": "Yes. BentoPDF is fully GDPR compliant. Since all file processing happens locally in your browser and we never collect or transmit your files to any server, we have no access to your data. This ensures you are always in control of your documents." + }, + "dataStorage": { + "question": "Do you store or track any of my files?", + "answer": "No. We never store, track, or log your files. Everything you do on BentoPDF happens in your browser memory and disappears once you close the page. There are no uploads, no history logs, and no servers involved." + }, + "different": { + "question": "What makes BentoPDF different from other PDF tools?", + "answer": "Most PDF tools upload your files to a server for processing. BentoPDF never does that. We use secure, modern web technology to process your files directly in your browser. This means faster performance, stronger privacy, and complete peace of mind." + }, + "browserBased": { + "question": "How does browser-based processing keep me safe?", + "answer": "By running entirely inside your browser, BentoPDF ensures that your files never leave your device. This eliminates the risks of server hacks, data breaches, or unauthorized access. Your files remain yours—always." + }, + "analytics": { + "question": "Do you use cookies or analytics to track me?", + "answer": "We care about your privacy. BentoPDF does not track personal information. We use Simple Analytics solely to see anonymous visit counts. This means we can know how many users visit our site, but we never know who you are. Simple Analytics is fully GDPR-compliant and respects your privacy." + } + }, + "testimonials": { + "title": "What Our", + "users": "Users", + "say": "Say" + }, + "support": { + "title": "Like My Work?", + "description": "BentoPDF is a passion project, built to provide a free, private, and powerful PDF toolkit for everyone. If you find it useful, consider supporting its development. Every coffee helps!", + "buyMeCoffee": "Buy Me a Coffee" + }, + "footer": { + "copyright": "© 2026 BentoPDF. All rights reserved.", + "version": "Version", + "company": "Company", + "aboutUs": "About Us", + "faqLink": "FAQ", + "contactUs": "Contact Us", + "legal": "Legal", + "termsAndConditions": "Terms and Conditions", + "privacyPolicy": "Privacy Policy", + "followUs": "Follow Us" + }, + "merge": { + "title": "Merge PDFs", + "description": "Combine whole files, or select specific pages to merge into a new document.", + "fileMode": "File Mode", + "pageMode": "Page Mode", + "howItWorks": "How it works:", + "fileModeInstructions": [ + "Click and drag the icon to change the order of the files.", + "In the \"Pages\" box for each file, you can specify ranges (e.g., \"1-3, 5\") to merge only those pages.", + "Leave the \"Pages\" box blank to include all pages from that file." + ], + "pageModeInstructions": [ + "All pages from your uploaded PDFs are shown below.", + "Simply drag and drop the individual page thumbnails to create the exact order you want for your new file." + ], + "mergePdfs": "Merge PDFs" + }, + "common": { + "page": "Page", + "pages": "Pages", + "of": "of", + "download": "Download", + "cancel": "Cancel", + "save": "Save", + "delete": "Delete", + "edit": "Edit", + "add": "Add", + "remove": "Remove", + "loading": "Loading...", + "error": "Error", + "success": "Success", + "file": "File", + "files": "Files", + "close": "Close" + }, + "about": { + "hero": { + "title": "We believe PDF tools should be", + "subtitle": "fast, private, and free.", + "noCompromises": "No compromises." + }, + "mission": { + "title": "Our Mission", + "description": "To provide the most comprehensive PDF toolbox that respects your privacy and never asks for payment. We believe essential document tools should be accessible to everyone, everywhere, without barriers." + }, + "philosophy": { + "label": "Our Core Philosophy", + "title": "Privacy First. Always.", + "description": "In an era where data is a commodity, we take a different approach. All processing for Bentopdf tools happens locally in your browser. This means your files never touch our servers, we never see your documents, and we don't track what you do. Your documents remain completely and unequivocally private. It's not just a feature; it's our foundation." + }, + "whyBentopdf": { + "title": "Why", + "speed": { + "title": "Built for Speed", + "description": "No waiting for uploads or downloads to a server. By processing files directly in your browser using modern web technologies like WebAssembly, we offer unparalleled speed for all our tools." + }, + "free": { + "title": "Completely Free", + "description": "No trials, no subscriptions, no hidden fees, and no \"premium\" features held hostage. We believe powerful PDF tools should be a public utility, not a profit center." + }, + "noAccount": { + "title": "No Account Required", + "description": "Start using any tool immediately. We don't need your email, a password, or any personal information. Your workflow should be frictionless and anonymous." + }, + "openSource": { + "title": "Open Source Spirit", + "description": "Built with transparency in mind. We leverage incredible open-source libraries like PDF-lib and PDF.js, and believe in the community-driven effort to make powerful tools accessible to everyone." + } + }, + "cta": { + "title": "Ready to get started?", + "description": "Join thousands of users who trust Bentopdf for their daily document needs. Experience the difference that privacy and performance can make.", + "button": "Explore All Tools" + } + }, + "contact": { + "title": "Get in Touch", + "subtitle": "We'd love to hear from you. Whether you have a question, feedback, or a feature request, please don't hesitate to reach out.", + "email": "You can reach us directly by email at:" + }, + "licensing": { + "title": "Licensing for", + "subtitle": "Choose the license that fits your needs." + }, + "multiTool": { + "uploadPdfs": "Upload PDFs", + "upload": "Upload", + "addBlankPage": "Add Blank Page", + "edit": "Edit:", + "undo": "Undo", + "redo": "Redo", + "reset": "Reset", + "selection": "Selection:", + "selectAll": "Select All", + "deselectAll": "Deselect All", + "rotate": "Rotate:", + "rotateLeft": "Left", + "rotateRight": "Right", + "transform": "Transform:", + "duplicate": "Duplicate", + "split": "Split", + "clear": "Clear:", + "delete": "Delete", + "download": "Download:", + "downloadSelected": "Download Selected", + "exportPdf": "Export PDF", + "uploadPdfFiles": "Select PDF Files", + "dragAndDrop": "Drag and drop PDF files here, or click to select", + "selectFiles": "Select Files", + "renderingPages": "Rendering pages...", + "actions": { + "duplicatePage": "Duplicate this page", + "deletePage": "Delete this page", + "insertPdf": "Insert PDF after this page", + "toggleSplit": "Toggle split after this page" + }, + "pleaseWait": "Please Wait", + "pagesRendering": "Pages are still being rendered. Please wait...", + "noPagesSelected": "No Pages Selected", + "selectOnePage": "Please select at least one page to download.", + "noPages": "No Pages", + "noPagesToExport": "There are no pages to export.", + "renderingTitle": "Rendering page previews", + "errorRendering": "Failed to render page thumbnails", + "error": "Error", + "failedToLoad": "Failed to load" + }, + "simpleMode": { + "title": "PDF Tools", + "subtitle": "Select a tool to get started" + } +} diff --git a/public/locales/en/tools.json b/public/locales/en/tools.json index 3ebe684..9783bee 100644 --- a/public/locales/en/tools.json +++ b/public/locales/en/tools.json @@ -1,282 +1,631 @@ { - "categories": { - "popularTools": "Popular Tools", - "editAnnotate": "Edit & Annotate", - "convertToPdf": "Convert to PDF", - "convertFromPdf": "Convert from PDF", - "organizeManage": "Organize & Manage", - "optimizeRepair": "Optimize & Repair", - "securePdf": "Secure PDF" - }, - "pdfMultiTool": { - "name": "PDF Multi Tool", - "subtitle": "Merge, Split, Organize, Delete, Rotate, Add Blank Pages, Extract and Duplicate in an unified interface." - }, - "mergePdf": { - "name": "Merge PDF", - "subtitle": "Combine multiple PDFs into one file. Preserves Bookmarks." - }, - "splitPdf": { - "name": "Split PDF", - "subtitle": "Extract a range of pages into a new PDF." - }, - "compressPdf": { - "name": "Compress PDF", - "subtitle": "Reduce the file size of your PDF." - }, - "pdfEditor": { - "name": "PDF Editor", - "subtitle": "Annotate, highlight, redact, comment, add shapes/images, search, and view PDFs." - }, - "jpgToPdf": { - "name": "JPG to PDF", - "subtitle": "Create a PDF from one or more JPG images." - }, - "signPdf": { - "name": "Sign PDF", - "subtitle": "Draw, type, or upload your signature." - }, - "cropPdf": { - "name": "Crop PDF", - "subtitle": "Trim the margins of every page in your PDF." - }, - "extractPages": { - "name": "Extract Pages", - "subtitle": "Save a selection of pages as new files." - }, - "duplicateOrganize": { - "name": "Duplicate & Organize", - "subtitle": "Duplicate, reorder, and delete pages." - }, - "deletePages": { - "name": "Delete Pages", - "subtitle": "Remove specific pages from your document." - }, - "editBookmarks": { - "name": "Edit Bookmarks", - "subtitle": "Add, edit, import, delete and extract PDF bookmarks." - }, - "tableOfContents": { - "name": "Table of Contents", - "subtitle": "Generate a table of contents page from PDF bookmarks." - }, - "pageNumbers": { - "name": "Page Numbers", - "subtitle": "Insert page numbers into your document." - }, - "addWatermark": { - "name": "Add Watermark", - "subtitle": "Stamp text or an image over your PDF pages." - }, - "headerFooter": { - "name": "Header & Footer", - "subtitle": "Add text to the top and bottom of pages." - }, - "invertColors": { - "name": "Invert Colors", - "subtitle": "Create a \"dark mode\" version of your PDF." - }, - "backgroundColor": { - "name": "Background Color", - "subtitle": "Change the background color of your PDF." - }, - "changeTextColor": { - "name": "Change Text Color", - "subtitle": "Change the color of text in your PDF." - }, - "addStamps": { - "name": "Add Stamps", - "subtitle": "Add image stamps to your PDF using the annotation toolbar.", - "usernameLabel": "Stamp Username", - "usernamePlaceholder": "Enter your name (for stamps)", - "usernameHint": "This name will appear on stamps you create." - }, - "removeAnnotations": { - "name": "Remove Annotations", - "subtitle": "Strip comments, highlights, and links." - }, - "pdfFormFiller": { - "name": "PDF Form Filler", - "subtitle": "Fill in forms directly in the browser. Also supports XFA forms." - }, - "createPdfForm": { - "name": "Create PDF Form", - "subtitle": "Create fillable PDF forms with drag-and-drop text fields." - }, - "removeBlankPages": { - "name": "Remove Blank Pages", - "subtitle": "Automatically detect and delete blank pages." - }, - "imageToPdf": { - "name": "Image to PDF", - "subtitle": "Convert JPG, PNG, WebP, BMP, TIFF, SVG, HEIC to PDF." - }, - "pngToPdf": { - "name": "PNG to PDF", - "subtitle": "Create a PDF from one or more PNG images." - }, - "webpToPdf": { - "name": "WebP to PDF", - "subtitle": "Create a PDF from one or more WebP images." - }, - "svgToPdf": { - "name": "SVG to PDF", - "subtitle": "Create a PDF from one or more SVG images." - }, - "bmpToPdf": { - "name": "BMP to PDF", - "subtitle": "Create a PDF from one or more BMP images." - }, - "heicToPdf": { - "name": "HEIC to PDF", - "subtitle": "Create a PDF from one or more HEIC images." - }, - "tiffToPdf": { - "name": "TIFF to PDF", - "subtitle": "Create a PDF from one or more TIFF images." - }, - "textToPdf": { - "name": "Text to PDF", - "subtitle": "Convert a plain text file into a PDF." - }, - "jsonToPdf": { - "name": "JSON to PDF", - "subtitle": "Convert JSON files to PDF format." - }, - "pdfToJpg": { - "name": "PDF to JPG", - "subtitle": "Convert each PDF page into a JPG image." - }, - "pdfToPng": { - "name": "PDF to PNG", - "subtitle": "Convert each PDF page into a PNG image." - }, - "pdfToWebp": { - "name": "PDF to WebP", - "subtitle": "Convert each PDF page into a WebP image." - }, - "pdfToBmp": { - "name": "PDF to BMP", - "subtitle": "Convert each PDF page into a BMP image." - }, - "pdfToTiff": { - "name": "PDF to TIFF", - "subtitle": "Convert each PDF page into a TIFF image." - }, - "pdfToGreyscale": { - "name": "PDF to Greyscale", - "subtitle": "Convert all colors to black and white." - }, - "pdfToJson": { - "name": "PDF to JSON", - "subtitle": "Convert PDF files to JSON format." - }, - "ocrPdf": { - "name": "OCR PDF", - "subtitle": "Make a PDF searchable and copyable." - }, - "alternateMix": { - "name": "Alternate & Mix Pages", - "subtitle": "Merge PDFs by alternating pages from each PDF. Preserves Bookmarks." - }, - "addAttachments": { - "name": "Add Attachments", - "subtitle": "Embed one or more files into your PDF." - }, - "extractAttachments": { - "name": "Extract Attachments", - "subtitle": "Extract all embedded files from PDF(s) as a ZIP." - }, - "editAttachments": { - "name": "Edit Attachments", - "subtitle": "View or remove attachments in your PDF." - }, - "dividePages": { - "name": "Divide Pages", - "subtitle": "Divide pages horizontally or vertically." - }, - "addBlankPage": { - "name": "Add Blank Page", - "subtitle": "Insert an empty page anywhere in your PDF." - }, - "reversePages": { - "name": "Reverse Pages", - "subtitle": "Flip the order of all pages in your document." - }, - "rotatePdf": { - "name": "Rotate PDF", - "subtitle": "Turn pages in 90-degree increments." - }, - "nUpPdf": { - "name": "N-Up PDF", - "subtitle": "Arrange multiple pages onto a single sheet." - }, - "combineToSinglePage": { - "name": "Combine to Single Page", - "subtitle": "Stitch all pages into one continuous scroll." - }, - "viewMetadata": { - "name": "View Metadata", - "subtitle": "Inspect the hidden properties of your PDF." - }, - "editMetadata": { - "name": "Edit Metadata", - "subtitle": "Change the author, title, and other properties." - }, - "pdfsToZip": { - "name": "PDFs to ZIP", - "subtitle": "Package multiple PDF files into a ZIP archive." - }, - "comparePdfs": { - "name": "Compare PDFs", - "subtitle": "Compare two PDFs side by side." - }, - "posterizePdf": { - "name": "Posterize PDF", - "subtitle": "Split a large page into multiple smaller pages." - }, - "fixPageSize": { - "name": "Fix Page Size", - "subtitle": "Standardize all pages to a uniform size." - }, - "linearizePdf": { - "name": "Linearize PDF", - "subtitle": "Optimize PDF for fast web viewing." - }, - "pageDimensions": { - "name": "Page Dimensions", - "subtitle": "Analyze page size, orientation, and units." - }, - "removeRestrictions": { - "name": "Remove Restrictions", - "subtitle": "Remove password protection and security restrictions associated with digitally signed PDF files." - }, - "repairPdf": { - "name": "Repair PDF", - "subtitle": "Recover data from corrupted or damaged PDF files." - }, - "encryptPdf": { - "name": "Encrypt PDF", - "subtitle": "Lock your PDF by adding a password." - }, - "sanitizePdf": { - "name": "Sanitize PDF", - "subtitle": "Remove metadata, annotations, scripts, and more." - }, - "decryptPdf": { - "name": "Decrypt PDF", - "subtitle": "Unlock PDF by removing password protection." - }, - "flattenPdf": { - "name": "Flatten PDF", - "subtitle": "Make form fields and annotations non-editable." - }, - "removeMetadata": { - "name": "Remove Metadata", - "subtitle": "Strip hidden data from your PDF." - }, - "changePermissions": { - "name": "Change Permissions", - "subtitle": "Set or change user permissions on a PDF." - } -} \ No newline at end of file + "categories": { + "popularTools": "Popular Tools", + "editAnnotate": "Edit & Annotate", + "convertToPdf": "Convert to PDF", + "convertFromPdf": "Convert from PDF", + "organizeManage": "Organize & Manage", + "optimizeRepair": "Optimize & Repair", + "securePdf": "Secure PDF" + }, + "pdfMultiTool": { + "name": "PDF Multi Tool", + "subtitle": "Merge, Split, Organize, Delete, Rotate, Add Blank Pages, Extract and Duplicate in an unified interface." + }, + "mergePdf": { + "name": "Merge PDF", + "subtitle": "Combine multiple PDFs into one file. Preserves Bookmarks." + }, + "splitPdf": { + "name": "Split PDF", + "subtitle": "Extract a range of pages into a new PDF." + }, + "compressPdf": { + "name": "Compress PDF", + "subtitle": "Reduce the file size of your PDF.", + "algorithmLabel": "Compression Algorithm", + "condense": "Condense (Recommended)", + "photon": "Photon (For Photo-Heavy PDFs)", + "condenseInfo": "Condense uses advanced compression: removes dead-weight, optimizes images, subsets fonts. Best for most PDFs.", + "photonInfo": "Photon converts pages to images. Use for photo-heavy/scanned PDFs.", + "photonWarning": "Warning: Text will become non-selectable and links will stop working.", + "levelLabel": "Compression Level", + "light": "Light (Preserve Quality)", + "balanced": "Balanced (Recommended)", + "aggressive": "Aggressive (Smaller Files)", + "extreme": "Extreme (Maximum Compression)", + "grayscale": "Convert to Grayscale", + "grayscaleHint": "Reduces file size by removing color information", + "customSettings": "Custom Settings", + "customSettingsHint": "Fine-tune compression parameters:", + "outputQuality": "Output Quality", + "resizeImagesTo": "Resize Images To", + "onlyProcessAbove": "Only Process Above", + "removeMetadata": "Remove metadata", + "subsetFonts": "Subset fonts (remove unused glyphs)", + "removeThumbnails": "Remove embedded thumbnails", + "compressButton": "Compress PDF" + }, + "pdfEditor": { + "name": "PDF Editor", + "subtitle": "Annotate, highlight, redact, comment, add shapes/images, search, and view PDFs." + }, + "jpgToPdf": { + "name": "JPG to PDF", + "subtitle": "Create a PDF from JPG, JPEG, and JPEG2000 (JP2/JPX) images." + }, + "signPdf": { + "name": "Sign PDF", + "subtitle": "Draw, type, or upload your signature." + }, + "cropPdf": { + "name": "Crop PDF", + "subtitle": "Trim the margins of every page in your PDF." + }, + "extractPages": { + "name": "Extract Pages", + "subtitle": "Save a selection of pages as new files." + }, + "duplicateOrganize": { + "name": "Duplicate & Organize", + "subtitle": "Duplicate, reorder, and delete pages." + }, + "deletePages": { + "name": "Delete Pages", + "subtitle": "Remove specific pages from your document." + }, + "editBookmarks": { + "name": "Edit Bookmarks", + "subtitle": "Add, edit, import, delete and extract PDF bookmarks." + }, + "tableOfContents": { + "name": "Table of Contents", + "subtitle": "Generate a table of contents page from PDF bookmarks." + }, + "pageNumbers": { + "name": "Page Numbers", + "subtitle": "Insert page numbers into your document." + }, + "batesNumbering": { + "name": "Bates Numbering", + "subtitle": "Add sequential Bates numbers across one or more PDF files." + }, + "addWatermark": { + "name": "Add Watermark", + "subtitle": "Stamp text or an image over your PDF pages.", + "applyToAllPages": "Apply to all pages" + }, + "headerFooter": { + "name": "Header & Footer", + "subtitle": "Add text to the top and bottom of pages." + }, + "invertColors": { + "name": "Invert Colors", + "subtitle": "Create a \"dark mode\" version of your PDF." + }, + "scannerEffect": { + "name": "Scanner Effect", + "subtitle": "Make your PDF look like a scanned document.", + "scanSettings": "Scan Settings", + "colorspace": "Colorspace", + "gray": "Gray", + "border": "Border", + "rotate": "Rotate", + "rotateVariance": "Rotate Variance", + "brightness": "Brightness", + "contrast": "Contrast", + "blur": "Blur", + "noise": "Noise", + "yellowish": "Yellowish", + "resolution": "Resolution", + "processButton": "Apply Scanner Effect" + }, + "adjustColors": { + "name": "Adjust Colors", + "subtitle": "Fine-tune brightness, contrast, saturation and more in your PDF.", + "colorSettings": "Color Settings", + "brightness": "Brightness", + "contrast": "Contrast", + "saturation": "Saturation", + "hueShift": "Hue Shift", + "temperature": "Temperature", + "tint": "Tint", + "gamma": "Gamma", + "sepia": "Sepia", + "processButton": "Apply Color Adjustments" + }, + "backgroundColor": { + "name": "Background Color", + "subtitle": "Change the background color of your PDF." + }, + "changeTextColor": { + "name": "Change Text Color", + "subtitle": "Change the color of text in your PDF." + }, + "addStamps": { + "name": "Add Stamps", + "subtitle": "Add image stamps to your PDF using the annotation toolbar.", + "usernameLabel": "Stamp Username", + "usernamePlaceholder": "Enter your name (for stamps)", + "usernameHint": "This name will appear on stamps you create." + }, + "removeAnnotations": { + "name": "Remove Annotations", + "subtitle": "Strip comments, highlights, and links." + }, + "pdfFormFiller": { + "name": "PDF Form Filler", + "subtitle": "Fill in forms directly in the browser. Also supports XFA forms." + }, + "createPdfForm": { + "name": "Create PDF Form", + "subtitle": "Create fillable PDF forms with drag-and-drop text fields." + }, + "removeBlankPages": { + "name": "Remove Blank Pages", + "subtitle": "Automatically detect and delete blank pages.", + "sensitivityHint": "Higher = stricter, only purely blank pages. Lower = allows pages with some content." + }, + "imageToPdf": { + "name": "Images to PDF", + "subtitle": "Convert JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP to PDF." + }, + "pngToPdf": { + "name": "PNG to PDF", + "subtitle": "Create a PDF from one or more PNG images." + }, + "webpToPdf": { + "name": "WebP to PDF", + "subtitle": "Create a PDF from one or more WebP images." + }, + "svgToPdf": { + "name": "SVG to PDF", + "subtitle": "Create a PDF from one or more SVG images." + }, + "bmpToPdf": { + "name": "BMP to PDF", + "subtitle": "Create a PDF from one or more BMP images." + }, + "heicToPdf": { + "name": "HEIC to PDF", + "subtitle": "Create a PDF from one or more HEIC images." + }, + "tiffToPdf": { + "name": "TIFF to PDF", + "subtitle": "Create a PDF from one or more TIFF images." + }, + "textToPdf": { + "name": "Text to PDF", + "subtitle": "Convert a plain text file into a PDF." + }, + "jsonToPdf": { + "name": "JSON to PDF", + "subtitle": "Convert JSON files to PDF format." + }, + "pdfToJpg": { + "name": "PDF to JPG", + "subtitle": "Convert each PDF page into a JPG image." + }, + "pdfToPng": { + "name": "PDF to PNG", + "subtitle": "Convert each PDF page into a PNG image." + }, + "pdfToWebp": { + "name": "PDF to WebP", + "subtitle": "Convert each PDF page into a WebP image." + }, + "pdfToBmp": { + "name": "PDF to BMP", + "subtitle": "Convert each PDF page into a BMP image." + }, + "pdfToTiff": { + "name": "PDF to TIFF", + "subtitle": "Convert each PDF page into a TIFF image." + }, + "pdfToGreyscale": { + "name": "PDF to Greyscale", + "subtitle": "Convert all colors to black and white." + }, + "pdfToJson": { + "name": "PDF to JSON", + "subtitle": "Convert PDF files to JSON format." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Make a PDF searchable and copyable." + }, + "alternateMix": { + "name": "Alternate & Mix Pages", + "subtitle": "Merge PDFs by alternating pages from each PDF. Preserves Bookmarks." + }, + "addAttachments": { + "name": "Add Attachments", + "subtitle": "Embed one or more files into your PDF." + }, + "extractAttachments": { + "name": "Extract Attachments", + "subtitle": "Extract all embedded files from PDF(s) as a ZIP." + }, + "editAttachments": { + "name": "Edit Attachments", + "subtitle": "View or remove attachments in your PDF." + }, + "dividePages": { + "name": "Divide Pages", + "subtitle": "Divide pages horizontally or vertically." + }, + "addBlankPage": { + "name": "Add Blank Page", + "subtitle": "Insert an empty page anywhere in your PDF." + }, + "reversePages": { + "name": "Reverse Pages", + "subtitle": "Flip the order of all pages in your document." + }, + "rotatePdf": { + "name": "Rotate PDF", + "subtitle": "Turn pages in 90-degree increments." + }, + "rotateCustom": { + "name": "Rotate by Custom Degrees", + "subtitle": "Rotate pages by any custom angle." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "Arrange multiple pages onto a single sheet." + }, + "combineToSinglePage": { + "name": "Combine to Single Page", + "subtitle": "Stitch all pages into one continuous scroll." + }, + "viewMetadata": { + "name": "View Metadata", + "subtitle": "Inspect the hidden properties of your PDF." + }, + "editMetadata": { + "name": "Edit Metadata", + "subtitle": "Change the author, title, and other properties." + }, + "pdfsToZip": { + "name": "PDFs to ZIP", + "subtitle": "Package multiple PDF files into a ZIP archive." + }, + "comparePdfs": { + "name": "Compare PDFs", + "subtitle": "Compare two PDFs side by side." + }, + "posterizePdf": { + "name": "Posterize PDF", + "subtitle": "Split a large page into multiple smaller pages." + }, + "fixPageSize": { + "name": "Fix Page Size", + "subtitle": "Standardize all pages to a uniform size." + }, + "linearizePdf": { + "name": "Linearize PDF", + "subtitle": "Optimize PDF for fast web viewing." + }, + "pageDimensions": { + "name": "Page Dimensions", + "subtitle": "Analyze page size, orientation, and units." + }, + "removeRestrictions": { + "name": "Remove Restrictions", + "subtitle": "Remove password protection and security restrictions associated with digitally signed PDF files." + }, + "repairPdf": { + "name": "Repair PDF", + "subtitle": "Recover data from corrupted or damaged PDF files." + }, + "encryptPdf": { + "name": "Encrypt PDF", + "subtitle": "Lock your PDF by adding a password." + }, + "sanitizePdf": { + "name": "Sanitize PDF", + "subtitle": "Remove metadata, annotations, scripts, and more." + }, + "decryptPdf": { + "name": "Decrypt PDF", + "subtitle": "Unlock PDF by removing password protection." + }, + "flattenPdf": { + "name": "Flatten PDF", + "subtitle": "Make form fields and annotations non-editable." + }, + "removeMetadata": { + "name": "Remove Metadata", + "subtitle": "Strip hidden data from your PDF." + }, + "changePermissions": { + "name": "Change Permissions", + "subtitle": "Set or change user permissions on a PDF." + }, + "odtToPdf": { + "name": "ODT to PDF", + "subtitle": "Convert OpenDocument Text files to PDF format. Supports multiple files.", + "acceptedFormats": "ODT files", + "convertButton": "Convert to PDF" + }, + "csvToPdf": { + "name": "CSV to PDF", + "subtitle": "Convert CSV spreadsheet files to PDF format. Supports multiple files.", + "acceptedFormats": "CSV files", + "convertButton": "Convert to PDF" + }, + "rtfToPdf": { + "name": "RTF to PDF", + "subtitle": "Convert Rich Text Format documents to PDF. Supports multiple files.", + "acceptedFormats": "RTF files", + "convertButton": "Convert to PDF" + }, + "wordToPdf": { + "name": "Word to PDF", + "subtitle": "Convert Word documents (DOCX, DOC, ODT, RTF) to PDF format. Supports multiple files.", + "acceptedFormats": "DOCX, DOC, ODT, RTF files", + "convertButton": "Convert to PDF" + }, + "excelToPdf": { + "name": "Excel to PDF", + "subtitle": "Convert Excel spreadsheets (XLSX, XLS, ODS, CSV) to PDF format. Supports multiple files.", + "acceptedFormats": "XLSX, XLS, ODS, CSV files", + "convertButton": "Convert to PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint to PDF", + "subtitle": "Convert PowerPoint presentations (PPTX, PPT, ODP) to PDF format. Supports multiple files.", + "acceptedFormats": "PPTX, PPT, ODP files", + "convertButton": "Convert to PDF" + }, + "markdownToPdf": { + "name": "Markdown to PDF", + "subtitle": "Write or paste Markdown and export it as a beautifully formatted PDF.", + "paneMarkdown": "Markdown", + "panePreview": "Preview", + "btnUpload": "Upload", + "btnSyncScroll": "Sync Scroll", + "btnSettings": "Settings", + "btnExportPdf": "Export PDF", + "settingsTitle": "Markdown Settings", + "settingsPreset": "Preset", + "presetDefault": "Default (GFM-like)", + "presetCommonmark": "CommonMark (strict)", + "presetZero": "Minimal (no features)", + "settingsOptions": "Markdown Options", + "optAllowHtml": "Allow HTML tags", + "optBreaks": "Convert newlines to
", + "optLinkify": "Auto-convert URLs to links", + "optTypographer": "Typographer (smart quotes, etc.)" + }, + "pdfBooklet": { + "name": "PDF Booklet", + "subtitle": "Rearrange pages for double-sided booklet printing. Fold and staple to create a booklet.", + "howItWorks": "How it works:", + "step1": "Upload a PDF file.", + "step2": "Pages will be rearranged in booklet order.", + "step3": "Print double-sided, flip on short edge, fold and staple.", + "paperSize": "Paper Size", + "orientation": "Orientation", + "portrait": "Portrait", + "landscape": "Landscape", + "pagesPerSheet": "Pages per Sheet", + "createBooklet": "Create Booklet", + "processing": "Processing...", + "pageCount": "Page count will be padded to multiple of 4 if needed." + }, + "xpsToPdf": { + "name": "XPS to PDF", + "subtitle": "Convert XPS/OXPS documents to PDF format. Supports multiple files.", + "acceptedFormats": "XPS, OXPS files", + "convertButton": "Convert to PDF" + }, + "mobiToPdf": { + "name": "MOBI to PDF", + "subtitle": "Convert MOBI e-books to PDF format. Supports multiple files.", + "acceptedFormats": "MOBI files", + "convertButton": "Convert to PDF" + }, + "epubToPdf": { + "name": "EPUB to PDF", + "subtitle": "Convert EPUB e-books to PDF format. Supports multiple files.", + "acceptedFormats": "EPUB files", + "convertButton": "Convert to PDF" + }, + "fb2ToPdf": { + "name": "FB2 to PDF", + "subtitle": "Convert FictionBook (FB2) e-books to PDF format. Supports multiple files.", + "acceptedFormats": "FB2 files", + "convertButton": "Convert to PDF" + }, + "cbzToPdf": { + "name": "CBZ to PDF", + "subtitle": "Convert comic book archives (CBZ/CBR) to PDF format. Supports multiple files.", + "acceptedFormats": "CBZ, CBR files", + "convertButton": "Convert to PDF" + }, + "wpdToPdf": { + "name": "WPD to PDF", + "subtitle": "Convert WordPerfect documents (WPD) to PDF format. Supports multiple files.", + "acceptedFormats": "WPD files", + "convertButton": "Convert to PDF" + }, + "wpsToPdf": { + "name": "WPS to PDF", + "subtitle": "Convert WPS Office documents to PDF format. Supports multiple files.", + "acceptedFormats": "WPS files", + "convertButton": "Convert to PDF" + }, + "xmlToPdf": { + "name": "XML to PDF", + "subtitle": "Convert XML documents to PDF format. Supports multiple files.", + "acceptedFormats": "XML files", + "convertButton": "Convert to PDF" + }, + "pagesToPdf": { + "name": "Pages to PDF", + "subtitle": "Convert Apple Pages documents to PDF format. Supports multiple files.", + "acceptedFormats": "Pages files", + "convertButton": "Convert to PDF" + }, + "odgToPdf": { + "name": "ODG to PDF", + "subtitle": "Convert OpenDocument Graphics (ODG) files to PDF format. Supports multiple files.", + "acceptedFormats": "ODG files", + "convertButton": "Convert to PDF" + }, + "odsToPdf": { + "name": "ODS to PDF", + "subtitle": "Convert OpenDocument Spreadsheet (ODS) files to PDF format. Supports multiple files.", + "acceptedFormats": "ODS files", + "convertButton": "Convert to PDF" + }, + "odpToPdf": { + "name": "ODP to PDF", + "subtitle": "Convert OpenDocument Presentation (ODP) files to PDF format. Supports multiple files.", + "acceptedFormats": "ODP files", + "convertButton": "Convert to PDF" + }, + "pubToPdf": { + "name": "PUB to PDF", + "subtitle": "Convert Microsoft Publisher (PUB) files to PDF format. Supports multiple files.", + "acceptedFormats": "PUB files", + "convertButton": "Convert to PDF" + }, + "vsdToPdf": { + "name": "VSD to PDF", + "subtitle": "Convert Microsoft Visio (VSD, VSDX) files to PDF format. Supports multiple files.", + "acceptedFormats": "VSD, VSDX files", + "convertButton": "Convert to PDF" + }, + "psdToPdf": { + "name": "PSD to PDF", + "subtitle": "Convert Adobe Photoshop (PSD) files to PDF format. Supports multiple files.", + "acceptedFormats": "PSD files", + "convertButton": "Convert to PDF" + }, + "pdfToSvg": { + "name": "PDF to SVG", + "subtitle": "Convert each page of a PDF file into a scalable vector graphic (SVG) for perfect quality at any size." + }, + "extractTables": { + "name": "Extract PDF Tables", + "subtitle": "Extract tables from PDF files and export as CSV, JSON, or Markdown." + }, + "pdfToCsv": { + "name": "PDF to CSV", + "subtitle": "Extract tables from PDF and convert to CSV format." + }, + "pdfToExcel": { + "name": "PDF to Excel", + "subtitle": "Extract tables from PDF and convert to Excel (XLSX) format." + }, + "pdfToText": { + "name": "PDF to Text", + "subtitle": "Extract text from PDF files and save as plain text (.txt). Supports multiple files.", + "note": "This tool works ONLY with digitally created PDFs. For scanned documents or image-based PDFs, use our OCR PDF tool instead.", + "convertButton": "Extract Text" + }, + "digitalSignPdf": { + "name": "Digital Signature PDF", + "pageTitle": "Digital Signature PDF - Add Cryptographic Signature | BentoPDF", + "subtitle": "Add a cryptographic digital signature to your PDF using X.509 certificates. Supports PKCS#12 (.pfx, .p12) and PEM formats. Your private key never leaves your browser.", + "certificateSection": "Certificate", + "uploadCert": "Upload certificate (.pfx, .p12)", + "certPassword": "Certificate Password", + "certPasswordPlaceholder": "Enter certificate password", + "certInfo": "Certificate Information", + "certSubject": "Subject", + "certIssuer": "Issuer", + "certValidity": "Valid", + "signatureDetails": "Signature Details (Optional)", + "reason": "Reason", + "reasonPlaceholder": "e.g., I approve this document", + "location": "Location", + "locationPlaceholder": "e.g., New York, USA", + "contactInfo": "Contact Info", + "contactPlaceholder": "e.g., email@example.com", + "applySignature": "Apply Digital Signature", + "successMessage": "PDF signed successfully! The signature can be verified in any PDF reader." + }, + "validateSignaturePdf": { + "name": "Validate PDF Signature", + "pageTitle": "Validate PDF Signature - Verify Digital Signatures | BentoPDF", + "subtitle": "Verify digital signatures in your PDF files. Check certificate validity, view signer details, and confirm document integrity. All processing happens in your browser." + }, + "emailToPdf": { + "name": "Email to PDF", + "subtitle": "Convert email files (EML, MSG) to PDF format. Supports Outlook exports and standard email formats.", + "acceptedFormats": "EML, MSG files", + "convertButton": "Convert to PDF" + }, + "fontToOutline": { + "name": "Font to Outline", + "subtitle": "Convert all fonts to vector outlines for consistent rendering across all devices." + }, + "deskewPdf": { + "name": "Deskew PDF", + "subtitle": "Automatically straighten tilted scanned pages using OpenCV." + }, + "pdfToWord": { + "name": "PDF to Word", + "subtitle": "Convert PDF files to editable Word documents." + }, + "extractImages": { + "name": "Extract Images", + "subtitle": "Extract all embedded images from your PDF files." + }, + "pdfToMarkdown": { + "name": "PDF to Markdown", + "subtitle": "Convert PDF text and tables to Markdown format." + }, + "preparePdfForAi": { + "name": "Prepare PDF for AI", + "subtitle": "Extract PDF content as LlamaIndex JSON for RAG/LLM pipelines." + }, + "pdfOcg": { + "name": "PDF OCG", + "subtitle": "View, toggle, add, and delete OCG layers in your PDF." + }, + "pdfToPdfa": { + "name": "PDF to PDF/A", + "subtitle": "Convert PDF to PDF/A for long-term archiving." + }, + "rasterizePdf": { + "name": "Rasterize PDF", + "subtitle": "Convert PDF to image-based PDF. Flatten layers and remove selectable text." + }, + "pdfWorkflow": { + "name": "PDF Workflow Builder", + "subtitle": "Build custom PDF processing pipelines with a visual node editor.", + "nodes": "Nodes", + "searchNodes": "Search nodes...", + "run": "Run", + "clear": "Clear", + "save": "Save", + "load": "Load", + "export": "Export", + "import": "Import", + "ready": "Ready", + "settings": "Settings", + "processing": "Processing...", + "saveTemplate": "Save Template", + "templateName": "Template Name", + "templatePlaceholder": "e.g. Invoice Workflow", + "cancel": "Cancel", + "loadTemplate": "Load Template", + "noTemplates": "No saved templates yet.", + "ok": "OK", + "workflowCompleted": "Workflow completed", + "errorDuringExecution": "Error during execution", + "addNodeError": "Add at least one node to run the workflow.", + "needInputOutput": "Your workflow needs at least one input node and one output node to run.", + "enterName": "Please enter a name.", + "templateExists": "A template with this name already exists.", + "templateSaved": "Template \"{{name}}\" saved.", + "templateLoaded": "Template \"{{name}}\" loaded.", + "failedLoadTemplate": "Failed to load template.", + "noSettings": "No configurable settings for this node.", + "advancedSettings": "Advanced Settings" + } +} diff --git a/public/locales/es/common.json b/public/locales/es/common.json new file mode 100644 index 0000000..690ba7b --- /dev/null +++ b/public/locales/es/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "Inicio", + "about": "Acerca de", + "contact": "Contacto", + "licensing": "Licencias", + "allTools": "Todas las Herramientas", + "openMainMenu": "Abrir menú principal", + "language": "Idioma" + }, + "donation": { + "message": "¿Te encanta BentoPDF? ¡Ayúdanos a mantenerlo gratis y de código abierto!", + "button": "Donar" + }, + "hero": { + "title": "El", + "pdfToolkit": "Kit de Herramientas PDF", + "builtForPrivacy": "diseñado para la privacidad", + "noSignups": "Sin Registro", + "unlimitedUse": "Uso Ilimitado", + "worksOffline": "Funciona Sin Conexión", + "startUsing": "Comenzar a Usar Ahora" + }, + "usedBy": { + "title": "Usado por empresas y personas que trabajan en" + }, + "features": { + "title": "¿Por qué elegir", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "Sin Registro", + "description": "Comienza al instante, sin cuentas ni correos electrónicos." + }, + "noUploads": { + "title": "Sin Cargas", + "description": "100% del lado del cliente, tus archivos nunca salen de tu dispositivo." + }, + "foreverFree": { + "title": "Gratis para Siempre", + "description": "Todas las herramientas, sin pruebas, sin restricciones de pago." + }, + "noLimits": { + "title": "Sin Límites", + "description": "Usa tanto como quieras, sin límites ocultos." + }, + "batchProcessing": { + "title": "Procesamiento por Lotes", + "description": "Maneja PDFs ilimitados de una sola vez." + }, + "lightningFast": { + "title": "Ultrarrápido", + "description": "Procesa PDFs al instante, sin esperas ni retrasos." + } + }, + "tools": { + "title": "Comienza con", + "toolsLabel": "Herramientas", + "subtitle": "Haz clic en una herramienta para abrir el cargador de archivos", + "searchPlaceholder": "Buscar una herramienta (ej., 'dividir', 'organizar'...)", + "backToTools": "Volver a Herramientas", + "firstLoadNotice": "La primera carga toma un momento mientras descargamos nuestro motor de conversión. Después de eso, todas las cargas serán instantáneas." + }, + "upload": { + "clickToSelect": "Haz clic para seleccionar un archivo", + "orDragAndDrop": "o arrastra y suelta", + "pdfOrImages": "PDFs o Imágenes", + "filesNeverLeave": "Tus archivos nunca salen de tu dispositivo.", + "addMore": "Agregar Más Archivos", + "clearAll": "Limpiar Todo", + "clearFiles": "Borrar archivos", + "hints": { + "singlePdf": "Un solo archivo PDF", + "pdfFile": "Archivo PDF", + "multiplePdfs2": "Múltiples archivos PDF (al menos 2)", + "bmpImages": "Imágenes BMP", + "oneOrMorePdfs": "Uno o más archivos PDF", + "pdfDocuments": "Documentos PDF", + "oneOrMoreCsv": "Uno o más archivos CSV", + "multiplePdfsSupported": "Múltiples archivos PDF compatibles", + "singleOrMultiplePdfs": "Uno o varios archivos PDF compatibles", + "singlePdfFile": "Un solo archivo PDF", + "pdfWithForms": "Archivo PDF con campos de formulario", + "heicImages": "Imágenes HEIC/HEIF", + "jpgImages": "Imágenes JPG, JPEG, JP2, JPX", + "pdfsOrImages": "PDFs o imágenes", + "oneOrMoreOdt": "Uno o más archivos ODT", + "singlePdfOnly": "Solo un archivo PDF", + "pdfFiles": "Archivos PDF", + "multiplePdfs": "Múltiples archivos PDF", + "pngImages": "Imágenes PNG", + "pdfFilesOneOrMore": "Archivos PDF (uno o más)", + "oneOrMoreRtf": "Uno o más archivos RTF", + "svgGraphics": "Gráficos SVG", + "tiffImages": "Imágenes TIFF", + "webpImages": "Imágenes WebP" + } + }, + "loader": { + "processing": "Procesando..." + }, + "alert": { + "title": "Alerta", + "ok": "OK" + }, + "preview": { + "title": "Vista Previa del Documento", + "downloadAsPdf": "Descargar como PDF", + "close": "Cerrar" + }, + "settings": { + "title": "Configuración", + "shortcuts": "Atajos", + "preferences": "Preferencias", + "displayPreferences": "Preferencias de Visualización", + "searchShortcuts": "Buscar atajos...", + "shortcutsInfo": "Mantén presionadas las teclas para establecer un atajo. Los cambios se guardan automáticamente.", + "shortcutsWarning": "⚠️ Evita los atajos comunes del navegador (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N, etc.) ya que pueden no funcionar de manera confiable.", + "import": "Importar", + "export": "Exportar", + "resetToDefaults": "Restaurar Valores Predeterminados", + "fullWidthMode": "Modo de Ancho Completo", + "fullWidthDescription": "Usa el ancho completo de la pantalla para todas las herramientas en lugar de un contenedor centrado", + "settingsAutoSaved": "La configuración se guarda automáticamente", + "clickToSet": "Haz clic para establecer", + "pressKeys": "Presiona teclas...", + "warnings": { + "alreadyInUse": "Atajo Ya en Uso", + "assignedTo": "ya está asignado a:", + "chooseDifferent": "Por favor elige un atajo diferente.", + "reserved": "Advertencia de Atajo Reservado", + "commonlyUsed": "se usa comúnmente para:", + "unreliable": "Este atajo puede no funcionar de manera confiable o puede entrar en conflicto con el comportamiento del navegador/sistema.", + "useAnyway": "¿Quieres usarlo de todos modos?", + "resetTitle": "Restablecer Atajos", + "resetMessage": "¿Estás seguro de que quieres restablecer todos los atajos a los valores predeterminados?

Esta acción no se puede deshacer.", + "importSuccessTitle": "Importación Exitosa", + "importSuccessMessage": "¡Atajos importados exitosamente!", + "importFailTitle": "Importación Fallida", + "importFailMessage": "Error al importar atajos. Formato de archivo inválido." + } + }, + "warning": { + "title": "Advertencia", + "cancel": "Cancelar", + "proceed": "Continuar" + }, + "compliance": { + "title": "Tus datos nunca salen de tu dispositivo", + "weKeep": "Mantenemos", + "yourInfoSafe": "tu información segura", + "byFollowingStandards": "siguiendo estándares de seguridad globales.", + "processingLocal": "Todo el procesamiento ocurre localmente en tu dispositivo.", + "gdpr": { + "title": "Cumplimiento GDPR", + "description": "Protege los datos personales y la privacidad de las personas dentro de la Unión Europea." + }, + "ccpa": { + "title": "Cumplimiento CCPA", + "description": "Otorga a los residentes de California derechos sobre cómo se recopila, usa y comparte su información personal." + }, + "hipaa": { + "title": "Cumplimiento HIPAA", + "description": "Establece salvaguardas para el manejo de información de salud sensible en el sistema de atención médica de Estados Unidos." + } + }, + "faq": { + "title": "Preguntas", + "questions": "Frecuentes", + "isFree": { + "question": "¿BentoPDF es realmente gratis?", + "answer": "Sí, absolutamente. Todas las herramientas en BentoPDF son 100% gratuitas, sin límites de archivos, sin registro y sin marcas de agua. Creemos que todos merecen acceso a herramientas PDF simples y potentes sin un muro de pago." + }, + "areFilesSecure": { + "question": "¿Mis archivos están seguros? ¿Dónde se procesan?", + "answer": "Tus archivos están lo más seguros posible porque nunca salen de tu computadora. Todo el procesamiento ocurre directamente en tu navegador web (del lado del cliente). Nunca cargamos tus archivos a un servidor, por lo que mantienes total privacidad y control sobre tus documentos." + }, + "platforms": { + "question": "¿Funciona en Mac, Windows y Móvil?", + "answer": "¡Sí! Dado que BentoPDF se ejecuta completamente en tu navegador, funciona en cualquier sistema operativo con un navegador web moderno, incluyendo Windows, macOS, Linux, iOS y Android." + }, + "gdprCompliant": { + "question": "¿BentoPDF cumple con GDPR?", + "answer": "Sí. BentoPDF cumple completamente con GDPR. Dado que todo el procesamiento de archivos ocurre localmente en tu navegador y nunca recopilamos ni transmitimos tus archivos a ningún servidor, no tenemos acceso a tus datos. Esto garantiza que siempre tengas el control de tus documentos." + }, + "dataStorage": { + "question": "¿Almacenan o rastrean alguno de mis archivos?", + "answer": "No. Nunca almacenamos, rastreamos ni registramos tus archivos. Todo lo que haces en BentoPDF ocurre en la memoria de tu navegador y desaparece una vez que cierras la página. No hay cargas, no hay registros de historial y no hay servidores involucrados." + }, + "different": { + "question": "¿Qué hace que BentoPDF sea diferente de otras herramientas PDF?", + "answer": "La mayoría de las herramientas PDF cargan tus archivos a un servidor para procesarlos. BentoPDF nunca hace eso. Utilizamos tecnología web moderna y segura para procesar tus archivos directamente en tu navegador. Esto significa un rendimiento más rápido, mayor privacidad y total tranquilidad." + }, + "browserBased": { + "question": "¿Cómo me mantiene seguro el procesamiento basado en navegador?", + "answer": "Al ejecutarse completamente dentro de tu navegador, BentoPDF garantiza que tus archivos nunca salgan de tu dispositivo. Esto elimina los riesgos de hackeos de servidores, violaciones de datos o accesos no autorizados. Tus archivos siguen siendo tuyos, siempre." + }, + "analytics": { + "question": "¿Usan cookies o análisis para rastrearme?", + "answer": "Nos preocupamos por tu privacidad. BentoPDF no rastrea información personal. Usamos Simple Analytics únicamente para ver recuentos de visitas anónimas. Esto significa que podemos saber cuántos usuarios visitan nuestro sitio, pero nunca sabemos quién eres. Simple Analytics cumple completamente con GDPR y respeta tu privacidad." + }, + "sectionTitle": "Preguntas frecuentes" + }, + "testimonials": { + "title": "Lo que Nuestros", + "users": "Usuarios", + "say": "Dicen" + }, + "support": { + "title": "¿Te Gusta Mi Trabajo?", + "description": "BentoPDF es un proyecto de pasión, creado para proporcionar un kit de herramientas PDF gratuito, privado y potente para todos. Si te resulta útil, considera apoyar su desarrollo. ¡Cada café ayuda!", + "buyMeCoffee": "Cómprame un Café" + }, + "footer": { + "copyright": "© 2025 BentoPDF. Todos los derechos reservados.", + "version": "Versión", + "company": "Empresa", + "aboutUs": "Acerca de Nosotros", + "faqLink": "Preguntas Frecuentes", + "contactUs": "Contáctanos", + "legal": "Legal", + "termsAndConditions": "Términos y Condiciones", + "privacyPolicy": "Política de Privacidad", + "followUs": "Síguenos" + }, + "merge": { + "title": "Fusionar PDFs", + "description": "Combina archivos completos o selecciona páginas específicas para fusionar en un nuevo documento.", + "fileMode": "Modo Archivo", + "pageMode": "Modo Página", + "howItWorks": "Cómo funciona:", + "fileModeInstructions": [ + "Haz clic y arrastra el ícono para cambiar el orden de los archivos.", + "En el cuadro \"Páginas\" para cada archivo, puedes especificar rangos (ej., \"1-3, 5\") para fusionar solo esas páginas.", + "Deja el cuadro \"Páginas\" en blanco para incluir todas las páginas de ese archivo." + ], + "pageModeInstructions": [ + "Todas las páginas de tus PDFs cargados se muestran a continuación.", + "Simplemente arrastra y suelta las miniaturas de páginas individuales para crear el orden exacto que deseas para tu nuevo archivo." + ], + "mergePdfs": "Fusionar PDFs" + }, + "common": { + "page": "Página", + "pages": "Páginas", + "of": "de", + "download": "Descargar", + "cancel": "Cancelar", + "save": "Guardar", + "delete": "Eliminar", + "edit": "Editar", + "add": "Agregar", + "remove": "Remover", + "loading": "Cargando...", + "error": "Error", + "success": "Éxito", + "file": "Archivo", + "files": "Archivos", + "close": "Cerrar" + }, + "about": { + "hero": { + "title": "Creemos que las herramientas PDF deben ser", + "subtitle": "rápidas, privadas y gratuitas.", + "noCompromises": "Sin compromisos." + }, + "mission": { + "title": "Nuestra Misión", + "description": "Proporcionar la caja de herramientas PDF más completa que respete tu privacidad y nunca pida pago. Creemos que las herramientas de documentos esenciales deben ser accesibles para todos, en todas partes, sin barreras." + }, + "philosophy": { + "label": "Nuestra Filosofía Central", + "title": "Privacidad Primero. Siempre.", + "description": "En una era donde los datos son una mercancía, adoptamos un enfoque diferente. Todo el procesamiento de las herramientas de Bentopdf ocurre localmente en tu navegador. Esto significa que tus archivos nunca tocan nuestros servidores, nunca vemos tus documentos y no rastreamos lo que haces. Tus documentos permanecen completa e inequívocamente privados. No es solo una característica; es nuestra base." + }, + "whyBentopdf": { + "title": "Por qué", + "speed": { + "title": "Diseñado para la Velocidad", + "description": "Sin esperar cargas o descargas a un servidor. Al procesar archivos directamente en tu navegador usando tecnologías web modernas como WebAssembly, ofrecemos una velocidad incomparable para todas nuestras herramientas." + }, + "free": { + "title": "Completamente Gratis", + "description": "Sin pruebas, sin suscripciones, sin tarifas ocultas y sin funciones \"premium\" retenidas como rehenes. Creemos que las herramientas PDF potentes deben ser una utilidad pública, no un centro de ganancias." + }, + "noAccount": { + "title": "No Requiere Cuenta", + "description": "Comienza a usar cualquier herramienta de inmediato. No necesitamos tu correo electrónico, una contraseña o cualquier información personal. Tu flujo de trabajo debe ser sin fricciones y anónimo." + }, + "openSource": { + "title": "Espíritu de Código Abierto", + "description": "Construido con transparencia en mente. Aprovechamos increíbles bibliotecas de código abierto como PDF-lib y PDF.js, y creemos en el esfuerzo impulsado por la comunidad para hacer que las herramientas potentes sean accesibles para todos." + } + }, + "cta": { + "title": "¿Listo para comenzar?", + "description": "Únete a miles de usuarios que confían en Bentopdf para sus necesidades diarias de documentos. Experimenta la diferencia que la privacidad y el rendimiento pueden hacer.", + "button": "Explorar Todas las Herramientas" + } + }, + "contact": { + "title": "Ponte en Contacto", + "subtitle": "Nos encantaría saber de ti. Ya sea que tengas una pregunta, comentario o solicitud de función, no dudes en comunicarte.", + "email": "Puedes contactarnos directamente por correo electrónico en:" + }, + "licensing": { + "title": "Licencias para", + "subtitle": "Elige la licencia que se ajuste a tus necesidades." + }, + "multiTool": { + "uploadPdfs": "Cargar PDFs", + "upload": "Cargar", + "addBlankPage": "Agregar Página en Blanco", + "edit": "Editar:", + "undo": "Deshacer", + "redo": "Rehacer", + "reset": "Restablecer", + "selection": "Selección:", + "selectAll": "Seleccionar Todo", + "deselectAll": "Deseleccionar Todo", + "rotate": "Rotar:", + "rotateLeft": "Izquierda", + "rotateRight": "Derecha", + "transform": "Transformar:", + "duplicate": "Duplicar", + "split": "Dividir", + "clear": "Limpiar:", + "delete": "Eliminar", + "download": "Descargar:", + "downloadSelected": "Descargar Seleccionados", + "exportPdf": "Exportar PDF", + "uploadPdfFiles": "Seleccionar Archivos PDF", + "dragAndDrop": "Arrastra y suelta archivos PDF aquí, o haz clic para seleccionar", + "selectFiles": "Seleccionar Archivos", + "renderingPages": "Renderizando páginas...", + "actions": { + "duplicatePage": "Duplicar esta página", + "deletePage": "Eliminar esta página", + "insertPdf": "Insertar PDF después de esta página", + "toggleSplit": "Alternar división después de esta página" + }, + "pleaseWait": "Por Favor Espera", + "pagesRendering": "Las páginas aún se están renderizando. Por favor espera...", + "noPagesSelected": "No Se Seleccionaron Páginas", + "selectOnePage": "Por favor selecciona al menos una página para descargar.", + "noPages": "Sin Páginas", + "noPagesToExport": "No hay páginas para exportar.", + "renderingTitle": "Renderizando vistas previas de páginas", + "errorRendering": "Error al renderizar miniaturas de páginas", + "error": "Error", + "failedToLoad": "Error al cargar" + }, + "howItWorks": { + "title": "Cómo funciona", + "step1": "Haz clic o arrastra tu archivo aquí", + "step2": "Haz clic en el botón de procesar", + "step3": "Guarda tu archivo procesado al instante" + }, + "relatedTools": { + "title": "Herramientas PDF relacionadas" + }, + "simpleMode": { + "title": "Herramientas PDF", + "subtitle": "Selecciona una herramienta para comenzar" + } +} diff --git a/public/locales/es/tools.json b/public/locales/es/tools.json new file mode 100644 index 0000000..1ca9a1f --- /dev/null +++ b/public/locales/es/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "Herramientas Populares", + "editAnnotate": "Editar y Anotar", + "convertToPdf": "Convertir a PDF", + "convertFromPdf": "Convertir desde PDF", + "organizeManage": "Organizar y Gestionar", + "optimizeRepair": "Optimizar y Reparar", + "securePdf": "Asegurar PDF" + }, + "pdfMultiTool": { + "name": "Multiherramienta PDF", + "subtitle": "Fusionar, Dividir, Organizar, Eliminar, Rotar, Agregar Páginas en Blanco, Extraer y Duplicar en una interfaz unificada." + }, + "mergePdf": { + "name": "Fusionar PDF", + "subtitle": "Combina múltiples PDFs en un solo archivo. Preserva Marcadores." + }, + "splitPdf": { + "name": "Dividir PDF", + "subtitle": "Extrae un rango de páginas en un nuevo PDF." + }, + "compressPdf": { + "name": "Comprimir PDF", + "subtitle": "Reduce el tamaño de archivo de tu PDF.", + "algorithmLabel": "Algoritmo de Compresión", + "condense": "Condensar (Recomendado)", + "photon": "Photon (Para PDFs con Muchas Fotos)", + "condenseInfo": "Condensar usa compresión avanzada: elimina peso muerto, optimiza imágenes, reduce fuentes. Mejor para la mayoría de PDFs.", + "photonInfo": "Photon convierte páginas en imágenes. Úsalo para PDFs con muchas fotos/escaneados.", + "photonWarning": "Advertencia: El texto dejará de ser seleccionable y los enlaces dejarán de funcionar.", + "levelLabel": "Nivel de Compresión", + "light": "Ligero (Preservar Calidad)", + "balanced": "Equilibrado (Recomendado)", + "aggressive": "Agresivo (Archivos Más Pequeños)", + "extreme": "Extremo (Compresión Máxima)", + "grayscale": "Convertir a Escala de Grises", + "grayscaleHint": "Reduce el tamaño del archivo eliminando información de color", + "customSettings": "Configuración Personalizada", + "customSettingsHint": "Ajusta los parámetros de compresión:", + "outputQuality": "Calidad de Salida", + "resizeImagesTo": "Redimensionar Imágenes a", + "onlyProcessAbove": "Solo Procesar Arriba de", + "removeMetadata": "Eliminar metadatos", + "subsetFonts": "Reducir fuentes (eliminar glifos no usados)", + "removeThumbnails": "Eliminar miniaturas incrustadas", + "compressButton": "Comprimir PDF" + }, + "pdfEditor": { + "name": "Editor PDF", + "subtitle": "Anotar, resaltar, redactar, comentar, agregar formas/imágenes, buscar y ver PDFs." + }, + "jpgToPdf": { + "name": "JPG a PDF", + "subtitle": "Crea un PDF desde imágenes JPG, JPEG y JPEG2000 (JP2/JPX)." + }, + "signPdf": { + "name": "Firmar PDF", + "subtitle": "Dibuja, escribe o carga tu firma." + }, + "cropPdf": { + "name": "Recortar PDF", + "subtitle": "Recorta los márgenes de cada página en tu PDF." + }, + "extractPages": { + "name": "Extraer Páginas", + "subtitle": "Guarda una selección de páginas como nuevos archivos." + }, + "duplicateOrganize": { + "name": "Duplicar y Organizar", + "subtitle": "Duplica, reordena y elimina páginas." + }, + "deletePages": { + "name": "Eliminar Páginas", + "subtitle": "Elimina páginas específicas de tu documento." + }, + "editBookmarks": { + "name": "Editar Marcadores", + "subtitle": "Agrega, edita, importa, elimina y extrae marcadores PDF." + }, + "tableOfContents": { + "name": "Tabla de Contenidos", + "subtitle": "Genera una página de tabla de contenidos desde los marcadores PDF." + }, + "pageNumbers": { + "name": "Números de Página", + "subtitle": "Inserta números de página en tu documento." + }, + "batesNumbering": { + "name": "Numeración Bates", + "subtitle": "Añadir números Bates secuenciales en uno o más archivos PDF." + }, + "addWatermark": { + "name": "Agregar Marca de Agua", + "subtitle": "Estampa texto o una imagen sobre tus páginas PDF.", + "applyToAllPages": "Aplicar a todas las páginas" + }, + "headerFooter": { + "name": "Encabezado y Pie de Página", + "subtitle": "Agrega texto en la parte superior e inferior de las páginas." + }, + "invertColors": { + "name": "Invertir Colores", + "subtitle": "Crea una versión en \"modo oscuro\" de tu PDF." + }, + "scannerEffect": { + "name": "Efecto escáner", + "subtitle": "Haz que tu PDF parezca un documento escaneado.", + "scanSettings": "Ajustes de escaneo", + "colorspace": "Espacio de color", + "gray": "Gris", + "border": "Borde", + "rotate": "Rotar", + "rotateVariance": "Variación de rotación", + "brightness": "Brillo", + "contrast": "Contraste", + "blur": "Desenfoque", + "noise": "Ruido", + "yellowish": "Amarillento", + "resolution": "Resolución", + "processButton": "Aplicar efecto escáner" + }, + "adjustColors": { + "name": "Ajustar colores", + "subtitle": "Ajusta brillo, contraste, saturación y más en tu PDF.", + "colorSettings": "Configuración de color", + "brightness": "Brillo", + "contrast": "Contraste", + "saturation": "Saturación", + "hueShift": "Tono", + "temperature": "Temperatura", + "tint": "Matiz", + "gamma": "Gamma", + "sepia": "Sepia", + "processButton": "Aplicar ajustes de color" + }, + "backgroundColor": { + "name": "Color de Fondo", + "subtitle": "Cambia el color de fondo de tu PDF." + }, + "changeTextColor": { + "name": "Cambiar Color de Texto", + "subtitle": "Cambia el color del texto en tu PDF." + }, + "addStamps": { + "name": "Agregar Sellos", + "subtitle": "Agrega sellos de imagen a tu PDF usando la barra de herramientas de anotación.", + "usernameLabel": "Nombre de Usuario del Sello", + "usernamePlaceholder": "Ingresa tu nombre (para sellos)", + "usernameHint": "Este nombre aparecerá en los sellos que crees." + }, + "removeAnnotations": { + "name": "Eliminar Anotaciones", + "subtitle": "Elimina comentarios, resaltados y enlaces." + }, + "pdfFormFiller": { + "name": "Rellenar Formularios PDF", + "subtitle": "Rellena formularios directamente en el navegador. También soporta formularios XFA." + }, + "createPdfForm": { + "name": "Crear Formulario PDF", + "subtitle": "Crea formularios PDF rellenables con campos de texto arrastrables." + }, + "removeBlankPages": { + "name": "Eliminar Páginas en Blanco", + "subtitle": "Detecta y elimina automáticamente páginas en blanco.", + "sensitivityHint": "Mayor = más estricto, solo páginas completamente en blanco. Menor = permite páginas con algo de contenido." + }, + "imageToPdf": { + "name": "Imágenes a PDF", + "subtitle": "Convierte JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP a PDF." + }, + "pngToPdf": { + "name": "PNG a PDF", + "subtitle": "Crea un PDF desde una o más imágenes PNG." + }, + "webpToPdf": { + "name": "WebP a PDF", + "subtitle": "Crea un PDF desde una o más imágenes WebP." + }, + "svgToPdf": { + "name": "SVG a PDF", + "subtitle": "Crea un PDF desde una o más imágenes SVG." + }, + "bmpToPdf": { + "name": "BMP a PDF", + "subtitle": "Crea un PDF desde una o más imágenes BMP." + }, + "heicToPdf": { + "name": "HEIC a PDF", + "subtitle": "Crea un PDF desde una o más imágenes HEIC." + }, + "tiffToPdf": { + "name": "TIFF a PDF", + "subtitle": "Crea un PDF desde una o más imágenes TIFF." + }, + "textToPdf": { + "name": "Texto a PDF", + "subtitle": "Convierte un archivo de texto plano en un PDF." + }, + "jsonToPdf": { + "name": "JSON a PDF", + "subtitle": "Convierte archivos JSON a formato PDF." + }, + "pdfToJpg": { + "name": "PDF a JPG", + "subtitle": "Convierte cada página PDF en una imagen JPG." + }, + "pdfToPng": { + "name": "PDF a PNG", + "subtitle": "Convierte cada página PDF en una imagen PNG." + }, + "pdfToWebp": { + "name": "PDF a WebP", + "subtitle": "Convierte cada página PDF en una imagen WebP." + }, + "pdfToBmp": { + "name": "PDF a BMP", + "subtitle": "Convierte cada página PDF en una imagen BMP." + }, + "pdfToTiff": { + "name": "PDF a TIFF", + "subtitle": "Convierte cada página PDF en una imagen TIFF." + }, + "pdfToGreyscale": { + "name": "PDF a Escala de Grises", + "subtitle": "Convierte todos los colores a blanco y negro." + }, + "pdfToJson": { + "name": "PDF a JSON", + "subtitle": "Convierte archivos PDF a formato JSON." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Hace que un PDF sea buscable y copiable." + }, + "alternateMix": { + "name": "Alternar y Mezclar Páginas", + "subtitle": "Fusiona PDFs alternando páginas de cada PDF. Preserva Marcadores." + }, + "addAttachments": { + "name": "Agregar Adjuntos", + "subtitle": "Incrusta uno o más archivos en tu PDF." + }, + "extractAttachments": { + "name": "Extraer Adjuntos", + "subtitle": "Extrae todos los archivos incrustados de PDF(s) como un ZIP." + }, + "editAttachments": { + "name": "Editar Adjuntos", + "subtitle": "Ve o elimina adjuntos en tu PDF." + }, + "dividePages": { + "name": "Dividir Páginas", + "subtitle": "Divide páginas horizontal o verticalmente." + }, + "addBlankPage": { + "name": "Agregar Página en Blanco", + "subtitle": "Inserta una página vacía en cualquier lugar de tu PDF." + }, + "reversePages": { + "name": "Invertir Páginas", + "subtitle": "Invierte el orden de todas las páginas en tu documento." + }, + "rotatePdf": { + "name": "Rotar PDF", + "subtitle": "Gira páginas en incrementos de 90 grados." + }, + "rotateCustom": { + "name": "Rotar por Grados Personalizados", + "subtitle": "Rota páginas por cualquier ángulo personalizado." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "Organiza múltiples páginas en una sola hoja." + }, + "combineToSinglePage": { + "name": "Combinar en Una Sola Página", + "subtitle": "Une todas las páginas en un desplazamiento continuo." + }, + "viewMetadata": { + "name": "Ver Metadatos", + "subtitle": "Inspecciona las propiedades ocultas de tu PDF." + }, + "editMetadata": { + "name": "Editar Metadatos", + "subtitle": "Cambia el autor, título y otras propiedades." + }, + "pdfsToZip": { + "name": "PDFs a ZIP", + "subtitle": "Empaqueta múltiples archivos PDF en un archivo ZIP." + }, + "comparePdfs": { + "name": "Comparar PDFs", + "subtitle": "Compara dos PDFs lado a lado." + }, + "posterizePdf": { + "name": "Posterizar PDF", + "subtitle": "Divide una página grande en múltiples páginas más pequeñas." + }, + "fixPageSize": { + "name": "Fijar Tamaño de Página", + "subtitle": "Estandariza todas las páginas a un tamaño uniforme." + }, + "linearizePdf": { + "name": "Linealizar PDF", + "subtitle": "Optimiza el PDF para visualización web rápida." + }, + "pageDimensions": { + "name": "Dimensiones de Página", + "subtitle": "Analiza el tamaño, orientación y unidades de página." + }, + "removeRestrictions": { + "name": "Eliminar Restricciones", + "subtitle": "Elimina la protección por contraseña y las restricciones de seguridad asociadas con archivos PDF firmados digitalmente." + }, + "repairPdf": { + "name": "Reparar PDF", + "subtitle": "Recupera datos de archivos PDF corruptos o dañados." + }, + "encryptPdf": { + "name": "Cifrar PDF", + "subtitle": "Bloquea tu PDF agregando una contraseña." + }, + "sanitizePdf": { + "name": "Sanear PDF", + "subtitle": "Elimina metadatos, anotaciones, scripts y más." + }, + "decryptPdf": { + "name": "Descifrar PDF", + "subtitle": "Desbloquea PDF eliminando la protección por contraseña." + }, + "flattenPdf": { + "name": "Aplanar PDF", + "subtitle": "Hace que los campos de formulario y las anotaciones no sean editables." + }, + "removeMetadata": { + "name": "Eliminar Metadatos", + "subtitle": "Elimina datos ocultos de tu PDF." + }, + "changePermissions": { + "name": "Cambiar Permisos", + "subtitle": "Establece o cambia los permisos de usuario en un PDF." + }, + "odtToPdf": { + "name": "ODT a PDF", + "subtitle": "Convierte archivos OpenDocument Text a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos ODT", + "convertButton": "Convertir a PDF" + }, + "csvToPdf": { + "name": "CSV a PDF", + "subtitle": "Convierte archivos de hoja de cálculo CSV a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos CSV", + "convertButton": "Convertir a PDF" + }, + "rtfToPdf": { + "name": "RTF a PDF", + "subtitle": "Convierte documentos Rich Text Format a PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos RTF", + "convertButton": "Convertir a PDF" + }, + "wordToPdf": { + "name": "Word a PDF", + "subtitle": "Convierte documentos Word (DOCX, DOC, ODT, RTF) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos DOCX, DOC, ODT, RTF", + "convertButton": "Convertir a PDF" + }, + "excelToPdf": { + "name": "Excel a PDF", + "subtitle": "Convierte hojas de cálculo Excel (XLSX, XLS, ODS, CSV) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos XLSX, XLS, ODS, CSV", + "convertButton": "Convertir a PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint a PDF", + "subtitle": "Convierte presentaciones PowerPoint (PPTX, PPT, ODP) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos PPTX, PPT, ODP", + "convertButton": "Convertir a PDF" + }, + "markdownToPdf": { + "name": "Markdown a PDF", + "subtitle": "Escribe o pega Markdown y expórtalo como un PDF bellamente formateado.", + "paneMarkdown": "Markdown", + "panePreview": "Vista Previa", + "btnUpload": "Cargar", + "btnSyncScroll": "Sincronizar Desplazamiento", + "btnSettings": "Configuración", + "btnExportPdf": "Exportar PDF", + "settingsTitle": "Configuración de Markdown", + "settingsPreset": "Predefinido", + "presetDefault": "Predeterminado (similar a GFM)", + "presetCommonmark": "CommonMark (estricto)", + "presetZero": "Mínimo (sin funciones)", + "settingsOptions": "Opciones de Markdown", + "optAllowHtml": "Permitir etiquetas HTML", + "optBreaks": "Convertir saltos de línea a
", + "optLinkify": "Auto-convertir URLs a enlaces", + "optTypographer": "Tipógrafo (comillas inteligentes, etc.)" + }, + "pdfBooklet": { + "name": "Folleto PDF", + "subtitle": "Reorganiza páginas para impresión de folleto a doble cara. Dobla y engrapa para crear un folleto.", + "howItWorks": "Cómo funciona:", + "step1": "Carga un archivo PDF.", + "step2": "Las páginas se reorganizarán en orden de folleto.", + "step3": "Imprime a doble cara, voltea por el borde corto, dobla y engrapa.", + "paperSize": "Tamaño de Papel", + "orientation": "Orientación", + "portrait": "Vertical", + "landscape": "Horizontal", + "pagesPerSheet": "Páginas por Hoja", + "createBooklet": "Crear Folleto", + "processing": "Procesando...", + "pageCount": "El recuento de páginas se rellenará a múltiplo de 4 si es necesario." + }, + "xpsToPdf": { + "name": "XPS a PDF", + "subtitle": "Convierte documentos XPS/OXPS a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos XPS, OXPS", + "convertButton": "Convertir a PDF" + }, + "mobiToPdf": { + "name": "MOBI a PDF", + "subtitle": "Convierte e-books MOBI a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos MOBI", + "convertButton": "Convertir a PDF" + }, + "epubToPdf": { + "name": "EPUB a PDF", + "subtitle": "Convierte e-books EPUB a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos EPUB", + "convertButton": "Convertir a PDF" + }, + "fb2ToPdf": { + "name": "FB2 a PDF", + "subtitle": "Convierte e-books FictionBook (FB2) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos FB2", + "convertButton": "Convertir a PDF" + }, + "cbzToPdf": { + "name": "CBZ a PDF", + "subtitle": "Convierte archivos de cómics (CBZ/CBR) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos CBZ, CBR", + "convertButton": "Convertir a PDF" + }, + "wpdToPdf": { + "name": "WPD a PDF", + "subtitle": "Convierte documentos WordPerfect (WPD) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos WPD", + "convertButton": "Convertir a PDF" + }, + "wpsToPdf": { + "name": "WPS a PDF", + "subtitle": "Convierte documentos WPS Office a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos WPS", + "convertButton": "Convertir a PDF" + }, + "xmlToPdf": { + "name": "XML a PDF", + "subtitle": "Convierte documentos XML a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos XML", + "convertButton": "Convertir a PDF" + }, + "pagesToPdf": { + "name": "Pages a PDF", + "subtitle": "Convierte documentos Apple Pages a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos Pages", + "convertButton": "Convertir a PDF" + }, + "odgToPdf": { + "name": "ODG a PDF", + "subtitle": "Convierte archivos OpenDocument Graphics (ODG) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos ODG", + "convertButton": "Convertir a PDF" + }, + "odsToPdf": { + "name": "ODS a PDF", + "subtitle": "Convierte archivos OpenDocument Spreadsheet (ODS) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos ODS", + "convertButton": "Convertir a PDF" + }, + "odpToPdf": { + "name": "ODP a PDF", + "subtitle": "Convierte archivos OpenDocument Presentation (ODP) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos ODP", + "convertButton": "Convertir a PDF" + }, + "pubToPdf": { + "name": "PUB a PDF", + "subtitle": "Convierte archivos Microsoft Publisher (PUB) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos PUB", + "convertButton": "Convertir a PDF" + }, + "vsdToPdf": { + "name": "VSD a PDF", + "subtitle": "Convierte archivos Microsoft Visio (VSD, VSDX) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos VSD, VSDX", + "convertButton": "Convertir a PDF" + }, + "psdToPdf": { + "name": "PSD a PDF", + "subtitle": "Convierte archivos Adobe Photoshop (PSD) a formato PDF. Soporta múltiples archivos.", + "acceptedFormats": "Archivos PSD", + "convertButton": "Convertir a PDF" + }, + "pdfToSvg": { + "name": "PDF a SVG", + "subtitle": "Convierte cada página de un archivo PDF en un gráfico vectorial escalable (SVG) para calidad perfecta a cualquier tamaño." + }, + "extractTables": { + "name": "Extraer Tablas de PDF", + "subtitle": "Extrae tablas de archivos PDF y exporta como CSV, JSON o Markdown." + }, + "pdfToCsv": { + "name": "PDF a CSV", + "subtitle": "Extrae tablas de PDF y convierte a formato CSV." + }, + "pdfToExcel": { + "name": "PDF a Excel", + "subtitle": "Extrae tablas de PDF y convierte a formato Excel (XLSX)." + }, + "pdfToText": { + "name": "PDF a Texto", + "subtitle": "Extrae texto de archivos PDF y guarda como texto plano (.txt). Soporta múltiples archivos.", + "note": "Esta herramienta funciona SOLO con PDFs creados digitalmente. Para documentos escaneados o PDFs basados en imágenes, usa nuestra herramienta OCR PDF en su lugar.", + "convertButton": "Extraer Texto" + }, + "digitalSignPdf": { + "name": "Firma Digital PDF", + "pageTitle": "Firma Digital PDF - Agregar Firma Criptográfica | BentoPDF", + "subtitle": "Agrega una firma digital criptográfica a tu PDF usando certificados X.509. Soporta formatos PKCS#12 (.pfx, .p12) y PEM. Tu clave privada nunca sale de tu navegador.", + "certificateSection": "Certificado", + "uploadCert": "Cargar certificado (.pfx, .p12)", + "certPassword": "Contraseña del Certificado", + "certPasswordPlaceholder": "Ingresa la contraseña del certificado", + "certInfo": "Información del Certificado", + "certSubject": "Sujeto", + "certIssuer": "Emisor", + "certValidity": "Válido", + "signatureDetails": "Detalles de la Firma (Opcional)", + "reason": "Razón", + "reasonPlaceholder": "ej., Apruebo este documento", + "location": "Ubicación", + "locationPlaceholder": "ej., Madrid, España", + "contactInfo": "Información de Contacto", + "contactPlaceholder": "ej., email@ejemplo.com", + "applySignature": "Aplicar Firma Digital", + "successMessage": "¡PDF firmado exitosamente! La firma se puede verificar en cualquier lector de PDF." + }, + "validateSignaturePdf": { + "name": "Validar Firma PDF", + "pageTitle": "Validar Firma PDF - Verificar Firmas Digitales | BentoPDF", + "subtitle": "Verifica firmas digitales en tus archivos PDF. Comprueba la validez del certificado, ve los detalles del firmante y confirma la integridad del documento." + }, + "emailToPdf": { + "name": "Email a PDF", + "subtitle": "Convierte archivos de correo (EML, MSG) a formato PDF. Soporta exportaciones de Outlook y formatos de correo estándar.", + "acceptedFormats": "Archivos EML, MSG", + "convertButton": "Convertir a PDF" + }, + "fontToOutline": { + "name": "Fuente a Contorno", + "subtitle": "Convierte todas las fuentes a contornos vectoriales para una renderización consistente en todos los dispositivos." + }, + "deskewPdf": { + "name": "Enderezar PDF", + "subtitle": "Endereza automáticamente páginas escaneadas inclinadas usando OpenCV." + }, + "pdfToWord": { + "name": "PDF a Word", + "subtitle": "Convertir archivos PDF a documentos Word editables." + }, + "extractImages": { + "name": "Extraer imágenes", + "subtitle": "Extraer todas las imágenes incrustadas de sus archivos PDF." + }, + "pdfToMarkdown": { + "name": "PDF a Markdown", + "subtitle": "Convertir texto y tablas de PDF a formato Markdown." + }, + "preparePdfForAi": { + "name": "Preparar PDF para IA", + "subtitle": "Extraer contenido PDF como JSON de LlamaIndex para pipelines RAG/LLM." + }, + "pdfOcg": { + "name": "Capas PDF (OCG)", + "subtitle": "Ver, alternar, agregar y eliminar capas OCG en su PDF." + }, + "pdfToPdfa": { + "name": "PDF a PDF/A", + "subtitle": "Convertir PDF a PDF/A para archivado a largo plazo." + }, + "rasterizePdf": { + "name": "Rasterizar PDF", + "subtitle": "Convertir PDF a PDF basado en imágenes. Aplanar capas y eliminar texto seleccionable." + }, + "pdfWorkflow": { + "name": "Constructor de flujos de trabajo PDF", + "subtitle": "Cree pipelines de procesamiento PDF personalizados con un editor visual de nodos.", + "nodes": "Nodos", + "searchNodes": "Buscar nodos...", + "run": "Ejecutar", + "clear": "Limpiar", + "save": "Guardar", + "load": "Cargar", + "export": "Exportar", + "import": "Importar", + "ready": "Listo", + "settings": "Configuración", + "processing": "Procesando...", + "saveTemplate": "Guardar plantilla", + "templateName": "Nombre de la plantilla", + "templatePlaceholder": "ej. Flujo de trabajo de facturación", + "cancel": "Cancelar", + "loadTemplate": "Cargar plantilla", + "noTemplates": "Aún no hay plantillas guardadas.", + "ok": "OK", + "workflowCompleted": "Flujo de trabajo completado", + "errorDuringExecution": "Error durante la ejecución", + "addNodeError": "Agregue al menos un nodo para ejecutar el flujo de trabajo.", + "needInputOutput": "Su flujo de trabajo necesita al menos un nodo de entrada y un nodo de salida para ejecutarse.", + "enterName": "Por favor, introduzca un nombre.", + "templateExists": "Ya existe una plantilla con este nombre.", + "templateSaved": "Plantilla \"{{name}}\" guardada.", + "templateLoaded": "Plantilla \"{{name}}\" cargada.", + "failedLoadTemplate": "Error al cargar la plantilla.", + "noSettings": "No hay opciones configurables para este nodo.", + "advancedSettings": "Configuración avanzada" + } +} diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json new file mode 100644 index 0000000..cbd7211 --- /dev/null +++ b/public/locales/fr/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "Accueil", + "about": "À propos", + "contact": "Contact", + "licensing": "Licence", + "allTools": "Tous les outils", + "openMainMenu": "Ouvrir le menu principal", + "language": "Langue" + }, + "donation": { + "message": "Vous aimez BentoPDF ? Aidez-nous à le garder open source !", + "button": "Soutenir" + }, + "hero": { + "title": "La", + "pdfToolkit": "palette d’outils PDF", + "builtForPrivacy": "conçue pour la confidentialité", + "noSignups": "Sans inscription", + "unlimitedUse": "Utilisation illimitée", + "worksOffline": "Fonctionne hors ligne", + "startUsing": "Commencer maintenant" + }, + "usedBy": { + "title": "Utilisé par des entreprises et des professionnels de" + }, + "features": { + "title": "Pourquoi choisir", + "bentoPdf": "BentoPDF ?", + "noSignup": { + "title": "Sans inscription", + "description": "Utilisation immédiate, sans compte ni email." + }, + "noUploads": { + "title": "Aucun envoi de fichiers", + "description": "100 % côté navigateur, vos fichiers ne quittent jamais votre appareil." + }, + "foreverFree": { + "title": "Gratuit pour toujours", + "description": "Tous les outils, sans essai, sans paiement, sans restrictions." + }, + "noLimits": { + "title": "Sans limites", + "description": "Utilisez autant que vous voulez, sans plafonds cachés." + }, + "batchProcessing": { + "title": "Traitement par lots", + "description": "Gérez un nombre illimité de PDF en une seule fois." + }, + "lightningFast": { + "title": "Ultra rapide", + "description": "Traitez vos PDF instantanément, sans attente." + } + }, + "tools": { + "title": "Commencer avec", + "toolsLabel": "Les outils", + "subtitle": "Cliquez sur un outil pour importer vos fichiers", + "searchPlaceholder": "Rechercher un outil (ex. « scinder », « organiser »...)", + "backToTools": "Retour aux outils", + "firstLoadNotice": "Le premier chargement peut prendre quelques instants, le temps de charger notre moteur de conversion. Les prochaines fois, tout se chargera instantanément." + }, + "upload": { + "clickToSelect": "Cliquez pour sélectionner un fichier", + "orDragAndDrop": "ou glissez-déposez", + "pdfOrImages": "PDF ou images", + "filesNeverLeave": "Vos fichiers restent sur votre appareil.", + "addMore": "Ajouter d’autres fichiers", + "clearAll": "Tout effacer", + "clearFiles": "Effacer les fichiers", + "hints": { + "singlePdf": "Un seul fichier PDF", + "pdfFile": "Fichier PDF", + "multiplePdfs2": "Plusieurs fichiers PDF (au moins 2)", + "bmpImages": "Images BMP", + "oneOrMorePdfs": "Un ou plusieurs fichiers PDF", + "pdfDocuments": "Documents PDF", + "oneOrMoreCsv": "Un ou plusieurs fichiers CSV", + "multiplePdfsSupported": "Plusieurs fichiers PDF pris en charge", + "singleOrMultiplePdfs": "Un ou plusieurs fichiers PDF pris en charge", + "singlePdfFile": "Un seul fichier PDF", + "pdfWithForms": "Fichier PDF avec champs de formulaire", + "heicImages": "Images HEIC/HEIF", + "jpgImages": "Images JPG, JPEG, JP2, JPX", + "pdfsOrImages": "PDFs ou images", + "oneOrMoreOdt": "Un ou plusieurs fichiers ODT", + "singlePdfOnly": "Un seul fichier PDF uniquement", + "pdfFiles": "Fichiers PDF", + "multiplePdfs": "Plusieurs fichiers PDF", + "pngImages": "Images PNG", + "pdfFilesOneOrMore": "Fichiers PDF (un ou plusieurs)", + "oneOrMoreRtf": "Un ou plusieurs fichiers RTF", + "svgGraphics": "Graphiques SVG", + "tiffImages": "Images TIFF", + "webpImages": "Images WebP" + } + }, + "loader": { + "processing": "Traitement en cours..." + }, + "alert": { + "title": "Alerte", + "ok": "OK" + }, + "preview": { + "title": "Aperçu du document", + "downloadAsPdf": "Télécharger en PDF", + "close": "Fermer" + }, + "settings": { + "title": "Paramètres", + "shortcuts": "Raccourcis", + "preferences": "Préférences", + "displayPreferences": "Préférences d’affichage", + "searchShortcuts": "Rechercher un raccourci...", + "shortcutsInfo": "Maintenez les touches pour définir un raccourci. Les changements sont enregistrés automatiquement.", + "shortcutsWarning": "⚠️ Évitez les raccourcis courants du navigateur (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N, etc.), ils peuvent ne pas fonctionner correctement.", + "import": "Importer", + "export": "Exporter", + "resetToDefaults": "Rétablir les paramètres par défaut", + "fullWidthMode": "Mode pleine largeur", + "fullWidthDescription": "Utiliser toute la largeur de l’écran au lieu d’un affichage centré", + "settingsAutoSaved": "Les paramètres sont enregistrés automatiquement", + "clickToSet": "Cliquez pour définir", + "pressKeys": "Appuyez sur les touches...", + "warnings": { + "alreadyInUse": "Raccourci déjà utilisé", + "assignedTo": "est déjà attribué à :", + "chooseDifferent": "Veuillez choisir un autre raccourci.", + "reserved": "Avertissement de raccourci réservé", + "commonlyUsed": "est couramment utilisé pour :", + "unreliable": "Ce raccourci peut ne pas fonctionner correctement ou entrer en conflit avec le navigateur ou le système.", + "useAnyway": "Souhaitez-vous l’utiliser quand même ?", + "resetTitle": "Réinitialiser les raccourcis", + "resetMessage": "Êtes-vous sûr de vouloir réinitialiser tous les raccourcis par défaut ?

Cette action est irréversible.", + "importSuccessTitle": "Importation réussie", + "importSuccessMessage": "Les raccourcis ont été importés avec succès !", + "importFailTitle": "Échec de l’importation", + "importFailMessage": "Impossible d’importer les raccourcis. Format de fichier invalide." + } + }, + "warning": { + "title": "Attention", + "cancel": "Annuler", + "proceed": "Continuer" + }, + "compliance": { + "title": "Vos données ne quittent jamais votre appareil", + "weKeep": "Nous protégeons", + "yourInfoSafe": "vos informations", + "byFollowingStandards": "en respectant les normes de sécurité internationales.", + "processingLocal": "Tous les traitements sont effectués localement sur votre appareil.", + "gdpr": { + "title": "Conformité RGPD", + "description": "Protège les données personnelles et la vie privée des citoyens de l’Union européenne." + }, + "ccpa": { + "title": "Conformité CCPA", + "description": "Accorde aux résidents de Californie des droits sur l’utilisation de leurs données personnelles." + }, + "hipaa": { + "title": "Conformité HIPAA", + "description": "Définit des règles strictes pour la gestion des données de santé aux États-Unis." + } + }, + "faq": { + "title": "Questions", + "questions": "fréquentes", + "isFree": { + "question": "BentoPDF est-il vraiment gratuit ?", + "answer": "Oui, totalement. Tous les outils BentoPDF sont 100 % gratuits, sans limite de fichiers, sans inscription et sans filigrane. Nous pensons que chacun doit avoir accès à des outils PDF simples et puissants, sans barrière payante." + }, + "areFilesSecure": { + "question": "Mes fichiers sont-ils en sécurité ? Où sont-ils traités ?", + "answer": "Vos fichiers sont parfaitement sécurisés car ils ne quittent jamais votre ordinateur. Tous les traitements se font directement dans votre navigateur. Aucun fichier n’est envoyé sur un serveur." + }, + "platforms": { + "question": "Est-ce compatible avec Mac, Windows et mobile ?", + "answer": "Oui ! BentoPDF fonctionne entièrement dans le navigateur et est compatible avec Windows, macOS, Linux, iOS et Android." + }, + "gdprCompliant": { + "question": "BentoPDF est-il conforme au RGPD ?", + "answer": "Oui. Comme tous les traitements sont locaux et qu’aucune donnée n’est collectée ou transmise, vous restez entièrement maître de vos documents." + }, + "dataStorage": { + "question": "Stockez-vous ou suivez-vous mes fichiers ?", + "answer": "Non. Aucun stockage, aucun suivi, aucun historique. Tout disparaît dès que vous fermez la page." + }, + "different": { + "question": "Qu’est-ce qui différencie BentoPDF des autres outils PDF ?", + "answer": "La plupart des outils envoient vos fichiers sur un serveur. BentoPDF traite tout localement dans votre navigateur, pour plus de rapidité, de confidentialité et de tranquillité d’esprit." + }, + "browserBased": { + "question": "Pourquoi le traitement dans le navigateur est-il plus sûr ?", + "answer": "Parce que vos fichiers restent sur votre appareil. Aucun risque de fuite, de piratage ou d’accès non autorisé." + }, + "analytics": { + "question": "Utilisez-vous des cookies ou des outils de suivi ?", + "answer": "Nous respectons votre vie privée. BentoPDF utilise uniquement des statistiques anonymes pour connaître le nombre de visites, sans jamais identifier les utilisateurs." + }, + "sectionTitle": "Questions fréquemment posées" + }, + "testimonials": { + "title": "Ce que disent", + "users": "nos utilisateurs", + "say": "" + }, + "support": { + "title": "Vous aimez ce projet ?", + "description": "BentoPDF est un projet passion, créé pour offrir une palette d’outils PDF gratuite, privée et puissante. Si cela vous aide, vous pouvez soutenir son développement. Chaque café compte !", + "buyMeCoffee": "M’offrir un café" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Tous droits réservés.", + "version": "Version", + "company": "Entreprise", + "aboutUs": "À propos", + "faqLink": "FAQ", + "contactUs": "Nous contacter", + "legal": "Mentions légales", + "termsAndConditions": "Conditions générales", + "privacyPolicy": "Politique de confidentialité", + "followUs": "Nous suivre" + }, + "merge": { + "title": "Fusionner des PDF", + "description": "Combinez des fichiers entiers ou sélectionnez des pages spécifiques pour créer un nouveau document.", + "fileMode": "Mode fichiers", + "pageMode": "Mode pages", + "howItWorks": "Fonctionnement :", + "fileModeInstructions": [ + "Cliquez-glissez l’icône pour modifier l’ordre des fichiers.", + "Dans le champ « Pages » de chaque fichier, vous pouvez définir des plages (ex. « 1-3, 5 ») pour ne fusionner que certaines pages.", + "Laissez le champ « Pages » vide pour inclure toutes les pages du fichier." + ], + "pageModeInstructions": [ + "Toutes les pages de vos PDF importés s’affichent ci-dessous.", + "Glissez-déposez simplement les miniatures pour définir l’ordre exact de votre nouveau document." + ], + "mergePdfs": "Fusionner les PDF" + }, + "common": { + "page": "Page", + "pages": "Pages", + "of": "sur", + "download": "Télécharger", + "cancel": "Annuler", + "save": "Enregistrer", + "delete": "Supprimer", + "edit": "Modifier", + "add": "Ajouter", + "remove": "Retirer", + "loading": "Chargement...", + "error": "Erreur", + "success": "Succès", + "file": "Fichier", + "files": "Fichiers", + "close": "Fermer" + }, + "about": { + "hero": { + "title": "Nous pensons que les outils PDF doivent être", + "subtitle": "rapides, privés et gratuits.", + "noCompromises": "Sans compromis." + }, + "mission": { + "title": "Notre mission", + "description": "Proposer la palette d’outils PDF la plus complète, tout en respectant votre vie privée et sans jamais demander de paiement. Les outils essentiels doivent être accessibles à tous, partout, sans barrières." + }, + "philosophy": { + "label": "Notre philosophie", + "title": "La confidentialité avant tout. Toujours.", + "description": "À une époque où les données sont devenues une monnaie, nous faisons un choix différent. Tous les traitements des outils BentoPDF sont effectués localement dans votre navigateur. Vos fichiers ne passent jamais par nos serveurs, nous ne voyons jamais vos documents et nous ne suivons pas votre activité. Ce n’est pas une option, c’est notre fondation." + }, + "whyBentopdf": { + "title": "Pourquoi", + "speed": { + "title": "Pensé pour la vitesse", + "description": "Aucune attente liée aux envois ou téléchargements serveur. Grâce au traitement local et aux technologies web modernes comme WebAssembly, nos outils sont extrêmement rapides." + }, + "free": { + "title": "Entièrement gratuit", + "description": "Aucun essai, aucun abonnement, aucun coût caché, aucune fonctionnalité « premium » bloquée. Les outils PDF doivent être un service public, pas un produit de luxe." + }, + "noAccount": { + "title": "Aucun compte requis", + "description": "Utilisez n’importe quel outil immédiatement. Pas d’email, pas de mot de passe, aucune donnée personnelle. Votre flux de travail reste fluide et anonyme." + }, + "openSource": { + "title": "Esprit open source", + "description": "Conçu dans un esprit de transparence. Nous utilisons des bibliothèques open source reconnues comme PDF-lib et PDF.js, et croyons en la force de la communauté." + } + }, + "cta": { + "title": "Prêt à commencer ?", + "description": "Rejoignez des milliers d’utilisateurs qui font confiance à BentoPDF au quotidien. Découvrez la différence qu’apportent la confidentialité et la performance.", + "button": "Explorer tous les outils" + } + }, + "contact": { + "title": "Nous contacter", + "subtitle": "Nous serions ravis d’échanger avec vous. Question, retour ou suggestion de fonctionnalité, n’hésitez pas à nous écrire.", + "email": "Vous pouvez nous contacter directement par email à :" + }, + "licensing": { + "title": "Licences pour", + "subtitle": "Choisissez la licence adaptée à vos besoins." + }, + "multiTool": { + "uploadPdfs": "Importer des PDF", + "upload": "Importer", + "addBlankPage": "Ajouter une page vierge", + "edit": "Modifier :", + "undo": "Annuler", + "redo": "Rétablir", + "reset": "Réinitialiser", + "selection": "Sélection :", + "selectAll": "Tout sélectionner", + "deselectAll": "Tout désélectionner", + "rotate": "Rotation :", + "rotateLeft": "Gauche", + "rotateRight": "Droite", + "transform": "Transformer :", + "duplicate": "Dupliquer", + "split": "Scinder", + "clear": "Effacer :", + "delete": "Supprimer", + "download": "Téléchargement :", + "downloadSelected": "Télécharger la sélection", + "exportPdf": "Exporter en PDF", + "uploadPdfFiles": "Sélectionner des fichiers PDF", + "dragAndDrop": "Glissez-déposez vos fichiers PDF ici ou cliquez pour sélectionner", + "selectFiles": "Sélectionner des fichiers", + "renderingPages": "Rendu des pages...", + "actions": { + "duplicatePage": "Dupliquer cette page", + "deletePage": "Supprimer cette page", + "insertPdf": "Insérer un PDF après cette page", + "toggleSplit": "Activer/désactiver la séparation après cette page" + }, + "pleaseWait": "Veuillez patienter", + "pagesRendering": "Les pages sont en cours de rendu. Veuillez patienter...", + "noPagesSelected": "Aucune page sélectionnée", + "selectOnePage": "Veuillez sélectionner au moins une page à télécharger.", + "noPages": "Aucune page", + "noPagesToExport": "Aucune page à exporter.", + "renderingTitle": "Génération des aperçus", + "errorRendering": "Échec du rendu des miniatures", + "error": "Erreur", + "failedToLoad": "Échec du chargement" + }, + "howItWorks": { + "title": "Comment ça marche", + "step1": "Cliquez ou glissez votre fichier ici", + "step2": "Cliquez sur le bouton de traitement", + "step3": "Enregistrez votre fichier traité instantanément" + }, + "relatedTools": { + "title": "Outils PDF associés" + }, + "simpleMode": { + "title": "Outils PDF", + "subtitle": "Sélectionnez un outil pour commencer" + } +} diff --git a/public/locales/fr/tools.json b/public/locales/fr/tools.json new file mode 100644 index 0000000..4a208dd --- /dev/null +++ b/public/locales/fr/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "Outils populaires", + "editAnnotate": "Éditer et annoter", + "convertToPdf": "Convertir en PDF", + "convertFromPdf": "Convertir depuis le PDF", + "organizeManage": "Organiser et gérer", + "optimizeRepair": "Optimiser et réparer", + "securePdf": "Sécuriser les PDF" + }, + "pdfMultiTool": { + "name": "Outil PDF tout-en-un", + "subtitle": "Fusionner, scinder, organiser, supprimer, faire pivoter, ajouter des pages vierges, extraire et dupliquer dans une interface unifiée." + }, + "mergePdf": { + "name": "Fusionner des PDF", + "subtitle": "Assembler plusieurs PDF en un seul fichier, tout en conservant les signets." + }, + "splitPdf": { + "name": "Scinder un PDF", + "subtitle": "Extraire une plage de pages dans un nouveau PDF." + }, + "compressPdf": { + "name": "Compresser un PDF", + "subtitle": "Réduire la taille du fichier PDF.", + "algorithmLabel": "Algorithme de compression", + "condense": "Condensé (recommandé)", + "photon": "Photon (pour les PDF riches en photos)", + "condenseInfo": "Condensé utilise une compression avancée : suppression du superflu, optimisation des images, sous-ensemble des polices. Idéal pour la plupart des PDF.", + "photonInfo": "Photon convertit les pages en images. À utiliser pour les PDF contenant beaucoup de photos ou scannés.", + "photonWarning": "Attention : le texte ne sera plus sélectionnable et les liens ne fonctionneront plus.", + "levelLabel": "Niveau de compression", + "light": "Léger (préserver la qualité)", + "balanced": "Équilibré (recommandé)", + "aggressive": "Agressif (fichiers plus petits)", + "extreme": "Extrême (compression maximale)", + "grayscale": "Convertir en niveaux de gris", + "grayscaleHint": "Réduit la taille du fichier en supprimant les informations de couleur", + "customSettings": "Paramètres personnalisés", + "customSettingsHint": "Affiner les paramètres de compression :", + "outputQuality": "Qualité de sortie", + "resizeImagesTo": "Redimensionner les images à", + "onlyProcessAbove": "Traiter uniquement au-dessus de", + "removeMetadata": "Supprimer les métadonnées", + "subsetFonts": "Sous-ensemble des polices (supprimer les glyphes inutilisés)", + "removeThumbnails": "Supprimer les vignettes intégrées", + "compressButton": "Compresser le PDF" + }, + "pdfEditor": { + "name": "Éditeur PDF", + "subtitle": "Annoter, surligner, masquer, commenter, ajouter des formes ou images, rechercher et afficher des PDF." + }, + "jpgToPdf": { + "name": "JPG vers PDF", + "subtitle": "Créer un PDF à partir d’une ou plusieurs images JPG." + }, + "signPdf": { + "name": "Signer un PDF", + "subtitle": "Dessiner, saisir ou importer votre signature." + }, + "cropPdf": { + "name": "Rogner un PDF", + "subtitle": "Ajuster les marges de chaque page du PDF." + }, + "extractPages": { + "name": "Extraire des pages", + "subtitle": "Enregistrer une sélection de pages dans de nouveaux fichiers." + }, + "duplicateOrganize": { + "name": "Dupliquer et organiser", + "subtitle": "Dupliquer, réorganiser et supprimer des pages." + }, + "deletePages": { + "name": "Supprimer des pages", + "subtitle": "Retirer des pages spécifiques du document." + }, + "editBookmarks": { + "name": "Modifier les signets", + "subtitle": "Ajouter, modifier, importer, supprimer et extraire des signets PDF." + }, + "tableOfContents": { + "name": "Table des matières", + "subtitle": "Générer une table des matières à partir des signets du PDF." + }, + "pageNumbers": { + "name": "Numéros de page", + "subtitle": "Insérer une numérotation dans le document." + }, + "batesNumbering": { + "name": "Numérotation Bates", + "subtitle": "Ajouter des numéros Bates séquentiels sur un ou plusieurs fichiers PDF." + }, + "addWatermark": { + "name": "Ajouter un filigrane", + "subtitle": "Apposer un texte ou une image sur les pages du PDF.", + "applyToAllPages": "Appliquer à toutes les pages" + }, + "headerFooter": { + "name": "En-tête et pied de page", + "subtitle": "Ajouter du texte en haut et en bas des pages." + }, + "invertColors": { + "name": "Inverser les couleurs", + "subtitle": "Créer une version « mode sombre » du PDF." + }, + "scannerEffect": { + "name": "Effet scanner", + "subtitle": "Donnez à votre PDF l'apparence d'un document scanné.", + "scanSettings": "Paramètres de numérisation", + "colorspace": "Espace colorimétrique", + "gray": "Gris", + "border": "Bordure", + "rotate": "Rotation", + "rotateVariance": "Variance de rotation", + "brightness": "Luminosité", + "contrast": "Contraste", + "blur": "Flou", + "noise": "Bruit", + "yellowish": "Jaunissement", + "resolution": "Résolution", + "processButton": "Appliquer l'effet scanner" + }, + "adjustColors": { + "name": "Ajuster les couleurs", + "subtitle": "Affinez la luminosité, le contraste, la saturation et plus dans votre PDF.", + "colorSettings": "Paramètres de couleur", + "brightness": "Luminosité", + "contrast": "Contraste", + "saturation": "Saturation", + "hueShift": "Teinte", + "temperature": "Température", + "tint": "Nuance", + "gamma": "Gamma", + "sepia": "Sépia", + "processButton": "Appliquer les ajustements" + }, + "backgroundColor": { + "name": "Couleur de fond", + "subtitle": "Modifier la couleur de fond du PDF." + }, + "changeTextColor": { + "name": "Changer la couleur du texte", + "subtitle": "Modifier la couleur du texte dans le PDF." + }, + "addStamps": { + "name": "Ajouter des tampons", + "subtitle": "Ajouter des tampons image via la barre d’annotations.", + "usernameLabel": "Nom du tampon", + "usernamePlaceholder": "Entrez votre nom (pour les tampons)", + "usernameHint": "Ce nom apparaîtra sur les tampons que vous créez." + }, + "removeAnnotations": { + "name": "Supprimer les annotations", + "subtitle": "Retirer les commentaires, surlignages et liens." + }, + "pdfFormFiller": { + "name": "Remplir un formulaire PDF", + "subtitle": "Remplir des formulaires directement dans le navigateur, y compris les formulaires XFA." + }, + "createPdfForm": { + "name": "Créer un formulaire PDF", + "subtitle": "Créer des formulaires PDF interactifs avec des champs glisser-déposer." + }, + "removeBlankPages": { + "name": "Supprimer les pages blanches", + "subtitle": "Détecter et supprimer automatiquement les pages vides.", + "sensitivityHint": "Plus élevé = plus strict, uniquement les pages vierges. Plus bas = autorise les pages avec du contenu." + }, + "imageToPdf": { + "name": "Images vers PDF", + "subtitle": "Convertir un JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP en PDF." + }, + "pngToPdf": { + "name": "PNG vers PDF", + "subtitle": "Créer un PDF à partir d’une ou plusieurs images PNG." + }, + "webpToPdf": { + "name": "WebP vers PDF", + "subtitle": "Créer un PDF à partir d’une ou plusieurs images WebP." + }, + "svgToPdf": { + "name": "SVG vers PDF", + "subtitle": "Créer un PDF à partir d’une ou plusieurs images SVG." + }, + "bmpToPdf": { + "name": "BMP vers PDF", + "subtitle": "Créer un PDF à partir d’une ou plusieurs images BMP." + }, + "heicToPdf": { + "name": "HEIC vers PDF", + "subtitle": "Créer un PDF à partir d’une ou plusieurs images HEIC." + }, + "tiffToPdf": { + "name": "TIFF vers PDF", + "subtitle": "Créer un PDF à partir d’une ou plusieurs images TIFF." + }, + "textToPdf": { + "name": "Texte vers PDF", + "subtitle": "Convertir un fichier texte en PDF." + }, + "jsonToPdf": { + "name": "JSON vers PDF", + "subtitle": "Convertir des fichiers JSON en PDF." + }, + "pdfToJpg": { + "name": "PDF vers JPG", + "subtitle": "Convertir chaque page du PDF en image JPG." + }, + "pdfToPng": { + "name": "PDF vers PNG", + "subtitle": "Convertir chaque page du PDF en image PNG." + }, + "pdfToWebp": { + "name": "PDF vers WebP", + "subtitle": "Convertir chaque page du PDF en image WebP." + }, + "pdfToBmp": { + "name": "PDF vers BMP", + "subtitle": "Convertir chaque page du PDF en image BMP." + }, + "pdfToTiff": { + "name": "PDF vers TIFF", + "subtitle": "Convertir chaque page du PDF en image TIFF." + }, + "pdfToGreyscale": { + "name": "PDF en niveaux de gris", + "subtitle": "Convertir toutes les couleurs en noir et blanc." + }, + "pdfToJson": { + "name": "PDF vers JSON", + "subtitle": "Convertir des fichiers PDF en JSON." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Rendre un PDF consultable et copiable." + }, + "alternateMix": { + "name": "Alterner et mélanger les pages", + "subtitle": "Fusionner des PDF en alternant les pages de chaque fichier, tout en conservant les signets." + }, + "addAttachments": { + "name": "Ajouter des pièces jointes", + "subtitle": "Intégrer un ou plusieurs fichiers dans le PDF." + }, + "extractAttachments": { + "name": "Extraire les pièces jointes", + "subtitle": "Extraire tous les fichiers intégrés des PDF dans une archive ZIP." + }, + "editAttachments": { + "name": "Gérer les pièces jointes", + "subtitle": "Afficher ou supprimer les pièces jointes du PDF." + }, + "dividePages": { + "name": "Diviser les pages", + "subtitle": "Diviser les pages horizontalement ou verticalement." + }, + "addBlankPage": { + "name": "Ajouter une page vierge", + "subtitle": "Insérer une page vide à n’importe quel endroit du PDF." + }, + "reversePages": { + "name": "Inverser l’ordre des pages", + "subtitle": "Renverser l’ordre de toutes les pages du document." + }, + "rotatePdf": { + "name": "Faire pivoter un PDF", + "subtitle": "Tourner les pages par incréments de 90°." + }, + "rotateCustom": { + "name": "Rotation par angle personnalisé", + "subtitle": "Faire pivoter les pages selon un angle personnalisé." + }, + "nUpPdf": { + "name": "PDF N-up", + "subtitle": "Afficher plusieurs pages sur une seule feuille." + }, + "combineToSinglePage": { + "name": "Combiner en une seule page", + "subtitle": "Assembler toutes les pages en un défilement continu." + }, + "viewMetadata": { + "name": "Afficher les métadonnées", + "subtitle": "Consulter les propriétés internes du PDF." + }, + "editMetadata": { + "name": "Modifier les métadonnées", + "subtitle": "Changer l’auteur, le titre et autres propriétés." + }, + "pdfsToZip": { + "name": "PDF vers ZIP", + "subtitle": "Regrouper plusieurs fichiers PDF dans une archive ZIP." + }, + "comparePdfs": { + "name": "Comparer des PDF", + "subtitle": "Comparer deux PDF côte à côte." + }, + "posterizePdf": { + "name": "Posteriser un PDF", + "subtitle": "Découper une grande page en plusieurs pages plus petites." + }, + "fixPageSize": { + "name": "Uniformiser la taille des pages", + "subtitle": "Standardiser toutes les pages à un format identique." + }, + "linearizePdf": { + "name": "Optimiser pour le web", + "subtitle": "Optimiser le PDF pour un affichage rapide en ligne." + }, + "pageDimensions": { + "name": "Dimensions des pages", + "subtitle": "Analyser la taille, l’orientation et les unités des pages." + }, + "removeRestrictions": { + "name": "Supprimer les restrictions", + "subtitle": "Supprimer les protections par mot de passe et restrictions de sécurité des PDF signés." + }, + "repairPdf": { + "name": "Réparer un PDF", + "subtitle": "Récupérer les données de fichiers PDF corrompus ou endommagés." + }, + "encryptPdf": { + "name": "Chiffrer un PDF", + "subtitle": "Protéger le PDF en ajoutant un mot de passe." + }, + "sanitizePdf": { + "name": "Nettoyer un PDF", + "subtitle": "Supprimer les métadonnées, annotations, scripts et autres éléments sensibles." + }, + "decryptPdf": { + "name": "Déverrouiller un PDF", + "subtitle": "Supprimer la protection par mot de passe." + }, + "flattenPdf": { + "name": "Aplatir le PDF", + "subtitle": "Rendre les champs de formulaire et annotations non modifiables." + }, + "removeMetadata": { + "name": "Supprimer les métadonnées", + "subtitle": "Effacer les données cachées du PDF." + }, + "changePermissions": { + "name": "Modifier les autorisations", + "subtitle": "Définir ou modifier les permissions utilisateur du PDF." + }, + "odtToPdf": { + "name": "ODT vers PDF", + "subtitle": "Convertir des fichiers OpenDocument Text au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers ODT", + "convertButton": "Convertir en PDF" + }, + "csvToPdf": { + "name": "CSV vers PDF", + "subtitle": "Convertir des fichiers tableur CSV au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers CSV", + "convertButton": "Convertir en PDF" + }, + "rtfToPdf": { + "name": "RTF vers PDF", + "subtitle": "Convertir des documents Rich Text Format en PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers RTF", + "convertButton": "Convertir en PDF" + }, + "wordToPdf": { + "name": "Word vers PDF", + "subtitle": "Convertir des documents Word (DOCX, DOC, ODT, RTF) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers DOCX, DOC, ODT, RTF", + "convertButton": "Convertir en PDF" + }, + "excelToPdf": { + "name": "Excel vers PDF", + "subtitle": "Convertir des feuilles de calcul Excel (XLSX, XLS, ODS, CSV) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers XLSX, XLS, ODS, CSV", + "convertButton": "Convertir en PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint vers PDF", + "subtitle": "Convertir des présentations PowerPoint (PPTX, PPT, ODP) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers PPTX, PPT, ODP", + "convertButton": "Convertir en PDF" + }, + "markdownToPdf": { + "name": "Markdown vers PDF", + "subtitle": "Écrire ou coller du Markdown et l’exporter en PDF avec une mise en forme soignée.", + "paneMarkdown": "Markdown", + "panePreview": "Aperçu", + "btnUpload": "Téléverser", + "btnSyncScroll": "Synchroniser le défilement", + "btnSettings": "Paramètres", + "btnExportPdf": "Exporter en PDF", + "settingsTitle": "Paramètres Markdown", + "settingsPreset": "Préréglage", + "presetDefault": "Par défaut (type GFM)", + "presetCommonmark": "CommonMark (strict)", + "presetZero": "Minimal (aucune fonctionnalité)", + "settingsOptions": "Options Markdown", + "optAllowHtml": "Autoriser les balises HTML", + "optBreaks": "Convertir les retours à la ligne en
", + "optLinkify": "Convertir automatiquement les URL en liens", + "optTypographer": "Typographie (guillemets intelligents, etc.)" + }, + "pdfBooklet": { + "name": "Livret PDF", + "subtitle": "Réorganiser les pages pour l’impression recto verso en livret. Pliez et agrafez pour créer un livret.", + "howItWorks": "Fonctionnement :", + "step1": "Téléversez un fichier PDF.", + "step2": "Les pages seront réorganisées dans l’ordre du livret.", + "step3": "Imprimez en recto verso, retournement sur le bord court, pliez et agrafez.", + "paperSize": "Format du papier", + "orientation": "Orientation", + "portrait": "Portrait", + "landscape": "Paysage", + "pagesPerSheet": "Pages par feuille", + "createBooklet": "Créer le livret", + "processing": "Traitement...", + "pageCount": "Le nombre de pages sera complété au multiple de 4 si nécessaire." + }, + "xpsToPdf": { + "name": "XPS vers PDF", + "subtitle": "Convertir des documents XPS/OXPS au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers XPS, OXPS", + "convertButton": "Convertir en PDF" + }, + "mobiToPdf": { + "name": "MOBI vers PDF", + "subtitle": "Convertir des livres numériques MOBI au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers MOBI", + "convertButton": "Convertir en PDF" + }, + "epubToPdf": { + "name": "EPUB vers PDF", + "subtitle": "Convertir des livres numériques EPUB au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers EPUB", + "convertButton": "Convertir en PDF" + }, + "fb2ToPdf": { + "name": "FB2 vers PDF", + "subtitle": "Convertir des livres numériques FictionBook (FB2) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers FB2", + "convertButton": "Convertir en PDF" + }, + "cbzToPdf": { + "name": "CBZ vers PDF", + "subtitle": "Convertir des archives de bandes dessinées (CBZ/CBR) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers CBZ, CBR", + "convertButton": "Convertir en PDF" + }, + "wpdToPdf": { + "name": "WPD vers PDF", + "subtitle": "Convertir des documents WordPerfect (WPD) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers WPD", + "convertButton": "Convertir en PDF" + }, + "wpsToPdf": { + "name": "WPS vers PDF", + "subtitle": "Convertir des documents WPS Office au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers WPS", + "convertButton": "Convertir en PDF" + }, + "xmlToPdf": { + "name": "XML vers PDF", + "subtitle": "Convertir des documents XML au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers XML", + "convertButton": "Convertir en PDF" + }, + "pagesToPdf": { + "name": "Pages vers PDF", + "subtitle": "Convertir des documents Apple Pages au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers Pages", + "convertButton": "Convertir en PDF" + }, + "odgToPdf": { + "name": "ODG vers PDF", + "subtitle": "Convertir des fichiers OpenDocument Graphics (ODG) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers ODG", + "convertButton": "Convertir en PDF" + }, + "odsToPdf": { + "name": "ODS vers PDF", + "subtitle": "Convertir des fichiers OpenDocument Spreadsheet (ODS) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers ODS", + "convertButton": "Convertir en PDF" + }, + "odpToPdf": { + "name": "ODP vers PDF", + "subtitle": "Convertir des fichiers OpenDocument Presentation (ODP) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers ODP", + "convertButton": "Convertir en PDF" + }, + "pubToPdf": { + "name": "PUB vers PDF", + "subtitle": "Convertir des fichiers Microsoft Publisher (PUB) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers PUB", + "convertButton": "Convertir en PDF" + }, + "vsdToPdf": { + "name": "VSD vers PDF", + "subtitle": "Convertir des fichiers Microsoft Visio (VSD, VSDX) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers VSD, VSDX", + "convertButton": "Convertir en PDF" + }, + "psdToPdf": { + "name": "PSD vers PDF", + "subtitle": "Convertir des fichiers Adobe Photoshop (PSD) au format PDF. Prend en charge plusieurs fichiers.", + "acceptedFormats": "Fichiers PSD", + "convertButton": "Convertir en PDF" + }, + "pdfToSvg": { + "name": "PDF vers SVG", + "subtitle": "Convertir chaque page d’un fichier PDF en graphique vectoriel évolutif (SVG) pour une qualité parfaite à toutes les tailles." + }, + "extractTables": { + "name": "Extraire les tableaux PDF", + "subtitle": "Extraire les tableaux des fichiers PDF et les exporter en CSV, JSON ou Markdown." + }, + "pdfToCsv": { + "name": "PDF vers CSV", + "subtitle": "Extraire les tableaux d’un PDF et les convertir au format CSV." + }, + "pdfToExcel": { + "name": "PDF vers Excel", + "subtitle": "Extraire les tableaux d’un PDF et les convertir au format Excel (XLSX)." + }, + "pdfToText": { + "name": "PDF vers texte", + "subtitle": "Extraire le texte des fichiers PDF et l’enregistrer en texte brut (.txt). Prend en charge plusieurs fichiers.", + "note": "Cet outil fonctionne UNIQUEMENT avec des PDF créés numériquement. Pour les documents scannés ou les PDF basés sur des images, utilisez plutôt notre outil OCR PDF.", + "convertButton": "Extraire le texte" + }, + "digitalSignPdf": { + "name": "Signature numérique PDF", + "pageTitle": "Signature numérique PDF - Ajouter une signature cryptographique | BentoPDF", + "subtitle": "Ajouter une signature numérique cryptographique à votre PDF à l’aide de certificats X.509. Prend en charge les formats PKCS#12 (.pfx, .p12) et PEM. Votre clé privée ne quitte jamais votre navigateur.", + "certificateSection": "Certificat", + "uploadCert": "Téléverser un certificat (.pfx, .p12)", + "certPassword": "Mot de passe du certificat", + "certPasswordPlaceholder": "Saisissez le mot de passe du certificat", + "certInfo": "Informations du certificat", + "certSubject": "Sujet", + "certIssuer": "Émetteur", + "certValidity": "Validité", + "signatureDetails": "Détails de la signature (facultatif)", + "reason": "Motif", + "reasonPlaceholder": "ex. : J’approuve ce document", + "location": "Lieu", + "locationPlaceholder": "ex. : Paris, France", + "contactInfo": "Coordonnées", + "contactPlaceholder": "ex. : email@exemple.com", + "applySignature": "Appliquer la signature numérique", + "successMessage": "PDF signé avec succès ! La signature peut être vérifiée dans n’importe quel lecteur PDF." + }, + "validateSignaturePdf": { + "name": "Valider la signature PDF", + "pageTitle": "Valider la signature PDF - Vérifier les signatures numériques | BentoPDF", + "subtitle": "Vérifier les signatures numériques de vos fichiers PDF. Contrôlez la validité du certificat, consultez les informations du signataire et confirmez l’intégrité du document. Tout le traitement s’effectue dans votre navigateur." + }, + "emailToPdf": { + "name": "Email vers PDF", + "subtitle": "Convertir des fichiers email (EML, MSG) au format PDF. Prend en charge les exports Outlook et les formats email standards.", + "acceptedFormats": "Fichiers EML, MSG", + "convertButton": "Convertir en PDF" + }, + "fontToOutline": { + "name": "Polices en contours", + "subtitle": "Convertir toutes les polices en contours vectoriels pour un rendu cohérent sur tous les appareils." + }, + "deskewPdf": { + "name": "Redresser un PDF", + "subtitle": "Redresser automatiquement les pages scannées inclinées à l’aide d’OpenCV." + }, + "pdfToWord": { + "name": "PDF vers Word", + "subtitle": "Convertir des fichiers PDF en documents Word modifiables." + }, + "extractImages": { + "name": "Extraire les images", + "subtitle": "Extraire toutes les images intégrées de vos fichiers PDF." + }, + "pdfToMarkdown": { + "name": "PDF vers Markdown", + "subtitle": "Convertir le texte et les tableaux PDF au format Markdown." + }, + "preparePdfForAi": { + "name": "Préparer le PDF pour l'IA", + "subtitle": "Extraire le contenu PDF en JSON LlamaIndex pour les pipelines RAG/LLM." + }, + "pdfOcg": { + "name": "Calques PDF (OCG)", + "subtitle": "Afficher, basculer, ajouter et supprimer les calques OCG de votre PDF." + }, + "pdfToPdfa": { + "name": "PDF vers PDF/A", + "subtitle": "Convertir un PDF en PDF/A pour l'archivage à long terme." + }, + "rasterizePdf": { + "name": "Rastériser le PDF", + "subtitle": "Convertir un PDF en PDF basé sur des images. Aplatir les calques et supprimer le texte sélectionnable." + }, + "pdfWorkflow": { + "name": "Constructeur de workflow PDF", + "subtitle": "Créez des pipelines de traitement PDF personnalisés avec un éditeur de nœuds visuel.", + "nodes": "Nœuds", + "searchNodes": "Rechercher des nœuds...", + "run": "Exécuter", + "clear": "Effacer", + "save": "Enregistrer", + "load": "Charger", + "export": "Exporter", + "import": "Importer", + "ready": "Prêt", + "settings": "Paramètres", + "processing": "Traitement en cours...", + "saveTemplate": "Enregistrer le modèle", + "templateName": "Nom du modèle", + "templatePlaceholder": "ex. Workflow de facturation", + "cancel": "Annuler", + "loadTemplate": "Charger un modèle", + "noTemplates": "Aucun modèle enregistré pour le moment.", + "ok": "OK", + "workflowCompleted": "Workflow terminé", + "errorDuringExecution": "Erreur lors de l'exécution", + "addNodeError": "Ajoutez au moins un nœud pour exécuter le workflow.", + "needInputOutput": "Votre workflow nécessite au moins un nœud d'entrée et un nœud de sortie pour fonctionner.", + "enterName": "Veuillez saisir un nom.", + "templateExists": "Un modèle portant ce nom existe déjà.", + "templateSaved": "Modèle \"{{name}}\" enregistré.", + "templateLoaded": "Modèle \"{{name}}\" chargé.", + "failedLoadTemplate": "Échec du chargement du modèle.", + "noSettings": "Aucun paramètre configurable pour ce nœud.", + "advancedSettings": "Paramètres avancés" + } +} diff --git a/public/locales/id/common.json b/public/locales/id/common.json new file mode 100644 index 0000000..f178594 --- /dev/null +++ b/public/locales/id/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "Beranda", + "about": "Tentang", + "contact": "Kontak", + "licensing": "Lisensi", + "allTools": "Semua Alat", + "openMainMenu": "Buka menu utama", + "language": "Bahasa" + }, + "donation": { + "message": "Suka BentoPDF? Bantu kami menjaganya tetap gratis dan sumber terbuka!", + "button": "Donasi" + }, + "hero": { + "title": " ", + "pdfToolkit": "Toolkit PDF", + "builtForPrivacy": "yang dibuat untuk privasi", + "noSignups": "Tidak Ada Pendaftaran", + "unlimitedUse": "Penggunaan Tak Terbatas", + "worksOffline": "Bekerja Offline ", + "startUsing": "Mulai Menggunakan Sekarang" + }, + "usedBy": { + "title": "Digunakan oleh perusahaan dan orang yang bekerja di" + }, + "features": { + "title": "Mengapa memilih", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "Tidak Ada Pendaftaran", + "description": "Mulai seketika, tanpa akun atau email." + }, + "noUploads": { + "title": "Tidak Ada Unggahan", + "description": "100% di sisi klien, file Anda tidak pernah meninggalkan perangkat Anda." + }, + "foreverFree": { + "title": "Gratis Selamanya", + "description": "Semua alat, tidak ada uji coba, tidak ada biaya berlangganan." + }, + "noLimits": { + "title": "Tanpa Batas", + "description": "Gunakan sebanyak yang Anda inginkan, tanpa batas tersembunyi." + }, + "batchProcessing": { + "title": "Pemrosesan Kelompok", + "description": "Tangani PDF tak terbatas dalam satu kali jalan." + }, + "lightningFast": { + "title": "Sangat Cepat", + "description": "Proses PDF secara instan, tanpa menunggu atau penundaan." + } + }, + "tools": { + "title": "Mulai dengan", + "toolsLabel": "Alat", + "subtitle": "Klik alat untuk membuka pengunggah file", + "searchPlaceholder": "Cari alat (contoh, 'pisah', 'organisir'...)", + "backToTools": "Kembali ke Alat", + "firstLoadNotice": "Pemuatan pertama memerlukan waktu sebentar karena kami mengunduh mesin konversi kami. Setelah itu, semua pemuatan akan instan." + }, + "upload": { + "clickToSelect": "Klik untuk memilih file", + "orDragAndDrop": "atau seret dan jatuhkan", + "pdfOrImages": "PDF atau Gambar", + "filesNeverLeave": "File Anda tidak pernah meninggalkan perangkat Anda.", + "addMore": "Tambah Lebih Banyak File", + "clearAll": "Hapus Semua", + "clearFiles": "Hapus file", + "hints": { + "singlePdf": "Satu file PDF", + "pdfFile": "File PDF", + "multiplePdfs2": "Beberapa file PDF (minimal 2)", + "bmpImages": "Gambar BMP", + "oneOrMorePdfs": "Satu atau lebih file PDF", + "pdfDocuments": "Dokumen PDF", + "oneOrMoreCsv": "Satu atau lebih file CSV", + "multiplePdfsSupported": "Beberapa file PDF didukung", + "singleOrMultiplePdfs": "Satu atau beberapa file PDF didukung", + "singlePdfFile": "Satu file PDF", + "pdfWithForms": "File PDF dengan kolom formulir", + "heicImages": "Gambar HEIC/HEIF", + "jpgImages": "Gambar JPG, JPEG, JP2, JPX", + "pdfsOrImages": "PDF atau gambar", + "oneOrMoreOdt": "Satu atau lebih file ODT", + "singlePdfOnly": "Hanya satu file PDF", + "pdfFiles": "File PDF", + "multiplePdfs": "Beberapa file PDF", + "pngImages": "Gambar PNG", + "pdfFilesOneOrMore": "File PDF (satu atau lebih)", + "oneOrMoreRtf": "Satu atau lebih file RTF", + "svgGraphics": "Grafik SVG", + "tiffImages": "Gambar TIFF", + "webpImages": "Gambar WebP" + } + }, + "loader": { + "processing": "Memproses..." + }, + "alert": { + "title": "Peringatan", + "ok": "OK" + }, + "preview": { + "title": "Pratinjau Dokumen", + "downloadAsPdf": "Unduh sebagai PDF", + "close": "Tutup" + }, + "settings": { + "title": "Pengaturan", + "shortcuts": "Pintasan", + "preferences": "Preferensi", + "displayPreferences": "Preferensi Tampilan", + "searchShortcuts": "Cari pintasan...", + "shortcutsInfo": "Tekan dan tahan tombol untuk mengatur pintasan. Perubahan disimpan otomatis.", + "shortcutsWarning": "⚠️ Hindari pintasan browser umum (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N dll.) karena mungkin tidak bekerja dengan baik.", + "import": "Impor", + "export": "Ekspor", + "resetToDefaults": "Atur Ulang ke Default", + "fullWidthMode": "Mode Lebar Penuh", + "fullWidthDescription": "Gunakan lebar layar penuh untuk semua alat, bukan wadah yang dipusatkan", + "settingsAutoSaved": "Pengaturan disimpan secara otomatis", + "clickToSet": "Klik untuk mengatur", + "pressKeys": "Tekan tombol...", + "warnings": { + "alreadyInUse": "Pintasan Sudah Digunakan", + "assignedTo": "sudah ditugaskan ke:", + "chooseDifferent": "Silakan pilih pintasan yang berbeda.", + "reserved": "Peringatan Pintasan Cadangan", + "commonlyUsed": "biasanya digunakan untuk:", + "unreliable": "Pintasan ini mungkin tidak berfungsi dengan andal atau mungkin bertentangan dengan perilaku browser/sistem.", + "useAnyway": "Apakah Anda ingin menggunakannya saja?", + "resetTitle": "Atur Ulang Pintasan", + "resetMessage": "Apakah Anda yakin ingin mengatur ulang semua pintasan ke default?

Tindakan ini tidak dapat dibatalkan.", + "importSuccessTitle": "Impor Berhasil", + "importSuccessMessage": "Pintasan berhasil diimpor!", + "importFailTitle": "Impor Gagal", + "importFailMessage": "Gagal mengimpor pintasan. Format file tidak valid." + } + }, + "warning": { + "title": "Peringatan", + "cancel": "Batal", + "proceed": "Lanjutkan" + }, + "compliance": { + "title": "Data Anda tidak pernah meninggalkan perangkat Anda", + "weKeep": "Kami menjaga", + "yourInfoSafe": "informasi Anda aman", + "byFollowingStandards": "dengan mengikuti standar keamanan global.", + "processingLocal": "Semua pemrosesan terjadi secara lokal di perangkat Anda.", + "gdpr": { + "title": "Kepatuhan GDPR", + "description": "Melindungi data pribadi dan privasi individu dalam Uni Eropa." + }, + "ccpa": { + "title": "Kepatuhan CCPA", + "description": "Memberikan hak kepada penduduk California atas bagaimana informasi pribadi mereka dikumpulkan, digunakan, dan dibagikan." + }, + "hipaa": { + "title": "Kepatuhan HIPAA", + "description": "Menetapkan perlindungan untuk menangani informasi kesehatan sensitif dalam sistem perawatan kesehatan Amerika Serikat." + } + }, + "faq": { + "title": "Pertanyaan yang Sering Diajukan", + "questions": "Pertanyaan", + "isFree": { + "question": "Apakah BentoPDF benar-benar gratis?", + "answer": "Ya, tentu saja. Semua alat di BentoPDF 100% gratis digunakan, tanpa batas file, tanpa pendaftaran, dan tanpa watermark. Kami percaya semua orang berhak mendapatkan akses ke alat PDF sederhana dan kuat tanpa paywall." + }, + "areFilesSecure": { + "question": "Apakah file saya aman? Di mana file diproses?", + "answer": "File Anda sangat aman karena tidak pernah meninggalkan komputer Anda. Semua pemrosesan terjadi langsung di browser web Anda (sisi klien). Kami tidak pernah mengunggah file Anda ke server, sehingga Anda mempertahankan privasi dan kontrol lengkap atas dokumen Anda." + }, + "platforms": { + "question": "Apakah ini bekerja di Mac, Windows, dan Mobile?", + "answer": "Ya! Karena BentoPDF berjalan sepenuhnya di browser Anda, ini bekerja di sistem operasi apa pun dengan browser web modern, termasuk Windows, macOS, Linux, iOS, dan Android." + }, + "gdprCompliant": { + "question": "Apakah BentoPDF patuh GDPR?", + "answer": "Ya. BentoPDF sepenuhnya patuh GDPR. Karena semua pemrosesan file terjadi secara lokal di browser Anda dan kami tidak pernah mengumpulkan atau mentransmisikan file Anda ke server mana pun, kami tidak memiliki akses ke data Anda. Ini memastikan Anda selalu mengontrol dokumen Anda." + }, + "dataStorage": { + "question": "Apakah Anda menyimpan atau melacak file saya?", + "answer": "Tidak. Kami tidak pernah menyimpan, melacak, atau mencatat file Anda. Semua yang Anda lakukan di BentoPDF terjadi di memori browser Anda dan hilang setelah Anda menutup halaman. Tidak ada unggahan, tidak ada log riwayat, dan tidak ada server yang terlibat." + }, + "different": { + "question": "Apa yang membuat BentoPDF berbeda dari alat PDF lainnya?", + "answer": "Kebanyakan alat PDF mengunggah file Anda ke server untuk diproses. BentoPDF nggak pernah begitu. Kami pakai teknologi web modern dan aman buat memproses file Anda langsung di browser Anda. Ini berarti performa lebih cepat, privasi lebih kuat, dan ketenangan pikiran." + }, + "browserBased": { + "question": "Bagaimana pemrosesan berbasis browser menjaga keamanan saya?", + "answer": "Dengan berjalan sepenuhnya di dalam browser Anda, BentoPDF memastikan file Anda tidak pernah meninggalkan perangkat Anda. Ini menghilangkan risiko peretasan server, pelanggaran data, atau akses tidak sah. File Anda tetap milik Anda—selalu." + }, + "analytics": { + "question": "Apakah Anda menggunakan cookie atau analitik untuk melacak saya?", + "answer": "Kami peduli dengan privasi Anda. BentoPDF tidak melacak informasi pribadi. Kami menggunakan Simple Analytics hanya untuk melihat jumlah kunjungan anonim. Ini berarti kami dapat mengetahui berapa banyak pengguna yang mengunjungi situs kami, tetapi kami tidak pernah tahu siapa Anda. Simple Analytics sepenuhnya patuh GDPR dan menghormati privasi Anda." + }, + "sectionTitle": "Pertanyaan yang Sering Diajukan" + }, + "testimonials": { + "title": "Apa Kata", + "users": "Pengguna", + "say": "Kami" + }, + "support": { + "title": "Suka Karya Saya?", + "description": "BentoPDF adalah proyek yang dibuat dengan sepenuh hati, untuk menyediakan toolkit PDF gratis, pribadi, dan kuat untuk semua orang. Kalau kamu merasa ini berguna, pertimbangkan untuk mendukung pengembangannya. Setiap secangkir kopi membantu!", + "buyMeCoffee": "Beli Kopi untuk Saya" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Hak cipta dilindungi.", + "version": "Versi", + "company": "Perusahaan", + "aboutUs": "Tentang Kami", + "faqLink": "FAQ", + "contactUs": "Hubungi Kami", + "legal": "Hukum", + "termsAndConditions": "Syarat dan Ketentuan", + "privacyPolicy": "Kebijakan Privasi", + "followUs": "Ikuti Kami" + }, + "merge": { + "title": "Gabung PDF", + "description": "Gabungkan file utuh, atau pilih halaman tertentu untuk digabungkan ke dalam dokumen baru.", + "fileMode": "Mode File", + "pageMode": "Mode Halaman", + "howItWorks": "Cara kerjanya:", + "fileModeInstructions": [ + "Klik dan seret ikon untuk mengubah urutan file.", + "Di kotak \"Halaman\" untuk setiap file, Anda dapat menentukan rentang (misalnya, \"1-3, 5\") untuk menggabungkan hanya halaman tersebut.", + "Biarkan kotak \"Halaman\" kosong untuk menyertakan semua halaman dari file tersebut." + ], + "pageModeInstructions": [ + "Semua halaman dari PDF yang Anda unggah ditampilkan di bawah.", + "Cukup seret dan jatuhkan thumbnail halaman satu per satu untuk membuat urutan yang tepat sesuai keinginan kamu untuk file baru." + ], + "mergePdfs": "Gabung PDF" + }, + "common": { + "page": "Halaman", + "pages": "Halaman", + "of": "dari", + "download": "Unduh", + "cancel": "Batal", + "save": "Simpan", + "delete": "Hapus", + "edit": "Edit", + "add": "Tambah", + "remove": "Hapus", + "loading": "Memuat...", + "error": "Kesalahan", + "success": "Berhasil", + "file": "File", + "files": "File", + "close": "Tutup" + }, + "about": { + "hero": { + "title": "Kami percaya alat PDF harus", + "subtitle": "cepat, pribadi, dan gratis.", + "noCompromises": "Tidak ada kompromi." + }, + "mission": { + "title": "Misi Kami", + "description": "Untuk menyediakan toolbox PDF paling komprehensif yang menghormati privasi Anda dan tidak pernah meminta pembayaran. Kami percaya alat dokumen penting harus dapat diakses oleh semua orang, di mana saja, tanpa hambatan." + }, + "philosophy": { + "label": "Filosofi Inti Kami", + "title": "Privasi Yang Utama. Selalu.", + "description": "Di era di mana data adalah komoditas, kami mengambil pendekatan berbeda. Semua pemrosesan untuk alat Bentopdf terjadi secara lokal di browser Anda. Ini berarti file Anda tidak pernah menyentuh server kami, kami tidak pernah melihat dokumen Anda, dan kami tidak melacak apa yang Anda lakukan. Dokumen Anda tetap utuh dan privat secara tegas. Ini bukan hanya fitur; ini adalah fondasi kami." + }, + "whyBentopdf": { + "title": "Mengapa", + "speed": { + "title": "Dibuat untuk Kecepatan", + "description": "Tidak ada menunggu unggahan atau unduhan ke server. Dengan memproses file langsung di browser Anda menggunakan teknologi web modern seperti WebAssembly, kami menawarkan kecepatan yang tak tertandingi untuk semua alat kami." + }, + "free": { + "title": "Sepenuhnya Gratis", + "description": "Tidak ada uji coba, tidak ada langganan, tidak ada biaya tersembunyi, dan tidak ada fitur \"premium\" yang ditahan sebagai sandera. Kami percaya alat PDF yang kuat harus menjadi utilitas publik, bukan pusat keuntungan." + }, + "noAccount": { + "title": "Tidak Perlu Akun", + "description": "Langsung mulai pakai alat apa saja. Kami tidak perlu email Anda, kata sandi, atau informasi pribadi apa pun. Alur kerja Anda harus lancar dan anonim." + }, + "openSource": { + "title": "Semangat Sumber Terbuka", + "description": "Dibangun dengan transparansi sebagai prioritas. Kami manfaatkan pustaka open-source yang luar biasa seperti PDF-lib dan PDF.js, dan percaya pada upaya yang didorong komunitas untuk membuat alat-alat kuat bisa diakses semua orang." + } + }, + "cta": { + "title": "Siap untuk memulai?", + "description": "Bergabunglah dengan ribuan pengguna yang percaya Bentopdf untuk kebutuhan dokumen harian mereka. Rasakan bedanya privasi dan performa yang ditawarkan.", + "button": "Jelajahi Semua Alat" + } + }, + "contact": { + "title": "Hubungi Kami", + "subtitle": "Kami senang mendengar dari Anda. Apakah Anda memiliki pertanyaan, umpan balik, atau permintaan fitur, jangan ragu untuk menghubungi kami.", + "email": "Anda dapat menghubungi kami secara langsung melalui email di:" + }, + "licensing": { + "title": "Lisensi untuk", + "subtitle": "Pilih lisensi yang sesuai dengan kebutuhan Anda." + }, + "multiTool": { + "uploadPdfs": "Unggah PDF", + "upload": "Unggah", + "addBlankPage": "Tambah Halaman Kosong", + "edit": "Edit:", + "undo": "Undo", + "redo": "Redo", + "reset": "Atur Ulang", + "selection": "Pilihan:", + "selectAll": "Pilih Semua", + "deselectAll": "Batalkan Pilih Semua", + "rotate": "Putar:", + "rotateLeft": "Kiri", + "rotateRight": "Kanan", + "transform": "Transform:", + "duplicate": "Duplikat", + "split": "Pisah", + "clear": "Hapus:", + "delete": "Hapus", + "download": "Unduh:", + "downloadSelected": "Unduh yang Dipilih", + "exportPdf": "Ekspor PDF", + "uploadPdfFiles": "Pilih File PDF", + "dragAndDrop": "Seret dan jatuhkan file PDF di sini, atau klik untuk memilih", + "selectFiles": "Pilih File", + "renderingPages": "Merender halaman...", + "actions": { + "duplicatePage": "Duplikat halaman ini", + "deletePage": "Hapus halaman ini", + "insertPdf": "Sisipkan PDF setelah halaman ini", + "toggleSplit": "Alihkan pisah setelah halaman ini" + }, + "pleaseWait": "Harap Tunggu", + "pagesRendering": "Halaman masih dirender. Harap tunggu...", + "noPagesSelected": "Tidak Ada Halaman yang Dipilih", + "selectOnePage": "Silakan pilih setidaknya satu halaman untuk diunduh.", + "noPages": "Tidak Ada Halaman", + "noPagesToExport": "Tidak ada halaman untuk diekspor.", + "renderingTitle": "Merender pratinjau halaman", + "errorRendering": "Gagal merender thumbnail halaman", + "error": "Kesalahan", + "failedToLoad": "Gagal memuat" + }, + "howItWorks": { + "title": "Cara Kerja", + "step1": "Klik atau seret file Anda ke sini", + "step2": "Klik tombol proses untuk memulai", + "step3": "Simpan file yang diproses secara instan" + }, + "relatedTools": { + "title": "Alat PDF Terkait" + }, + "simpleMode": { + "title": "Alat PDF", + "subtitle": "Pilih alat untuk memulai" + } +} diff --git a/public/locales/id/tools.json b/public/locales/id/tools.json new file mode 100644 index 0000000..b75ea0f --- /dev/null +++ b/public/locales/id/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "Alat Populer", + "editAnnotate": "Edit & Anotasi", + "convertToPdf": "Konversi ke PDF", + "convertFromPdf": "Konversi dari PDF", + "organizeManage": "Atur & Kelola", + "optimizeRepair": "Optimalkan & Perbaiki", + "securePdf": "Amankan PDF" + }, + "pdfMultiTool": { + "name": "Alat Multi PDF", + "subtitle": "Gabung, Pisah, Atur, Hapus, Putar, Tambah Halaman Kosong, Ekstrak dan Duplikat dalam antarmuka terpadu." + }, + "mergePdf": { + "name": "Gabung PDF", + "subtitle": "Gabungkan beberapa PDF menjadi satu file. Mempertahankan Bookmark." + }, + "splitPdf": { + "name": "Pisah PDF", + "subtitle": "Ekstrak rentang halaman ke PDF baru." + }, + "compressPdf": { + "name": "Kompres PDF", + "subtitle": "Kurangi ukuran file PDF Anda.", + "algorithmLabel": "Algoritma Kompresi", + "condense": "Kondensasi (Direkomendasikan)", + "photon": "Photon (Untuk PDF yang penuh Foto)", + "condenseInfo": "Kondensasi menggunakan kompresi lanjutan: menghapus bobot mati, mengoptimalkan gambar, subset font. Terbaik untuk sebagian besar PDF.", + "photonInfo": "Photon mengkonversi halaman ke gambar. Gunakan untuk PDF penuh foto/terpindai.", + "photonWarning": "Peringatan: Teks akan menjadi tidak dapat dipilih dan tautan akan berhenti berfungsi.", + "levelLabel": "Tingkat Kompresi", + "light": "Ringan (Pertahankan Kualitas)", + "balanced": "Seimbang (Direkomendasikan)", + "aggressive": "Agresif (File Lebih Kecil)", + "extreme": "Ekstrem (Kompresi Maksimum)", + "grayscale": "Konversi ke Grayscale", + "grayscaleHint": "Mengurangi ukuran file dengan menghapus informasi warna", + "customSettings": "Pengaturan Kustom", + "customSettingsHint": "Sesuaikan parameter kompresi:", + "outputQuality": "Kualitas Output", + "resizeImagesTo": "Ubah Ukuran Gambar Ke", + "onlyProcessAbove": "Hanya Proses Di Atas", + "removeMetadata": "Hapus metadata", + "subsetFonts": "Subset font (hapus glyph yang tidak digunakan)", + "removeThumbnails": "Hapus thumbnail tersemat", + "compressButton": "Kompres PDF" + }, + "pdfEditor": { + "name": "Editor PDF", + "subtitle": "Anotasi, sorot, redaksi, komentar, tambah bentuk/gambar, cari, dan lihat PDF." + }, + "jpgToPdf": { + "name": "JPG ke PDF", + "subtitle": "Buat PDF dari gambar JPG, JPEG, dan JPEG2000 (JP2/JPX)." + }, + "signPdf": { + "name": "Tanda Tangan PDF", + "subtitle": "Gambar, ketik, atau unggah tanda tangan Anda." + }, + "cropPdf": { + "name": "Potong PDF", + "subtitle": "Potong margin setiap halaman di PDF Anda." + }, + "extractPages": { + "name": "Ekstrak Halaman", + "subtitle": "Simpan pilihan halaman sebagai file baru." + }, + "duplicateOrganize": { + "name": "Duplikat & Atur", + "subtitle": "Duplikat, susun ulang, dan hapus halaman." + }, + "deletePages": { + "name": "Hapus Halaman", + "subtitle": "Hapus halaman tertentu dari dokumen Anda." + }, + "editBookmarks": { + "name": "Edit Bookmark", + "subtitle": "Tambah, edit, impor, hapus dan ekstrak bookmark PDF." + }, + "tableOfContents": { + "name": "Daftar Isi", + "subtitle": "Hasilkan halaman daftar isi dari bookmark PDF." + }, + "pageNumbers": { + "name": "Nomor Halaman", + "subtitle": "Sisipkan nomor halaman ke dokumen Anda." + }, + "batesNumbering": { + "name": "Penomoran Bates", + "subtitle": "Tambahkan nomor Bates berurutan pada satu atau lebih file PDF." + }, + "addWatermark": { + "name": "Tambah Watermark", + "subtitle": "Cap teks atau gambar di atas halaman PDF Anda.", + "applyToAllPages": "Terapkan ke semua halaman" + }, + "headerFooter": { + "name": "Header & Footer", + "subtitle": "Tambah teks ke bagian atas dan bawah halaman." + }, + "invertColors": { + "name": "Balik Warna", + "subtitle": "Buat versi \"mode gelap\" dari PDF Anda." + }, + "scannerEffect": { + "name": "Efek Pemindai", + "subtitle": "Buat PDF Anda terlihat seperti dokumen yang dipindai.", + "scanSettings": "Pengaturan Pemindaian", + "colorspace": "Ruang Warna", + "gray": "Skala Abu-abu", + "border": "Batas", + "rotate": "Putar", + "rotateVariance": "Variasi Putaran", + "brightness": "Kecerahan", + "contrast": "Kontras", + "blur": "Blur", + "noise": "Noise", + "yellowish": "Kekuningan", + "resolution": "Resolusi", + "processButton": "Terapkan Efek Pemindai" + }, + "adjustColors": { + "name": "Sesuaikan Warna", + "subtitle": "Sesuaikan kecerahan, kontras, saturasi dan lainnya pada PDF Anda.", + "colorSettings": "Pengaturan Warna", + "brightness": "Kecerahan", + "contrast": "Kontras", + "saturation": "Saturasi", + "hueShift": "Pergeseran Rona", + "temperature": "Suhu", + "tint": "Pewarnaan", + "gamma": "Gamma", + "sepia": "Sepia", + "processButton": "Terapkan Penyesuaian Warna" + }, + "backgroundColor": { + "name": "Warna Latar Belakang", + "subtitle": "Ubah warna latar belakang PDF Anda." + }, + "changeTextColor": { + "name": "Ubah Warna Teks", + "subtitle": "Ubah warna teks di PDF Anda." + }, + "addStamps": { + "name": "Tambah Stempel", + "subtitle": "Tambah stempel gambar ke PDF Anda menggunakan toolbar anotasi.", + "usernameLabel": "Nama Pengguna Stempel", + "usernamePlaceholder": "Masukkan nama Anda (untuk stempel)", + "usernameHint": "Nama ini akan muncul pada stempel yang Anda buat." + }, + "removeAnnotations": { + "name": "Hapus Anotasi", + "subtitle": "Hapus komentar, sorotan, dan tautan." + }, + "pdfFormFiller": { + "name": "Pengisi Formulir PDF", + "subtitle": "Isi formulir langsung di browser. Juga mendukung formulir XFA." + }, + "createPdfForm": { + "name": "Buat Formulir PDF", + "subtitle": "Buat formulir PDF yang dapat diisi dengan bidang teks drag-and-drop." + }, + "removeBlankPages": { + "name": "Hapus Halaman Kosong", + "subtitle": "Deteksi dan hapus halaman kosong secara otomatis.", + "sensitivityHint": "Lebih tinggi = lebih ketat, hanya halaman yang benar-benar kosong. Lebih rendah = mengizinkan halaman dengan sedikit konten." + }, + "imageToPdf": { + "name": "Gambar ke PDF", + "subtitle": "Konversi JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP ke PDF." + }, + "pngToPdf": { + "name": "PNG ke PDF", + "subtitle": "Buat PDF dari satu atau lebih gambar PNG." + }, + "webpToPdf": { + "name": "WebP ke PDF", + "subtitle": "Buat PDF dari satu atau lebih gambar WebP." + }, + "svgToPdf": { + "name": "SVG ke PDF", + "subtitle": "Buat PDF dari satu atau lebih gambar SVG." + }, + "bmpToPdf": { + "name": "BMP ke PDF", + "subtitle": "Buat PDF dari satu atau lebih gambar BMP." + }, + "heicToPdf": { + "name": "HEIC ke PDF", + "subtitle": "Buat PDF dari satu atau lebih gambar HEIC." + }, + "tiffToPdf": { + "name": "TIFF ke PDF", + "subtitle": "Buat PDF dari satu atau lebih gambar TIFF." + }, + "textToPdf": { + "name": "Teks ke PDF", + "subtitle": "Konversi file teks biasa menjadi PDF." + }, + "jsonToPdf": { + "name": "JSON ke PDF", + "subtitle": "Konversi file JSON ke format PDF." + }, + "pdfToJpg": { + "name": "PDF ke JPG", + "subtitle": "Konversi setiap halaman PDF menjadi gambar JPG." + }, + "pdfToPng": { + "name": "PDF ke PNG", + "subtitle": "Konversi setiap halaman PDF menjadi gambar PNG." + }, + "pdfToWebp": { + "name": "PDF ke WebP", + "subtitle": "Konversi setiap halaman PDF menjadi gambar WebP." + }, + "pdfToBmp": { + "name": "PDF ke BMP", + "subtitle": "Konversi setiap halaman PDF menjadi gambar BMP." + }, + "pdfToTiff": { + "name": "PDF ke TIFF", + "subtitle": "Konversi setiap halaman PDF menjadi gambar TIFF." + }, + "pdfToGreyscale": { + "name": "PDF ke Skala Abu-abu", + "subtitle": "Konversi semua warna ke hitam dan putih." + }, + "pdfToJson": { + "name": "PDF ke JSON", + "subtitle": "Konversi file PDF ke format JSON." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Buat PDF dapat dicari dan disalin." + }, + "alternateMix": { + "name": "Alternatif & Campur Halaman", + "subtitle": "Gabung PDF dengan menggantikan halaman dari setiap PDF. Mempertahankan Bookmark." + }, + "addAttachments": { + "name": "Tambah Lampiran", + "subtitle": "Sematkan satu atau lebih file ke PDF Anda." + }, + "extractAttachments": { + "name": "Ekstrak Lampiran", + "subtitle": "Ekstrak semua file tersemat dari PDF sebagai ZIP." + }, + "editAttachments": { + "name": "Edit Lampiran", + "subtitle": "Lihat atau hapus lampiran di PDF Anda." + }, + "dividePages": { + "name": "Bagi Halaman", + "subtitle": "Bagi halaman secara horizontal atau vertikal." + }, + "addBlankPage": { + "name": "Tambah Halaman Kosong", + "subtitle": "Sisipkan halaman kosong di mana saja di PDF Anda." + }, + "reversePages": { + "name": "Balik Halaman", + "subtitle": "Balik urutan semua halaman di dokumen Anda." + }, + "rotatePdf": { + "name": "Putar PDF", + "subtitle": "Putar halaman dalam kenaikan 90 derajat." + }, + "rotateCustom": { + "name": "Putar dengan Derajat Kustom", + "subtitle": "Putar halaman dengan sudut kustom apa pun." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "Susun beberapa halaman ke satu lembar." + }, + "combineToSinglePage": { + "name": "Gabung ke Halaman Tunggal", + "subtitle": "Jahit semua halaman menjadi satu gulungan berkelanjutan." + }, + "viewMetadata": { + "name": "Lihat Metadata", + "subtitle": "Periksa properti tersembunyi PDF Anda." + }, + "editMetadata": { + "name": "Edit Metadata", + "subtitle": "Ubah penulis, judul, dan properti lainnya." + }, + "pdfsToZip": { + "name": "PDF ke ZIP", + "subtitle": "Paket beberapa file PDF ke arsip ZIP." + }, + "comparePdfs": { + "name": "Bandingkan PDF", + "subtitle": "Bandingkan dua PDF berdampingan." + }, + "posterizePdf": { + "name": "Posterisasi PDF", + "subtitle": "Pisah halaman besar menjadi beberapa halaman lebih kecil." + }, + "fixPageSize": { + "name": "Perbaiki Ukuran Halaman", + "subtitle": "Standarkan semua halaman ke ukuran seragam." + }, + "linearizePdf": { + "name": "Linierisasi PDF", + "subtitle": "Optimalkan PDF untuk tampilan web cepat." + }, + "pageDimensions": { + "name": "Dimensi Halaman", + "subtitle": "Analisis ukuran halaman, orientasi, dan unit." + }, + "removeRestrictions": { + "name": "Hapus Pembatasan", + "subtitle": "Hapus perlindungan kata sandi dan pembatasan keamanan yang terkait dengan file PDF yang ditandatangani secara digital." + }, + "repairPdf": { + "name": "Perbaiki PDF", + "subtitle": "Pulihkan data dari file PDF yang rusak atau rusak." + }, + "encryptPdf": { + "name": "Enkripsi PDF", + "subtitle": "Kunci PDF Anda dengan menambahkan kata sandi." + }, + "sanitizePdf": { + "name": "Sanitasi PDF", + "subtitle": "Hapus metadata, anotasi, skrip, dan lainnya." + }, + "decryptPdf": { + "name": "Dekripsi PDF", + "subtitle": "Buka kunci PDF dengan menghapus perlindungan kata sandi." + }, + "flattenPdf": { + "name": "Ratakan PDF", + "subtitle": "Buat bidang formulir dan anotasi tidak dapat diedit." + }, + "removeMetadata": { + "name": "Hapus Metadata", + "subtitle": "Hapus data tersembunyi dari PDF Anda." + }, + "changePermissions": { + "name": "Ubah Izin", + "subtitle": "Atur atau ubah izin pengguna pada PDF." + }, + "odtToPdf": { + "name": "ODT ke PDF", + "subtitle": "Konversi file OpenDocument Text ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File ODT", + "convertButton": "Konversi ke PDF" + }, + "csvToPdf": { + "name": "CSV ke PDF", + "subtitle": "Konversi file spreadsheet CSV ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File CSV", + "convertButton": "Konversi ke PDF" + }, + "rtfToPdf": { + "name": "RTF ke PDF", + "subtitle": "Konversi dokumen Rich Text Format ke PDF. Mendukung beberapa file.", + "acceptedFormats": "File RTF", + "convertButton": "Konversi ke PDF" + }, + "wordToPdf": { + "name": "Word ke PDF", + "subtitle": "Konversi dokumen Word (DOCX, DOC, ODT, RTF) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File DOCX, DOC, ODT, RTF", + "convertButton": "Konversi ke PDF" + }, + "excelToPdf": { + "name": "Excel ke PDF", + "subtitle": "Konversi spreadsheet Excel (XLSX, XLS, ODS, CSV) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File XLSX, XLS, ODS, CSV", + "convertButton": "Konversi ke PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint ke PDF", + "subtitle": "Konversi presentasi PowerPoint (PPTX, PPT, ODP) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File PPTX, PPT, ODP", + "convertButton": "Konversi ke PDF" + }, + "markdownToPdf": { + "name": "Markdown ke PDF", + "subtitle": "Tulis atau tempel Markdown dan ekspor sebagai PDF yang diformat dengan indah.", + "paneMarkdown": "Markdown", + "panePreview": "Pratinjau", + "btnUpload": "Unggah", + "btnSyncScroll": "Sinkron Gulir", + "btnSettings": "Pengaturan", + "btnExportPdf": "Ekspor PDF", + "settingsTitle": "Pengaturan Markdown", + "settingsPreset": "Preset", + "presetDefault": "Default (seperti GFM)", + "presetCommonmark": "CommonMark (ketat)", + "presetZero": "Minimal (tanpa fitur)", + "settingsOptions": "Opsi Markdown", + "optAllowHtml": "Izinkan tag HTML", + "optBreaks": "Konversi baris baru ke
", + "optLinkify": "Konversi otomatis URL ke tautan", + "optTypographer": "Typographer (kutipan cerdas, dll.)" + }, + "pdfBooklet": { + "name": "Buku Kecil PDF", + "subtitle": "Susun ulang halaman untuk pencetakan buku kecil dua sisi. Lipat dan jahit untuk membuat buku kecil.", + "howItWorks": "Cara kerjanya:", + "step1": "Unggah file PDF.", + "step2": "Halaman akan disusun ulang dalam urutan buku kecil.", + "step3": "Cetak dua sisi, balik di tepi pendek, lipat dan jahit.", + "paperSize": "Ukuran Kertas", + "orientation": "Orientasi", + "portrait": "Potret", + "landscape": "Lanskap", + "pagesPerSheet": "Halaman per Lembar", + "createBooklet": "Buat Buku Kecil", + "processing": "Memproses...", + "pageCount": "Jumlah halaman akan diisi ke kelipatan 4 jika diperlukan." + }, + "xpsToPdf": { + "name": "XPS ke PDF", + "subtitle": "Konversi dokumen XPS/OXPS ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File XPS, OXPS", + "convertButton": "Konversi ke PDF" + }, + "mobiToPdf": { + "name": "MOBI ke PDF", + "subtitle": "Konversi e-book MOBI ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File MOBI", + "convertButton": "Konversi ke PDF" + }, + "epubToPdf": { + "name": "EPUB ke PDF", + "subtitle": "Konversi e-book EPUB ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File EPUB", + "convertButton": "Konversi ke PDF" + }, + "fb2ToPdf": { + "name": "FB2 ke PDF", + "subtitle": "Konversi e-book FictionBook (FB2) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File FB2", + "convertButton": "Konversi ke PDF" + }, + "cbzToPdf": { + "name": "CBZ ke PDF", + "subtitle": "Konversi arsip buku komik (CBZ/CBR) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File CBZ, CBR", + "convertButton": "Konversi ke PDF" + }, + "wpdToPdf": { + "name": "WPD ke PDF", + "subtitle": "Konversi dokumen WordPerfect (WPD) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File WPD", + "convertButton": "Konversi ke PDF" + }, + "wpsToPdf": { + "name": "WPS ke PDF", + "subtitle": "Konversi dokumen WPS Office ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File WPS", + "convertButton": "Konversi ke PDF" + }, + "xmlToPdf": { + "name": "XML ke PDF", + "subtitle": "Konversi dokumen XML ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File XML", + "convertButton": "Konversi ke PDF" + }, + "pagesToPdf": { + "name": "Pages ke PDF", + "subtitle": "Konversi dokumen Apple Pages ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File Pages", + "convertButton": "Konversi ke PDF" + }, + "odgToPdf": { + "name": "ODG ke PDF", + "subtitle": "Konversi file OpenDocument Graphics (ODG) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File ODG", + "convertButton": "Konversi ke PDF" + }, + "odsToPdf": { + "name": "ODS ke PDF", + "subtitle": "Konversi file OpenDocument Spreadsheet (ODS) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File ODS", + "convertButton": "Konversi ke PDF" + }, + "odpToPdf": { + "name": "ODP ke PDF", + "subtitle": "Konversi file OpenDocument Presentation (ODP) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File ODP", + "convertButton": "Konversi ke PDF" + }, + "pubToPdf": { + "name": "PUB ke PDF", + "subtitle": "Konversi file Microsoft Publisher (PUB) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File PUB", + "convertButton": "Konversi ke PDF" + }, + "vsdToPdf": { + "name": "VSD ke PDF", + "subtitle": "Konversi file Microsoft Visio (VSD, VSDX) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File VSD, VSDX", + "convertButton": "Konversi ke PDF" + }, + "psdToPdf": { + "name": "PSD ke PDF", + "subtitle": "Konversi file Adobe Photoshop (PSD) ke format PDF. Mendukung beberapa file.", + "acceptedFormats": "File PSD", + "convertButton": "Konversi ke PDF" + }, + "pdfToSvg": { + "name": "PDF ke SVG", + "subtitle": "Konversi setiap halaman file PDF menjadi grafik vektor skalabel (SVG) untuk kualitas sempurna di ukuran apa pun." + }, + "extractTables": { + "name": "Ekstrak Tabel PDF", + "subtitle": "Ekstrak tabel dari file PDF dan ekspor sebagai CSV, JSON, atau Markdown." + }, + "pdfToCsv": { + "name": "PDF ke CSV", + "subtitle": "Ekstrak tabel dari PDF dan konversi ke format CSV." + }, + "pdfToExcel": { + "name": "PDF ke Excel", + "subtitle": "Ekstrak tabel dari PDF dan konversi ke format Excel (XLSX)." + }, + "pdfToText": { + "name": "PDF ke Teks", + "subtitle": "Ekstrak teks dari file PDF dan simpan sebagai teks biasa (.txt). Mendukung beberapa file.", + "note": "Alat ini bekerja HANYA dengan PDF yang dibuat secara digital. Untuk dokumen terpindai atau PDF berbasis gambar, gunakan alat OCR PDF kami sebagai gantinya.", + "convertButton": "Ekstrak Teks" + }, + "digitalSignPdf": { + "name": "Tanda Tangan Digital PDF", + "pageTitle": "Tanda Tangan Digital PDF - Tambah Tanda Tangan Kriptografi | BentoPDF", + "subtitle": "Tambah tanda tangan digital kriptografi ke PDF Anda menggunakan sertifikat X.509. Mendukung format PKCS#12 (.pfx, .p12) dan PEM. Kunci pribadi Anda tidak pernah meninggalkan browser Anda.", + "certificateSection": "Sertifikat", + "uploadCert": "Unggah sertifikat (.pfx, .p12)", + "certPassword": "Kata Sandi Sertifikat", + "certPasswordPlaceholder": "Masukkan kata sandi sertifikat", + "certInfo": "Informasi Sertifikat", + "certSubject": "Subjek", + "certIssuer": "Penerbit", + "certValidity": "Valid", + "signatureDetails": "Detail Tanda Tangan (Opsional)", + "reason": "Alasan", + "reasonPlaceholder": "misalnya, Saya menyetujui dokumen ini", + "location": "Lokasi", + "locationPlaceholder": "misalnya, Jakarta, Indonesia", + "contactInfo": "Info Kontak", + "contactPlaceholder": "misalnya, email@contoh.com", + "applySignature": "Terapkan Tanda Tangan Digital", + "successMessage": "PDF berhasil ditandatangani! Tanda tangan dapat diverifikasi di pembaca PDF apa pun." + }, + "validateSignaturePdf": { + "name": "Validasi Tanda Tangan PDF", + "pageTitle": "Validasi Tanda Tangan PDF - Verifikasi Tanda Tangan Digital | BentoPDF", + "subtitle": "Verifikasi tanda tangan digital di file PDF Anda. Periksa validitas sertifikat, lihat detail penandatangan, dan konfirmasi integritas dokumen. Semua pemrosesan terjadi di browser Anda." + }, + "emailToPdf": { + "name": "Email ke PDF", + "subtitle": "Konversi file email (EML, MSG) ke format PDF. Mendukung ekspor Outlook dan format email standar.", + "acceptedFormats": "File EML, MSG", + "convertButton": "Konversi ke PDF" + }, + "fontToOutline": { + "name": "Font ke Garis Tepi", + "subtitle": "Konversi semua font ke garis tepi vektor untuk tampilan konsisten di semua perangkat." + }, + "deskewPdf": { + "name": "Luruskan PDF", + "subtitle": "Otomatis meluruskan halaman hasil pindai yang miring menggunakan OpenCV." + }, + "pdfToWord": { + "name": "PDF ke Word", + "subtitle": "Konversi file PDF ke dokumen Word yang dapat diedit." + }, + "extractImages": { + "name": "Ekstrak Gambar", + "subtitle": "Ekstrak semua gambar yang tertanam dari file PDF Anda." + }, + "pdfToMarkdown": { + "name": "PDF ke Markdown", + "subtitle": "Konversi teks dan tabel PDF ke format Markdown." + }, + "preparePdfForAi": { + "name": "Siapkan PDF untuk AI", + "subtitle": "Ekstrak konten PDF sebagai JSON LlamaIndex untuk pipeline RAG/LLM." + }, + "pdfOcg": { + "name": "Lapisan PDF (OCG)", + "subtitle": "Lihat, beralih, tambah, dan hapus lapisan OCG di PDF Anda." + }, + "pdfToPdfa": { + "name": "PDF ke PDF/A", + "subtitle": "Konversi PDF ke PDF/A untuk pengarsipan jangka panjang." + }, + "rasterizePdf": { + "name": "Rasterisasi PDF", + "subtitle": "Konversi PDF ke PDF berbasis gambar. Ratakan lapisan dan hapus teks yang dapat dipilih." + }, + "pdfWorkflow": { + "name": "Pembangun Alur Kerja PDF", + "subtitle": "Bangun alur pemrosesan PDF kustom dengan editor node visual.", + "nodes": "Node", + "searchNodes": "Cari node...", + "run": "Jalankan", + "clear": "Bersihkan", + "save": "Simpan", + "load": "Muat", + "export": "Ekspor", + "import": "Impor", + "ready": "Siap", + "settings": "Pengaturan", + "processing": "Memproses...", + "saveTemplate": "Simpan Template", + "templateName": "Nama Template", + "templatePlaceholder": "mis. Alur Kerja Faktur", + "cancel": "Batal", + "loadTemplate": "Muat Template", + "noTemplates": "Belum ada template tersimpan.", + "ok": "OK", + "workflowCompleted": "Alur kerja selesai", + "errorDuringExecution": "Kesalahan saat eksekusi", + "addNodeError": "Tambahkan setidaknya satu node untuk menjalankan alur kerja.", + "needInputOutput": "Alur kerja Anda memerlukan setidaknya satu node input dan satu node output untuk dijalankan.", + "enterName": "Silakan masukkan nama.", + "templateExists": "Template dengan nama ini sudah ada.", + "templateSaved": "Template \"{{name}}\" tersimpan.", + "templateLoaded": "Template \"{{name}}\" dimuat.", + "failedLoadTemplate": "Gagal memuat template.", + "noSettings": "Tidak ada pengaturan yang dapat dikonfigurasi untuk node ini.", + "advancedSettings": "Pengaturan Lanjutan" + } +} diff --git a/public/locales/it/common.json b/public/locales/it/common.json new file mode 100644 index 0000000..e938a8a --- /dev/null +++ b/public/locales/it/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "Home", + "about": "Chi siamo", + "contact": "Contatti", + "licensing": "Licenze", + "allTools": "Tutti gli strumenti", + "openMainMenu": "Apri il menu principale", + "language": "Lingua" + }, + "donation": { + "message": "Ti piace BentoPDF? Aiutaci a mantenerlo gratuito e open source!", + "button": "Dona" + }, + "hero": { + "title": "Il", + "pdfToolkit": "kit di strumenti PDF", + "builtForPrivacy": "pensato per la privacy", + "noSignups": "Nessuna registrazione", + "unlimitedUse": "Uso illimitato", + "worksOffline": "Funziona offline", + "startUsing": "Inizia a usarlo ora" + }, + "usedBy": { + "title": "Usato da aziende e persone che lavorano in" + }, + "features": { + "title": "Perché scegliere", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "Nessuna registrazione", + "description": "Inizia subito, senza account né email." + }, + "noUploads": { + "title": "Nessun caricamento", + "description": "100% client-side, i tuoi file non lasciano mai il dispositivo." + }, + "foreverFree": { + "title": "Sempre gratis", + "description": "Tutti gli strumenti, nessuna prova, nessun paywall." + }, + "noLimits": { + "title": "Senza limiti", + "description": "Usalo quanto vuoi, senza limiti nascosti." + }, + "batchProcessing": { + "title": "Elaborazione in batch", + "description": "Gestisci un numero illimitato di PDF in un'unica operazione." + }, + "lightningFast": { + "title": "Fulmineo", + "description": "Elabora i PDF istantaneamente, senza attese o ritardi." + } + }, + "tools": { + "title": "Inizia con", + "toolsLabel": "Strumenti", + "subtitle": "Clicca su uno strumento per aprire il caricatore di file", + "searchPlaceholder": "Cerca uno strumento (es. \"split\", \"organizza\"...)", + "backToTools": "Torna agli strumenti", + "firstLoadNotice": "Il primo caricamento richiede qualche istante mentre scarichiamo il motore di conversione. Dopo di ciò, tutti i caricamenti saranno immediati." + }, + "upload": { + "clickToSelect": "Clicca per selezionare un file", + "orDragAndDrop": "oppure trascina e rilascia", + "pdfOrImages": "PDF o immagini", + "filesNeverLeave": "I tuoi file non lasciano mai il tuo dispositivo.", + "addMore": "Aggiungi altri file", + "clearAll": "Svuota tutto", + "clearFiles": "Cancella file", + "hints": { + "singlePdf": "Un singolo file PDF", + "pdfFile": "File PDF", + "multiplePdfs2": "Più file PDF (almeno 2)", + "bmpImages": "Immagini BMP", + "oneOrMorePdfs": "Uno o più file PDF", + "pdfDocuments": "Documenti PDF", + "oneOrMoreCsv": "Uno o più file CSV", + "multiplePdfsSupported": "Più file PDF supportati", + "singleOrMultiplePdfs": "Uno o più file PDF supportati", + "singlePdfFile": "Un singolo file PDF", + "pdfWithForms": "File PDF con campi modulo", + "heicImages": "Immagini HEIC/HEIF", + "jpgImages": "Immagini JPG, JPEG, JP2, JPX", + "pdfsOrImages": "PDF o immagini", + "oneOrMoreOdt": "Uno o più file ODT", + "singlePdfOnly": "Solo un file PDF", + "pdfFiles": "File PDF", + "multiplePdfs": "Più file PDF", + "pngImages": "Immagini PNG", + "pdfFilesOneOrMore": "File PDF (uno o più)", + "oneOrMoreRtf": "Uno o più file RTF", + "svgGraphics": "Grafica SVG", + "tiffImages": "Immagini TIFF", + "webpImages": "Immagini WebP" + } + }, + "loader": { + "processing": "Elaborazione..." + }, + "alert": { + "title": "Avviso", + "ok": "OK" + }, + "preview": { + "title": "Anteprima documento", + "downloadAsPdf": "Scarica come PDF", + "close": "Chiudi" + }, + "settings": { + "title": "Impostazioni", + "shortcuts": "Scorciatoie", + "preferences": "Preferenze", + "displayPreferences": "Preferenze di visualizzazione", + "searchShortcuts": "Cerca scorciatoie...", + "shortcutsInfo": "Premi e tieni premuti i tasti per impostare una scorciatoia. Le modifiche vengono salvate automaticamente.", + "shortcutsWarning": "⚠️ Evita le scorciatoie comuni del browser (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N ecc.) perché potrebbero non funzionare in modo affidabile.", + "import": "Importa", + "export": "Esporta", + "resetToDefaults": "Reimposta ai valori predefiniti", + "fullWidthMode": "Modalità a tutta larghezza", + "fullWidthDescription": "Usa l'intera larghezza dello schermo per tutti gli strumenti invece di un contenitore centrato", + "settingsAutoSaved": "Le impostazioni vengono salvate automaticamente", + "clickToSet": "Clicca per impostare", + "pressKeys": "Premi i tasti...", + "warnings": { + "alreadyInUse": "Scorciatoia già in uso", + "assignedTo": "è già assegnata a:", + "chooseDifferent": "Per favore scegli una scorciatoia diversa.", + "reserved": "Avviso: scorciatoia riservata", + "commonlyUsed": "è comunemente usata per:", + "unreliable": "Questa scorciatoia potrebbe non funzionare in modo affidabile o potrebbe avere conflitti con il comportamento del browser/sistema.", + "useAnyway": "Vuoi usarla comunque?", + "resetTitle": "Reimposta scorciatoie", + "resetMessage": "Sei sicuro di voler reimpostare tutte le scorciatoie ai valori predefiniti?

Questa azione non può essere annullata.", + "importSuccessTitle": "Importazione riuscita", + "importSuccessMessage": "Scorciatoie importate con successo!", + "importFailTitle": "Importazione fallita", + "importFailMessage": "Impossibile importare le scorciatoie. Formato file non valido." + } + }, + "warning": { + "title": "Avviso", + "cancel": "Annulla", + "proceed": "Procedi" + }, + "compliance": { + "title": "I tuoi dati non lasciano mai il tuo dispositivo", + "weKeep": "Manteniamo", + "yourInfoSafe": "le tue informazioni al sicuro", + "byFollowingStandards": "seguendo gli standard di sicurezza globali.", + "processingLocal": "Tutte le elaborazioni avvengono localmente sul tuo dispositivo.", + "gdpr": { + "title": "Conformità GDPR", + "description": "Protegge i dati personali e la privacy delle persone all'interno dell'Unione Europea." + }, + "ccpa": { + "title": "Conformità CCPA", + "description": "Conferisce ai residenti della California diritti su come le loro informazioni personali vengono raccolte, usate e condivise." + }, + "hipaa": { + "title": "Conformità HIPAA", + "description": "Stabilisce salvaguardie per la gestione delle informazioni sanitarie sensibili nel sistema sanitario degli Stati Uniti." + } + }, + "faq": { + "title": "Domande", + "questions": "Frequenti", + "isFree": { + "question": "BentoPDF è davvero gratuito?", + "answer": "Sì, assolutamente. Tutti gli strumenti su BentoPDF sono gratuiti al 100%, senza limiti di file, senza registrazioni e senza filigrane. Crediamo che tutti debbano poter accedere a strumenti PDF semplici e potenti senza barriere a pagamento." + }, + "areFilesSecure": { + "question": "I miei file sono sicuri? Dove vengono elaborati?", + "answer": "I tuoi file sono il più sicuri possibile perché non lasciano mai il tuo computer. Tutte le elaborazioni avvengono direttamente nel tuo browser (lato client). Non carichiamo mai i tuoi file su un server, quindi mantieni la privacy completa e il controllo sui tuoi documenti." + }, + "platforms": { + "question": "Funziona su Mac, Windows e dispositivi mobili?", + "answer": "Sì! Poiché BentoPDF viene eseguito interamente nel tuo browser, funziona su qualsiasi sistema operativo con un browser moderno, inclusi Windows, macOS, Linux, iOS e Android." + }, + "gdprCompliant": { + "question": "BentoPDF è conforme al GDPR?", + "answer": "Sì. BentoPDF è pienamente conforme al GDPR. Poiché tutte le elaborazioni avvengono localmente nel tuo browser e non raccogliamo né trasmettiamo i tuoi file a server, non abbiamo accesso ai tuoi dati. Questo garantisce che tu abbia sempre il controllo sui tuoi documenti." + }, + "dataStorage": { + "question": "Conservate o tracciate i miei file?", + "answer": "No. Non conserviamo, tracciamo né registriamo i tuoi file. Tutto ciò che fai su BentoPDF avviene nella memoria del tuo browser e scompare quando chiudi la pagina. Non ci sono upload, né log di cronologia, né server coinvolti." + }, + "different": { + "question": "Cosa rende BentoPDF diverso dagli altri strumenti PDF?", + "answer": "La maggior parte degli strumenti PDF carica i file su un server per l'elaborazione. BentoPDF non lo fa mai. Utilizziamo tecnologie web moderne e sicure per elaborare i tuoi file direttamente nel browser. Questo significa prestazioni più rapide, maggiore privacy e tranquillità." + }, + "browserBased": { + "question": "In che modo l'elaborazione nel browser mi protegge?", + "answer": "Eseguendosi interamente nel browser, BentoPDF garantisce che i tuoi file non lascino mai il tuo dispositivo. Questo elimina i rischi di attacchi ai server, violazioni dei dati o accessi non autorizzati. I tuoi file restano sempre tuoi." + }, + "analytics": { + "question": "Usate cookie o analytics per tracciarmi?", + "answer": "Ci teniamo alla tua privacy. BentoPDF non traccia informazioni personali. Usiamo Simple Analytics solo per visualizzare conteggi di visite anonime. Questo significa che possiamo sapere quante persone visitano il sito, ma non chi sei. Simple Analytics è pienamente conforme al GDPR e rispetta la tua privacy." + }, + "sectionTitle": "Domande frequenti" + }, + "testimonials": { + "title": "Cosa", + "users": "dicono i nostri", + "say": "utenti" + }, + "support": { + "title": "Ti piace il mio lavoro?", + "description": "BentoPDF è un progetto nato per passione, creato per offrire una cassetta degli attrezzi PDF gratuita, privata e potente per tutti. Se lo trovi utile, considera di supportarne lo sviluppo. Ogni caffè aiuta!", + "buyMeCoffee": "Offrimi un caffè" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Tutti i diritti riservati.", + "version": "Versione", + "company": "Azienda", + "aboutUs": "Chi siamo", + "faqLink": "FAQ", + "contactUs": "Contattaci", + "legal": "Legale", + "termsAndConditions": "Termini e condizioni", + "privacyPolicy": "Informativa sulla privacy", + "followUs": "Seguici" + }, + "merge": { + "title": "Unisci PDF", + "description": "Combina file interi, oppure seleziona pagine specifiche da unire in un nuovo documento.", + "fileMode": "Modalità file", + "pageMode": "Modalità pagina", + "howItWorks": "Come funziona:", + "fileModeInstructions": [ + "Clicca e trascina l'icona per cambiare l'ordine dei file.", + "Nella casella \"Pagine\" per ogni file, puoi specificare intervalli (es., \"1-3, 5\") per unire solo quelle pagine.", + "Lascia vuota la casella \"Pagine\" per includere tutte le pagine di quel file." + ], + "pageModeInstructions": [ + "Tutte le pagine dei tuoi PDF caricati vengono mostrate qui sotto.", + "Trascina e rilascia le miniature delle singole pagine per creare l'ordine esatto che desideri per il nuovo file." + ], + "mergePdfs": "Unisci PDF" + }, + "common": { + "page": "Pagina", + "pages": "Pagine", + "of": "di", + "download": "Scarica", + "cancel": "Annulla", + "save": "Salva", + "delete": "Elimina", + "edit": "Modifica", + "add": "Aggiungi", + "remove": "Rimuovi", + "loading": "Caricamento...", + "error": "Errore", + "success": "Successo", + "file": "File", + "files": "File", + "close": "Chiudi" + }, + "about": { + "hero": { + "title": "Crediamo che gli strumenti PDF debbano essere", + "subtitle": "veloci, privati e gratuiti.", + "noCompromises": "Nessun compromesso." + }, + "mission": { + "title": "La nostra missione", + "description": "Fornire la cassetta degli attrezzi per PDF più completa che rispetti la tua privacy e non richieda mai pagamenti. Crediamo che gli strumenti essenziali per i documenti debbano essere accessibili a tutti, ovunque, senza barriere." + }, + "philosophy": { + "label": "La nostra filosofia fondamentale", + "title": "Prima la privacy. Sempre.", + "description": "In un'epoca in cui i dati sono una merce, adottiamo un approccio diverso. Tutte le elaborazioni per gli strumenti BentoPDF avvengono localmente nel tuo browser. Questo significa che i tuoi file non toccano mai i nostri server, non vediamo i tuoi documenti e non tracciamo ciò che fai. I tuoi documenti restano completamente e inequivocabilmente privati. Non è solo una funzionalità; è il nostro fondamento." + }, + "whyBentopdf": { + "title": "Perché BentoPDF", + "speed": { + "title": "Progettato per la velocità", + "description": "Nessuna attesa per upload o download verso un server. Elaborando i file direttamente nel browser con tecnologie web moderne come WebAssembly, offriamo una velocità impareggiabile per tutti i nostri strumenti." + }, + "free": { + "title": "Completamente gratuito", + "description": "Niente prove, niente abbonamenti, nessun costo nascosto e nessuna funzione \"premium\" tenuta in ostaggio. Crediamo che potenti strumenti PDF debbano essere un servizio pubblico, non una fonte di profitto." + }, + "noAccount": { + "title": "Nessun account richiesto", + "description": "Inizia a usare qualsiasi strumento immediatamente. Non abbiamo bisogno della tua email, di una password o di informazioni personali. Il tuo flusso di lavoro deve essere semplice e anonimo." + }, + "openSource": { + "title": "Spirito open source", + "description": "Costruito con trasparenza. Sfruttiamo librerie open‑source incredibili come PDF-lib e PDF.js, e crediamo nello sforzo della community per rendere strumenti potenti accessibili a tutti." + } + }, + "cta": { + "title": "Pronto per iniziare?", + "description": "Unisciti a migliaia di utenti che si affidano a BentoPDF per le loro esigenze quotidiane sui documenti. Sperimenta la differenza che privacy e prestazioni possono offrire.", + "button": "Esplora tutti gli strumenti" + } + }, + "contact": { + "title": "Contattaci", + "subtitle": "Ci farebbe piacere sentirti. Che tu abbia una domanda, un feedback o una richiesta di funzionalità, non esitare a contattarci.", + "email": "Puoi contattarci direttamente via email a:" + }, + "licensing": { + "title": "Licenze per", + "subtitle": "Scegli la licenza che si adatta alle tue esigenze." + }, + "multiTool": { + "uploadPdfs": "Carica PDF", + "upload": "Carica", + "addBlankPage": "Aggiungi pagina vuota", + "edit": "Modifica:", + "undo": "Annulla", + "redo": "Ripeti", + "reset": "Reimposta", + "selection": "Selezione:", + "selectAll": "Seleziona tutto", + "deselectAll": "Deseleziona tutto", + "rotate": "Ruota:", + "rotateLeft": "Sinistra", + "rotateRight": "Destra", + "transform": "Trasforma:", + "duplicate": "Duplica", + "split": "Dividi", + "clear": "Svuota:", + "delete": "Elimina", + "download": "Scarica:", + "downloadSelected": "Scarica selezionati", + "exportPdf": "Esporta PDF", + "uploadPdfFiles": "Seleziona file PDF", + "dragAndDrop": "Trascina e rilascia i file PDF qui, oppure clicca per selezionare", + "selectFiles": "Seleziona file", + "renderingPages": "Generazione anteprime pagine...", + "actions": { + "duplicatePage": "Duplica questa pagina", + "deletePage": "Elimina questa pagina", + "insertPdf": "Inserisci PDF dopo questa pagina", + "toggleSplit": "Attiva la divisione dopo questa pagina" + }, + "pleaseWait": "Attendere...", + "pagesRendering": "Le pagine sono ancora in fase di generazione. Attendere...", + "noPagesSelected": "Nessuna pagina selezionata", + "selectOnePage": "Seleziona almeno una pagina da scaricare.", + "noPages": "Nessuna pagina", + "noPagesToExport": "Non ci sono pagine da esportare.", + "renderingTitle": "Generazione anteprime delle pagine", + "errorRendering": "Impossibile generare le miniature delle pagine", + "error": "Errore", + "failedToLoad": "Caricamento fallito" + }, + "howItWorks": { + "title": "Come funziona", + "step1": "Clicca o trascina il tuo file qui", + "step2": "Clicca il pulsante di elaborazione", + "step3": "Salva il tuo file elaborato istantaneamente" + }, + "relatedTools": { + "title": "Strumenti PDF correlati" + }, + "simpleMode": { + "title": "Strumenti PDF", + "subtitle": "Seleziona uno strumento per iniziare" + } +} diff --git a/public/locales/it/tools.json b/public/locales/it/tools.json new file mode 100644 index 0000000..2736bc5 --- /dev/null +++ b/public/locales/it/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "Strumenti popolari", + "editAnnotate": "Modifica e Annota", + "convertToPdf": "Converti in PDF", + "convertFromPdf": "Converti da PDF", + "organizeManage": "Organizza e Gestisci", + "optimizeRepair": "Ottimizza e Ripara", + "securePdf": "Proteggi PDF" + }, + "pdfMultiTool": { + "name": "Strumento PDF multifunzione", + "subtitle": "Unisci, dividi, organizza, elimina, ruota, aggiungi pagine vuote, estrai e duplica in un'interfaccia unificata." + }, + "mergePdf": { + "name": "Unisci PDF", + "subtitle": "Unisci più PDF in un unico file. Conserva i segnalibri." + }, + "splitPdf": { + "name": "Dividi PDF", + "subtitle": "Estrai un insieme di pagine in un nuovo PDF." + }, + "compressPdf": { + "name": "Comprimi PDF", + "subtitle": "Riduci le dimensioni del tuo PDF.", + "algorithmLabel": "Algoritmo di compressione", + "condense": "Condensa (Consigliato)", + "photon": "Photon (Per PDF con molte foto)", + "condenseInfo": "Condensa usa la compressione avanzata: rimuove peso inutile, ottimizza le immagini, riduce i font. Migliore per la maggior parte dei PDF.", + "photonInfo": "Photon converte le pagine in immagini. Usalo per PDF con molte foto o scannerizzati.", + "photonWarning": "Attenzione: il testo non sarà selezionabile e i link smetteranno di funzionare.", + "levelLabel": "Livello di compressione", + "light": "Leggero (Preserva qualità)", + "balanced": "Bilanciato (Consigliato)", + "aggressive": "Aggressivo (File più piccoli)", + "extreme": "Estremo (Compressione massima)", + "grayscale": "Converti in scala di grigi", + "grayscaleHint": "Riduce le dimensioni rimuovendo le informazioni di colore", + "customSettings": "Impostazioni personalizzate", + "customSettingsHint": "Affina i parametri di compressione:", + "outputQuality": "Qualità di output", + "resizeImagesTo": "Ridimensiona le immagini a", + "onlyProcessAbove": "Elabora solo sopra", + "removeMetadata": "Rimuovi metadati", + "subsetFonts": "Riduci i font (rimuovi glifi non usati)", + "removeThumbnails": "Rimuovi miniature incorporate", + "compressButton": "Comprimi PDF" + }, + "pdfEditor": { + "name": "Editor PDF", + "subtitle": "Annota, evidenzia, redigi, commenta, aggiungi forme/immagini, cerca e visualizza PDF." + }, + "jpgToPdf": { + "name": "JPG in PDF", + "subtitle": "Crea un PDF da immagini JPG, JPEG e JPEG2000 (JP2/JPX)." + }, + "signPdf": { + "name": "Firma PDF", + "subtitle": "Disegna, digita o carica la tua firma." + }, + "cropPdf": { + "name": "Ritaglia PDF", + "subtitle": "Rimuovi i margini di ogni pagina del tuo PDF." + }, + "extractPages": { + "name": "Estrai Pagine", + "subtitle": "Salva una selezione di pagine come nuovi file." + }, + "duplicateOrganize": { + "name": "Duplica e Organizza", + "subtitle": "Duplica, riordina ed elimina pagine." + }, + "deletePages": { + "name": "Elimina Pagine", + "subtitle": "Rimuovi pagine specifiche dal tuo documento." + }, + "editBookmarks": { + "name": "Modifica Segnalibri", + "subtitle": "Aggiungi, modifica, importa, elimina ed estrai segnalibri PDF." + }, + "tableOfContents": { + "name": "Indice", + "subtitle": "Genera una pagina di indice dai segnalibri del PDF." + }, + "pageNumbers": { + "name": "Numeri di Pagina", + "subtitle": "Inserisci i numeri di pagina nel tuo documento." + }, + "batesNumbering": { + "name": "Numerazione Bates", + "subtitle": "Aggiungi numeri Bates sequenziali su uno o più file PDF." + }, + "addWatermark": { + "name": "Aggiungi Filigrana", + "subtitle": "Applica testo o un'immagine sulle pagine del tuo PDF.", + "applyToAllPages": "Applica a tutte le pagine" + }, + "headerFooter": { + "name": "Intestazione e Piè di Pagina", + "subtitle": "Aggiungi testo nella parte superiore e inferiore delle pagine." + }, + "invertColors": { + "name": "Inverti Colori", + "subtitle": "Crea una versione \"modalità scura\" del tuo PDF." + }, + "scannerEffect": { + "name": "Effetto scanner", + "subtitle": "Fai sembrare il tuo PDF un documento scansionato.", + "scanSettings": "Impostazioni di scansione", + "colorspace": "Spazio colore", + "gray": "Grigio", + "border": "Bordo", + "rotate": "Rotazione", + "rotateVariance": "Variazione rotazione", + "brightness": "Luminosità", + "contrast": "Contrasto", + "blur": "Sfocatura", + "noise": "Rumore", + "yellowish": "Ingiallimento", + "resolution": "Risoluzione", + "processButton": "Applica effetto scanner" + }, + "adjustColors": { + "name": "Regola colori", + "subtitle": "Regola luminosità, contrasto, saturazione e altro nel tuo PDF.", + "colorSettings": "Impostazioni colore", + "brightness": "Luminosità", + "contrast": "Contrasto", + "saturation": "Saturazione", + "hueShift": "Tonalità", + "temperature": "Temperatura", + "tint": "Tinta", + "gamma": "Gamma", + "sepia": "Seppia", + "processButton": "Applica regolazioni colore" + }, + "backgroundColor": { + "name": "Colore di Sfondo", + "subtitle": "Cambia il colore di sfondo del tuo PDF." + }, + "changeTextColor": { + "name": "Cambia Colore Testo", + "subtitle": "Cambia il colore del testo nel tuo PDF." + }, + "addStamps": { + "name": "Aggiungi Timbri", + "subtitle": "Aggiungi timbri immagine al tuo PDF usando la barra degli strumenti di annotazione.", + "usernameLabel": "Nome sul timbro", + "usernamePlaceholder": "Inserisci il tuo nome (per i timbri)", + "usernameHint": "Questo nome apparirà sui timbri che crei." + }, + "removeAnnotations": { + "name": "Rimuovi Annotazioni", + "subtitle": "Rimuovi commenti, evidenziazioni e link." + }, + "pdfFormFiller": { + "name": "Compilatore Moduli PDF", + "subtitle": "Compila i moduli direttamente nel browser. Supporta anche i moduli XFA." + }, + "createPdfForm": { + "name": "Crea Modulo PDF", + "subtitle": "Crea moduli PDF compilabili con campi di testo drag-and-drop." + }, + "removeBlankPages": { + "name": "Rimuovi Pagine Vuote", + "subtitle": "Rileva e elimina automaticamente le pagine vuote.", + "sensitivityHint": "Più alto = più rigoroso, solo pagine completamente vuote. Più basso = consente pagine con qualche contenuto." + }, + "imageToPdf": { + "name": "Immagini in PDF", + "subtitle": "Converti JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP in PDF." + }, + "pngToPdf": { + "name": "PNG in PDF", + "subtitle": "Crea un PDF da una o più immagini PNG." + }, + "webpToPdf": { + "name": "WebP in PDF", + "subtitle": "Crea un PDF da una o più immagini WebP." + }, + "svgToPdf": { + "name": "SVG in PDF", + "subtitle": "Crea un PDF da una o più immagini SVG." + }, + "bmpToPdf": { + "name": "BMP in PDF", + "subtitle": "Crea un PDF da una o più immagini BMP." + }, + "heicToPdf": { + "name": "HEIC in PDF", + "subtitle": "Crea un PDF da una o più immagini HEIC." + }, + "tiffToPdf": { + "name": "TIFF in PDF", + "subtitle": "Crea un PDF da una o più immagini TIFF." + }, + "textToPdf": { + "name": "Testo in PDF", + "subtitle": "Converti un file di testo semplice in un PDF." + }, + "jsonToPdf": { + "name": "JSON in PDF", + "subtitle": "Converti file JSON in formato PDF." + }, + "pdfToJpg": { + "name": "PDF in JPG", + "subtitle": "Converti ogni pagina del PDF in un'immagine JPG." + }, + "pdfToPng": { + "name": "PDF in PNG", + "subtitle": "Converti ogni pagina del PDF in un'immagine PNG." + }, + "pdfToWebp": { + "name": "PDF in WebP", + "subtitle": "Converti ogni pagina del PDF in un'immagine WebP." + }, + "pdfToBmp": { + "name": "PDF in BMP", + "subtitle": "Converti ogni pagina del PDF in un'immagine BMP." + }, + "pdfToTiff": { + "name": "PDF in TIFF", + "subtitle": "Converti ogni pagina del PDF in un'immagine TIFF." + }, + "pdfToGreyscale": { + "name": "PDF in Scala di Grigi", + "subtitle": "Converti tutti i colori in scala di grigi." + }, + "pdfToJson": { + "name": "PDF in JSON", + "subtitle": "Converti file PDF in formato JSON." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Rendi un PDF ricercabile e copiabile." + }, + "alternateMix": { + "name": "Alterna e Riordina Pagine", + "subtitle": "Unisci i PDF alternando le pagine di ogni file. Conserva i segnalibri." + }, + "addAttachments": { + "name": "Aggiungi Allegati", + "subtitle": "Incorpora uno o più file nel tuo PDF." + }, + "extractAttachments": { + "name": "Estrai Allegati", + "subtitle": "Estrai tutti i file incorporati dai PDF come archivio ZIP." + }, + "editAttachments": { + "name": "Modifica Allegati", + "subtitle": "Visualizza o rimuovi gli allegati nel tuo PDF." + }, + "dividePages": { + "name": "Dividi Pagine", + "subtitle": "Dividi le pagine orizzontalmente o verticalmente." + }, + "addBlankPage": { + "name": "Aggiungi Pagina Vuota", + "subtitle": "Inserisci una pagina vuota in qualsiasi punto del tuo PDF." + }, + "reversePages": { + "name": "Inverti Pagine", + "subtitle": "Inverti l'ordine di tutte le pagine del documento." + }, + "rotatePdf": { + "name": "Ruota PDF", + "subtitle": "Ruota le pagine per multipli di 90 gradi." + }, + "rotateCustom": { + "name": "Ruota di Gradi Personalizzati", + "subtitle": "Ruota le pagine di un angolo personalizzato." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "Disponi più pagine su un unico foglio." + }, + "combineToSinglePage": { + "name": "Combina in Una Pagina", + "subtitle": "Unisci tutte le pagine in un'unica pagina continua." + }, + "viewMetadata": { + "name": "Visualizza Metadati", + "subtitle": "Ispeziona le proprietà nascoste del tuo PDF." + }, + "editMetadata": { + "name": "Modifica Metadati", + "subtitle": "Modifica autore, titolo e altre proprietà." + }, + "pdfsToZip": { + "name": "PDF in ZIP", + "subtitle": "Raggruppa più file PDF in un archivio ZIP." + }, + "comparePdfs": { + "name": "Confronta PDF", + "subtitle": "Confronta due PDF fianco a fianco." + }, + "posterizePdf": { + "name": "Posterizza PDF", + "subtitle": "Dividi una pagina grande in più pagine più piccole." + }, + "fixPageSize": { + "name": "Correggi Dimensione Pagina", + "subtitle": "Uniforma tutte le pagine a una dimensione standard." + }, + "linearizePdf": { + "name": "Linearizza PDF", + "subtitle": "Ottimizza il PDF per una visualizzazione web più veloce." + }, + "pageDimensions": { + "name": "Dimensioni Pagina", + "subtitle": "Analizza dimensione, orientamento e unità delle pagine." + }, + "removeRestrictions": { + "name": "Rimuovi Restrizioni", + "subtitle": "Rimuovi la protezione tramite password e le restrizioni di sicurezza associate ai PDF firmati digitalmente." + }, + "repairPdf": { + "name": "Ripara PDF", + "subtitle": "Recupera i dati da file PDF corrotti o danneggiati." + }, + "encryptPdf": { + "name": "Cripta PDF", + "subtitle": "Proteggi il tuo PDF aggiungendo una password." + }, + "sanitizePdf": { + "name": "Sanitizza PDF", + "subtitle": "Rimuovi metadati, annotazioni, script e altro." + }, + "decryptPdf": { + "name": "Decrittografa PDF", + "subtitle": "Sblocca il PDF rimuovendo la protezione tramite password." + }, + "flattenPdf": { + "name": "Appiattisci PDF", + "subtitle": "Rendi i campi dei moduli e le annotazioni non modificabili." + }, + "removeMetadata": { + "name": "Rimuovi metadati", + "subtitle": "Rimuovi i dati nascosti dal tuo PDF." + }, + "changePermissions": { + "name": "Modifica permessi", + "subtitle": "Imposta o modifica i permessi utente su un PDF." + }, + "odtToPdf": { + "name": "ODT in PDF", + "subtitle": "Converti file OpenDocument Text in formato PDF. Supporta più file.", + "acceptedFormats": "File ODT", + "convertButton": "Converti in PDF" + }, + "csvToPdf": { + "name": "CSV in PDF", + "subtitle": "Converti file di foglio di calcolo CSV in formato PDF. Supporta più file.", + "acceptedFormats": "File CSV", + "convertButton": "Converti in PDF" + }, + "rtfToPdf": { + "name": "RTF in PDF", + "subtitle": "Converti documenti Rich Text Format in PDF. Supporta più file.", + "acceptedFormats": "File RTF", + "convertButton": "Converti in PDF" + }, + "wordToPdf": { + "name": "Word in PDF", + "subtitle": "Converti documenti Word (DOCX, DOC, ODT, RTF) in formato PDF. Supporta più file.", + "acceptedFormats": "File DOCX, DOC, ODT, RTF", + "convertButton": "Converti in PDF" + }, + "excelToPdf": { + "name": "Excel in PDF", + "subtitle": "Converti fogli Excel (XLSX, XLS, ODS, CSV) in formato PDF. Supporta più file.", + "acceptedFormats": "File XLSX, XLS, ODS, CSV", + "convertButton": "Converti in PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint in PDF", + "subtitle": "Converti presentazioni PowerPoint (PPTX, PPT, ODP) in formato PDF. Supporta più file.", + "acceptedFormats": "File PPTX, PPT, ODP", + "convertButton": "Converti in PDF" + }, + "markdownToPdf": { + "name": "Markdown in PDF", + "subtitle": "Scrivi o incolla Markdown ed esportalo come un PDF ben formattato.", + "paneMarkdown": "Markdown", + "panePreview": "Anteprima", + "btnUpload": "Carica", + "btnSyncScroll": "Sincronizza scorrimento", + "btnSettings": "Impostazioni", + "btnExportPdf": "Esporta PDF", + "settingsTitle": "Impostazioni Markdown", + "settingsPreset": "Preset", + "presetDefault": "Predefinito (simile a GFM)", + "presetCommonmark": "CommonMark (rigoroso)", + "presetZero": "Minimale (nessuna funzionalità)", + "settingsOptions": "Opzioni Markdown", + "optAllowHtml": "Consenti tag HTML", + "optBreaks": "Converti nuove righe in
", + "optLinkify": "Converti automaticamente gli URL in link", + "optTypographer": "Tipografia (virgolette intelligenti, ecc.)" + }, + "pdfBooklet": { + "name": "Opuscolo PDF", + "subtitle": "Riorganizza le pagine per la stampa di opuscoli fronte-retro. Piega e pinza per creare un opuscolo.", + "howItWorks": "Come funziona:", + "step1": "Carica un file PDF.", + "step2": "Le pagine saranno riorganizzate in ordine per opuscolo.", + "step3": "Stampa fronte-retro, capovolgi sul lato corto, piega e pinza.", + "paperSize": "Formato carta", + "orientation": "Orientamento", + "portrait": "Ritratto", + "landscape": "Paesaggio", + "pagesPerSheet": "Pagine per foglio", + "createBooklet": "Crea opuscolo", + "processing": "Elaborazione...", + "pageCount": "Il conteggio delle pagine verrà arrotondato ad un multiplo di 4 se necessario." + }, + "xpsToPdf": { + "name": "XPS in PDF", + "subtitle": "Converti documenti XPS/OXPS in formato PDF. Supporta più file.", + "acceptedFormats": "File XPS, OXPS", + "convertButton": "Converti in PDF" + }, + "mobiToPdf": { + "name": "MOBI in PDF", + "subtitle": "Converti e-book MOBI in formato PDF. Supporta più file.", + "acceptedFormats": "File MOBI", + "convertButton": "Converti in PDF" + }, + "epubToPdf": { + "name": "EPUB in PDF", + "subtitle": "Converti e-book EPUB in formato PDF. Supporta più file.", + "acceptedFormats": "File EPUB", + "convertButton": "Converti in PDF" + }, + "fb2ToPdf": { + "name": "FB2 in PDF", + "subtitle": "Converti e-book FictionBook (FB2) in formato PDF. Supporta più file.", + "acceptedFormats": "File FB2", + "convertButton": "Converti in PDF" + }, + "cbzToPdf": { + "name": "CBZ in PDF", + "subtitle": "Converti archivi di fumetti (CBZ/CBR) in formato PDF. Supporta più file.", + "acceptedFormats": "File CBZ, CBR", + "convertButton": "Converti in PDF" + }, + "wpdToPdf": { + "name": "WPD in PDF", + "subtitle": "Converti documenti WordPerfect (WPD) in formato PDF. Supporta più file.", + "acceptedFormats": "File WPD", + "convertButton": "Converti in PDF" + }, + "wpsToPdf": { + "name": "WPS in PDF", + "subtitle": "Converti documenti WPS Office in formato PDF. Supporta più file.", + "acceptedFormats": "File WPS", + "convertButton": "Converti in PDF" + }, + "xmlToPdf": { + "name": "XML in PDF", + "subtitle": "Converti documenti XML in formato PDF. Supporta più file.", + "acceptedFormats": "File XML", + "convertButton": "Converti in PDF" + }, + "pagesToPdf": { + "name": "Pages in PDF", + "subtitle": "Converti documenti Apple Pages in formato PDF. Supporta più file.", + "acceptedFormats": "File Pages", + "convertButton": "Converti in PDF" + }, + "odgToPdf": { + "name": "ODG in PDF", + "subtitle": "Converti OpenDocument Graphics (ODG) in formato PDF. Supporta più file.", + "acceptedFormats": "File ODG", + "convertButton": "Converti in PDF" + }, + "odsToPdf": { + "name": "ODS in PDF", + "subtitle": "Converti fogli OpenDocument (ODS) in formato PDF. Supporta più file.", + "acceptedFormats": "File ODS", + "convertButton": "Converti in PDF" + }, + "odpToPdf": { + "name": "ODP in PDF", + "subtitle": "Converti presentazioni OpenDocument (ODP) in formato PDF. Supporta più file.", + "acceptedFormats": "File ODP", + "convertButton": "Converti in PDF" + }, + "pubToPdf": { + "name": "PUB in PDF", + "subtitle": "Converti file Microsoft Publisher (PUB) in formato PDF. Supporta più file.", + "acceptedFormats": "File PUB", + "convertButton": "Converti in PDF" + }, + "vsdToPdf": { + "name": "VSD in PDF", + "subtitle": "Converti file Microsoft Visio (VSD, VSDX) in formato PDF. Supporta più file.", + "acceptedFormats": "File VSD, VSDX", + "convertButton": "Converti in PDF" + }, + "psdToPdf": { + "name": "PSD in PDF", + "subtitle": "Converti file Adobe Photoshop (PSD) in formato PDF. Supporta più file.", + "acceptedFormats": "File PSD", + "convertButton": "Converti in PDF" + }, + "pdfToSvg": { + "name": "PDF in SVG", + "subtitle": "Converti ogni pagina di un file PDF in un'immagine vettoriale scalabile (SVG) per qualità perfetta a qualsiasi dimensione." + }, + "extractTables": { + "name": "Estrai tabelle PDF", + "subtitle": "Estrai le tabelle dai file PDF ed esportale come CSV, JSON o Markdown." + }, + "pdfToCsv": { + "name": "PDF in CSV", + "subtitle": "Estrai tabelle dai PDF e convertili in formato CSV." + }, + "pdfToExcel": { + "name": "PDF in Excel", + "subtitle": "Estrai tabelle dai PDF e convertili in Excel (XLSX)." + }, + "pdfToText": { + "name": "PDF in Testo", + "subtitle": "Estrai il testo dai file PDF e salvalo come testo semplice (.txt). Supporta più file.", + "note": "Questo strumento funziona SOLO con PDF creati digitalmente. Per documenti scansionati o basati su immagini, usa invece il nostro strumento OCR PDF.", + "convertButton": "Estrai testo" + }, + "digitalSignPdf": { + "name": "Firma digitale PDF", + "pageTitle": "Firma digitale PDF - Aggiungi firma crittografica | BentoPDF", + "subtitle": "Aggiungi una firma digitale crittografica al tuo PDF usando certificati X.509. Supporta i formati PKCS#12 (.pfx, .p12) e PEM. La tua chiave privata non lascia mai il browser.", + "certificateSection": "Certificato", + "uploadCert": "Carica certificato (.pfx, .p12)", + "certPassword": "Password del certificato", + "certPasswordPlaceholder": "Inserisci la password del certificato", + "certInfo": "Informazioni sul certificato", + "certSubject": "Soggetto", + "certIssuer": "Emittente", + "certValidity": "Valido", + "signatureDetails": "Dettagli della firma (opzionale)", + "reason": "Motivo", + "reasonPlaceholder": "es. Approvo questo documento", + "location": "Luogo", + "locationPlaceholder": "es. Roma, Italia", + "contactInfo": "Contatto", + "contactPlaceholder": "es. email@example.com", + "applySignature": "Applica firma digitale", + "successMessage": "PDF firmato con successo! La firma può essere verificata in qualsiasi lettore PDF." + }, + "validateSignaturePdf": { + "name": "Verifica firma PDF", + "pageTitle": "Verifica firma PDF - Controlla firme digitali | BentoPDF", + "subtitle": "Verifica le firme digitali nei tuoi PDF. Controlla la validità del certificato, visualizza i dati del firmatario e conferma l'integrità del documento. Tutto avviene nel tuo browser." + }, + "emailToPdf": { + "name": "Email in PDF", + "subtitle": "Converti file email (EML, MSG) in formato PDF. Supporta esportazioni Outlook e formati email standard.", + "acceptedFormats": "File EML, MSG", + "convertButton": "Converti in PDF" + }, + "fontToOutline": { + "name": "Font in Contorni", + "subtitle": "Converti tutti i font in contorni vettoriali per una visualizzazione coerente su tutti i dispositivi." + }, + "deskewPdf": { + "name": "Raddrizza PDF", + "subtitle": "Raddrizza automaticamente le pagine scansionate inclinate usando OpenCV." + }, + "pdfToWord": { + "name": "PDF in Word", + "subtitle": "Converti file PDF in documenti Word modificabili." + }, + "extractImages": { + "name": "Estrai immagini", + "subtitle": "Estrai tutte le immagini incorporate dai tuoi file PDF." + }, + "pdfToMarkdown": { + "name": "PDF in Markdown", + "subtitle": "Converti testo e tabelle PDF in formato Markdown." + }, + "preparePdfForAi": { + "name": "Prepara PDF per IA", + "subtitle": "Estrai il contenuto PDF come JSON LlamaIndex per pipeline RAG/LLM." + }, + "pdfOcg": { + "name": "Livelli PDF (OCG)", + "subtitle": "Visualizza, attiva/disattiva, aggiungi ed elimina livelli OCG nel tuo PDF." + }, + "pdfToPdfa": { + "name": "PDF in PDF/A", + "subtitle": "Converti PDF in PDF/A per l'archiviazione a lungo termine." + }, + "rasterizePdf": { + "name": "Rasterizza PDF", + "subtitle": "Converti PDF in PDF basato su immagini. Appiattisci i livelli e rimuovi il testo selezionabile." + }, + "pdfWorkflow": { + "name": "Costruttore di workflow PDF", + "subtitle": "Crea pipeline di elaborazione PDF personalizzate con un editor visuale a nodi.", + "nodes": "Nodi", + "searchNodes": "Cerca nodi...", + "run": "Esegui", + "clear": "Cancella", + "save": "Salva", + "load": "Carica", + "export": "Esporta", + "import": "Importa", + "ready": "Pronto", + "settings": "Impostazioni", + "processing": "Elaborazione in corso...", + "saveTemplate": "Salva modello", + "templateName": "Nome del modello", + "templatePlaceholder": "es. Workflow fatturazione", + "cancel": "Annulla", + "loadTemplate": "Carica modello", + "noTemplates": "Nessun modello salvato al momento.", + "ok": "OK", + "workflowCompleted": "Workflow completato", + "errorDuringExecution": "Errore durante l'esecuzione", + "addNodeError": "Aggiungi almeno un nodo per eseguire il workflow.", + "needInputOutput": "Il tuo workflow necessita di almeno un nodo di input e un nodo di output per funzionare.", + "enterName": "Inserisci un nome.", + "templateExists": "Esiste già un modello con questo nome.", + "templateSaved": "Modello \"{{name}}\" salvato.", + "templateLoaded": "Modello \"{{name}}\" caricato.", + "failedLoadTemplate": "Impossibile caricare il modello.", + "noSettings": "Nessuna impostazione configurabile per questo nodo.", + "advancedSettings": "Impostazioni avanzate" + } +} diff --git a/public/locales/ko/common.json b/public/locales/ko/common.json new file mode 100644 index 0000000..bbe93bd --- /dev/null +++ b/public/locales/ko/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "홈", + "about": "소개", + "contact": "문의", + "licensing": "라이선스", + "allTools": "모든 도구", + "openMainMenu": "메인 메뉴 열기", + "language": "언어" + }, + "donation": { + "message": "BentoPDF가 마음에 드셨나요? 무료 오픈소스로 유지될 수 있도록 도와주세요!", + "button": "후원하기" + }, + "hero": { + "title": "개인정보 보호를 위해 만든", + "pdfToolkit": "PDF 도구 모음", + "builtForPrivacy": " ", + "noSignups": "회원가입 불필요", + "unlimitedUse": "무제한 사용", + "worksOffline": "오프라인 지원", + "startUsing": "지금 시작하기" + }, + "usedBy": { + "title": "다양한 기업과 사용자들이 함께하고 있습니다" + }, + "features": { + "title": "왜", + "bentoPdf": "BentoPDF일까요?", + "noSignup": { + "title": "회원가입 불필요", + "description": "계정이나 이메일 없이 바로 시작하세요." + }, + "noUploads": { + "title": "파일 업로드 없음", + "description": "100% 브라우저에서 처리되어 파일이 기기 밖으로 나가지 않습니다." + }, + "foreverFree": { + "title": "영원히 무료", + "description": "모든 도구를 무료로, 체험판도 유료 기능도 없습니다." + }, + "noLimits": { + "title": "사용 제한 없음", + "description": "원하는 만큼 자유롭게 사용하세요." + }, + "batchProcessing": { + "title": "일괄 처리", + "description": "여러 PDF를 한 번에 처리할 수 있습니다." + }, + "lightningFast": { + "title": "빠른 처리 속도", + "description": "기다릴 필요 없이 PDF를 즉시 처리합니다." + } + }, + "tools": { + "title": "시작하기", + "toolsLabel": "도구", + "subtitle": "도구를 클릭하면 파일 업로더가 열립니다", + "searchPlaceholder": "도구 검색 (예: '분할', '병합'...)", + "backToTools": "도구 목록으로 돌아가기", + "firstLoadNotice": "처음에는 변환 엔진을 다운로드하느라 시간이 조금 걸릴 수 있습니다. 이후에는 바로 실행됩니다." + }, + "upload": { + "clickToSelect": "클릭하여 파일 선택", + "orDragAndDrop": "또는 파일을 끌어다 놓으세요", + "pdfOrImages": "PDF 또는 이미지", + "filesNeverLeave": "파일이 기기 밖으로 나가지 않습니다.", + "addMore": "파일 추가", + "clearAll": "모두 지우기", + "clearFiles": "파일 지우기", + "hints": { + "singlePdf": "PDF 파일 1개", + "pdfFile": "PDF 파일", + "multiplePdfs2": "PDF 파일 2개 이상", + "bmpImages": "BMP 이미지", + "oneOrMorePdfs": "PDF 파일 1개 이상", + "pdfDocuments": "PDF 문서", + "oneOrMoreCsv": "CSV 파일 1개 이상", + "multiplePdfsSupported": "여러 PDF 파일 지원", + "singleOrMultiplePdfs": "PDF 파일 1개 또는 여러 개", + "singlePdfFile": "PDF 파일 1개", + "pdfWithForms": "양식이 포함된 PDF 파일", + "heicImages": "HEIC/HEIF 이미지", + "jpgImages": "JPG, JPEG, JP2, JPX 이미지", + "pdfsOrImages": "PDF 또는 이미지", + "oneOrMoreOdt": "ODT 파일 1개 이상", + "singlePdfOnly": "PDF 파일 1개만", + "pdfFiles": "PDF 파일", + "multiplePdfs": "여러 PDF 파일", + "pngImages": "PNG 이미지", + "pdfFilesOneOrMore": "PDF 파일 (1개 이상)", + "oneOrMoreRtf": "RTF 파일 1개 이상", + "svgGraphics": "SVG 그래픽", + "tiffImages": "TIFF 이미지", + "webpImages": "WebP 이미지" + } + }, + "howItWorks": { + "title": "이용 방법", + "step1": "파일을 클릭하거나 끌어다 놓아 시작하세요", + "step2": "처리 버튼을 눌러 변환을 시작하세요", + "step3": "완성된 파일을 바로 저장하세요" + }, + "relatedTools": { + "title": "관련 PDF 도구" + }, + "loader": { + "processing": "처리 중..." + }, + "alert": { + "title": "알림", + "ok": "확인" + }, + "preview": { + "title": "문서 미리보기", + "downloadAsPdf": "PDF로 다운로드", + "close": "닫기" + }, + "settings": { + "title": "설정", + "shortcuts": "단축키", + "preferences": "환경설정", + "displayPreferences": "화면 설정", + "searchShortcuts": "단축키 검색...", + "shortcutsInfo": "키를 길게 눌러 단축키를 설정할 수 있습니다. 변경 사항은 자동 저장됩니다.", + "shortcutsWarning": "⚠️ 브라우저 기본 단축키(Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N 등)는 제대로 작동하지 않을 수 있으니 피해 주세요.", + "import": "가져오기", + "export": "내보내기", + "resetToDefaults": "기본값으로 초기화", + "fullWidthMode": "전체 너비 모드", + "fullWidthDescription": "가운데 정렬 대신 화면 전체 너비를 사용합니다", + "settingsAutoSaved": "설정은 자동으로 저장됩니다", + "clickToSet": "클릭하여 설정", + "pressKeys": "키를 누르세요...", + "warnings": { + "alreadyInUse": "이미 사용 중인 단축키", + "assignedTo": "다음 항목에 이미 지정되어 있습니다:", + "chooseDifferent": "다른 단축키를 선택해 주세요.", + "reserved": "예약된 단축키 경고", + "commonlyUsed": "일반적으로 다음 용도로 사용됩니다:", + "unreliable": "이 단축키는 브라우저나 시스템 동작과 충돌할 수 있습니다.", + "useAnyway": "그래도 사용하시겠습니까?", + "resetTitle": "단축키 초기화", + "resetMessage": "모든 단축키를 기본값으로 초기화할까요?

이 작업은 되돌릴 수 없습니다.", + "importSuccessTitle": "가져오기 완료", + "importSuccessMessage": "단축키를 가져왔습니다!", + "importFailTitle": "가져오기 실패", + "importFailMessage": "단축키를 가져올 수 없습니다. 파일 형식이 올바르지 않습니다." + } + }, + "warning": { + "title": "경고", + "cancel": "취소", + "proceed": "계속" + }, + "compliance": { + "title": "데이터가 기기 밖으로 나가지 않습니다", + "weKeep": "글로벌 보안 표준을 준수하여", + "yourInfoSafe": "여러분의 정보를", + "byFollowingStandards": "안전하게 보호합니다.", + "processingLocal": "모든 처리는 사용자의 기기에서 직접 이루어집니다.", + "gdpr": { + "title": "GDPR 준수", + "description": "유럽연합 내 개인정보 및 프라이버시를 보호합니다." + }, + "ccpa": { + "title": "CCPA 준수", + "description": "캘리포니아 주민의 개인정보 수집, 사용, 공유에 대한 권리를 보장합니다." + }, + "hipaa": { + "title": "HIPAA 준수", + "description": "미국 의료 시스템에서 민감한 건강 정보를 안전하게 보호합니다." + } + }, + "faq": { + "title": "자주 묻는", + "questions": "질문", + "sectionTitle": "자주 묻는 질문", + "isFree": { + "question": "BentoPDF는 정말 무료인가요?", + "answer": "네, 완전히 무료입니다. BentoPDF의 모든 도구는 파일 수 제한, 회원가입, 워터마크 없이 자유롭게 사용할 수 있습니다. 누구나 비용 걱정 없이 편리한 PDF 도구를 쓸 수 있어야 한다고 생각합니다." + }, + "areFilesSecure": { + "question": "파일이 안전한가요? 어디에서 처리되나요?", + "answer": "파일은 여러분의 컴퓨터를 절대 떠나지 않기 때문에 최대한 안전합니다. 모든 작업은 웹 브라우저 안에서 직접 처리되며, 서버에 파일을 업로드하지 않으므로 문서의 프라이버시를 완벽하게 지킬 수 있습니다." + }, + "platforms": { + "question": "Mac, Windows, 모바일에서도 되나요?", + "answer": "네! BentoPDF는 브라우저에서 실행되므로 Windows, macOS, Linux, iOS, Android 등 최신 웹 브라우저가 있는 어떤 기기에서든 사용할 수 있습니다." + }, + "gdprCompliant": { + "question": "BentoPDF는 GDPR을 준수하나요?", + "answer": "네, 완전히 준수합니다. 모든 파일 처리가 브라우저에서 이루어지고 서버로 파일을 전송하지 않기 때문에, 저희는 여러분의 데이터에 접근할 수 없습니다. 문서에 대한 통제권은 항상 여러분에게 있습니다." + }, + "dataStorage": { + "question": "파일을 저장하거나 추적하나요?", + "answer": "아니요. 파일을 저장하거나 추적하거나 기록하지 않습니다. BentoPDF에서 하는 모든 작업은 브라우저 메모리에서 처리되며, 페이지를 닫으면 모두 사라집니다. 업로드도, 이용 기록도, 서버도 관여하지 않습니다." + }, + "different": { + "question": "BentoPDF가 다른 PDF 도구와 다른 점은 뭔가요?", + "answer": "대부분의 PDF 도구는 파일을 서버에 업로드해서 처리합니다. BentoPDF는 그렇게 하지 않습니다. 최신 웹 기술을 활용해 브라우저에서 바로 처리하기 때문에 더 빠르고, 더 안전하며, 걱정 없이 사용할 수 있습니다." + }, + "browserBased": { + "question": "브라우저 기반 처리가 왜 안전한가요?", + "answer": "BentoPDF는 브라우저 안에서만 실행되기 때문에 파일이 기기 밖으로 나가지 않습니다. 서버 해킹, 데이터 유출, 무단 접근 같은 위험이 원천적으로 차단됩니다. 파일은 언제나 여러분만의 것입니다." + }, + "analytics": { + "question": "쿠키나 분석 도구로 사용자를 추적하나요?", + "answer": "여러분의 프라이버시를 소중히 여깁니다. BentoPDF는 개인정보를 추적하지 않으며, 익명 방문 수 확인만을 위해 Simple Analytics를 사용합니다. 방문자 수는 알 수 있지만 누가 방문했는지는 절대 알 수 없습니다. Simple Analytics는 GDPR을 완전히 준수합니다." + } + }, + "testimonials": { + "title": "사용자", + "users": "후기", + "say": "" + }, + "support": { + "title": "도움이 되셨나요?", + "description": "BentoPDF는 누구나 무료로, 안전하게, 강력한 PDF 도구를 사용할 수 있도록 만든 프로젝트입니다. 유용하게 쓰고 계시다면 개발을 응원해 주세요. 작은 후원이 큰 힘이 됩니다!", + "buyMeCoffee": "커피 한 잔 사주기" + }, + "footer": { + "copyright": "© 2026 BentoPDF. All rights reserved.", + "version": "버전", + "company": "회사", + "aboutUs": "소개", + "faqLink": "FAQ", + "contactUs": "문의하기", + "legal": "법적 고지", + "termsAndConditions": "이용약관", + "privacyPolicy": "개인정보처리방침", + "followUs": "팔로우" + }, + "merge": { + "title": "PDF 병합", + "description": "여러 파일을 합치거나, 원하는 페이지만 골라 새 문서로 만들 수 있습니다.", + "fileMode": "파일 모드", + "pageMode": "페이지 모드", + "howItWorks": "이용 방법:", + "fileModeInstructions": [ + "아이콘을 드래그하여 파일 순서를 변경할 수 있습니다.", + "각 파일의 \"페이지\" 란에 원하는 범위를 입력하세요 (예: \"1-3, 5\").", + "\"페이지\" 란을 비워두면 해당 파일의 전체 페이지가 포함됩니다." + ], + "pageModeInstructions": [ + "업로드한 PDF의 모든 페이지가 아래에 나타납니다.", + "페이지 미리보기를 드래그하여 원하는 순서로 배치하세요." + ], + "mergePdfs": "PDF 병합하기" + }, + "common": { + "page": "페이지", + "pages": "페이지", + "of": "/", + "download": "다운로드", + "cancel": "취소", + "save": "저장", + "delete": "삭제", + "edit": "편집", + "add": "추가", + "remove": "제거", + "loading": "불러오는 중...", + "error": "오류", + "success": "완료", + "file": "파일", + "files": "파일", + "close": "닫기" + }, + "about": { + "hero": { + "title": "PDF 도구는 마땅히", + "subtitle": "빠르고, 안전하고, 무료여야 합니다.", + "noCompromises": "타협 없이." + }, + "mission": { + "title": "우리의 목표", + "description": "프라이버시를 지키면서도 비용 부담 없는, 가장 완벽한 PDF 도구를 만드는 것입니다. 꼭 필요한 문서 도구는 누구나, 어디서든 자유롭게 쓸 수 있어야 합니다." + }, + "philosophy": { + "label": "핵심 철학", + "title": "개인정보 보호가 최우선. 언제나.", + "description": "데이터가 곧 상품인 시대에, 우리는 다른 길을 걷습니다. BentoPDF의 모든 처리는 브라우저에서 직접 이루어집니다. 파일이 서버로 전송되지 않고, 문서를 들여다보지 않으며, 사용 내역을 추적하지 않습니다. 여러분의 문서는 철저하게 비공개로 유지됩니다. 이것은 하나의 기능이 아니라 우리의 근본입니다." + }, + "whyBentopdf": { + "title": "왜", + "speed": { + "title": "속도에 최적화", + "description": "서버로 파일을 올리고 내려받을 필요가 없습니다. WebAssembly 등 최신 웹 기술로 브라우저에서 바로 처리하여 압도적인 속도를 제공합니다." + }, + "free": { + "title": "완전 무료", + "description": "체험판, 구독, 숨겨진 요금, 잠긴 \"프리미엄\" 기능이 없습니다. 강력한 PDF 도구는 돈을 내야 쓸 수 있는 것이 아니라 누구에게나 열려 있어야 합니다." + }, + "noAccount": { + "title": "계정 없이 바로 사용", + "description": "이메일도, 비밀번호도, 개인정보도 필요 없습니다. 원하는 도구를 바로 사용하세요." + }, + "openSource": { + "title": "오픈소스 정신", + "description": "투명성을 바탕으로 만들어졌습니다. PDF-lib, PDF.js 등 뛰어난 오픈소스 라이브러리를 활용하며, 좋은 도구를 누구나 쓸 수 있게 만드는 커뮤니티의 힘을 믿습니다." + } + }, + "cta": { + "title": "시작할 준비가 되셨나요?", + "description": "수많은 사용자가 일상적인 문서 작업에 BentoPDF를 활용하고 있습니다. 프라이버시와 빠른 성능이 가져다주는 차이를 직접 느껴보세요.", + "button": "모든 도구 둘러보기" + } + }, + "contact": { + "title": "문의하기", + "subtitle": "질문, 피드백, 기능 요청 등 무엇이든 편하게 연락해 주세요.", + "email": "아래 이메일로 직접 연락하실 수 있습니다:" + }, + "licensing": { + "title": "라이선스", + "subtitle": "필요에 맞는 라이선스를 선택하세요." + }, + "multiTool": { + "uploadPdfs": "PDF 업로드", + "upload": "업로드", + "addBlankPage": "빈 페이지 추가", + "edit": "편집:", + "undo": "실행 취소", + "redo": "다시 실행", + "reset": "초기화", + "selection": "선택:", + "selectAll": "모두 선택", + "deselectAll": "선택 해제", + "rotate": "회전:", + "rotateLeft": "왼쪽", + "rotateRight": "오른쪽", + "transform": "변환:", + "duplicate": "복제", + "split": "분할", + "clear": "지우기:", + "delete": "삭제", + "download": "다운로드:", + "downloadSelected": "선택 항목 다운로드", + "exportPdf": "PDF 내보내기", + "uploadPdfFiles": "PDF 파일 선택", + "dragAndDrop": "PDF 파일을 여기에 끌어다 놓거나 클릭하여 선택하세요", + "selectFiles": "파일 선택", + "renderingPages": "페이지 렌더링 중...", + "actions": { + "duplicatePage": "이 페이지 복제", + "deletePage": "이 페이지 삭제", + "insertPdf": "이 페이지 뒤에 PDF 삽입", + "toggleSplit": "이 페이지 뒤에서 분할" + }, + "pleaseWait": "잠시만요", + "pagesRendering": "페이지를 렌더링하는 중입니다. 잠시만 기다려 주세요...", + "noPagesSelected": "선택된 페이지 없음", + "selectOnePage": "다운로드할 페이지를 하나 이상 선택해 주세요.", + "noPages": "페이지 없음", + "noPagesToExport": "내보낼 페이지가 없습니다.", + "renderingTitle": "페이지 미리보기 생성 중", + "errorRendering": "페이지 미리보기를 만들지 못했습니다", + "error": "오류", + "failedToLoad": "불러오기 실패" + }, + "simpleMode": { + "title": "PDF 도구", + "subtitle": "사용할 도구를 선택하세요" + } +} diff --git a/public/locales/ko/tools.json b/public/locales/ko/tools.json new file mode 100644 index 0000000..7991f5f --- /dev/null +++ b/public/locales/ko/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "인기 도구", + "editAnnotate": "편집 및 주석", + "convertToPdf": "PDF로 변환", + "convertFromPdf": "PDF에서 변환", + "organizeManage": "정리 및 관리", + "optimizeRepair": "최적화 및 복구", + "securePdf": "PDF 보안" + }, + "pdfMultiTool": { + "name": "PDF 멀티 도구", + "subtitle": "병합, 분할, 정리, 삭제, 회전, 빈 페이지 추가, 추출, 복제를 하나의 화면에서." + }, + "mergePdf": { + "name": "PDF 병합", + "subtitle": "여러 PDF를 하나로 합칩니다. 북마크도 유지됩니다." + }, + "splitPdf": { + "name": "PDF 분할", + "subtitle": "원하는 페이지 범위를 새 PDF로 추출합니다." + }, + "compressPdf": { + "name": "PDF 압축", + "subtitle": "PDF 파일 크기를 줄입니다.", + "algorithmLabel": "압축 알고리즘", + "condense": "Condense (권장)", + "photon": "Photon (사진이 많은 PDF용)", + "condenseInfo": "Condense는 불필요한 데이터 제거, 이미지 최적화, 폰트 서브셋 등 고급 압축을 적용합니다. 대부분의 PDF에 적합합니다.", + "photonInfo": "Photon은 페이지를 이미지로 변환합니다. 사진이 많거나 스캔된 PDF에 적합합니다.", + "photonWarning": "주의: 텍스트 선택이 불가능해지고 링크가 작동하지 않게 됩니다.", + "levelLabel": "압축 수준", + "light": "낮음 (품질 유지)", + "balanced": "보통 (권장)", + "aggressive": "높음 (파일 크기 우선)", + "extreme": "최대 (최고 압축률)", + "grayscale": "흑백으로 변환", + "grayscaleHint": "색상 정보를 제거하여 파일 크기를 줄입니다", + "customSettings": "사용자 설정", + "customSettingsHint": "압축 세부 설정을 조정합니다:", + "outputQuality": "출력 품질", + "resizeImagesTo": "이미지 크기 조정", + "onlyProcessAbove": "이 크기 이상만 처리", + "removeMetadata": "메타데이터 제거", + "subsetFonts": "폰트 서브셋 (미사용 글자 제거)", + "removeThumbnails": "내장 미리보기 이미지 제거", + "compressButton": "PDF 압축하기" + }, + "pdfEditor": { + "name": "PDF 편집기", + "subtitle": "주석, 하이라이트, 마스킹, 댓글, 도형/이미지 추가, 검색 및 보기." + }, + "jpgToPdf": { + "name": "JPG를 PDF로", + "subtitle": "JPG, JPEG, JPEG2000(JP2/JPX) 이미지로 PDF를 만듭니다." + }, + "signPdf": { + "name": "PDF 서명", + "subtitle": "서명을 그리거나, 입력하거나, 이미지로 추가하세요." + }, + "cropPdf": { + "name": "PDF 자르기", + "subtitle": "PDF 모든 페이지의 여백을 잘라냅니다." + }, + "extractPages": { + "name": "페이지 추출", + "subtitle": "원하는 페이지를 골라 새 파일로 저장합니다." + }, + "duplicateOrganize": { + "name": "복제 및 정리", + "subtitle": "페이지를 복제하고, 순서를 바꾸고, 삭제합니다." + }, + "deletePages": { + "name": "페이지 삭제", + "subtitle": "문서에서 특정 페이지를 제거합니다." + }, + "editBookmarks": { + "name": "북마크 편집", + "subtitle": "PDF 북마크를 추가, 편집, 가져오기, 삭제, 추출합니다." + }, + "tableOfContents": { + "name": "목차 생성", + "subtitle": "PDF 북마크를 기반으로 목차 페이지를 만듭니다." + }, + "pageNumbers": { + "name": "페이지 번호", + "subtitle": "문서에 페이지 번호를 삽입합니다." + }, + "batesNumbering": { + "name": "베이츠 번호 매기기", + "subtitle": "하나 이상의 PDF에 순차적 베이츠 번호를 추가합니다." + }, + "addWatermark": { + "name": "워터마크 추가", + "subtitle": "PDF 페이지에 텍스트 또는 이미지 워터마크를 넣습니다.", + "applyToAllPages": "모든 페이지에 적용" + }, + "headerFooter": { + "name": "머리글 및 바닥글", + "subtitle": "페이지 상단과 하단에 텍스트를 추가합니다." + }, + "invertColors": { + "name": "색상 반전", + "subtitle": "PDF를 다크 모드 버전으로 만듭니다." + }, + "scannerEffect": { + "name": "스캔 효과", + "subtitle": "PDF를 스캔한 문서처럼 보이게 만듭니다.", + "scanSettings": "스캔 설정", + "colorspace": "색 공간", + "gray": "흑백", + "border": "테두리", + "rotate": "회전", + "rotateVariance": "회전 변동", + "brightness": "밝기", + "contrast": "대비", + "blur": "흐림", + "noise": "노이즈", + "yellowish": "누런 효과", + "resolution": "해상도", + "processButton": "스캔 효과 적용" + }, + "adjustColors": { + "name": "색상 조정", + "subtitle": "PDF의 밝기, 대비, 채도 등을 세밀하게 조정합니다.", + "colorSettings": "색상 설정", + "brightness": "밝기", + "contrast": "대비", + "saturation": "채도", + "hueShift": "색조", + "temperature": "색온도", + "tint": "틴트", + "gamma": "감마", + "sepia": "세피아", + "processButton": "색상 조정 적용" + }, + "backgroundColor": { + "name": "배경색 변경", + "subtitle": "PDF의 배경색을 변경합니다." + }, + "changeTextColor": { + "name": "텍스트 색상 변경", + "subtitle": "PDF의 텍스트 색상을 변경합니다." + }, + "addStamps": { + "name": "도장 추가", + "subtitle": "주석 도구 모음을 사용하여 PDF에 이미지 도장을 추가합니다.", + "usernameLabel": "도장 사용자 이름", + "usernamePlaceholder": "이름을 입력하세요 (도장용)", + "usernameHint": "이 이름이 도장에 표시됩니다." + }, + "removeAnnotations": { + "name": "주석 제거", + "subtitle": "댓글, 하이라이트, 링크를 제거합니다." + }, + "pdfFormFiller": { + "name": "PDF 양식 작성", + "subtitle": "브라우저에서 직접 양식을 작성합니다. XFA 양식도 지원됩니다." + }, + "createPdfForm": { + "name": "PDF 양식 만들기", + "subtitle": "드래그 앤 드롭으로 작성 가능한 PDF 양식을 만듭니다." + }, + "removeBlankPages": { + "name": "빈 페이지 제거", + "subtitle": "빈 페이지를 자동으로 감지하고 삭제합니다.", + "sensitivityHint": "높을수록 완전히 빈 페이지만 감지합니다. 낮추면 약간의 내용이 있는 페이지도 포함됩니다." + }, + "imageToPdf": { + "name": "이미지를 PDF로", + "subtitle": "JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP를 PDF로 변환합니다." + }, + "pngToPdf": { + "name": "PNG를 PDF로", + "subtitle": "PNG 이미지로 PDF를 만듭니다." + }, + "webpToPdf": { + "name": "WebP를 PDF로", + "subtitle": "WebP 이미지로 PDF를 만듭니다." + }, + "svgToPdf": { + "name": "SVG를 PDF로", + "subtitle": "SVG 이미지로 PDF를 만듭니다." + }, + "bmpToPdf": { + "name": "BMP를 PDF로", + "subtitle": "BMP 이미지로 PDF를 만듭니다." + }, + "heicToPdf": { + "name": "HEIC를 PDF로", + "subtitle": "HEIC 이미지로 PDF를 만듭니다." + }, + "tiffToPdf": { + "name": "TIFF를 PDF로", + "subtitle": "TIFF 이미지로 PDF를 만듭니다." + }, + "textToPdf": { + "name": "텍스트를 PDF로", + "subtitle": "일반 텍스트 파일을 PDF로 변환합니다." + }, + "jsonToPdf": { + "name": "JSON을 PDF로", + "subtitle": "JSON 파일을 PDF 형식으로 변환합니다." + }, + "pdfToJpg": { + "name": "PDF를 JPG로", + "subtitle": "PDF의 각 페이지를 JPG 이미지로 변환합니다." + }, + "pdfToPng": { + "name": "PDF를 PNG로", + "subtitle": "PDF의 각 페이지를 PNG 이미지로 변환합니다." + }, + "pdfToWebp": { + "name": "PDF를 WebP로", + "subtitle": "PDF의 각 페이지를 WebP 이미지로 변환합니다." + }, + "pdfToBmp": { + "name": "PDF를 BMP로", + "subtitle": "PDF의 각 페이지를 BMP 이미지로 변환합니다." + }, + "pdfToTiff": { + "name": "PDF를 TIFF로", + "subtitle": "PDF의 각 페이지를 TIFF 이미지로 변환합니다." + }, + "pdfToGreyscale": { + "name": "PDF 흑백 변환", + "subtitle": "모든 색상을 흑백으로 변환합니다." + }, + "pdfToJson": { + "name": "PDF를 JSON으로", + "subtitle": "PDF 파일을 JSON 형식으로 변환합니다." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "PDF의 텍스트를 검색하고 복사할 수 있게 만듭니다." + }, + "alternateMix": { + "name": "페이지 교차 병합", + "subtitle": "각 PDF의 페이지를 번갈아 가며 병합합니다. 북마크도 유지됩니다." + }, + "addAttachments": { + "name": "첨부파일 추가", + "subtitle": "PDF에 파일을 첨부합니다." + }, + "extractAttachments": { + "name": "첨부파일 추출", + "subtitle": "PDF에 첨부된 모든 파일을 ZIP으로 추출합니다." + }, + "editAttachments": { + "name": "첨부파일 편집", + "subtitle": "PDF의 첨부파일을 확인하거나 제거합니다." + }, + "dividePages": { + "name": "페이지 분할", + "subtitle": "페이지를 가로 또는 세로로 나눕니다." + }, + "addBlankPage": { + "name": "빈 페이지 추가", + "subtitle": "PDF 원하는 위치에 빈 페이지를 삽입합니다." + }, + "reversePages": { + "name": "페이지 역순 정렬", + "subtitle": "문서의 모든 페이지 순서를 뒤집습니다." + }, + "rotatePdf": { + "name": "PDF 회전", + "subtitle": "페이지를 90도 단위로 회전합니다." + }, + "rotateCustom": { + "name": "사용자 지정 각도 회전", + "subtitle": "원하는 각도로 페이지를 회전합니다." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "한 장에 여러 페이지를 배치합니다." + }, + "combineToSinglePage": { + "name": "한 페이지로 합치기", + "subtitle": "모든 페이지를 하나의 긴 페이지로 이어 붙입니다." + }, + "viewMetadata": { + "name": "메타데이터 보기", + "subtitle": "PDF에 숨겨진 속성 정보를 확인합니다." + }, + "editMetadata": { + "name": "메타데이터 편집", + "subtitle": "저자, 제목 등의 속성을 변경합니다." + }, + "pdfsToZip": { + "name": "PDF를 ZIP으로", + "subtitle": "여러 PDF 파일을 ZIP 파일로 묶습니다." + }, + "comparePdfs": { + "name": "PDF 비교", + "subtitle": "두 PDF를 나란히 비교합니다." + }, + "posterizePdf": { + "name": "PDF 포스터화", + "subtitle": "큰 페이지를 여러 작은 페이지로 나눕니다." + }, + "fixPageSize": { + "name": "페이지 크기 통일", + "subtitle": "모든 페이지를 동일한 크기로 맞춥니다." + }, + "linearizePdf": { + "name": "PDF 선형화", + "subtitle": "웹에서 빠르게 볼 수 있도록 PDF를 최적화합니다." + }, + "pageDimensions": { + "name": "페이지 크기 정보", + "subtitle": "페이지 크기, 방향, 단위를 분석합니다." + }, + "removeRestrictions": { + "name": "제한 해제", + "subtitle": "디지털 서명된 PDF의 비밀번호 보호 및 보안 제한을 해제합니다." + }, + "repairPdf": { + "name": "PDF 복구", + "subtitle": "손상된 PDF 파일에서 데이터를 복구합니다." + }, + "encryptPdf": { + "name": "PDF 암호화", + "subtitle": "비밀번호를 설정하여 PDF를 보호합니다." + }, + "sanitizePdf": { + "name": "PDF 정리", + "subtitle": "메타데이터, 주석, 스크립트 등을 제거합니다." + }, + "decryptPdf": { + "name": "PDF 복호화", + "subtitle": "비밀번호를 제거하여 PDF 잠금을 해제합니다." + }, + "flattenPdf": { + "name": "PDF 평탄화", + "subtitle": "양식 필드와 주석을 편집 불가능하게 만듭니다." + }, + "removeMetadata": { + "name": "메타데이터 제거", + "subtitle": "PDF에서 숨겨진 데이터를 삭제합니다." + }, + "changePermissions": { + "name": "권한 변경", + "subtitle": "PDF의 사용자 권한을 설정하거나 변경합니다." + }, + "odtToPdf": { + "name": "ODT를 PDF로", + "subtitle": "ODT(OpenDocument 텍스트) 파일을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "ODT 파일", + "convertButton": "PDF로 변환" + }, + "csvToPdf": { + "name": "CSV를 PDF로", + "subtitle": "CSV 스프레드시트 파일을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "CSV 파일", + "convertButton": "PDF로 변환" + }, + "rtfToPdf": { + "name": "RTF를 PDF로", + "subtitle": "RTF(서식 있는 텍스트) 문서를 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "RTF 파일", + "convertButton": "PDF로 변환" + }, + "wordToPdf": { + "name": "Word를 PDF로", + "subtitle": "Word 문서(DOCX, DOC, ODT, RTF)를 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "DOCX, DOC, ODT, RTF 파일", + "convertButton": "PDF로 변환" + }, + "excelToPdf": { + "name": "Excel을 PDF로", + "subtitle": "Excel 스프레드시트(XLSX, XLS, ODS, CSV)를 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "XLSX, XLS, ODS, CSV 파일", + "convertButton": "PDF로 변환" + }, + "powerpointToPdf": { + "name": "PowerPoint를 PDF로", + "subtitle": "PowerPoint 프레젠테이션(PPTX, PPT, ODP)을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "PPTX, PPT, ODP 파일", + "convertButton": "PDF로 변환" + }, + "markdownToPdf": { + "name": "Markdown을 PDF로", + "subtitle": "Markdown을 작성하거나 붙여넣고 깔끔하게 서식이 적용된 PDF로 내보냅니다.", + "paneMarkdown": "Markdown", + "panePreview": "미리보기", + "btnUpload": "업로드", + "btnSyncScroll": "스크롤 동기화", + "btnSettings": "설정", + "btnExportPdf": "PDF 내보내기", + "settingsTitle": "Markdown 설정", + "settingsPreset": "프리셋", + "presetDefault": "기본 (GFM 스타일)", + "presetCommonmark": "CommonMark (엄격)", + "presetZero": "최소 (기능 없음)", + "settingsOptions": "Markdown 옵션", + "optAllowHtml": "HTML 태그 허용", + "optBreaks": "줄바꿈을
로 변환", + "optLinkify": "URL을 자동으로 링크로 변환", + "optTypographer": "타이포그래퍼 (스마트 따옴표 등)" + }, + "pdfBooklet": { + "name": "PDF 소책자", + "subtitle": "양면 인쇄용으로 페이지를 재배치합니다. 접고 스테이플러로 철하면 소책자가 됩니다.", + "howItWorks": "이용 방법:", + "step1": "PDF 파일을 업로드합니다.", + "step2": "페이지가 소책자 순서로 재배치됩니다.", + "step3": "양면 인쇄 후 짧은 면을 기준으로 접어 철합니다.", + "paperSize": "용지 크기", + "orientation": "방향", + "portrait": "세로", + "landscape": "가로", + "pagesPerSheet": "한 면당 페이지 수", + "createBooklet": "소책자 만들기", + "processing": "처리 중...", + "pageCount": "필요 시 페이지 수가 4의 배수로 맞춰집니다." + }, + "xpsToPdf": { + "name": "XPS를 PDF로", + "subtitle": "XPS/OXPS 문서를 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "XPS, OXPS 파일", + "convertButton": "PDF로 변환" + }, + "mobiToPdf": { + "name": "MOBI를 PDF로", + "subtitle": "MOBI 전자책을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "MOBI 파일", + "convertButton": "PDF로 변환" + }, + "epubToPdf": { + "name": "EPUB를 PDF로", + "subtitle": "EPUB 전자책을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "EPUB 파일", + "convertButton": "PDF로 변환" + }, + "fb2ToPdf": { + "name": "FB2를 PDF로", + "subtitle": "FictionBook(FB2) 전자책을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "FB2 파일", + "convertButton": "PDF로 변환" + }, + "cbzToPdf": { + "name": "CBZ를 PDF로", + "subtitle": "만화 아카이브(CBZ/CBR)를 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "CBZ, CBR 파일", + "convertButton": "PDF로 변환" + }, + "wpdToPdf": { + "name": "WPD를 PDF로", + "subtitle": "WordPerfect(WPD) 문서를 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "WPD 파일", + "convertButton": "PDF로 변환" + }, + "wpsToPdf": { + "name": "WPS를 PDF로", + "subtitle": "WPS Office 문서를 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "WPS 파일", + "convertButton": "PDF로 변환" + }, + "xmlToPdf": { + "name": "XML을 PDF로", + "subtitle": "XML 문서를 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "XML 파일", + "convertButton": "PDF로 변환" + }, + "pagesToPdf": { + "name": "Pages를 PDF로", + "subtitle": "Apple Pages 문서를 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "Pages 파일", + "convertButton": "PDF로 변환" + }, + "odgToPdf": { + "name": "ODG를 PDF로", + "subtitle": "ODG(OpenDocument 그래픽) 파일을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "ODG 파일", + "convertButton": "PDF로 변환" + }, + "odsToPdf": { + "name": "ODS를 PDF로", + "subtitle": "ODS(OpenDocument 스프레드시트) 파일을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "ODS 파일", + "convertButton": "PDF로 변환" + }, + "odpToPdf": { + "name": "ODP를 PDF로", + "subtitle": "ODP(OpenDocument 프레젠테이션) 파일을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "ODP 파일", + "convertButton": "PDF로 변환" + }, + "pubToPdf": { + "name": "PUB를 PDF로", + "subtitle": "Microsoft Publisher(PUB) 파일을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "PUB 파일", + "convertButton": "PDF로 변환" + }, + "vsdToPdf": { + "name": "VSD를 PDF로", + "subtitle": "Microsoft Visio(VSD, VSDX) 파일을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "VSD, VSDX 파일", + "convertButton": "PDF로 변환" + }, + "psdToPdf": { + "name": "PSD를 PDF로", + "subtitle": "Adobe Photoshop(PSD) 파일을 PDF로 변환합니다. 여러 파일 지원.", + "acceptedFormats": "PSD 파일", + "convertButton": "PDF로 변환" + }, + "pdfToSvg": { + "name": "PDF를 SVG로", + "subtitle": "PDF의 각 페이지를 SVG(벡터 그래픽)로 변환하여 어떤 크기에서도 선명하게 볼 수 있습니다." + }, + "extractTables": { + "name": "PDF 표 추출", + "subtitle": "PDF에서 표를 추출하여 CSV, JSON, Markdown으로 내보냅니다." + }, + "pdfToCsv": { + "name": "PDF를 CSV로", + "subtitle": "PDF에서 표를 추출하여 CSV로 변환합니다." + }, + "pdfToExcel": { + "name": "PDF를 Excel로", + "subtitle": "PDF에서 표를 추출하여 Excel(XLSX)로 변환합니다." + }, + "pdfToText": { + "name": "PDF를 텍스트로", + "subtitle": "PDF에서 텍스트를 추출하여 텍스트 파일(.txt)로 저장합니다. 여러 파일 지원.", + "note": "이 도구는 디지털로 생성된 PDF에서만 작동합니다. 스캔 문서나 이미지 기반 PDF는 OCR PDF 도구를 사용해 주세요.", + "convertButton": "텍스트 추출" + }, + "digitalSignPdf": { + "name": "PDF 디지털 서명", + "pageTitle": "PDF 디지털 서명 - 암호화 서명 추가 | BentoPDF", + "subtitle": "X.509 인증서를 사용하여 PDF에 암호화 디지털 서명을 추가합니다. PKCS#12(.pfx, .p12) 및 PEM 형식을 지원합니다. 개인키는 브라우저 밖으로 나가지 않습니다.", + "certificateSection": "인증서", + "uploadCert": "인증서 업로드 (.pfx, .p12)", + "certPassword": "인증서 비밀번호", + "certPasswordPlaceholder": "인증서 비밀번호 입력", + "certInfo": "인증서 정보", + "certSubject": "소유자", + "certIssuer": "발급자", + "certValidity": "유효 기간", + "signatureDetails": "서명 세부 정보 (선택 사항)", + "reason": "서명 사유", + "reasonPlaceholder": "예: 이 문서를 승인합니다", + "location": "위치", + "locationPlaceholder": "예: 서울, 대한민국", + "contactInfo": "연락처", + "contactPlaceholder": "예: email@example.com", + "applySignature": "디지털 서명 적용", + "successMessage": "PDF 서명이 완료되었습니다! 모든 PDF 뷰어에서 서명을 확인할 수 있습니다." + }, + "validateSignaturePdf": { + "name": "PDF 서명 검증", + "pageTitle": "PDF 서명 검증 - 디지털 서명 확인 | BentoPDF", + "subtitle": "PDF의 디지털 서명을 검증합니다. 인증서 유효성 확인, 서명자 정보 조회, 문서 무결성 확인이 가능합니다. 모든 처리는 브라우저에서 이루어집니다." + }, + "emailToPdf": { + "name": "이메일을 PDF로", + "subtitle": "이메일 파일(EML, MSG)을 PDF로 변환합니다. Outlook 내보내기 및 표준 이메일 형식을 지원합니다.", + "acceptedFormats": "EML, MSG 파일", + "convertButton": "PDF로 변환" + }, + "fontToOutline": { + "name": "폰트 윤곽선 변환", + "subtitle": "모든 폰트를 벡터 윤곽선으로 변환하여 어떤 기기에서든 동일하게 표시됩니다." + }, + "deskewPdf": { + "name": "PDF 기울기 보정", + "subtitle": "기울어진 스캔 페이지를 OpenCV로 자동 보정합니다." + }, + "pdfToWord": { + "name": "PDF를 Word로", + "subtitle": "PDF를 편집 가능한 Word 문서로 변환합니다." + }, + "extractImages": { + "name": "이미지 추출", + "subtitle": "PDF에 포함된 모든 이미지를 추출합니다." + }, + "pdfToMarkdown": { + "name": "PDF를 Markdown으로", + "subtitle": "PDF의 텍스트와 표를 Markdown 형식으로 변환합니다." + }, + "preparePdfForAi": { + "name": "AI용 PDF 준비", + "subtitle": "RAG/LLM 파이프라인을 위해 PDF를 LlamaIndex JSON으로 추출합니다." + }, + "pdfOcg": { + "name": "PDF OCG", + "subtitle": "PDF의 OCG(Optional Content Group) 레이어를 보고, 전환하고, 추가하고, 삭제합니다." + }, + "pdfToPdfa": { + "name": "PDF를 PDF/A로", + "subtitle": "장기 보관을 위해 PDF를 PDF/A로 변환합니다." + }, + "rasterizePdf": { + "name": "PDF 래스터화", + "subtitle": "PDF를 이미지 기반 PDF로 변환합니다. 레이어를 평탄화하고 선택 가능한 텍스트를 제거합니다." + }, + "pdfWorkflow": { + "name": "PDF 워크플로우 빌더", + "subtitle": "시각적 노드 편집기로 맞춤형 PDF 처리 파이프라인을 구성합니다.", + "nodes": "노드", + "searchNodes": "노드 검색...", + "run": "실행", + "clear": "지우기", + "save": "저장", + "load": "불러오기", + "export": "내보내기", + "import": "가져오기", + "ready": "준비 완료", + "settings": "설정", + "processing": "처리 중...", + "saveTemplate": "템플릿 저장", + "templateName": "템플릿 이름", + "templatePlaceholder": "예: 청구서 워크플로우", + "cancel": "취소", + "loadTemplate": "템플릿 불러오기", + "noTemplates": "저장된 템플릿이 없습니다.", + "ok": "확인", + "workflowCompleted": "워크플로우가 완료되었습니다", + "errorDuringExecution": "실행 중 오류 발생", + "addNodeError": "워크플로우를 실행하려면 노드를 하나 이상 추가하세요.", + "needInputOutput": "워크플로우를 실행하려면 입력 노드와 출력 노드가 각각 하나 이상 필요합니다.", + "enterName": "이름을 입력해 주세요.", + "templateExists": "같은 이름의 템플릿이 이미 있습니다.", + "templateSaved": "템플릿 \"{{name}}\"이(가) 저장되었습니다.", + "templateLoaded": "템플릿 \"{{name}}\"을(를) 불러왔습니다.", + "failedLoadTemplate": "템플릿을 불러오지 못했습니다.", + "noSettings": "이 노드에는 설정할 항목이 없습니다.", + "advancedSettings": "고급 설정" + } +} diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json new file mode 100644 index 0000000..09d487d --- /dev/null +++ b/public/locales/nl/common.json @@ -0,0 +1,366 @@ +{ + "nav": { + "home": "Thuis", + "about": "Over", + "contact": "Contact", + "licensing": "Licentie", + "allTools": "Alle Tools", + "openMainMenu": "Hoofdmenu openen", + "language": "Taal" + }, + "donation": { + "message": "Vind je BentoPDF geweldig? Help ons het gratis en open source te houden!", + "button": "Donatie" + }, + "hero": { + "title": "De", + "pdfToolkit": "PDF Toolkit", + "builtForPrivacy": "gemaakt voor privacy", + "noSignups": "Geen aanmelding", + "unlimitedUse": "Onbegrenst gebruik", + "worksOffline": "Werkt offline", + "startUsing": "Direct aan de slag" + }, + "usedBy": { + "title": "Gebruikt door bedrijven en personen werkzaam bij" + }, + "features": { + "title": "Waarom", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "Geen aanmelding", + "description": "Direct aan de slag, zonder account of e-mails." + }, + "noUploads": { + "title": "Geen uploads", + "description": "100% client-side, je bestanden blijven op jouw apparaat." + }, + "foreverFree": { + "title": "Voor altijd gratis", + "description": "Alle tools, geen proef, geen betaalmuur." + }, + "noLimits": { + "title": "Geen beperkingen", + "description": "Gebruik het zoveel je wilt, geen verborgen limiet." + }, + "batchProcessing": { + "title": "Reeksen verwerken", + "description": "Verwerk in een keer een onbeperkt aantal PDF's." + }, + "lightningFast": { + "title": "Bliksemsnel", + "description": "Verwerk PDF's direct, zonder wachten of vertraging." + } + }, + "tools": { + "title": "Aan de slag met", + "toolsLabel": "Tools", + "subtitle": "Klik een tool om de bestandslader te openen", + "searchPlaceholder": "Zoek een tool (bijv. 'splitsen', 'organiseren', ...)", + "backToTools": "Terug naar Tools", + "firstLoadNotice": "De eerste keer duurt het even om onze conversiemachine te laden. Daarna gaat alles meteen." + }, + "upload": { + "clickToSelect": "Klik om een bestand te selecteren", + "orDragAndDrop": "of sleep er een hierheen", + "pdfOrImages": "PDF's of afbeeldingen", + "filesNeverLeave": "Je bestanden blijven op jouw apparaat.", + "addMore": "Meer bestanden toevoegen", + "clearAll": "Alles wissen", + "clearFiles": "Bestanden wissen", + "hints": { + "singlePdf": "Een enkel PDF-bestand", + "pdfFile": "PDF-bestand", + "multiplePdfs2": "Meerdere PDF-bestanden (minimaal 2)", + "bmpImages": "BMP-afbeeldingen", + "oneOrMorePdfs": "Een of meer PDF-bestanden", + "pdfDocuments": "PDF-documenten", + "oneOrMoreCsv": "Een of meer CSV-bestanden", + "multiplePdfsSupported": "Meerdere PDF-bestanden ondersteund", + "singleOrMultiplePdfs": "Enkel of meerdere PDF-bestanden ondersteund", + "singlePdfFile": "Enkel PDF-bestand", + "pdfWithForms": "PDF-bestand met formuliervelden", + "heicImages": "HEIC/HEIF-afbeeldingen", + "jpgImages": "JPG, JPEG, JP2, JPX afbeeldingen", + "pdfsOrImages": "PDF's of afbeeldingen", + "oneOrMoreOdt": "Een of meer ODT-bestanden", + "singlePdfOnly": "Alleen een enkel PDF-bestand", + "pdfFiles": "PDF-bestanden", + "multiplePdfs": "Meerdere PDF-bestanden", + "pngImages": "PNG-afbeeldingen", + "pdfFilesOneOrMore": "PDF-bestanden (een of meer)", + "oneOrMoreRtf": "Een of meer RTF-bestanden", + "svgGraphics": "SVG-afbeeldingen", + "tiffImages": "TIFF-afbeeldingen", + "webpImages": "WebP-afbeeldingen" + } + }, + "howItWorks": { + "title": "Hoe het werkt", + "step1": "Klik of sleep uw bestand hierheen", + "step2": "Klik op de verwerkingsknop", + "step3": "Sla uw verwerkte bestand direct op" + }, + "relatedTools": { + "title": "Gerelateerde PDF-tools" + }, + "loader": { + "processing": "Verwerken..." + }, + "alert": { + "title": "Let op", + "ok": "OK" + }, + "preview": { + "title": "Document weergeven", + "downloadAsPdf": "Downloaden als PDF", + "close": "Sluiten" + }, + "settings": { + "title": "Instellingen", + "shortcuts": "Sneltoetsen", + "preferences": "Voorkeuren", + "displayPreferences": "Voorkeuren weergeven", + "searchShortcuts": "Sneltoetsen zoeken...", + "shortcutsInfo": "Houd toetsen ingedrukt om deze in te stellen als sneltoets. Aanpassingen worden automatisch opgeslagen.", + "shortcutsWarning": "⚠️ Voorkom algemene sneltoetsen voor browsers (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N etc.) aangezien deze niet betrouwbaar kunnen functioneren.", + "import": "Importeren", + "export": "Exporteren", + "resetToDefaults": "Terugzetten naar standaard", + "fullWidthMode": "Volledige breedte", + "fullWidthDescription": "Gebruik de volledige breedte van het scherm in plaats van een gecentreerde kolom", + "settingsAutoSaved": "Instellingen worden automatisch opgeslagen", + "clickToSet": "Klik om in te stellen", + "pressKeys": "Druk toetsen...", + "warnings": { + "alreadyInUse": "Sneltoets al in gebruik", + "assignedTo": "is al toegewezen aan:", + "chooseDifferent": "Kies een andere sneltoets.", + "reserved": "Waarschuwing voor gereserveerde sneltoets", + "commonlyUsed": "wordt algemeen gebruikt voor:", + "unreliable": "Deze sneltoets werkt mogelijk niet goed of is in conflict met gedrag van browser of systeem.", + "useAnyway": "Wil je het toch gebruiken?", + "resetTitle": "Sneltoetsen terugzetten", + "resetMessage": "Weet je zeker dat je alle sneltoetsen wilt terugzetten naar standaard?

Deze actie kan niet ongedaan worden gemaakt.", + "importSuccessTitle": "Import voltooid", + "importSuccessMessage": "Sneltoetsen zijn met succes geïmporteerd!", + "importFailTitle": "Import mislukt", + "importFailMessage": "Het importeren van sneltoetsen is mislukt. Ongeldig bestandsformaat." + } + }, + "warning": { + "title": "Waarschuwing", + "cancel": "Annuleren", + "proceed": "Verder" + }, + "compliance": { + "title": "Jouw gegevens blijven op jouw appraat", + "weKeep": "Wij houden", + "yourInfoSafe": "jouw informatie veilig", + "byFollowingStandards": "volgens de volgende algemene veiligheidsstandaarden.", + "processingLocal": "Alle verwerking vindt lokaal plaats op jouw apparaat.", + "gdpr": { + "title": "AVG-naleving", + "description": "Beschermt persoonlijke gegevens en privacy van individuën binnen de Europese Unie." + }, + "ccpa": { + "title": "CCPA-naleving", + "description": "Geeft inwoners van Californië rechten over hoe hun persoonlijke gegevens worden verzameld, gebruikt en gedeeld." + }, + "hipaa": { + "title": "HIPAA-naleving", + "description": "Stelt waarborgen in voor het omgaan met gevoelige gezondheidsinformatie in het gezondheidszorgsysteem van de Verenigde Staten." + } + }, + "faq": { + "title": "Veelgestelde", + "questions": "Vragen", + "sectionTitle": "Veelgestelde vragen", + "isFree": { + "question": "Is BentoPDF echt gratis?", + "answer": "Ja, absoluut. Alle tools op BentoPDF zijn 100% gratis te gebruiken, zonder bestandslimieten, zonder aanmeldingen en zonder watermerken. Wij vinden dat iedereen toegang verdient tot eenvoudige, krachtige PDF-tools zonder betaalmuur." + }, + "areFilesSecure": { + "question": "Zijn mijn bestanden veilig? Waar worden ze verwerkt?", + "answer": "Je bestanden zijn zo veilig mogelijk omdat ze je computer nooit verlaten. Alle verwerking gebeurt direct in je webbrowser (client-side). Je bestanden worden nooit naar een server ge-upload, zodat je volledige privacy en controle over je documenten behoudt." + }, + "platforms": { + "question": "Werkt het op Mac, Windows en mobiel?", + "answer": "Ja! Omdat BentoPDF volledig in je browser werkt, werkt het op elk besturingssysteem met een moderne webbrowser, inclusief Windows, macOS, Linux, iOS en Android." + }, + "gdprCompliant": { + "question": "Is BentoPDF AVG-conform?", + "answer": "Ja. BentoPDF voldoet volledig aan de AVG. Omdat alle bestandsverwerking lokaal in je browser gebeurt en we nooit je bestanden naar een server sturen of verzamelen, hebben wij geen toegang tot je gegevens. Zo houd jij altijd de controle over je documenten." + }, + "dataStorage": { + "question": "Slaan jullie mijn bestanden op of volgen jullie die?", + "answer": "Nee. We slaan je bestanden nooit op, volgen ze niet en houden er geen logboek van bij. Alles wat je op BentoPDF doet, gebeurt in het geheugen van je browser en verdwijnt zodra je de pagina sluit. Er is geen upload, geen geschiedenislogboek en geen servers bij betrokken." + }, + "different": { + "question": "Wat maakt BentoPDF anders dan andere PDF-tools?", + "answer": "De meeste PDF-tools verlangen dat je je bestanden voor verwerking naar een server uploadt. BentoPDF doet dat nooit. Wij gebruiken veilige, moderne webtechnologie om je bestanden direct in je browser te verwerken. Dit betekent snellere prestaties, betere privacy en totale gemoedsrust." + }, + "browserBased": { + "question": "Hoe zorgt browsergebaseerde verwerking ervoor dat ik veilig blijf?", + "answer": "Omdat BentoPDF helemaal in je browser draait, blijven je bestanden altijd op je apparaat. Daardoor hoef je je geen zorgen te maken over hacks, datalekken of ongeoorloofde toegang. Je bestanden zijn altijd van jou." + }, + "analytics": { + "question": "Gebruikt BentoPDF cookies of analytics om mij te volgen?", + "answer": "We geven om je privacy. BentoPDF houdt geen persoonlijke gegevens bij. We gebruiken Simple Analytics alleen om anonieme bezoekersaantallen te zien. Dit betekent dat we kunnen weten hoeveel mensen onze site bezoeken, maar we weten nooit wie jij bent. Simple Analytics is volledig AVG-conform en respecteert je privacy." + } + }, + "testimonials": { + "title": "Wat onze", + "users": "Gebruikers", + "say": "Vertellen" + }, + "support": { + "title": "Vind je mijn werk leuk?", + "description": "BentoPDF is een project van passie, gemaakt om iedereen een gratis, privé en krachtig PDF-gereedschap te bieden. Als je het handig vindt, overweeg dan om de ontwikkeling te steunen. Elke koffie helpt!", + "buyMeCoffee": "Koop een kopje koffie voor me" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Alle rechten voorbehouden.", + "version": "Versie", + "company": "Bedrijf", + "aboutUs": "Over ons", + "faqLink": "Veelgestelde vragen", + "contactUs": "Contact", + "legal": "Juridisch", + "termsAndConditions": "Algemene voorwaarden", + "privacyPolicy": "Privacybeleid", + "followUs": "Volgen" + }, + "merge": { + "title": "PDF's samenvoegen", + "description": "Combineer hele bestanden, of selecteer specifieke pagina's om te samen te voegen tot een nieuw document.", + "fileMode": "Bestandsmodus", + "pageMode": "Paginamodus", + "howItWorks": "Hoe het werkt:", + "fileModeInstructions": [ + "Klik en sleep het pictogram om de volgorde van de bestanden te wijzigen.", + "In het vak \"Pagina's\" voor elk bestand kan je reeksen opgeven (bijv. \"1-3, 5\") om alleen die pagina's samen te voegen.", + "Laat het vak \"Pagina's\" leeg om alle pagina's in het bestand op te nemen." + ], + "pageModeInstructions": [ + "Alle pagina's van je PDF's worden hieronder weergegeven.", + "Sleep afzonderlijke pagina-miniaturen in de gewenste volgorde voor je nieuwe bestand." + ], + "mergePdfs": "PDF's samenvoegen" + }, + "common": { + "page": "Pagina", + "pages": "Pagina's", + "of": "van", + "download": "Downloaden", + "cancel": "Annuleren", + "save": "Opslaan", + "delete": "Verwijderen", + "edit": "Bewerken", + "add": "Toevoegen", + "remove": "Verwijderen", + "loading": "Laden...", + "error": "Fout", + "success": "Success", + "file": "Bestand", + "files": "Bestanden", + "close": "Sluiten" + }, + "about": { + "hero": { + "title": "Wij vinden dat PDF-tools", + "subtitle": "snel, privé en gratis moeten zijn.", + "noCompromises": "Geen compromissen." + }, + "mission": { + "title": "Onze missie", + "description": "Ons doel is om de meest complete PDF-toolbox te bieden die je privacy respecteert en nooit om betaling vraagt. Wij geloven dat essentiële documententools voor iedereen, overal en zonder obstakels toegankelijk moeten zijn." + }, + "philosophy": { + "label": "Onze kernfilosofie", + "title": "Privacy First. Altijd.", + "description": "In een tijdperk waarin data een handelswaar is, doen wij het net even anders. Alle verwerking voor BentoPDF-tools gebeurt lokaal in je browser. Dat betekent dat je bestanden nooit oop onze servers terechtkomen, dat wij je documenten nooit zien en niets volgen van wat je doet. Je documenten blijven volledig privé, punt. Het is niet zomaar een functie; het is onze basis." + }, + "whyBentopdf": { + "title": "Waarom", + "speed": { + "title": "Gemaakt voor snelheid", + "description": "Geen wachttijd voor uploads of downloads naar een server. Door bestanden direct in je browser te verwerken met moderne webtechnologieën zoals WebAssembly, bieden we ongeëvenaarde snelheid voor al onze tools." + }, + "free": { + "title": "Volledig gratis", + "description": "Geen proefversies, geen abonnementen, geen verborgen kosten en geen \"premium\" functies die vworden achtergehouden. Wij vinden dat krachtige PDF-tools een openbare dienst moeten zijn, geen winstmachine." + }, + "noAccount": { + "title": "Geen account vereist", + "description": "Begin meteen met het gebruiken van een tool. Je e-mail, wachtwoord of persoonlijke gegevens hebben we niet nodig. Je workflow kan soepel en anoniem zijn." + }, + "openSource": { + "title": "De geest van Open Source", + "description": "Gebouwd met transparantie in gedachten. We maken gebruik van geweldige open-source bibliotheken zoals PDF-lib en PDF.js en geloven in de communitygedreven inspanning om krachtige tools voor iedereen toegankelijk te maken." + } + }, + "cta": { + "title": "Klaar om te beginnen?", + "description": "Sluit je aan bij duizenden gebruikers die BentoPDF vertrouwen voor hun dagelijkse documentbehoeften. Ervaar zelf het verschil dat privacy en prestaties kunnen maken.", + "button": "Ontdek alle tools" + } + }, + "contact": { + "title": "Neem contact op", + "subtitle": "We horen graag van je. Of je nu een vraag, feedback of een verzoek voor een functie hebt, aarzel dan niet om contact met ons op te nemen.", + "email": "Je kunt ons rechtstreeks bereiken via e-mail op:" + }, + "licensing": { + "title": "Licentie voor", + "subtitle": "Kies de licentie die bij je past." + }, + "multiTool": { + "uploadPdfs": "PDF's laden", + "upload": "Laden", + "addBlankPage": "Blanco pagina toevoegen", + "edit": "Bewerken:", + "undo": "Ongedaan maken", + "redo": "Opnieuw", + "reset": "Terugzetten", + "selection": "Selectie:", + "selectAll": "Alles selecteren", + "deselectAll": "Selectie opheffen", + "rotate": "Roteren:", + "rotateLeft": "Linksom", + "rotateRight": "Rechtsom", + "transform": "Transformeren:", + "duplicate": "Dupliceren", + "split": "Splitsen", + "clear": "Wissen:", + "delete": "Verwijderen", + "download": "Laden:", + "downloadSelected": "Selectie laden", + "exportPdf": "PDF exporteren", + "uploadPdfFiles": "PDF-bestanden selecteren", + "dragAndDrop": "Klik om een bestand te selecteren, of sleep er een hierheen", + "selectFiles": "Bestanden selecteren", + "renderingPages": "Pagina's vewerken...", + "actions": { + "duplicatePage": "Deze pagina dupliceren", + "deletePage": "Deze pagina verwijderen", + "insertPdf": "PDF achter deze pagina invoegen", + "toggleSplit": "Splitsing maken na deze pagina" + }, + "pleaseWait": "Even geduld", + "pagesRendering": "Pagina's worden nog verwerkt. Even geduld...", + "noPagesSelected": "Geen pagina's geselecteerd", + "selectOnePage": "Kies tenminste een pagina om te laden.", + "noPages": "Geen pagina's", + "noPagesToExport": "Er zijn geen pagina's om te exporteren.", + "renderingTitle": "Paginavoorbeeld verwerken", + "errorRendering": "Generatie van pagina-miniaturen is mislukt", + "error": "Fout", + "failedToLoad": "Laden is mislukt" + }, + "simpleMode": { + "title": "PDF-tools", + "subtitle": "Selecteer een tool om te beginnen" + } +} + diff --git a/public/locales/nl/tools.json b/public/locales/nl/tools.json new file mode 100644 index 0000000..c50eeca --- /dev/null +++ b/public/locales/nl/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "Populaire Tools", + "editAnnotate": "Bewerken & Annoteren", + "convertToPdf": "Converteren naar PDF", + "convertFromPdf": "Converteren van PDF", + "organizeManage": "Organiseren & Beheren", + "optimizeRepair": "Optimaliseren & Repareren", + "securePdf": "PDF beveiligen" + }, + "pdfMultiTool": { + "name": "PDF Multi-tool", + "subtitle": "Samenvoegen, Splitsen, Organiseren, Verwijderen, Roteren, Blanco pagina's toevoegen, Extraheren and Dupliceren in een enkele werkomgeving." + }, + "mergePdf": { + "name": "PDF Samenvoegen", + "subtitle": "Meerdere PDFs combineren tot een enkel bestand. Bladwijzers behouden." + }, + "splitPdf": { + "name": "PDF Splitsen", + "subtitle": "Een reeks pagina's opslaan in een nieuwe PDF." + }, + "compressPdf": { + "name": "PDF Comprimeren", + "subtitle": "De bestandsgrootte van je PDF verkleinen.", + "algorithmLabel": "Compressie-algoritme", + "condense": "Condense (Aanbevolen)", + "photon": "Photon (Voor PDF's met veel foto's)", + "condenseInfo": "Condense gebruikt geavanceerde compressie: verwijdert overbodige onderdelen, optimaliseert afbeeldingen en kiest alleen de benodigde letters uit fonts. Ideaal voor de meeste PDF's.", + "photonInfo": "Photon zet pagina's om in afbeeldingen. Handig voor PDF's met veel foto's of gescande documenten.", + "photonWarning": "Let op: De tekst kan dan niet meer geselecteerd worden en links werken niet meer.", + "levelLabel": "Compressieniveau", + "light": "Licht (Kwaliteit behouden)", + "balanced": "Gebalanceerd (Aanbevolen)", + "aggressive": "Agressief (Kleinere bestanden)", + "extreme": "Extreem (Maximale compressie)", + "grayscale": "Converteren naar grijswaarden", + "grayscaleHint": "Vermindert de bestandsgrootte door kleurinformatie te verwijderen", + "customSettings": "Aangepaste instellingen", + "customSettingsHint": "Compressie-instellingen verfijnen:", + "outputQuality": "Uitvoerkwaliteit", + "resizeImagesTo": "Formaat van afbeeldingen aanpassen naar", + "onlyProcessAbove": "Alleen verwerken boven", + "removeMetadata": "Metagegevens wissen", + "subsetFonts": "Subset-lettertypen (ongebruikte tekens verwijderen)", + "removeThumbnails": "Ingesloten miniaturen verwijderen", + "compressButton": "PDF Comprimeren" + }, + "pdfEditor": { + "name": "PDF Editor", + "subtitle": "PDFs annoteren, markeren, redigeren, commentaar toevoegen, vormen/afbeelding toevoegen, doorzoeken and weergeven." + }, + "jpgToPdf": { + "name": "JPG naar PDF", + "subtitle": "Een of meer JPG-afbeeldingen opslaan als PDF." + }, + "signPdf": { + "name": "PDF Ondertekenen", + "subtitle": "Je handtekening tekenen, typen, of invoegen." + }, + "cropPdf": { + "name": "PDF Bijsnijden", + "subtitle": "De marges aanpassen van alle pagina's in je PDF." + }, + "extractPages": { + "name": "Pagina's Extraheren", + "subtitle": "Een selectie van pagina's opslaan als nieuw bestand." + }, + "duplicateOrganize": { + "name": "Dupliceren & Organiseren", + "subtitle": "Pagina's dupliceren, ordenen en verwijderen." + }, + "deletePages": { + "name": "Pagina's Verwijderen", + "subtitle": "Specifieke pagina's uit een dodocument verwijderen." + }, + "editBookmarks": { + "name": "Bladwijzers Bewerken", + "subtitle": "PDF-bladwijzers toevoegen, bewerken, importeren, verwijderen and extraheren." + }, + "tableOfContents": { + "name": "Inhoudsopgave", + "subtitle": "Een inhoudsopgave genereren van PDF-bladwijzers." + }, + "pageNumbers": { + "name": "Paginanummers", + "subtitle": "Paginanummers aan je document toevoegen." + }, + "batesNumbering": { + "name": "Bates-nummering", + "subtitle": "Voeg opeenvolgende Bates-nummers toe aan een of meer PDF-bestanden." + }, + "addWatermark": { + "name": "Watermerk toevoegen", + "subtitle": "Tekst of een afbeelding over de pagina's van je PDF stempelen.", + "applyToAllPages": "Op alle pagina's toepassen" + }, + "headerFooter": { + "name": "Koptekst & Voettekst", + "subtitle": "Tekst boven- of onderaan de pagina's toevoegen." + }, + "invertColors": { + "name": "Kleuren Omkeren", + "subtitle": "Maak een \"donkere modus\"-versie van je PDF." + }, + "scannerEffect": { + "name": "Scannereffect", + "subtitle": "Laat je PDF eruitzien als een gescand document.", + "scanSettings": "Scaninstellingen", + "colorspace": "Kleurruimte", + "gray": "Grijswaarden", + "border": "Rand", + "rotate": "Roteren", + "rotateVariance": "Rotatievariatie", + "brightness": "Helderheid", + "contrast": "Contrast", + "blur": "Vervaging", + "noise": "Ruis", + "yellowish": "Geelheid", + "resolution": "Resolutie", + "processButton": "Scannereffect toepassen" + }, + "adjustColors": { + "name": "Kleuren aanpassen", + "subtitle": "Pas helderheid, contrast, verzadiging en meer aan in je PDF.", + "colorSettings": "Kleurinstellingen", + "brightness": "Helderheid", + "contrast": "Contrast", + "saturation": "Verzadiging", + "hueShift": "Tintrotatie", + "temperature": "Temperatuur", + "tint": "Tint", + "gamma": "Gamma", + "sepia": "Sepia", + "processButton": "Kleuraanpassingen toepassen" + }, + "backgroundColor": { + "name": "Achtergrondkleur", + "subtitle": "Wijzig de achtergrondkleur van je PDF." + }, + "changeTextColor": { + "name": "Tekstkleur", + "subtitle": "Wijzig de tekstkleur van je PDF." + }, + "addStamps": { + "name": "Stempels Toevoegen", + "subtitle": "Voeg afbeeldingsstempels toe aan je PDF met de werkbalk Annotatie.", + "usernameLabel": "Gebruikersnaam stempelen", + "usernamePlaceholder": "Voer je naam in (voor stempels)", + "usernameHint": "Deze naam verschijnt op stempels die je aanmaakt." + }, + "removeAnnotations": { + "name": "Annotaties Verwijderen", + "subtitle": "Commentaar, markeringen en links verwijderen." + }, + "pdfFormFiller": { + "name": "PDF-formulier Vullen", + "subtitle": "Vul formulieren in vanuit de browser, incl. ondersteuning voor XFA-formulieren." + }, + "createPdfForm": { + "name": "PDF-formulier Aanmaken", + "subtitle": "Maak invulbare PDF-formulieren aan met drag-and-drop tekstvelden." + }, + "removeBlankPages": { + "name": "Blanco pagina's Verwijderen", + "subtitle": "Automatisch blanco pagina's detecteren en verwijderen.", + "sensitivityHint": "Hoger = strenger, alleen volledig lege pagina's. Lager = staat pagina's met enige inhoud toe." + }, + "imageToPdf": { + "name": "Afbeelding naar PDF", + "subtitle": "Converteer JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP naar PDF." + }, + "pngToPdf": { + "name": "PNG naar PDF", + "subtitle": "Maak een PDF van een of meer PNG-afbeeldingen." + }, + "webpToPdf": { + "name": "WebP naar PDF", + "subtitle": "Maak een PDF van een of meer WebP-afbeeldingen." + }, + "svgToPdf": { + "name": "SVG naar PDF", + "subtitle": "Maak een PDF van een of meer SVG-afbeeldingen." + }, + "bmpToPdf": { + "name": "BMP naar PDF", + "subtitle": "Maak een PDF van een of meer BMP-afbeeldingen." + }, + "heicToPdf": { + "name": "HEIC naar PDF", + "subtitle": "Maak een PDF van een of meer HEIC-afbeeldingen." + }, + "tiffToPdf": { + "name": "TIFF naar PDF", + "subtitle": "Maak een PDF van een of meer TIFF-afbeeldingen." + }, + "textToPdf": { + "name": "Tekst naar PDF", + "subtitle": "Converteer een bestand met platte tekst naar een PDF." + }, + "jsonToPdf": { + "name": "JSON naar PDF", + "subtitle": "Converteer JSON-bestanden naar PDF-formaat." + }, + "pdfToJpg": { + "name": "PDF naar JPG", + "subtitle": "Converteer elke PDF-pagina naar een JPG-afbeelding." + }, + "pdfToPng": { + "name": "PDF naar PNG", + "subtitle": "Converteer elke PDF-pagina naar een PNG-afbeelding." + }, + "pdfToWebp": { + "name": "PDF naar WebP", + "subtitle": "Converteer elke PDF-pagina naar een WebP-afbeelding." + }, + "pdfToBmp": { + "name": "PDF naar BMP", + "subtitle": "Converteer elke PDF-pagina naar een BMP-afbeelding." + }, + "pdfToTiff": { + "name": "PDF naar TIFF", + "subtitle": "Converteer elke PDF-pagina naar een TIFF-afbeelding." + }, + "pdfToGreyscale": { + "name": "PDF naar Grijswaarden", + "subtitle": "Converteer alle kleuren naar zwart-wit." + }, + "pdfToJson": { + "name": "PDF naar JSON", + "subtitle": "Converteer PDF-bestanden naar JSON-formaat." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Maak een PDF doorzoekbaar en kopieerbaar." + }, + "alternateMix": { + "name": "Pagina's Afwisselen & Mixen", + "subtitle": "Voeg PDF's samen met afwisselende pagina's vanuit elke PDF. Bladwijzers behouden." + }, + "addAttachments": { + "name": "Bijlagen Toevoegen", + "subtitle": "Voeg een of meerbestanden in je PDF." + }, + "extractAttachments": { + "name": "Bijlagen Extraheren", + "subtitle": "Extraheer alle ingevoegde bestanden als ZIP uit PDF('s)." + }, + "editAttachments": { + "name": "Bijlagen Bewerken", + "subtitle": "Bijlagen in je PDF weergeven of verwijderen." + }, + "dividePages": { + "name": "Paginas Opdelen", + "subtitle": "Pagina's horizontaal of verticaal opdelen." + }, + "addBlankPage": { + "name": "Blanco pagina Toevoegen", + "subtitle": "Een blanco pagina ergens in je PDF invoegen." + }, + "reversePages": { + "name": "Paginavolgorde Omkeren", + "subtitle": "Keer de volgorde om van alle pagina's in je document." + }, + "rotatePdf": { + "name": "PDF Roteren", + "subtitle": "Roteer pagina's in stappen van 90 graden." + }, + "rotateCustom": { + "name": "Roteren met aangepaste hoek", + "subtitle": "Roteer pagina's met elke gewenste hoek." + }, + "nUpPdf": { + "name": "N+ PDF", + "subtitle": "Arrangeer meerdere pagina's op een enkel blad." + }, + "combineToSinglePage": { + "name": "Combineren tot Enkele pagina", + "subtitle": "Plak alle pagina's aan elkaar tot een doorlopende rol." + }, + "viewMetadata": { + "name": "Metadata Weergeven", + "subtitle": "Bekijk de verborgen eigenschappen van je PDF." + }, + "editMetadata": { + "name": "Metadata Bewerken", + "subtitle": "Auteur, titel en andere eigenschappen aanpassen." + }, + "pdfsToZip": { + "name": "PDF naar ZIP", + "subtitle": "Archiveer meerdere PDF's in een ZIP-bestand." + }, + "comparePdfs": { + "name": "PDF's Vergelijken", + "subtitle": "Twee PDF's zij-aan-zij vergelijken." + }, + "posterizePdf": { + "name": "PDF-Poster", + "subtitle": "Deel een grote pagina op in meerdere kleinere pagina's." + }, + "fixPageSize": { + "name": "Paginagrootte Fiksen", + "subtitle": "Alle pagina's aanpassen tot een uniform formaat." + }, + "linearizePdf": { + "name": "PDF Lineariseren", + "subtitle": "Optimaliseer een PDF voor snelle webweergave." + }, + "pageDimensions": { + "name": "Pagina Afmetingen", + "subtitle": "Maak een analyse van paginagrootte, oriëntatie en eenheden." + }, + "removeRestrictions": { + "name": "Beperkingen Verwijderen", + "subtitle": "Beveiligingswachtwoord en -beperkingen verwijderen van digitaal ondertekende PDF-bestanden." + }, + "repairPdf": { + "name": "PDF Repareren", + "subtitle": "Gegevens herstellen van beschadigde PDF-bestanden." + }, + "encryptPdf": { + "name": "PDF Versleutelen", + "subtitle": "Vegrendel je PDF door toevoeging van een toegangswachtwoord." + }, + "sanitizePdf": { + "name": "PDF Opschonen", + "subtitle": "Metadata, annotaties, scripts en meer verwijderen." + }, + "decryptPdf": { + "name": "PDF Ontgrendelen", + "subtitle": "PDF ontgrendelen door het verwijderen van de wachtwoordbeveiliging." + }, + "flattenPdf": { + "name": "PDF Platmaken", + "subtitle": "Maak formuliervelden en annotaties onbewerkbaar." + }, + "removeMetadata": { + "name": "Metadata Verwijderen", + "subtitle": "Verwijder verborgen gegevens uit je PDF." + }, + "changePermissions": { + "name": "Rechten Aanpassen", + "subtitle": "Gebruikersrechten van een PDF instellen of aanpasssen." + }, + "odtToPdf": { + "name": "ODT naar PDF", + "subtitle": "Converteer OpenDocument-tekstbestanden naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "ODT-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "csvToPdf": { + "name": "CSV naar PDF", + "subtitle": "Converteer CSV-spreadsheetbestanden naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "CSV-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "rtfToPdf": { + "name": "RTF naar PDF", + "subtitle": "Converteer Rich Text Format-documenten naar PDF. Ondersteunt meerdere bestanden.", + "acceptedFormats": "RTF-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "wordToPdf": { + "name": "Word naar PDF", + "subtitle": "Converteer Word-documenten (DOCX, DOC, ODT, RTF) naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "DOCX-, DOC-, ODT-, RTF-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "excelToPdf": { + "name": "Excel naar PDF", + "subtitle": "Converteer Excel-spreadsheets (XLSX, XLS, ODS, CSV) naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "XLSX-, XLS-, ODS-, CSV-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint naar PDF", + "subtitle": "Converteer PowerPoint-presentaties (PPTX, PPT, ODP) naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "PPTX-, PPT-, ODP-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "markdownToPdf": { + "name": "Markdown naar PDF", + "subtitle": "Schrijf of plak Markdown en zet het om in een opgemaakte PDF.", + "paneMarkdown": "Markdown", + "panePreview": "Voorbeeld", + "btnUpload": "Laden", + "btnSyncScroll": "Synchroon bladeren", + "btnSettings": "Instellingen", + "btnExportPdf": "PDF exporteren", + "settingsTitle": "Markdown-instellingen", + "settingsPreset": "Voorinstelling", + "presetDefault": "Standaard (als GFM)", + "presetCommonmark": "CommonMark (strict)", + "presetZero": "Minimaal (geen functies)", + "settingsOptions": "Markdown-opties", + "optAllowHtml": "HTML-tags toestaan", + "optBreaks": "Newlines omzetten naar
", + "optLinkify": "URL's autom. omzetten naar links", + "optTypographer": "Typograaf (slimme aanhalingstekens, etc.)" + }, + "pdfBooklet": { + "name": "PDF Boekje", + "subtitle": "Herschik de pagina's voor dubbelzijdig boekjeprinten. Vouw en niet ze om een boekje te maken.", + "howItWorks": "Het werkt zo:", + "step1": "Laad een PDF-bestand.", + "step2": "Pagina's worden in brochurevolgorde gerangschikt.", + "step3": "Dubbelzijdig afdrukken, omdraaien langs de korte kant, vouwen en nieten.", + "paperSize": "Paperformaat", + "orientation": "Oriëntatie", + "portrait": "Staand", + "landscape": "Liggend", + "pagesPerSheet": "Pagina's per vel", + "createBooklet": "Boekje aanmaken", + "processing": "Verwerken...", + "pageCount": "Het aantal pagina's wordt indien nodig op een meervoud van 4 afgerond." + }, + "xpsToPdf": { + "name": "XPS naar PDF", + "subtitle": "Converteer XPS/OXPS-documenten naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "XPS-, OXPS-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "mobiToPdf": { + "name": "MOBI naar PDF", + "subtitle": "Converteer MOBI e-books naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "MOBI-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "epubToPdf": { + "name": "EPUB naar PDF", + "subtitle": "Zet EPUB-e-books om naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "EPUB-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "fb2ToPdf": { + "name": "FB2 naar PDF", + "subtitle": "Converteer FictionBook (FB2) e-boeken naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "FB2-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "cbzToPdf": { + "name": "CBZ naar PDF", + "subtitle": "Converteer stripboekenarchieven (CBZ/CBR) naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "CBZ-, CBR-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "wpdToPdf": { + "name": "WPD naar PDF", + "subtitle": "Converteer WordPerfect-documenten (WPD) naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "WPD-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "wpsToPdf": { + "name": "WPS naar PDF", + "subtitle": "Converteer WPS Office-documenten naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "WPS-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "xmlToPdf": { + "name": "XML naar PDF", + "subtitle": "Converteer XML-documenten naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "XML-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "pagesToPdf": { + "name": "Pages naar PDF", + "subtitle": "Zet Apple Pages-documenten om naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "Pages-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "odgToPdf": { + "name": "ODG naar PDF", + "subtitle": "Converteer OpenDocument Graphics (ODG)-bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "ODG-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "odsToPdf": { + "name": "ODS naar PDF", + "subtitle": "Converteer OpenDocument Spreadsheet (ODS)-bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "ODS-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "odpToPdf": { + "name": "ODP naar PDF", + "subtitle": "Converteer OpenDocument-presentatie (ODP) bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "ODP-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "pubToPdf": { + "name": "PUB naar PDF", + "subtitle": "Converteer Microsoft Publisher (PUB) bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "PUB-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "vsdToPdf": { + "name": "VSD naar PDF", + "subtitle": "Converteer Microsoft Visio (VSD, VSDX) bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "VSD-, VSDX-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "psdToPdf": { + "name": "PSD naar PDF", + "subtitle": "Converteer Adobe Photoshop (PSD) bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.", + "acceptedFormats": "PSD-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "pdfToSvg": { + "name": "PDF naar SVG", + "subtitle": "Converteer elke pagina van een PDF-bestand naar een schaalbare vectorafbeelding (SVG) voor perfecte kwaliteit op elke grootte." + }, + "extractTables": { + "name": "PDF-tabellen extraheren", + "subtitle": "Haal tabellen uit PDF-bestanden en exporteer ze als CSV, JSON of Markdown." + }, + "pdfToCsv": { + "name": "PDF naar CSV", + "subtitle": "Haal tabellen uit een PDF en zet ze om naar CSV-formaat." + }, + "pdfToExcel": { + "name": "PDF naar Excel", + "subtitle": "Haal tabellen uit PDF en converteer ze naar Excel (XLSX) formaat." + }, + "pdfToText": { + "name": "PDF naar Text", + "subtitle": "Haal tekst uit PDF-bestanden en sla op als gewone tekst (.txt). Ondersteunt meerdere bestanden.", + "note": "Dit hulpmiddel werkt ALLEEN met digitaal gemaakte PDF's. Gebruik voor gescande documenten of op afbeeldingen gebaseerde PDF's in plaats hiervan de OCR PDF-tool.", + "convertButton": "Tekst extraheren" + }, + "digitalSignPdf": { + "name": "Digitale handtekening PDF", + "pageTitle": "Digitale handtekening PDF - Cryptografische handtekening toevoegen | BentoPDF", + "subtitle": "Voeg een cryptografische digitale handtekening toe aan je PDF met behulp van X.509-certificaten. Ondersteunt PKCS#12 (.pfx, .p12) en PEM-formaten. Je privésleutel verlaat jouw browser nooit.", + "certificateSection": "Certificaat", + "uploadCert": "Certificaat laden (.pfx, .p12)", + "certPassword": "Certificaatwachtwoord", + "certPasswordPlaceholder": "Voer het wachtwoord van het certificaat in", + "certInfo": "Certificaatinformatie", + "certSubject": "Onderwerp", + "certIssuer": "Uitgever", + "certValidity": "Geldigheid", + "signatureDetails": "Signature Details (Optioneel)", + "reason": "Reden", + "reasonPlaceholder": "bijv., Ik keur dit document goed", + "location": "Plaats", + "locationPlaceholder": "bijv., New York, USA", + "contactInfo": "Contactinformatie", + "contactPlaceholder": "bijv., email@example.com", + "applySignature": "Digitale handtekening toepassen", + "successMessage": "PDF is ondertekend! De handtekening kan in elke PDF-lezer worden geverifieerd." + }, + "validateSignaturePdf": { + "name": "PDF-handtekening valideren", + "pageTitle": "PDF-handtekening valideren - Digitale handtekeningen verifiëren | BentoPDF", + "subtitle": "Digitale handtekeningen in je PDF-bestanden verifiëren. Controleer de geldigheid van het certificaat, bekijk de gegevens van de ondertekenaar en bevestig de integriteit van het document. Alle verwerking gebeurt binnen jouw browser." + }, + "emailToPdf": { + "name": "E-mail naar PDF", + "subtitle": "Converteer e-mailbestanden (EML, MSG) naar PDF-formaat. Ondersteunt Outlook-exports en standaard e-mailformaten.", + "acceptedFormats": "EML-, MSG-bestanden", + "convertButton": "Omzetten naar PDF" + }, + "fontToOutline": { + "name": "Lettertype naar Contour", + "subtitle": "Converteer alle lettertypen naar vector-contouren voor consistente weergave over alle apparaten." + }, + "deskewPdf": { + "name": "PDF rechttrekken", + "subtitle": "Automatisch rechttrekken van scheef gescande pagina's met OpenCV." + }, + "pdfToWord": { + "name": "PDF naar Word", + "subtitle": "Converteer PDF-bestanden naar bewerkbare Word-documenten." + }, + "extractImages": { + "name": "Afbeeldingen extraheren", + "subtitle": "Extraheer alle ingesloten afbeeldingen uit uw PDF-bestanden." + }, + "pdfToMarkdown": { + "name": "PDF naar Markdown", + "subtitle": "Converteer PDF-tekst en tabellen naar Markdown-formaat." + }, + "preparePdfForAi": { + "name": "PDF voorbereiden voor AI", + "subtitle": "Extraheer PDF-inhoud als LlamaIndex JSON voor RAG/LLM-pipelines." + }, + "pdfOcg": { + "name": "PDF-lagen (OCG)", + "subtitle": "Bekijk, schakel, voeg toe en verwijder OCG-lagen in uw PDF." + }, + "pdfToPdfa": { + "name": "PDF naar PDF/A", + "subtitle": "Converteer PDF naar PDF/A voor langetermijnarchivering." + }, + "rasterizePdf": { + "name": "PDF rasteren", + "subtitle": "Converteer PDF naar beeldgebaseerde PDF. Lagen afvlakken en selecteerbare tekst verwijderen." + }, + "pdfWorkflow": { + "name": "PDF Werkproces Samenstellen", + "subtitle": "Bouw specifieke PDF-verwerkingsprocessen met een visuele knooppunten-bewerking.", + "nodes": "Knooppunten", + "searchNodes": "Knooppunten zoeken...", + "run": "Uitvoeren", + "clear": "Wissen", + "save": "Opslaan", + "load": "Laden", + "export": "Exporteren", + "import": "Importeren", + "ready": "Gereed", + "settings": "Instellingen", + "processing": "Verwerken...", + "saveTemplate": "Sjabloon opslaan", + "templateName": "Sjabloonnaam", + "templatePlaceholder": "bijv. Factuur-werkproces", + "cancel": "Annuleren", + "loadTemplate": "Sjabloon laden", + "noTemplates": "Nog geen opgeslagen sjablonen.", + "ok": "OK", + "workflowCompleted": "Werkproces voltooid", + "errorDuringExecution": "Fout tijdens uitvoering", + "addNodeError": "Voeg minimaal één knooppunt toe om het werkproces uit te voeren.", + "needInputOutput": "Het werkproces heeft tenminste een invoerknoopunt en een uitvoer-knooppunt nodig.", + "enterName": "Voer een naam in.", + "templateExists": "Er bestaat al een sjabloon met deze naam.", + "templateSaved": "Sjabloon \"{{name}}\" opgeslagen.", + "templateLoaded": "Sjabloon \"{{name}}\" geladen.", + "failedLoadTemplate": "Sjabloon laden mislukt.", + "noSettings": "Geen configureerbare instellingen voor deze node.", + "advancedSettings": "Geavanceerde instellingen" + } +} diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json new file mode 100644 index 0000000..632ebb9 --- /dev/null +++ b/public/locales/pt/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "Início", + "about": "Sobre", + "contact": "Contato", + "licensing": "Licenciamento", + "allTools": "Todas as Ferramentas", + "openMainMenu": "Abrir menu principal", + "language": "Idioma" + }, + "donation": { + "message": "Adora o BentoPDF? Ajude-nos a mantê-lo gratuito e de código aberto!", + "button": "Doar" + }, + "hero": { + "title": "O", + "pdfToolkit": "Kit de Ferramentas PDF", + "builtForPrivacy": "feito para sua privacidade", + "noSignups": "Sem Cadastros", + "unlimitedUse": "Uso Ilimitado", + "worksOffline": "Funciona Offline", + "startUsing": "Comece a Usar Agora" + }, + "usedBy": { + "title": "Usado por empresas e pessoas que trabalham em" + }, + "features": { + "title": "Por que escolher o", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "Sem Cadastro", + "description": "Comece instantaneamente, sem contas ou e-mails." + }, + "noUploads": { + "title": "Sem Uploads", + "description": "100% no navegador, seus arquivos nunca saem do seu dispositivo." + }, + "foreverFree": { + "title": "Sempre Grátis", + "description": "Todas as ferramentas, sem testes ou assinaturas." + }, + "noLimits": { + "title": "Sem Limites", + "description": "Use o quanto quiser, sem taxas escondidas." + }, + "batchProcessing": { + "title": "Processamento em Lote", + "description": "Gerencie vários PDFs de uma só vez." + }, + "lightningFast": { + "title": "Super Rápido", + "description": "Processe PDFs instantaneamente, sem esperas ou atrasos." + } + }, + "tools": { + "title": "Comece com as", + "toolsLabel": "Ferramentas", + "subtitle": "Clique em uma ferramenta para abrir o seletor de arquivos", + "searchPlaceholder": "Buscar ferramenta (ex: 'dividir', 'organizar'...)", + "backToTools": "Voltar para Ferramentas", + "firstLoadNotice": "O primeiro carregamento demora um momento enquanto baixamos nosso mecanismo de conversão. Depois disso, todos os carregamentos serão instantâneos." + }, + "upload": { + "clickToSelect": "Clique para selecionar um arquivo", + "orDragAndDrop": "ou arraste e solte", + "pdfOrImages": "PDFs ou Imagens", + "filesNeverLeave": "Seus arquivos nunca saem do seu dispositivo.", + "addMore": "Adicionar Mais Arquivos", + "clearAll": "Limpar Tudo", + "clearFiles": "Limpar arquivos", + "hints": { + "singlePdf": "Um único arquivo PDF", + "pdfFile": "Arquivo PDF", + "multiplePdfs2": "Múltiplos arquivos PDF (pelo menos 2)", + "bmpImages": "Imagens BMP", + "oneOrMorePdfs": "Um ou mais arquivos PDF", + "pdfDocuments": "Documentos PDF", + "oneOrMoreCsv": "Um ou mais arquivos CSV", + "multiplePdfsSupported": "Múltiplos arquivos PDF suportados", + "singleOrMultiplePdfs": "Um ou vários arquivos PDF suportados", + "singlePdfFile": "Um único arquivo PDF", + "pdfWithForms": "Arquivo PDF com campos de formulário", + "heicImages": "Imagens HEIC/HEIF", + "jpgImages": "Imagens JPG, JPEG, JP2, JPX", + "pdfsOrImages": "PDFs ou imagens", + "oneOrMoreOdt": "Um ou mais arquivos ODT", + "singlePdfOnly": "Apenas um arquivo PDF", + "pdfFiles": "Arquivos PDF", + "multiplePdfs": "Múltiplos arquivos PDF", + "pngImages": "Imagens PNG", + "pdfFilesOneOrMore": "Arquivos PDF (um ou mais)", + "oneOrMoreRtf": "Um ou mais arquivos RTF", + "svgGraphics": "Gráficos SVG", + "tiffImages": "Imagens TIFF", + "webpImages": "Imagens WebP" + } + }, + "loader": { + "processing": "Processando..." + }, + "alert": { + "title": "Alerta", + "ok": "OK" + }, + "preview": { + "title": "Visualização do Documento", + "downloadAsPdf": "Baixar como PDF", + "close": "Fechar" + }, + "settings": { + "title": "Configurações", + "shortcuts": "Atalhos", + "preferences": "Preferências", + "displayPreferences": "Preferências de Exibição", + "searchShortcuts": "Buscar atalhos...", + "shortcutsInfo": "Mantenha as teclas pressionadas para definir um atalho. As alterações são salvas automaticamente.", + "shortcutsWarning": "⚠️ Evite atalhos comuns do navegador (Cmd/Ctrl+W, T, N etc.), pois podem não funcionar corretamente.", + "import": "Importar", + "export": "Exportar", + "resetToDefaults": "Restaurar Padrões", + "fullWidthMode": "Modo Largura Total", + "fullWidthDescription": "Usa toda a largura da tela para as ferramentas em vez de um container centralizado", + "settingsAutoSaved": "As configurações são salvas automaticamente", + "clickToSet": "Clique para definir", + "pressKeys": "Pressione as teclas...", + "warnings": { + "alreadyInUse": "Atalho Já em Uso", + "assignedTo": "já está atribuído a:", + "chooseDifferent": "Por favor, escolha um atalho diferente.", + "reserved": "Aviso de Atalho Reservado", + "commonlyUsed": "é comumente usado para:", + "unreliable": "Este atalho pode não funcionar bem ou conflitar com o navegador/sistema.", + "useAnyway": "Deseja usar mesmo assim?", + "resetTitle": "Redefinir Atalhos", + "resetMessage": "Tem certeza que deseja redefinir todos os atalhos?

Esta ação não pode ser desfeita.", + "importSuccessTitle": "Importação Concluída", + "importSuccessMessage": "Atalhos importados com sucesso!", + "importFailTitle": "Falha na Importação", + "importFailMessage": "Falha ao importar atalhos. Formato de arquivo inválido." + } + }, + "warning": { + "title": "Aviso", + "cancel": "Cancelar", + "proceed": "Prosseguir" + }, + "compliance": { + "title": "Seus dados nunca saem do seu dispositivo", + "weKeep": "Mantemos", + "yourInfoSafe": "suas informações seguras", + "byFollowingStandards": "seguindo padrões globais de segurança.", + "processingLocal": "Todo o processamento acontece localmente no seu dispositivo.", + "gdpr": { + "title": "Conformidade GDPR", + "description": "Protege os dados pessoais e a privacidade de indivíduos na União Europeia." + }, + "ccpa": { + "title": "Conformidade CCPA", + "description": "Dá aos residentes da Califórnia direitos sobre como suas informações pessoais são coletadas e usadas." + }, + "hipaa": { + "title": "Conformidade HIPAA", + "description": "Estabelece salvaguardas para o tratamento de informações de saúde sensíveis nos Estados Unidos." + } + }, + "faq": { + "title": "Perguntas", + "questions": "Frequentes", + "isFree": { + "question": "O BentoPDF é realmente grátis?", + "answer": "Sim, com certeza. Todas as ferramentas do BentoPDF são 100% gratuitas, sem limites de arquivos, sem cadastros e sem marcas d'água. Acreditamos que todos merecem acesso a ferramentas PDF poderosas sem barreiras financeiras." + }, + "areFilesSecure": { + "question": "Meus arquivos estão seguros? Onde são processados?", + "answer": "Seus arquivos estão o mais seguros possível porque nunca saem do seu computador. Todo o processamento ocorre diretamente no seu navegador (client-side). Nunca fazemos upload para um servidor, garantindo privacidade total." + }, + "platforms": { + "question": "Funciona no Mac, Windows e Celular?", + "answer": "Sim! Como o BentoPDF roda inteiramente no navegador, funciona em qualquer sistema operacional moderno, incluindo Windows, macOS, Linux, iOS e Android." + }, + "gdprCompliant": { + "question": "O BentoPDF está em conformidade com a GDPR?", + "answer": "Sim. Como o processamento é local e não coletamos seus arquivos, não temos acesso aos seus dados. Isso garante total conformidade e controle por parte do usuário." + }, + "dataStorage": { + "question": "Vocês armazenam ou rastreiam meus arquivos?", + "answer": "Não. Nunca armazenamos, rastreamos ou registramos seus arquivos. Tudo acontece na memória do navegador e desaparece ao fechar a página. Não há logs nem servidores envolvidos." + }, + "different": { + "question": "O que torna o BentoPDF diferente de outras ferramentas?", + "answer": "A maioria das ferramentas faz upload dos arquivos para um servidor. O BentoPDF usa tecnologia web moderna para processar tudo localmente no seu navegador, garantindo mais velocidade e privacidade." + }, + "browserBased": { + "question": "Como o processamento no navegador me mantém seguro?", + "answer": "Ao rodar no seu dispositivo, eliminamos riscos de ataques a servidores ou vazamentos de dados de terceiros. Seus arquivos permanecem seus — sempre." + }, + "analytics": { + "question": "Vocês usam cookies ou rastreamento?", + "answer": "Usamos apenas o Simple Analytics para contar visitas de forma anônima. Sabemos quantos usuários nos visitam, mas nunca quem você é. O sistema respeita totalmente a GDPR." + }, + "sectionTitle": "Perguntas frequentes" + }, + "testimonials": { + "title": "O que nossos", + "users": "Usuários", + "say": "Dizem" + }, + "support": { + "title": "Gostou do Trabalho?", + "description": "O BentoPDF é um projeto pessoal feito para fornecer ferramentas poderosas e privadas para todos. Se for útil para você, considere apoiar o desenvolvimento!", + "buyMeCoffee": "Pague um Café" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Todos os direitos reservados.", + "version": "Versão", + "company": "Empresa", + "aboutUs": "Sobre Nós", + "faqLink": "FAQ", + "contactUs": "Contato", + "legal": "Jurídico", + "termsAndConditions": "Termos e Condições", + "privacyPolicy": "Política de Privacidade", + "followUs": "Siga-nos" + }, + "merge": { + "title": "Mesclar PDFs", + "description": "Combine arquivos inteiros ou selecione páginas específicas para criar um novo documento.", + "fileMode": "Modo Arquivo", + "pageMode": "Modo Página", + "howItWorks": "Como funciona:", + "fileModeInstructions": [ + "Clique e arraste o ícone para alterar a ordem dos arquivos.", + "No campo \"Páginas\", você pode definir intervalos (ex: \"1-3, 5\") para mesclar apenas essas páginas.", + "Deixe o campo em branco para incluir todas as páginas do arquivo." + ], + "pageModeInstructions": [ + "Todas as páginas dos PDFs enviados aparecem abaixo.", + "Arraste as miniaturas para criar a ordem exata que deseja no novo arquivo." + ], + "mergePdfs": "Mesclar PDFs" + }, + "common": { + "page": "Página", + "pages": "Páginas", + "of": "de", + "download": "Baixar", + "cancel": "Cancelar", + "save": "Salvar", + "delete": "Excluir", + "edit": "Editar", + "add": "Adicionar", + "remove": "Remover", + "loading": "Carregando...", + "error": "Erro", + "success": "Sucesso", + "file": "Arquivo", + "files": "Arquivos", + "close": "Fechar" + }, + "about": { + "hero": { + "title": "Acreditamos que ferramentas PDF devem ser", + "subtitle": "rápidas, privadas e gratuitas.", + "noCompromises": "Sem concessões." + }, + "mission": { + "title": "Nossa Missão", + "description": "Fornecer o kit de ferramentas PDF mais completo, respeitando sua privacidade e sem cobrar por isso. Ferramentas essenciais devem ser acessíveis a todos, sem barreiras." + }, + "philosophy": { + "label": "Nossa Filosofia", + "title": "Privacidade Primeiro. Sempre.", + "description": "Em uma era onde dados são mercadoria, seguimos outro caminho. Todo o processamento ocorre no seu navegador. Arquivos não tocam nossos servidores e não rastreamos você. Privacidade não é apenas um recurso; é nossa base." + }, + "whyBentopdf": { + "title": "Por que o BentoPDF?", + "speed": { + "title": "Feito para Velocidade", + "description": "Sem esperas de upload. Usando tecnologias como WebAssembly, processamos tudo diretamente no navegador com velocidade inigualável." + }, + "free": { + "title": "Totalmente Grátis", + "description": "Sem períodos de teste, assinaturas ou funções \"premium\" bloqueadas. Acreditamos em ferramentas como um serviço público." + }, + "noAccount": { + "title": "Sem Necessidade de Conta", + "description": "Use qualquer ferramenta imediatamente. Não pedimos e-mail, senha ou qualquer dado pessoal. Seu fluxo de trabalho deve ser anônimo." + }, + "openSource": { + "title": "Espírito Open Source", + "description": "Construído com transparência. Utilizamos bibliotecas incríveis como PDF-lib e PDF.js para democratizar o acesso a ferramentas poderosas." + } + }, + "cta": { + "title": "Pronto para começar?", + "description": "Junte-se a milhares de usuários que confiam no BentoPDF. Sinta a diferença da privacidade e do desempenho.", + "button": "Explorar Ferramentas" + } + }, + "contact": { + "title": "Entre em Contato", + "subtitle": "Adoraríamos ouvir você. Se tiver dúvidas, feedback ou sugestões de recursos, não hesite em nos contatar.", + "email": "Você pode nos contatar diretamente por e-mail em:" + }, + "licensing": { + "title": "Licenciamento de", + "subtitle": "Escolha a licença que melhor atende às suas necessidades." + }, + "multiTool": { + "uploadPdfs": "Enviar PDFs", + "upload": "Enviar", + "addBlankPage": "Adicionar Página em Branco", + "edit": "Editar:", + "undo": "Desfazer", + "redo": "Refazer", + "reset": "Redefinir", + "selection": "Seleção:", + "selectAll": "Selecionar Tudo", + "deselectAll": "Desmarcar Tudo", + "rotate": "Girar:", + "rotateLeft": "Esquerda", + "rotateRight": "Direita", + "transform": "Transformar:", + "duplicate": "Duplicar", + "split": "Dividir", + "clear": "Limpar:", + "delete": "Excluir", + "download": "Baixar:", + "downloadSelected": "Baixar Selecionadas", + "exportPdf": "Exportar PDF", + "uploadPdfFiles": "Selecionar Arquivos PDF", + "dragAndDrop": "Arraste arquivos PDF aqui ou clique para selecionar", + "selectFiles": "Selecionar Arquivos", + "renderingPages": "Renderizando páginas...", + "actions": { + "duplicatePage": "Duplicar esta página", + "deletePage": "Excluir esta página", + "insertPdf": "Inserir PDF após esta página", + "toggleSplit": "Alternar divisão após esta página" + }, + "pleaseWait": "Aguarde", + "pagesRendering": "As páginas ainda estão sendo renderizadas. Por favor, aguarde...", + "noPagesSelected": "Nenhuma Página Selecionada", + "selectOnePage": "Selecione pelo menos uma página para baixar.", + "noPages": "Sem Páginas", + "noPagesToExport": "Não há páginas para exportar.", + "renderingTitle": "Renderizando visualizações das páginas", + "errorRendering": "Falha ao renderizar miniaturas das páginas", + "error": "Erro", + "failedToLoad": "Falha ao carregar" + }, + "howItWorks": { + "title": "Como funciona", + "step1": "Clique ou arraste seu arquivo aqui", + "step2": "Clique no botão de processamento", + "step3": "Salve seu arquivo processado instantaneamente" + }, + "relatedTools": { + "title": "Ferramentas PDF relacionadas" + }, + "simpleMode": { + "title": "Ferramentas PDF", + "subtitle": "Selecione uma ferramenta para começar" + } +} diff --git a/public/locales/pt/tools.json b/public/locales/pt/tools.json new file mode 100644 index 0000000..1a16138 --- /dev/null +++ b/public/locales/pt/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "Ferramentas Populares", + "editAnnotate": "Editar e Anotar", + "convertToPdf": "Converter para PDF", + "convertFromPdf": "Converter de PDF", + "organizeManage": "Organizar e Gerenciar", + "optimizeRepair": "Otimizar e Reparar", + "securePdf": "Segurança de PDF" + }, + "pdfMultiTool": { + "name": "Multiferramenta PDF", + "subtitle": "Mesclar, dividir, organizar, excluir, girar, adicionar páginas em branco, extrair e duplicar em uma única interface." + }, + "mergePdf": { + "name": "Mesclar PDF", + "subtitle": "Combine vários PDFs em um único arquivo. Preserva os favoritos (bookmarks)." + }, + "splitPdf": { + "name": "Dividir PDF", + "subtitle": "Extraia um intervalo de páginas para um novo PDF." + }, + "compressPdf": { + "name": "Comprimir PDF", + "subtitle": "Reduza o tamanho do arquivo do seu PDF.", + "algorithmLabel": "Algoritmo de Compressão", + "condense": "Condense (Recomendado)", + "photon": "Photon (Para PDFs com Muitas Fotos)", + "condenseInfo": "Condense usa compressão avançada: remove dados desnecessários, otimiza imagens, subconjunta fontes. Ideal para a maioria dos PDFs.", + "photonInfo": "Photon converte páginas em imagens. Use para PDFs com muitas fotos/digitalizados.", + "photonWarning": "Aviso: O texto ficará não selecionável e os links deixarão de funcionar.", + "levelLabel": "Nível de Compressão", + "light": "Leve (Preservar Qualidade)", + "balanced": "Equilibrado (Recomendado)", + "aggressive": "Agressivo (Arquivos Menores)", + "extreme": "Extremo (Compressão Máxima)", + "grayscale": "Converter para Escala de Cinza", + "grayscaleHint": "Reduz o tamanho do arquivo removendo informações de cor", + "customSettings": "Configurações Personalizadas", + "customSettingsHint": "Ajuste fino dos parâmetros de compressão:", + "outputQuality": "Qualidade de Saída", + "resizeImagesTo": "Redimensionar Imagens Para", + "onlyProcessAbove": "Processar Apenas Acima de", + "removeMetadata": "Remover metadados", + "subsetFonts": "Subconjunto de fontes (remover glifos não utilizados)", + "removeThumbnails": "Remover miniaturas incorporadas", + "compressButton": "Comprimir PDF" + }, + "pdfEditor": { + "name": "Editor de PDF", + "subtitle": "Anotar, destacar, redigir, comentar, adicionar formas/imagens, pesquisar e visualizar PDFs." + }, + "jpgToPdf": { + "name": "JPG para PDF", + "subtitle": "Crie um PDF a partir de uma ou mais imagens JPG." + }, + "signPdf": { + "name": "Assinar PDF", + "subtitle": "Desenhe, digite ou faça upload da sua assinatura." + }, + "cropPdf": { + "name": "Cortar PDF", + "subtitle": "Corte as margens de cada página do seu PDF." + }, + "extractPages": { + "name": "Extrair Páginas", + "subtitle": "Salve uma seleção de páginas como novos arquivos." + }, + "duplicateOrganize": { + "name": "Duplicar e Organizar", + "subtitle": "Duplique, reordene e exclua páginas." + }, + "deletePages": { + "name": "Excluir Páginas", + "subtitle": "Remova páginas específicas do seu documento." + }, + "editBookmarks": { + "name": "Editar Favoritos", + "subtitle": "Adicione, edite, importe, exclua e extraia favoritos de PDF." + }, + "tableOfContents": { + "name": "Sumário", + "subtitle": "Gere uma página de sumário a partir dos favoritos do PDF." + }, + "pageNumbers": { + "name": "Números de Página", + "subtitle": "Insira números de página no seu documento." + }, + "batesNumbering": { + "name": "Numeração Bates", + "subtitle": "Adicionar números Bates sequenciais em um ou mais arquivos PDF." + }, + "addWatermark": { + "name": "Adicionar Marca d'Água", + "subtitle": "Carimbe texto ou uma imagem sobre as páginas do seu PDF.", + "applyToAllPages": "Aplicar a todas as páginas" + }, + "headerFooter": { + "name": "Cabeçalho e Rodapé", + "subtitle": "Adicione texto no topo e no final das páginas." + }, + "invertColors": { + "name": "Inverter Cores", + "subtitle": "Crie uma versão em \"modo escuro\" do seu PDF." + }, + "scannerEffect": { + "name": "Efeito Scanner", + "subtitle": "Faça seu PDF parecer um documento digitalizado.", + "scanSettings": "Configurações de Digitalização", + "colorspace": "Espaço de Cor", + "gray": "Cinza", + "border": "Borda", + "rotate": "Rotação", + "rotateVariance": "Variação de Rotação", + "brightness": "Brilho", + "contrast": "Contraste", + "blur": "Desfoque", + "noise": "Ruído", + "yellowish": "Amarelado", + "resolution": "Resolução", + "processButton": "Aplicar Efeito Scanner" + }, + "adjustColors": { + "name": "Ajustar Cores", + "subtitle": "Ajuste brilho, contraste, saturação e mais no seu PDF.", + "colorSettings": "Configurações de Cor", + "brightness": "Brilho", + "contrast": "Contraste", + "saturation": "Saturação", + "hueShift": "Matiz", + "temperature": "Temperatura", + "tint": "Tonalidade", + "gamma": "Gamma", + "sepia": "Sépia", + "processButton": "Aplicar Ajustes de Cor" + }, + "backgroundColor": { + "name": "Cor de Fundo", + "subtitle": "Altere a cor de fundo do seu PDF." + }, + "changeTextColor": { + "name": "Alterar Cor do Texto", + "subtitle": "Altere a cor do texto no seu PDF." + }, + "addStamps": { + "name": "Adicionar Carimbos", + "subtitle": "Adicione carimbos de imagem ao seu PDF usando a barra de ferramentas de anotação.", + "usernameLabel": "Nome do Usuário no Carimbo", + "usernamePlaceholder": "Digite seu nome (para os carimbos)", + "usernameHint": "Este nome aparecerá nos carimbos que você criar." + }, + "removeAnnotations": { + "name": "Remover Anotações", + "subtitle": "Remova comentários, destaques e links." + }, + "pdfFormFiller": { + "name": "Preenchimento de Formulário", + "subtitle": "Preencha formulários diretamente no navegador. Também suporta formulários XFA." + }, + "createPdfForm": { + "name": "Criar Formulário PDF", + "subtitle": "Crie formulários PDF preenchíveis com campos de texto de arrastar e soltar." + }, + "removeBlankPages": { + "name": "Remover Páginas em Branco", + "subtitle": "Detecte e exclua automaticamente páginas em branco.", + "sensitivityHint": "Maior = mais rigoroso, apenas páginas totalmente em branco. Menor = permite páginas com algum conteúdo." + }, + "imageToPdf": { + "name": "Imagem para PDF", + "subtitle": "Converta JPG, PNG, WebP, BMP, TIFF, SVG, HEIC para PDF." + }, + "pngToPdf": { + "name": "PNG para PDF", + "subtitle": "Crie um PDF a partir de uma ou mais imagens PNG." + }, + "webpToPdf": { + "name": "WebP para PDF", + "subtitle": "Crie um PDF a partir de uma ou mais imagens WebP." + }, + "svgToPdf": { + "name": "SVG para PDF", + "subtitle": "Crie um PDF a partir de uma ou mais imagens SVG." + }, + "bmpToPdf": { + "name": "BMP para PDF", + "subtitle": "Crie um PDF a partir de uma ou mais imagens BMP." + }, + "heicToPdf": { + "name": "HEIC para PDF", + "subtitle": "Crie um PDF a partir de uma ou mais imagens HEIC." + }, + "tiffToPdf": { + "name": "TIFF para PDF", + "subtitle": "Crie um PDF a partir de uma ou mais imagens TIFF." + }, + "textToPdf": { + "name": "Texto para PDF", + "subtitle": "Converta um arquivo de texto simples (.txt) em PDF." + }, + "jsonToPdf": { + "name": "JSON para PDF", + "subtitle": "Converta arquivos JSON para o formato PDF." + }, + "pdfToJpg": { + "name": "PDF para JPG", + "subtitle": "Converta cada página do PDF em uma imagem JPG." + }, + "pdfToPng": { + "name": "PDF para PNG", + "subtitle": "Converta cada página do PDF em uma imagem PNG." + }, + "pdfToWebp": { + "name": "PDF para WebP", + "subtitle": "Converta cada página do PDF em uma imagem WebP." + }, + "pdfToBmp": { + "name": "PDF para BMP", + "subtitle": "Converta cada página do PDF em uma imagem BMP." + }, + "pdfToTiff": { + "name": "PDF para TIFF", + "subtitle": "Converta cada página do PDF em uma imagem TIFF." + }, + "pdfToGreyscale": { + "name": "PDF para Tons de Cinza", + "subtitle": "Converta todas as cores para preto e branco." + }, + "pdfToJson": { + "name": "PDF para JSON", + "subtitle": "Converta arquivos PDF para o formato JSON." + }, + "ocrPdf": { + "name": "OCR para PDF", + "subtitle": "Torne um PDF pesquisável e copiável (reconhecimento de texto)." + }, + "alternateMix": { + "name": "Alternar e Misturar Páginas", + "subtitle": "Mescle PDFs alternando as páginas de cada arquivo. Preserva os favoritos." + }, + "addAttachments": { + "name": "Adicionar Anexos", + "subtitle": "Incorpore um ou mais arquivos dentro do seu PDF." + }, + "extractAttachments": { + "name": "Extrair Anexos", + "subtitle": "Extraia todos os arquivos incorporados de PDF(s) como um ZIP." + }, + "editAttachments": { + "name": "Editar Anexos", + "subtitle": "Visualize ou remova anexos do seu PDF." + }, + "dividePages": { + "name": "Dividir Páginas", + "subtitle": "Divida as páginas horizontalmente ou verticalmente." + }, + "addBlankPage": { + "name": "Adicionar Página em Branco", + "subtitle": "Insira uma página vazia em qualquer lugar do seu PDF." + }, + "reversePages": { + "name": "Inverter Páginas", + "subtitle": "Inverta a ordem de todas as páginas do seu documento." + }, + "rotatePdf": { + "name": "Girar PDF", + "subtitle": "Gire as páginas em incrementos de 90 graus." + }, + "nUpPdf": { + "name": "PDF N-Up", + "subtitle": "Organize várias páginas em uma única folha de impressão." + }, + "combineToSinglePage": { + "name": "Combinar em Página Única", + "subtitle": "Costure todas as páginas em um único fluxo contínuo." + }, + "viewMetadata": { + "name": "Ver Metadados", + "subtitle": "Inspecione as propriedades ocultas do seu PDF." + }, + "editMetadata": { + "name": "Editar Metadados", + "subtitle": "Altere o autor, título e outras propriedades." + }, + "pdfsToZip": { + "name": "PDFs para ZIP", + "subtitle": "Empacote vários arquivos PDF em um arquivo compactado ZIP." + }, + "comparePdfs": { + "name": "Comparar PDFs", + "subtitle": "Compare dois PDFs lado a lado." + }, + "posterizePdf": { + "name": "Posterizar PDF", + "subtitle": "Divida uma página grande em várias páginas menores." + }, + "fixPageSize": { + "name": "Ajustar Tamanho da Página", + "subtitle": "Padronize todas as páginas para um tamanho uniforme." + }, + "linearizePdf": { + "name": "Linearizar PDF", + "subtitle": "Otimize o PDF para visualização rápida na web." + }, + "pageDimensions": { + "name": "Dimensões da Página", + "subtitle": "Analise o tamanho, orientação e unidades das páginas." + }, + "removeRestrictions": { + "name": "Remover Restrições", + "subtitle": "Remova proteção por senha e restrições de segurança de arquivos assinados digitalmente." + }, + "repairPdf": { + "name": "Reparar PDF", + "subtitle": "Recupere dados de arquivos PDF corrompidos ou danificados." + }, + "encryptPdf": { + "name": "Criptografar PDF", + "subtitle": "Bloqueie seu PDF adicionando uma senha." + }, + "sanitizePdf": { + "name": "Sanitizar PDF", + "subtitle": "Remova metadados, anotações, scripts e outros dados ocultos." + }, + "decryptPdf": { + "name": "Descriptografar PDF", + "subtitle": "Desbloqueie o PDF removendo a proteção por senha." + }, + "flattenPdf": { + "name": "Achatar PDF (Flatten)", + "subtitle": "Torne os campos de formulário e anotações não editáveis." + }, + "removeMetadata": { + "name": "Remover Metadados", + "subtitle": "Limpe dados ocultos do seu PDF." + }, + "changePermissions": { + "name": "Alterar Permissões", + "subtitle": "Defina ou altere as permissões de usuário em um PDF." + }, + "emailToPdf": { + "name": "Email para PDF", + "subtitle": "Converta arquivos de email (EML, MSG) para PDF. Suporta exportações do Outlook e formatos de email padrão.", + "acceptedFormats": "Arquivos EML, MSG", + "convertButton": "Converter para PDF" + }, + "fontToOutline": { + "name": "Fonte para Contorno", + "subtitle": "Converta todas as fontes em contornos vetoriais para renderização consistente em todos os dispositivos." + }, + "deskewPdf": { + "name": "Desinclinar PDF", + "subtitle": "Endireite automaticamente páginas digitalizadas inclinadas usando OpenCV." + }, + "rotateCustom": { + "name": "Rotacionar em Graus Customizáveis", + "subtitle": "Rotaciona páginas em qualquer ângulo customizado." + }, + "odtToPdf": { + "name": "ODT para PDF", + "subtitle": "Converte arquivos em formato OpenDocument Text para PDF. Suporta multiplos arquivos.", + "acceptedFormats": "Arquivos ODT", + "convertButton": "Converter para PDF" + }, + "csvToPdf": { + "name": "CSV para PDF", + "subtitle": "Converte arquivos de planilhas CSV para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos CSV", + "convertButton": "Converter para PDF" + }, + "rtfToPdf": { + "name": "RTF para PDF", + "subtitle": "Converte documentos Rich Text Format para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos RTF", + "convertButton": "Converter para PDF" + }, + "wordToPdf": { + "name": "Word para PDF", + "subtitle": "Converte documentos Word documents (DOCX, DOC, ODT, RTF) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos DOCX, DOC, ODT, RTF", + "convertButton": "Converter para PDF" + }, + "excelToPdf": { + "name": "Excel para PDF", + "subtitle": "Converte planilhas Excel (XLSX, XLS, ODS, CSV) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos XLSX, XLS, ODS, CSV", + "convertButton": "Converter para PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint para PDF", + "subtitle": "Converte apresentações PowerPoint (PPTX, PPT, ODP) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos PPTX, PPT, ODP", + "convertButton": "Converter para PDF" + }, + "markdownToPdf": { + "name": "Markdown para PDF", + "subtitle": "Escreva ou cole Markdown e exporte como um belo formato PDF.", + "paneMarkdown": "Markdown", + "panePreview": "Pré-visualizar", + "btnUpload": "Upload", + "btnSyncScroll": "Sincronizar Scroll", + "btnSettings": "Configurações", + "btnExportPdf": "Exportar PDF", + "settingsTitle": "Configurações Markdown", + "settingsPreset": "Predefinição", + "presetDefault": "Padrão (tipo GFM)", + "presetCommonmark": "CommonMark (extrito)", + "presetZero": "Mínimo (sem features)", + "settingsOptions": "Opções de Markdown", + "optAllowHtml": "Permitir tags HTML", + "optBreaks": "Converter novas linhas em
", + "optLinkify": "Auto-converter URLs para links", + "optTypographer": "Tipógrafo (aspas inteligentes, etc.)" + }, + "pdfBooklet": { + "name": "PDF Booklet", + "subtitle": "Reordena páginas para impressão de livreto frente e verso. Dobre e grampeie para criar um livreto.", + "howItWorks": "Como funciona:", + "step1": "Faça o upload de um arquivo PDF.", + "step2": "As páginas serão reordenadas na ordem de livretos.", + "step3": "Imprima frente e verso, vire pelo topo, dobre e grampeie.", + "paperSize": "Tamanho do Papel", + "orientation": "Orientação", + "portrait": "Retrato", + "landscape": "Paisagem", + "pagesPerSheet": "Páginas por Folha", + "createBooklet": "Criar Livreto", + "processing": "Processando...", + "pageCount": "A contagem de páginas será ajustada para um múltiplo de 4, caso necessário." + }, + "xpsToPdf": { + "name": "XPS para PDF", + "subtitle": "Converte XPS/OXPS para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos XPS, OXPS", + "convertButton": "Converter para PDF" + }, + "mobiToPdf": { + "name": "MOBI para PDF", + "subtitle": "Converte e-books MOBI para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos MOBI", + "convertButton": "Converter para PDF" + }, + "epubToPdf": { + "name": "EPUB para PDF", + "subtitle": "Converte e-books EPUB para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos EPUB", + "convertButton": "Converter para PDF" + }, + "fb2ToPdf": { + "name": "FB2 para PDF", + "subtitle": "Converte e-books FictionBook (FB2) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos FB2", + "convertButton": "Converter para PDF" + }, + "cbzToPdf": { + "name": "CBZ para PDF", + "subtitle": "Converte comic book archives (CBZ/CBR) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos CBZ, CBR", + "convertButton": "Converter para PDF" + }, + "wpdToPdf": { + "name": "WPD para PDF", + "subtitle": "Converte WordPerfect (WPD) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos WPD", + "convertButton": "Converter para PDF" + }, + "wpsToPdf": { + "name": "WPS para PDF", + "subtitle": "Converte WPS Office para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos WPS", + "convertButton": "Converter para PDF" + }, + "xmlToPdf": { + "name": "XML para PDF", + "subtitle": "Converte XML para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos XML", + "convertButton": "Converter para PDF" + }, + "pagesToPdf": { + "name": "Pages para PDF", + "subtitle": "Converte Apple Pages para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos Pages", + "convertButton": "Converter para PDF" + }, + "odgToPdf": { + "name": "ODG para PDF", + "subtitle": "Converte OpenDocument Graphics (ODG) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos ODG", + "convertButton": "Converter para PDF" + }, + "odsToPdf": { + "name": "ODS para PDF", + "subtitle": "Converte OpenDocument Spreadsheet (ODS) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos ODS", + "convertButton": "Converter para PDF" + }, + "odpToPdf": { + "name": "ODP para PDF", + "subtitle": "Converte OpenDocument Presentation (ODP) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos ODP", + "convertButton": "Converter para PDF" + }, + "pubToPdf": { + "name": "PUB para PDF", + "subtitle": "Converte Microsoft Publisher (PUB) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos PUB", + "convertButton": "Converter para PDF" + }, + "vsdToPdf": { + "name": "VSD para PDF", + "subtitle": "Converte Microsoft Visio (VSD, VSDX) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos VSD, VSDX", + "convertButton": "Converter para PDF" + }, + "psdToPdf": { + "name": "PSD para PDF", + "subtitle": "Converte Adobe Photoshop (PSD) para PDF. Suporta múltiplos arquivos.", + "acceptedFormats": "Arquivos PSD", + "convertButton": "Converter para PDF" + }, + "pdfToSvg": { + "name": "PDF para SVG", + "subtitle": "Converte cada página de um arquivo PDF em um scalable vector graphic (SVG) para qualidade perfeita em qualquer tamanho." + }, + "extractTables": { + "name": "Extrair Tabelas PDF", + "subtitle": "Extrai tabelas de arquivos PDF e exporta como CSV, JSON, ou Markdown." + }, + "pdfToCsv": { + "name": "PDF para CSV", + "subtitle": "Extrai tabelas do PDF e converte em CSV." + }, + "pdfToExcel": { + "name": "PDF para Excel", + "subtitle": "Extrai tabelas do PDF e converte em Excel (XLSX)." + }, + "pdfToText": { + "name": "PDF para Texto", + "subtitle": "Extrai texto de arquivos PDF e salva como texto simples (.txt). Suporta múltiplos arquivos.", + "note": "Esta ferrament funciona SOMENTE com PDFs criados digitalmente. Para documentos escaneados ou PDFs baseados em imagens, use nossa ferramenta OCR para PDF no lugar.", + "convertButton": "Extrair Texto" + }, + "digitalSignPdf": { + "name": "Assinatura Digital PDF", + "pageTitle": "Assinatura Digital PDF - Adicionar Assinatura Criptográfica | BentoPDF", + "subtitle": "Adicione uma assinatura digital criptográfica ao seu PDF usando certificados X.509. Suporta formatos PKCS#12 (.pfx, .p12) e PEM. Sua chave privada nunca sai do seu navegador.", + "certificateSection": "Certificado", + "uploadCert": "Carregar certificado (.pfx, .p12)", + "certPassword": "Senha do Certificado", + "certPasswordPlaceholder": "Digite a senha", + "certInfo": "Informações do Certificado", + "certSubject": "Assunto", + "certIssuer": "Emissor", + "certValidity": "Válido", + "signatureDetails": "Detalhes da Assinatura (Opcional)", + "reason": "Razão", + "reasonPlaceholder": "ex: Eu aprovo este documento", + "location": "Localização", + "locationPlaceholder": "ex: Lisboa, Portugal", + "contactInfo": "Contato", + "contactPlaceholder": "ex: email@exemplo.com", + "applySignature": "Aplicar Assinatura", + "successMessage": "PDF assinado com sucesso! A assinatura pode ser verificada em qualquer leitor de PDF." + }, + "validateSignaturePdf": { + "name": "Validar Assinatura PDF", + "pageTitle": "Validar Assinatura PDF - Verificar Assinaturas Digitais | BentoPDF", + "subtitle": "Verifique assinaturas digitais em seus arquivos PDF. Verifique a validade do certificado e a integridade do documento." + }, + "pdfToWord": { + "name": "PDF para Word", + "subtitle": "Converter arquivos PDF em documentos Word editáveis." + }, + "extractImages": { + "name": "Extrair Imagens", + "subtitle": "Extrair todas as imagens incorporadas dos seus arquivos PDF." + }, + "pdfToMarkdown": { + "name": "PDF para Markdown", + "subtitle": "Converter texto e tabelas de PDF para formato Markdown." + }, + "preparePdfForAi": { + "name": "Preparar PDF para IA", + "subtitle": "Extrair conteúdo PDF como JSON LlamaIndex para pipelines RAG/LLM." + }, + "pdfOcg": { + "name": "Camadas PDF (OCG)", + "subtitle": "Visualizar, alternar, adicionar e excluir camadas OCG no seu PDF." + }, + "pdfToPdfa": { + "name": "PDF para PDF/A", + "subtitle": "Converter PDF em PDF/A para arquivamento de longo prazo." + }, + "rasterizePdf": { + "name": "Rasterizar PDF", + "subtitle": "Converter PDF em PDF baseado em imagens. Achatar camadas e remover texto selecionável." + }, + "pdfWorkflow": { + "name": "Construtor de fluxo de trabalho PDF", + "subtitle": "Crie pipelines de processamento PDF personalizados com um editor visual de nós.", + "nodes": "Nós", + "searchNodes": "Pesquisar nós...", + "run": "Executar", + "clear": "Limpar", + "save": "Salvar", + "load": "Carregar", + "export": "Exportar", + "import": "Importar", + "ready": "Pronto", + "settings": "Configurações", + "processing": "Processando...", + "saveTemplate": "Salvar modelo", + "templateName": "Nome do modelo", + "templatePlaceholder": "ex. Fluxo de trabalho de faturamento", + "cancel": "Cancelar", + "loadTemplate": "Carregar modelo", + "noTemplates": "Nenhum modelo salvo ainda.", + "ok": "OK", + "workflowCompleted": "Fluxo de trabalho concluído", + "errorDuringExecution": "Erro durante a execução", + "addNodeError": "Adicione pelo menos um nó para executar o fluxo de trabalho.", + "needInputOutput": "Seu fluxo de trabalho precisa de pelo menos um nó de entrada e um nó de saída para ser executado.", + "enterName": "Por favor, insira um nome.", + "templateExists": "Já existe um modelo com este nome.", + "templateSaved": "Modelo \"{{name}}\" salvo.", + "templateLoaded": "Modelo \"{{name}}\" carregado.", + "failedLoadTemplate": "Falha ao carregar o modelo.", + "noSettings": "Nenhuma configuração disponível para este nó.", + "advancedSettings": "Configurações avançadas" + } +} diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json new file mode 100644 index 0000000..a63647d --- /dev/null +++ b/public/locales/sv/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "Hem", + "about": "Om", + "contact": "Kontakt", + "licensing": "Licensiering", + "allTools": "Alla verktyg", + "openMainMenu": "Öppna huvudmenyn", + "language": "Språk" + }, + "donation": { + "message": "Gillar du BentoPDF? Hjälp oss att hålla det gratis och öppen källkod!", + "button": "Donera" + }, + "hero": { + "title": " ", + "pdfToolkit": "PDF-verktygslådan", + "builtForPrivacy": "byggd för integritet", + "noSignups": "Inga konton", + "unlimitedUse": "Obegränsad användning", + "worksOffline": "Fungerar offline", + "startUsing": "Börja använda nu" + }, + "usedBy": { + "title": "Används av företag och personer som arbetar på" + }, + "features": { + "title": "Varför välja", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "Ingen registrering", + "description": "Börja direkt, inga konton eller e-postadresser." + }, + "noUploads": { + "title": "Inga uppladdningar", + "description": "100% på klientsidan, dina filer lämnar aldrig din enhet." + }, + "foreverFree": { + "title": "Gratis för alltid", + "description": "Alla verktyg, inga provperioder, inga betalväggar." + }, + "noLimits": { + "title": "Inga begränsningar", + "description": "Använd så mycket du vill, inga dolda gränser." + }, + "batchProcessing": { + "title": "Batchbearbetning", + "description": "Hantera obegränsade PDF-filer på en gång." + }, + "lightningFast": { + "title": "Blixtrande snabb", + "description": "Bearbeta PDF-filer direkt, utan väntan eller förseningar." + } + }, + "tools": { + "title": "Kom igång med", + "toolsLabel": "verktyg", + "subtitle": "Klicka på ett verktyg för att öppna filväljaren", + "searchPlaceholder": "Sök efter ett verktyg (t.ex. 'dela', 'organisera'...)", + "backToTools": "Tillbaka till verktyg", + "firstLoadNotice": "Första laddningen tar ett ögonblick eftersom vi hämtar vår konverteringsmotor. Därefter blir alla laddningar omedelbara." + }, + "upload": { + "clickToSelect": "Klicka för att välja en fil", + "orDragAndDrop": "eller dra och släpp", + "pdfOrImages": "PDF-filer eller bilder", + "filesNeverLeave": "Dina filer lämnar aldrig din enhet.", + "addMore": "Lägg till fler filer", + "clearAll": "Rensa allt", + "clearFiles": "Rensa filer", + "hints": { + "singlePdf": "Ett enskilt PDF-dokument", + "pdfFile": "PDF-fil", + "multiplePdfs2": "Flera PDF-filer (minst 2)", + "bmpImages": "BMP-bilder", + "oneOrMorePdfs": "En eller flera PDF-filer", + "pdfDocuments": "PDF-dokument", + "oneOrMoreCsv": "En eller flera CSV-filer", + "multiplePdfsSupported": "Flera PDF-filer stöds", + "singleOrMultiplePdfs": "Enskild eller flera PDF-filer stöds", + "singlePdfFile": "Enskild PDF-fil", + "pdfWithForms": "PDF-fil med formulärfält", + "heicImages": "HEIC/HEIF-bilder", + "jpgImages": "JPG, JPEG, JP2, JPX-bilder", + "pdfsOrImages": "PDF-filer eller bilder", + "oneOrMoreOdt": "En eller flera ODT-filer", + "singlePdfOnly": "Endast enskild PDF-fil", + "pdfFiles": "PDF-filer", + "multiplePdfs": "Flera PDF-filer", + "pngImages": "PNG-bilder", + "pdfFilesOneOrMore": "PDF-filer (en eller flera)", + "oneOrMoreRtf": "En eller flera RTF-filer", + "svgGraphics": "SVG-grafik", + "tiffImages": "TIFF-bilder", + "webpImages": "WebP-bilder" + } + }, + "howItWorks": { + "title": "Så här fungerar det", + "step1": "Klicka eller dra och släpp din fil för att börja", + "step2": "Klicka på bearbeta-knappen för att starta", + "step3": "Spara din bearbetade fil direkt" + }, + "relatedTools": { + "title": "Relaterade PDF-verktyg" + }, + "loader": { + "processing": "Bearbetar..." + }, + "alert": { + "title": "Varning", + "ok": "OK" + }, + "preview": { + "title": "Förhandsvisning", + "downloadAsPdf": "Ladda ner som PDF", + "close": "Stäng" + }, + "settings": { + "title": "Inställningar", + "shortcuts": "Genvägar", + "preferences": "Inställningar", + "displayPreferences": "Visningsinställningar", + "searchShortcuts": "Sök genvägar...", + "shortcutsInfo": "Håll ned tangenterna för att ställa in en genväg. Ändringar sparas automatiskt.", + "shortcutsWarning": "⚠️ Undvik vanliga webbläsargenvägar (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N osv.) eftersom de kanske inte fungerar tillförlitligt.", + "import": "Importera", + "export": "Exportera", + "resetToDefaults": "Återställ till standard", + "fullWidthMode": "Fullbreddsläge", + "fullWidthDescription": "Använd hela skärmbredden för alla verktyg istället för en centrerad behållare", + "settingsAutoSaved": "Inställningar sparas automatiskt", + "clickToSet": "Klicka för att ställa in", + "pressKeys": "Tryck tangenter...", + "warnings": { + "alreadyInUse": "Genvägen används redan", + "assignedTo": "är redan tilldelad till:", + "chooseDifferent": "Välj en annan genväg.", + "reserved": "Reserverad genvägsvarning", + "commonlyUsed": "används vanligtvis för:", + "unreliable": "Den här genvägen fungerar kanske inte tillförlitligt eller kan komma i konflikt med webbläsarens/systemets beteende.", + "useAnyway": "Vill du använda den ändå?", + "resetTitle": "Återställ genvägar", + "resetMessage": "Är du säker på att du vill återställa alla genvägar till standard?

Den här åtgärden kan inte ångras.", + "importSuccessTitle": "Import lyckades", + "importSuccessMessage": "Genvägar importerades!", + "importFailTitle": "Import misslyckades", + "importFailMessage": "Det gick inte att importera genvägar. Ogiltigt filformat." + } + }, + "warning": { + "title": "Varning", + "cancel": "Avbryt", + "proceed": "Fortsätt" + }, + "compliance": { + "title": "Dina data lämnar aldrig din enhet", + "weKeep": "Vi håller", + "yourInfoSafe": "din information säker", + "byFollowingStandards": "genom att följa globala säkerhetsstandarder.", + "processingLocal": "All bearbetning sker lokalt på din enhet.", + "gdpr": { + "title": "GDPR-efterlevnad", + "description": "Skyddar personuppgifter och integritet för individer inom Europeiska unionen." + }, + "ccpa": { + "title": "CCPA-efterlevnad", + "description": "Ger Kaliforniens residenter rättigheter över hur deras personliga information samlas in, används och delas." + }, + "hipaa": { + "title": "HIPAA-efterlevnad", + "description": "Fastställer skyddsåtgärder för hantering av känslig hälsoinformation i det amerikanska sjukvårdssystemet." + } + }, + "faq": { + "title": "Vanliga", + "questions": "frågor", + "sectionTitle": "Vanliga frågor och svar", + "isFree": { + "question": "Är BentoPDF verkligen gratis?", + "answer": "Ja, absolut. Alla verktyg på BentoPDF är 100% gratis att använda, utan filgränser, utan registreringar och utan vattenstämplar. Vi tror att alla förtjänar tillgång till enkla, kraftfulla PDF-verktyg utan en betalvägg." + }, + "areFilesSecure": { + "question": "Är mina filer säkra? Var bearbetas de?", + "answer": "Dina filer är så säkra som möjligt eftersom de aldrig lämnar din dator. All bearbetning sker direkt i din webbläsare (klientsida). Vi laddar aldrig upp dina filer till en server, så du har fullständig integritet och kontroll över dina dokument." + }, + "platforms": { + "question": "Fungerar det på Mac, Windows och mobil?", + "answer": "Ja! Eftersom BentoPDF körs helt i din webbläsare fungerar det på alla operativsystem med en modern webbläsare, inklusive Windows, macOS, Linux, iOS och Android." + }, + "gdprCompliant": { + "question": "Är BentoPDF GDPR-kompatibelt?", + "answer": "Ja. BentoPDF är fullt GDPR-kompatibelt. Eftersom all filbearbetning sker lokalt i din webbläsare och vi aldrig samlar in eller överför dina filer till någon server, har vi ingen tillgång till dina data. Detta säkerställer att du alltid har kontroll över dina dokument." + }, + "dataStorage": { + "question": "Lagrar eller spårar ni några av mina filer?", + "answer": "Nej. Vi lagrar, spårar eller loggar aldrig dina filer. Allt du gör på BentoPDF händer i din webbläsares minne och försvinner när du stänger sidan. Det finns inga uppladdningar, inga historikloggar och inga servrar inblandade." + }, + "different": { + "question": "Vad gör BentoPDF annorlunda från andra PDF-verktyg?", + "answer": "De flesta PDF-verktyg laddar upp dina filer till en server för bearbetning. BentoPDF gör aldrig det. Vi använder säker, modern webbteknik för att bearbeta dina filer direkt i din webbläsare. Detta innebär snabbare prestanda, starkare integritet och fullständig trygghet." + }, + "browserBased": { + "question": "Hur håller webbläsarbaserad bearbetning mig säker?", + "answer": "Genom att köras helt inne i din webbläsare säkerställer BentoPDF att dina filer aldrig lämnar din enhet. Detta eliminerar riskerna för serverhack, dataintrång eller obehörig åtkomst. Dina filer förblir dina - alltid." + }, + "analytics": { + "question": "Använder ni cookies eller analys för att spåra mig?", + "answer": "Vi bryr oss om din integritet. BentoPDF spårar inte personlig information. Vi använder Simple Analytics enbart för att se anonyma besöksantal. Det betyder att vi kan se hur många användare som besöker vår site, men vi vet aldrig vem du är. Simple Analytics är fullt GDPR-kompatibelt och respekterar din integritet." + } + }, + "testimonials": { + "title": "Vad våra", + "users": "användare", + "say": "säger" + }, + "support": { + "title": "Gillar du mitt arbete?", + "description": "BentoPDF är ett passionerat projekt, byggt för att erbjuda en gratis, privat och kraftfull PDF-verktygslåda för alla. Om du tycker det är användbart, överväg att stödja utvecklingen. Varje kaffe hjälper!", + "buyMeCoffee": "Buy Me a Coffee" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Alla rättigheter reserverade.", + "version": "Version", + "company": "Företag", + "aboutUs": "Om oss", + "faqLink": "FAQ", + "contactUs": "Kontakta oss", + "legal": "Juridiskt", + "termsAndConditions": "Villkor", + "privacyPolicy": "Integritetspolicy", + "followUs": "Följ oss" + }, + "merge": { + "title": "Sammanfoga PDF:er", + "description": "Kombinera hela filer eller välj specifika sidor att sammanfoga till ett nytt dokument.", + "fileMode": "Filläge", + "pageMode": "Sidoläge", + "howItWorks": "Så här fungerar det:", + "fileModeInstructions": [ + "Klicka och dra ikonen för att ändra ordningen på filerna.", + "I rutan \"Sidor\" för varje fil kan du ange intervall (t.ex. \"1-3, 5\") för att sammanfoga endast de sidorna.", + "Lämna rutan \"Sidor\" tom för att inkludera alla sidor från den filen." + ], + "pageModeInstructions": [ + "Alla sidor från dina uppladdade PDF:er visas nedan.", + "Dra och släpp sidminiyrbilderna för att skapa den ordning du vill ha för din nya fil." + ], + "mergePdfs": "Sammanfoga PDF:er" + }, + "common": { + "page": "Sida", + "pages": "Sidor", + "of": "av", + "download": "Ladda ner", + "cancel": "Avbryt", + "save": "Spara", + "delete": "Ta bort", + "edit": "Redigera", + "add": "Lägg till", + "remove": "Ta bort", + "loading": "Laddar...", + "error": "Fel", + "success": "Klart", + "file": "Fil", + "files": "Filer", + "close": "Stäng" + }, + "about": { + "hero": { + "title": "Vi tror att PDF-verktyg ska vara", + "subtitle": "snabba, privata och gratis.", + "noCompromises": "Inga kompromisser." + }, + "mission": { + "title": "Vårt uppdrag", + "description": "Att tillhandahålla den mest omfattande PDF-verktygslådan som respekterar din integritet och aldrig ber om betalning. Vi tycker att viktiga dokumentverktyg ska vara tillgängliga för alla, överallt, utan hinder." + }, + "philosophy": { + "label": "Vår kärnfilosofi", + "title": "Integritet först. Alltid.", + "description": "I en tid där data är en handelsvara tar vi en annan väg. All bearbetning i BentoPDF sker lokalt i din webbläsare. Detta dina filer betyder att aldrig rör våra servrar, vi ser aldrig dina dokument och vi spårar inte vad du gör. Dina dokument förblir helt och hållet privata. Det är inte bara en funktion - det är vår grund." + }, + "whyBentopdf": { + "title": "Varför", + "speed": { + "title": "Byggd för hastighet", + "description": "Ingen väntan på uppladdningar eller nedladdningar till en server. Genom att bearbeta filer direkt i din webbläsare med moderna webbteknologier som WebAssembly erbjuder vi oöverträffad hastighet för alla våra verktyg." + }, + "free": { + "title": "Helt gratis", + "description": "Inga provperioder, inga prenumerationer, inga dolda avgifter och inga \"premium\"-funktioner bakom en spärr. Vi tycker att kraftfulla PDF-verktyg ska vara en allmän nyttighet, inte en vinstmaskin." + }, + "noAccount": { + "title": "Inget konto krävs", + "description": "Börja använda vilket verktyg som helst direkt. Vi behöver inte din e-post, ett lösenord eller någon personlig information. Ditt arbetsflöde ska vara friktionsfritt och anonymt." + }, + "openSource": { + "title": "Öppen källkod-anda", + "description": "Byggd med transparens i åtanke. Vi använder fantastiska öppna källkodsbibliotek som PDF-lib och PDF.js, och tror på community-driven utveckling för att göra kraftfulla verktyg tillgängliga för alla." + } + }, + "cta": { + "title": "Redo att komma igång?", + "description": "Gå med i tusentals användare som litar på Bentopdf för sina dagliga dokumentbehov. Upplev skillnaden som integritet och prestanda kan göra.", + "button": "Utforska alla verktyg" + } + }, + "contact": { + "title": "Kontakta oss", + "subtitle": "Vi vill gärna höra från dig. Oavsett om du har frågor, feedback eller funktionsförslag - tveka inte att höra av dig.", + "email": "Du kan nå oss direkt via e-post på:" + }, + "licensing": { + "title": "Licenser för", + "subtitle": "Välj den licens som passar dina behov." + }, + "multiTool": { + "uploadPdfs": "Ladda upp PDF:er", + "upload": "Ladda upp", + "addBlankPage": "Lägg till tom sida", + "edit": "Redigera:", + "undo": "Ångra", + "redo": "Gör om", + "reset": "Återställ", + "selection": "Markering:", + "selectAll": "Markera alla", + "deselectAll": "Avmarkera alla", + "rotate": "Rotera:", + "rotateLeft": "Vänster", + "rotateRight": "Höger", + "transform": "Transformera:", + "duplicate": "Duplicera", + "split": "Dela", + "clear": "Rensa:", + "delete": "Ta bort", + "download": "Ladda ner:", + "downloadSelected": "Ladda ner markerade", + "exportPdf": "Exportera PDF", + "uploadPdfFiles": "Välj PDF-filer", + "dragAndDrop": "Dra och släpp PDF-filer här, eller klicka för att välja", + "selectFiles": "Välj filer", + "renderingPages": "Renderar sidor...", + "actions": { + "duplicatePage": "Duplicera denna sida", + "deletePage": "Ta bort denna sida", + "insertPdf": "Infoga PDF efter denna sida", + "toggleSplit": "Växla delning efter denna sida" + }, + "pleaseWait": "Vänligen vänta", + "pagesRendering": "Sidor renderas fortfarande. Vänligen vänta...", + "noPagesSelected": "Inga sidor markerade", + "selectOnePage": "Vänligen välj minst en sida att ladda ner.", + "noPages": "Inga sidor", + "noPagesToExport": "Det finns inga sidor att exportera.", + "renderingTitle": "Renderar sidförhandsvisningar", + "errorRendering": "Det gick inte att rendera sidminiatyrer", + "error": "Fel", + "failedToLoad": "Det gick inte att ladda" + }, + "simpleMode": { + "title": "PDF-verktyg", + "subtitle": "Välj ett verktyg för att komma igång" + } +} diff --git a/public/locales/sv/tools.json b/public/locales/sv/tools.json new file mode 100644 index 0000000..d35aa9e --- /dev/null +++ b/public/locales/sv/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "Populära verktyg", + "editAnnotate": "Redigera & Kommentera", + "convertToPdf": "Konvertera till PDF", + "convertFromPdf": "Konvertera från PDF", + "organizeManage": "Organisera & Hantera", + "optimizeRepair": "Optimera & Reparera", + "securePdf": "Säkra PDF" + }, + "pdfMultiTool": { + "name": "PDF multiverktyg", + "subtitle": "Sammanfoga, dela, organisera, ta bort, rotera, lägg till tomma sidor, extrahera och duplicera i ett enhetligt gränssnitt." + }, + "mergePdf": { + "name": "Sammanfoga PDF", + "subtitle": "Kombinera flera PDF-filer till en fil. Bevarar bokmärken." + }, + "splitPdf": { + "name": "Dela PDF", + "subtitle": "Extrahera ett intervall av sidor till en ny PDF." + }, + "compressPdf": { + "name": "Komprimera PDF", + "subtitle": "Minska filstorleken på din PDF.", + "algorithmLabel": "Komprimeringsalgoritm", + "condense": "Kondensera (Rekommenderas)", + "photon": "Foton (För foto-tunga PDF:er)", + "condenseInfo": "Kondensera använder avancerad komprimering: tar bort död vikt, optimerar bilder, undergrupperar typsnitt. Bäst för de flesta PDF:er.", + "photonInfo": "Foton konverterar sidor till bilder. Använd för foto-tunga/scannade PDF:er.", + "photonWarning": "Varning: Text blir icke-valbar och länkarna slutar fungera.", + "levelLabel": "Komprimeringsnivå", + "light": "Lätt (Bevara kvalitet)", + "balanced": "Balanserad (Rekommenderas)", + "aggressive": "Aggressiv (Mindre filer)", + "extreme": "Extreme (Maximal komprimering)", + "grayscale": "Konvertera till gråskala", + "grayscaleHint": "Minskar filstorleken genom att ta bort färginformation", + "customSettings": "Anpassade inställningar", + "customSettingsHint": "Finjustera komprimeringsparametrar:", + "outputQuality": "Utdatakvalitet", + "resizeImagesTo": "Ändra bildstorlek till", + "onlyProcessAbove": "Bearbeta endast över", + "removeMetadata": "Ta bort metadata", + "subsetFonts": "Undergruppera typsnitt (ta bort oanvända glyfer)", + "removeThumbnails": "Ta bort inbäddade miniatyrer", + "compressButton": "Komprimera PDF" + }, + "pdfEditor": { + "name": "PDF-redigerare", + "subtitle": "Kommentera, markera, redigera, kommentera, lägg till former/bilder, sök och visa PDF:er." + }, + "jpgToPdf": { + "name": "JPG till PDF", + "subtitle": "Skapa en PDF från JPG, JPEG och JPEG2000 (JP2/JPX)-bilder." + }, + "signPdf": { + "name": "Signera PDF", + "subtitle": "Rita, skriv eller ladda upp din signatur." + }, + "cropPdf": { + "name": "Beskär PDF", + "subtitle": "Beskär marginalerna på varje sida i din PDF." + }, + "extractPages": { + "name": "Extrahera sidor", + "subtitle": "Spara ett urval av sidor som nya filer." + }, + "duplicateOrganize": { + "name": "Duplicera & organisera", + "subtitle": "Duplicera, ordna om och ta bort sidor." + }, + "deletePages": { + "name": "Ta bort sidor", + "subtitle": "Ta bort specifika sidor från ditt dokument." + }, + "editBookmarks": { + "name": "Redigera bokmärken", + "subtitle": "Lägg till, redigera, importera, ta bort och extrahera PDF-bokmärken." + }, + "tableOfContents": { + "name": "Innehållsförteckning", + "subtitle": "Generera en innehållsförteckningssida från PDF-bokmärken." + }, + "pageNumbers": { + "name": "Sidnummer", + "subtitle": "Infoga sidnummer i ditt dokument." + }, + "batesNumbering": { + "name": "Bates-numrering", + "subtitle": "Lägg till sekventiella Bates-nummer över en eller flera PDF-filer." + }, + "addWatermark": { + "name": "Lägg till vattenstämpel", + "subtitle": "Stämpla text eller en bild över dina PDF-sidor.", + "applyToAllPages": "Applicera på alla sidor" + }, + "headerFooter": { + "name": "Sidhuvud & sidfot", + "subtitle": "Lägg till text överst och längst ner på sidor." + }, + "invertColors": { + "name": "Invertera färger", + "subtitle": "Skapa en \"mörkt läge\"-version av din PDF." + }, + "scannerEffect": { + "name": "Skannereffekt", + "subtitle": "Få din PDF att se ut som ett scannat dokument.", + "scanSettings": "Skannerinställningar", + "colorspace": "Färgrymd", + "gray": "Grå", + "border": "Kant", + "rotate": "Rotera", + "rotateVariance": "Rotationsvarians", + "brightness": "Ljushet", + "contrast": "Kontrast", + "blur": "Oskärpa", + "noise": "Brus", + "yellowish": "Gulaktig", + "resolution": "Upplösning", + "processButton": "Applicera skannereffekt" + }, + "adjustColors": { + "name": "Justera färger", + "subtitle": "Finjustera ljusstyrka, kontrast, mättnad och mer i din PDF.", + "colorSettings": "Färginställningar", + "brightness": "Ljushet", + "contrast": "Kontrast", + "saturation": "Mättnad", + "hueShift": "Nyansskift", + "temperature": "Temperatur", + "tint": "Ton", + "gamma": "Gamma", + "sepia": "Sepia", + "processButton": "Applicera färgjusteringar" + }, + "backgroundColor": { + "name": "Bakgrundsfärg", + "subtitle": "Ändra bakgrundsfärgen på din PDF." + }, + "changeTextColor": { + "name": "Ändra textfärg", + "subtitle": "Ändra färgen på text i din PDF." + }, + "addStamps": { + "name": "Lägg till stämplar", + "subtitle": "Lägg till bildstämplar på din PDF med kommentarsverktygsfältet.", + "usernameLabel": "Stämpelanvändarnamn", + "usernamePlaceholder": "Ange ditt namn (för stämplar)", + "usernameHint": "Detta namn kommer att visas på stämplar du skapar." + }, + "removeAnnotations": { + "name": "Ta bort anteckningar", + "subtitle": "Ta bort kommentarer, markeringar och länkade." + }, + "pdfFormFiller": { + "name": "PDF-formulärifyllare", + "subtitle": "Fyll i formulär direkt i webbläsaren. Stöder även XFA-formulär." + }, + "createPdfForm": { + "name": "Skapa PDF-formulär", + "subtitle": "Skapa ifyllbara PDF-formulär med dra-och-släpp textfält." + }, + "removeBlankPages": { + "name": "Ta bort tomma sidor", + "subtitle": "Automatiskt identifiera och ta bort tomma sidor.", + "sensitivityHint": "Högre = striktare, bara helt tomma sidor. Lägre = tillåter sidor med visst innehåll." + }, + "imageToPdf": { + "name": "Bilder till PDF", + "subtitle": "Konvertera JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP till PDF." + }, + "pngToPdf": { + "name": "PNG till PDF", + "subtitle": "Skapa en PDF från en eller flera PNG-bilder." + }, + "webpToPdf": { + "name": "WebP till PDF", + "subtitle": "Skapa en PDF från en eller flera WebP-bilder." + }, + "svgToPdf": { + "name": "SVG till PDF", + "subtitle": "Skapa en PDF från en eller flera SVG-bilder." + }, + "bmpToPdf": { + "name": "BMP till PDF", + "subtitle": "Skapa en PDF från en eller flera BMP-bilder." + }, + "heicToPdf": { + "name": "HEIC till PDF", + "subtitle": "Skapa en PDF från en eller flera HEIC-bilder." + }, + "tiffToPdf": { + "name": "TIFF till PDF", + "subtitle": "Skapa en PDF från en eller flera TIFF-bilder." + }, + "textToPdf": { + "name": "Text till PDF", + "subtitle": "Konvertera en vanlig textfil till en PDF." + }, + "jsonToPdf": { + "name": "JSON till PDF", + "subtitle": "Konvertera JSON-filer till PDF-format." + }, + "pdfToJpg": { + "name": "PDF till JPG", + "subtitle": "Konvertera varje PDF-sida till en JPG-bild." + }, + "pdfToPng": { + "name": "PDF till PNG", + "subtitle": "Konvertera varje PDF-sida till en PNG-bild." + }, + "pdfToWebp": { + "name": "PDF till WebP", + "subtitle": "Konvertera varje PDF-sida till en WebP-bild." + }, + "pdfToBmp": { + "name": "PDF till BMP", + "subtitle": "Konvertera varje PDF-sida till en BMP-bild." + }, + "pdfToTiff": { + "name": "PDF till TIFF", + "subtitle": "Konvertera varje PDF-sida till en TIFF-bild." + }, + "pdfToGreyscale": { + "name": "PDF till gråskala", + "subtitle": "Konvertera alla färger till svart och vitt." + }, + "pdfToJson": { + "name": "PDF till JSON", + "subtitle": "Konvertera PDF-filer till JSON-format." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Gör en PDF sökbar och kopiaerbar." + }, + "alternateMix": { + "name": "Alternera & blanda sidor", + "subtitle": "Sammanfoga PDF:er genom att alternera sidor från varje PDF. Bevarar bokmärken." + }, + "addAttachments": { + "name": "Lägg till bilagor", + "subtitle": "Bädda in en eller flera filer i din PDF." + }, + "extractAttachments": { + "name": "Extrahera bilagor", + "subtitle": "Extrahera alla inbäddade filer från PDF(er) som en ZIP." + }, + "editAttachments": { + "name": "Redigera bilagor", + "subtitle": "Visa eller ta bort bilagor i din PDF." + }, + "dividePages": { + "name": "Dela sidor", + "subtitle": "Dela sidor horisontellt eller vertikalt." + }, + "addBlankPage": { + "name": "Lägg till tom sida", + "subtitle": "Infoga en tom sida var som helst i din PDF." + }, + "reversePages": { + "name": "Omvänd sidor", + "subtitle": "Vänd ordningen på alla sidor i ditt dokument." + }, + "rotatePdf": { + "name": "Rotera PDF", + "subtitle": "Vrid sidor i 90-graderssteg." + }, + "rotateCustom": { + "name": "Rotera efter anpassade grader", + "subtitle": "Rotera sidor efter valfri vinkel." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "Ordna flera sidor på ett enda ark." + }, + "combineToSinglePage": { + "name": "Kombinera till enskild sida", + "subtitle": "Sy ihop alla sidor till en kontinuerlig rullning." + }, + "viewMetadata": { + "name": "Visa metadata", + "subtitle": "Granska de dolda egenskaperna i din PDF." + }, + "editMetadata": { + "name": "Redigera metadata", + "subtitle": "Ändra författaren, titeln och andra egenskaper." + }, + "pdfsToZip": { + "name": "PDF:er till ZIP", + "subtitle": "Paketera flera PDF-filer till ett ZIP-arkiv." + }, + "comparePdfs": { + "name": "Jämför PDF:er", + "subtitle": "Jämför två PDF:er bredvid varandra." + }, + "posterizePdf": { + "name": "Postera PDF", + "subtitle": "Dela en stor sida i flera mindre sidor." + }, + "fixPageSize": { + "name": "Fixa sidstorlek", + "subtitle": "Standardisera alla sidor till en enhetlig storlek." + }, + "linearizePdf": { + "name": "Linearizera PDF", + "subtitle": "Optimera PDF för snabb webbvisning." + }, + "pageDimensions": { + "name": "Sidmått", + "subtitle": "Analysera sidstorlek, orientering och enheter." + }, + "removeRestrictions": { + "name": "Ta bort begränsningar", + "subtitle": "Ta bort lösenordsskydd och säkerhetsbegränsningar associerade med digitalt signerade PDF-filer." + }, + "repairPdf": { + "name": "Reparera PDF", + "subtitle": "Återställ data från skadade eller korrupta PDF-filer." + }, + "encryptPdf": { + "name": "Kryptera PDF", + "subtitle": "Lås din PDF genom att lägga till ett lösenord." + }, + "sanitizePdf": { + "name": "Sanera PDF", + "subtitle": "Ta bort metadata, anteckningar, skript och mer." + }, + "decryptPdf": { + "name": "Dekryptera PDF", + "subtitle": "Lås upp PDF genom att ta bort lösenordsskydd." + }, + "flattenPdf": { + "name": "Platta till PDF", + "subtitle": "Gör formulärfält och anteckningar icke-redigerbara." + }, + "removeMetadata": { + "name": "Ta bort metadata", + "subtitle": "Ta bort dold data från din PDF." + }, + "changePermissions": { + "name": "Ändra behörigheter", + "subtitle": "Ställ in eller ändra användarbehörigheter på en PDF." + }, + "odtToPdf": { + "name": "ODT till PDF", + "subtitle": "Konvertera OpenDocument Text-filer till PDF-format. Stöder flera filer.", + "acceptedFormats": "ODT-filer", + "convertButton": "Konvertera till PDF" + }, + "csvToPdf": { + "name": "CSV till PDF", + "subtitle": "Konvertera CSV-kalkylarksfiler till PDF-format. Stöder flera filer.", + "acceptedFormats": "CSV-filer", + "convertButton": "Konvertera till PDF" + }, + "rtfToPdf": { + "name": "RTF till PDF", + "subtitle": "Konvertera Rich Text Format-dokument till PDF. Stöder flera filer.", + "acceptedFormats": "RTF-filer", + "convertButton": "Konvertera till PDF" + }, + "wordToPdf": { + "name": "Word till PDF", + "subtitle": "Konvertera Word-dokument (DOCX, DOC, ODT, RTF) till PDF-format. Stöder flera filer.", + "acceptedFormats": "DOCX, DOC, ODT, RTF-filer", + "convertButton": "Konvertera till PDF" + }, + "excelToPdf": { + "name": "Excel till PDF", + "subtitle": "Konvertera Excel-kalkylark (XLSX, XLS, ODS, CSV) till PDF-format. Stöder flera filer.", + "acceptedFormats": "XLSX, XLS, ODS, CSV-filer", + "convertButton": "Konvertera till PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint till PDF", + "subtitle": "Konvertera PowerPoint-presentationer (PPTX, PPT, ODP) till PDF-format. Stöder flera filer.", + "acceptedFormats": "PPTX, PPT, ODP-filer", + "convertButton": "Konvertera till PDF" + }, + "markdownToPdf": { + "name": "Markdown till PDF", + "subtitle": "Skriv eller klistra in Markdown och exportera som en vackert formaterad PDF.", + "paneMarkdown": "Markdown", + "panePreview": "Förhandsvisning", + "btnUpload": "Ladda upp", + "btnSyncScroll": "Synkronisera scroll", + "btnSettings": "Inställningar", + "btnExportPdf": "Exportera PDF", + "settingsTitle": "Markdown-inställningar", + "settingsPreset": "Förinställning", + "presetDefault": "Standard (GFM-liknande)", + "presetCommonmark": "CommonMark (strikt)", + "presetZero": "Minimal (inga funktioner)", + "settingsOptions": "Markdown-alternativ", + "optAllowHtml": "Tillåt HTML-taggar", + "optBreaks": "Konvertera radbrytningar till
", + "optLinkify": "Auto-konvertera URL:er till länkade", + "optTypographer": "Typograf (smarta citattecken, etc.)" + }, + "pdfBooklet": { + "name": "PDF-broschyr", + "subtitle": "Ordna om sidor för dubbelsidig broschyutskrift. Vik och häfta för att skapa en broschyr.", + "howItWorks": "Så här fungerar det:", + "step1": "Ladda upp en PDF-fil.", + "step2": "Sidor kommer att ordnas om i broschyrordning.", + "step3": "Skriv ut dubbelsidigt, vik på kort kant, vik och häfta.", + "paperSize": "Pappersstorlek", + "orientation": "Orientering", + "portrait": "Stående", + "landscape": "Liggande", + "pagesPerSheet": "Sidor per ark", + "createBooklet": "Skapa broschyr", + "processing": "Bearbetar...", + "pageCount": "Sidantal kommer att fyllas ut till multipel av 4 om det behövs." + }, + "xpsToPdf": { + "name": "XPS till PDF", + "subtitle": "Konvertera XPS/OXPS-dokument till PDF-format. Stöder flera filer.", + "acceptedFormats": "XPS, OXPS-filer", + "convertButton": "Konvertera till PDF" + }, + "mobiToPdf": { + "name": "MOBI till PDF", + "subtitle": "Konvertera MOBI e-böcker till PDF-format. Stöder flera filer.", + "acceptedFormats": "MOBI-filer", + "convertButton": "Konvertera till PDF" + }, + "epubToPdf": { + "name": "EPUB till PDF", + "subtitle": "Konvertera EPUB e-böcker till PDF-format. Stöder flera filer.", + "acceptedFormats": "EPUB-filer", + "convertButton": "Konvertera till PDF" + }, + "fb2ToPdf": { + "name": "FB2 till PDF", + "subtitle": "Konvertera FictionBook (FB2) e-böcker till PDF-format. Stöder flera filer.", + "acceptedFormats": "FB2-filer", + "convertButton": "Konvertera till PDF" + }, + "cbzToPdf": { + "name": "CBZ till PDF", + "subtitle": "Konvertera serietidshäften (CBZ/CBR) till PDF-format. Stöder flera filer.", + "acceptedFormats": "CBZ, CBR-filer", + "convertButton": "Konvertera till PDF" + }, + "wpdToPdf": { + "name": "WPD till PDF", + "subtitle": "Konvertera WordPerfect-dokument (WPD) till PDF-format. Stöder flera filer.", + "acceptedFormats": "WPD-filer", + "convertButton": "Konvertera till PDF" + }, + "wpsToPdf": { + "name": "WPS till PDF", + "subtitle": "Konvertera WPS Office-dokument till PDF-format. Stöder flera filer.", + "acceptedFormats": "WPS-filer", + "convertButton": "Konvertera till PDF" + }, + "xmlToPdf": { + "name": "XML till PDF", + "subtitle": "Konvertera XML-dokument till PDF-format. Stöder flera filer.", + "acceptedFormats": "XML-filer", + "convertButton": "Konvertera till PDF" + }, + "pagesToPdf": { + "name": "Pages till PDF", + "subtitle": "Konvertera Apple Pages-dokument till PDF-format. Stöder flera filer.", + "acceptedFormats": "Pages-filer", + "convertButton": "Konvertera till PDF" + }, + "odgToPdf": { + "name": "ODG till PDF", + "subtitle": "Konvertera OpenDocument Graphics (ODG)-filer till PDF-format. Stöder flera filer.", + "acceptedFormats": "ODG-filer", + "convertButton": "Konvertera till PDF" + }, + "odsToPdf": { + "name": "ODS till PDF", + "subtitle": "Konvertera OpenDocument Spreadsheet (ODS)-filer till PDF-format. Stöder flera filer.", + "acceptedFormats": "ODS-filer", + "convertButton": "Konvertera till PDF" + }, + "odpToPdf": { + "name": "ODP till PDF", + "subtitle": "Konvertera OpenDocument Presentation (ODP)-filer till PDF-format. Stöder flera filer.", + "acceptedFormats": "ODP-filer", + "convertButton": "Konvertera till PDF" + }, + "pubToPdf": { + "name": "PUB till PDF", + "subtitle": "Konvertera Microsoft Publisher (PUB)-filer till PDF-format. Stöder flera filer.", + "acceptedFormats": "PUB-filer", + "convertButton": "Konvertera till PDF" + }, + "vsdToPdf": { + "name": "VSD till PDF", + "subtitle": "Konvertera Microsoft Visio (VSD, VSDX)-filer till PDF-format. Stöder flera filer.", + "acceptedFormats": "VSD, VSDX-filer", + "convertButton": "Konvertera till PDF" + }, + "psdToPdf": { + "name": "PSD till PDF", + "subtitle": "Konvertera Adobe Photoshop (PSD)-filer till PDF-format. Stöder flera filer.", + "acceptedFormats": "PSD-filer", + "convertButton": "Konvertera till PDF" + }, + "pdfToSvg": { + "name": "PDF till SVG", + "subtitle": "Konvertera varje sida i en PDF till en skalbar vektorgrafik (SVG) för perfekt kvalitet i valfri storlek." + }, + "extractTables": { + "name": "Extrahera PDF-tabeller", + "subtitle": "Extrahera tabeller från PDF-filer och exportera som CSV, JSON eller Markdown." + }, + "pdfToCsv": { + "name": "PDF till CSV", + "subtitle": "Extrahera tabeller från PDF och konvertera till CSV-format." + }, + "pdfToExcel": { + "name": "PDF till Excel", + "subtitle": "Extrahera tabeller från PDF och konvertera till Excel (XLSX)-format." + }, + "pdfToText": { + "name": "PDF till text", + "subtitle": "Extrahera text från PDF-filer och spara som vanlig text (.txt). Stöder flera filer.", + "note": "Detta verktyg fungerar ENDAST med digitalt skapade PDF:er. För scannade dokument eller bildbaserade PDF:er, använd vårt OCR PDF-verktyg istället.", + "convertButton": "Extrahera text" + }, + "digitalSignPdf": { + "name": "Digital signatur PDF", + "pageTitle": "Digital signatur PDF - Lägg till kryptografisk signatur | BentoPDF", + "subtitle": "Lägg till en kryptografisk digital signatur på din PDF med X.509-certifikat. Stöder PKCS#12 (.pfx, .p12) och PEM-format. Din privata nyckel lämnar aldrig din webbläsare.", + "certificateSection": "Certifikat", + "uploadCert": "Ladda upp certifikat (.pfx, .p12)", + "certPassword": "Certifikatlösenord", + "certPasswordPlaceholder": "Ange certifikatlösenord", + "certInfo": "Certifikatinformation", + "certSubject": "Ämne", + "certIssuer": "Utfärdare", + "certValidity": "Giltig", + "signatureDetails": "Signaturdetaljer (Valfritt)", + "reason": "Anledning", + "reasonPlaceholder": "t.ex. Jag godkänner detta dokument", + "location": "Plats", + "locationPlaceholder": "t.ex. Stockholm, Sverige", + "contactInfo": "Kontaktinformation", + "contactPlaceholder": "t.ex. email@example.com", + "applySignature": "Applicera digital signatur", + "successMessage": "PDF signerad framgångsrikt! Signaturen kan verifieras i vilken PDF-läsare som helst." + }, + "validateSignaturePdf": { + "name": "Validera PDF-signatur", + "pageTitle": "Validera PDF-signatur - Verifiera digitala signaturer | BentoPDF", + "subtitle": "Verifiera digitala signaturer i dina PDF-filer. Kontrollera certifikatgiltighet, visa signerardetaljer och bekräfta dokumentintegritet. All bearbetning sker i din webbläsare." + }, + "emailToPdf": { + "name": "E-post till PDF", + "subtitle": "Konvertera e-postfiler (EML, MSG) till PDF-format. Stöder Outlook-exporter och standard e-postformat.", + "acceptedFormats": "EML, MSG-filer", + "convertButton": "Konvertera till PDF" + }, + "fontToOutline": { + "name": "Typsnitt till kontur", + "subtitle": "Konvertera alla typsnitt till vektorkonturer för konsekvent rendering på alla enheter." + }, + "deskewPdf": { + "name": "Räta upp PDF", + "subtitle": "Automatiskt räta upp snedvinklade scannade sidor med OpenCV." + }, + "pdfToWord": { + "name": "PDF till Word", + "subtitle": "Konvertera PDF-filer till redigerbara Word-dokument." + }, + "extractImages": { + "name": "Extrahera bilder", + "subtitle": "Extrahera alla inbäddade bilder från dina PDF-filer." + }, + "pdfToMarkdown": { + "name": "PDF till Markdown", + "subtitle": "Konvertera PDF-text och tabeller till Markdown-format." + }, + "preparePdfForAi": { + "name": "Förbered PDF för AI", + "subtitle": "Extrahera PDF-innehåll som LlamaIndex JSON för RAG/LLM-pipelines." + }, + "pdfOcg": { + "name": "PDF OCG", + "subtitle": "Visa, växla, lägg till och ta bort OCG-lager i din PDF." + }, + "pdfToPdfa": { + "name": "PDF till PDF/A", + "subtitle": "Konvertera PDF till PDF/A för långtidsarkivering." + }, + "rasterizePdf": { + "name": "Rasterisera PDF", + "subtitle": "Konvertera PDF till bildbaserad PDF. Platta till lager och ta bort valbar text." + }, + "pdfWorkflow": { + "name": "PDF-arbetsflödesbyggare", + "subtitle": "Bygg anpassade PDF-bearbetningspipelines med en visuell nodredigerare.", + "nodes": "Noder", + "searchNodes": "Sök noder...", + "run": "Kör", + "clear": "Rensa", + "save": "Spara", + "load": "Ladda", + "export": "Exportera", + "import": "Importera", + "ready": "Redo", + "settings": "Inställningar", + "processing": "Bearbetar...", + "saveTemplate": "Spara mall", + "templateName": "Mallnamn", + "templatePlaceholder": "t.ex. Faktura-arbetsflöde", + "cancel": "Avbryt", + "loadTemplate": "Ladda mall", + "noTemplates": "Inga sparade mallar ännu.", + "ok": "OK", + "workflowCompleted": "Arbetsflöde klart", + "errorDuringExecution": "Fel under körning", + "addNodeError": "Lägg till minst en nod för att köra arbetsflödet.", + "needInputOutput": "Ditt arbetsflöde behöver minst en inmatningsnod och en utmatningsnod för att köra.", + "enterName": "Vänligen ange ett namn.", + "templateExists": "En mall med detta namn finns redan.", + "templateSaved": "Mallen \"{{name}}\" sparad.", + "templateLoaded": "Mallen \"{{name}}\" laddad.", + "failedLoadTemplate": "Det gick inte att ladda mallen.", + "noSettings": "Inga konfigurerbara inställningar för denna nod.", + "advancedSettings": "Avancerade inställningar" + } +} diff --git a/public/locales/tr/common.json b/public/locales/tr/common.json new file mode 100644 index 0000000..ca6fceb --- /dev/null +++ b/public/locales/tr/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "Ana Sayfa", + "about": "Hakkımızda", + "contact": "İletişim", + "licensing": "Lisanslama", + "allTools": "Tüm Araçlar", + "openMainMenu": "Ana menüyü aç", + "language": "Dil" + }, + "donation": { + "message": "BentoPDF'i seviyor musunuz? Ücretsiz ve açık kaynaklı kalmasına yardımcı olun!", + "button": "Bağış Yap" + }, + "hero": { + "title": " ", + "pdfToolkit": "PDF Toolkit", + "builtForPrivacy": "gizlilik için tasarlandı", + "noSignups": "Kayıt Gerekmez", + "unlimitedUse": "Sınırsız Kullanım", + "worksOffline": "Çevrimdışı Çalışır", + "startUsing": "Hemen Kullanmaya Başla" + }, + "usedBy": { + "title": "Şu şirketler ve çalışanları tarafından kullanılıyor" + }, + "features": { + "title": "Neden", + "bentoPdf": "BentoPDF'yi seçmelisiniz?", + "noSignup": { + "title": "Kayıt Gerekmez", + "description": "Hemen başlayın, hesap veya e-posta gerekmez." + }, + "noUploads": { + "title": "Yükleme Yok", + "description": "%100 istemci tarafında çalışır, dosyalarınız cihazınızı asla terk etmez." + }, + "foreverFree": { + "title": "Tamamen Ücretsiz", + "description": "Tüm araçlar, deneme sürümü yok, ödeme duvarı yok." + }, + "noLimits": { + "title": "Sınırsız", + "description": "İstediğiniz kadar kullanın, gizli sınırlar yok." + }, + "batchProcessing": { + "title": "Toplu İşlem", + "description": "Sınırsız sayıda PDF'yi tek seferde işleyin." + }, + "lightningFast": { + "title": "Şimşek Hızında", + "description": "PDF'leri anında işleyin, bekleme veya gecikme olmadan." + } + }, + "tools": { + "title": "Araçlarla", + "toolsLabel": "Başlayın", + "subtitle": "Dosya yükleyiciyi açmak için bir araç seçin", + "searchPlaceholder": "Bir araç arayın (örn. 'böl', 'düzenle'...)", + "backToTools": "Araçlara Dön", + "firstLoadNotice": "Dönüştürme motorumuzu indirirken ilk yükleme biraz zaman alır. Sonrasında tüm yüklemeler anında gerçekleşir." + }, + "upload": { + "clickToSelect": "Dosya seçmek için tıklayın", + "orDragAndDrop": "veya sürükleyip bırakın", + "pdfOrImages": "PDF veya Görseller", + "filesNeverLeave": "Dosyalarınız cihazınızı asla terk etmez.", + "addMore": "Daha Fazla Dosya Ekle", + "clearAll": "Tümünü Temizle", + "clearFiles": "Dosyaları temizle", + "hints": { + "singlePdf": "Tek bir PDF dosyası", + "pdfFile": "PDF dosyası", + "multiplePdfs2": "Birden fazla PDF dosyası (en az 2)", + "bmpImages": "BMP görüntüler", + "oneOrMorePdfs": "Bir veya daha fazla PDF dosyası", + "pdfDocuments": "PDF belgeleri", + "oneOrMoreCsv": "Bir veya daha fazla CSV dosyası", + "multiplePdfsSupported": "Birden fazla PDF dosyası desteklenir", + "singleOrMultiplePdfs": "Tek veya birden fazla PDF dosyası desteklenir", + "singlePdfFile": "Tek PDF dosyası", + "pdfWithForms": "Form alanları olan PDF dosyası", + "heicImages": "HEIC/HEIF görüntüler", + "jpgImages": "JPG, JPEG, JP2, JPX Görüntüler", + "pdfsOrImages": "PDF'ler veya görüntüler", + "oneOrMoreOdt": "Bir veya daha fazla ODT dosyası", + "singlePdfOnly": "Yalnızca tek PDF dosyası", + "pdfFiles": "PDF dosyaları", + "multiplePdfs": "Birden fazla PDF dosyası", + "pngImages": "PNG Görüntüler", + "pdfFilesOneOrMore": "PDF dosyaları (bir veya daha fazla)", + "oneOrMoreRtf": "Bir veya daha fazla RTF dosyası", + "svgGraphics": "SVG Grafikler", + "tiffImages": "TIFF görüntüler", + "webpImages": "WebP Görüntüler" + } + }, + "loader": { + "processing": "İşleniyor..." + }, + "alert": { + "title": "Uyarı", + "ok": "Tamam" + }, + "preview": { + "title": "Belge Önizleme", + "downloadAsPdf": "PDF Olarak İndir", + "close": "Kapat" + }, + "settings": { + "title": "Ayarlar", + "shortcuts": "Kısayollar", + "preferences": "Tercihler", + "displayPreferences": "Görüntü Tercihleri", + "searchShortcuts": "Kısayollarda ara...", + "shortcutsInfo": "Bir kısayol atamak için tuşlara basılı tutun. Değişiklikler otomatik olarak kaydedilir.", + "shortcutsWarning": "⚠️ Güvenilir çalışmayabileceğinden yaygın tarayıcı kısayollarından (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N vb.) kaçının.", + "import": "İçe Aktar", + "export": "Dışa Aktar", + "resetToDefaults": "Varsayılanlara Sıfırla", + "fullWidthMode": "Tam Genişlik Modu", + "fullWidthDescription": "Ortalanmış bir konteyner yerine tüm ekran genişliğini kullan", + "settingsAutoSaved": "Ayarlar otomatik olarak kaydedildi", + "clickToSet": "Ayarlamak için tıklayın", + "pressKeys": "Tuşlara basın...", + "warnings": { + "alreadyInUse": "Kısayol Zaten Kullanımda", + "assignedTo": "zaten şurada kullanılıyor:", + "chooseDifferent": "Lütfen farklı bir kısayol seçin.", + "reserved": "Ayrılmış Kısayol Uyarısı", + "commonlyUsed": "genellikle şunun için kullanılır:", + "unreliable": "Bu kısayol güvenilir çalışmayabilir veya tarayıcı/sistem davranışıyla çakışabilir.", + "useAnyway": "Yine de kullanmak istiyor musunuz?", + "resetTitle": "Kısayolları Sıfırla", + "resetMessage": "Tüm kısayolları varsayılan ayarlara sıfırlamak istediğinizden emin misiniz?

Bu işlem geri alınamaz.", + "importSuccessTitle": "İçe Aktarma Başarılı", + "importSuccessMessage": "Kısayollar başarıyla içe aktarıldı!", + "importFailTitle": "İçe Aktarma Başarısız", + "importFailMessage": "Kısayollar içe aktarılamadı. Geçersiz dosya biçimi." + } + }, + "warning": { + "title": "Uyarı", + "cancel": "İptal", + "proceed": "Devam Et" + }, + "compliance": { + "title": "Verileriniz cihazınızı asla terk etmez", + "weKeep": "Bilgilerinizi", + "yourInfoSafe": "güvende tutuyoruz", + "byFollowingStandards": "küresel güvenlik standartlarını takip ederek.", + "processingLocal": "Tüm işlemler cihazınızda yerel olarak gerçekleşir.", + "gdpr": { + "title": "GDPR uyumluluğu", + "description": "Avrupa Birliği'ndeki bireylerin kişisel verilerini ve gizliliğini korur." + }, + "ccpa": { + "title": "CCPA uyumluluğu", + "description": "Kaliforniya sakinlerine kişisel bilgilerinin nasıl toplandığı, kullanıldığı ve paylaşıldığı konusunda haklar tanır." + }, + "hipaa": { + "title": "HIPAA uyumluluğu", + "description": "ABD sağlık sisteminde hassas sağlık bilgilerinin işlenmesi için güvenlik önlemleri belirler." + } + }, + "faq": { + "title": "Sıkça Sorulan", + "questions": "Sorular", + "isFree": { + "question": "BentoPDF gerçekten ücretsiz mi?", + "answer": "Evet, kesinlikle. BentoPDF'deki tüm araçlar %100 ücretsizdir, dosya sınırı yoktur, kayıt gerekmez ve filigran eklenmez. Herkesin ödeme duvarı olmadan basit, güçlü PDF araçlarına erişimi hak ettiğine inanıyoruz." + }, + "areFilesSecure": { + "question": "Dosyalarım güvende mi? Nerede işleniyorlar?", + "answer": "Dosyalarınız mümkün olan en güvenli şekildedir çünkü bilgisayarınızı asla terk etmezler. Tüm işlemler doğrudan web tarayıcınızda (istemci tarafında) gerçekleşir. Dosyalarınızı asla bir sunucuya yüklemeyiz, böylece gizliliğiniz ve belgeleriniz üzerindeki kontrolünüz tam olarak sizde kalır." + }, + "platforms": { + "question": "Mac, Windows ve Mobil'de çalışıyor mu?", + "answer": "Evet! BentoPDF tamamen tarayıcınızda çalıştığı için, Windows, macOS, Linux, iOS ve Android dahil modern bir web tarayıcısı olan herhangi bir işletim sisteminde çalışır." + }, + "gdprCompliant": { + "question": "BentoPDF GDPR uyumlu mu?", + "answer": "Evet. BentoPDF tamamen GDPR uyumludur. Tüm dosya işlemleri tarayıcınızda yerel olarak gerçekleştiği ve dosyalarınızı herhangi bir sunucuya asla iletmediğimiz için verilerinize erişimimiz yoktur. Bu, belgeleriniz üzerindeki kontrolün her zaman sizde olduğundan emin olur." + }, + "dataStorage": { + "question": "Dosyalarımı saklıyor veya takip ediyor musunuz?", + "answer": "Hayır. Dosyalarınızı asla saklamıyor, takip etmiyor veya kaydetmiyoruz. BentoPDF'de yaptığınız her şey tarayıcı belleğinizde gerçekleşir ve sayfayı kapattığınızda silinir. Yükleme, geçmiş kaydı veya sunucu yoktur." + }, + "different": { + "question": "BentoPDF'yi diğer PDF araçlarından farklı kılan nedir?", + "answer": "Çoğu PDF aracı, işlem için dosyalarınızı bir sunucuya yükler. BentoPDF asla böyle yapmaz. Dosyalarınızı doğrudan tarayıcınızda işlemek için güvenli, modern web teknolojileri kullanırız. Bu, daha hızlı performans, daha güçlü gizlilik ve tam bir gönül rahatlığı anlamına gelir." + }, + "browserBased": { + "question": "Tarayıcı tabanlı işlem beni nasıl korur?", + "answer": "Tamamen tarayıcınızın içinde çalışarak, BentoPDF dosyalarınızın cihazınızı asla terk etmemesini sağlar. Bu, sunucu saldırıları, veri ihlalleri veya yetkisiz erişim risklerini ortadan kaldırır. Dosyalarınız her zaman sizin kalır." + }, + "analytics": { + "question": "Beni takip etmek için çerez veya analiz kullanıyor musunuz?", + "answer": "Gizliliğinizi önemsiyoruz. BentoPDF kişisel bilgileri takip etmez. Sadece anonim ziyaretçi sayılarını görmek için Simple Analytics kullanıyoruz. Bu, sitemizi kaç kişinin ziyaret ettiğini görebileceğimiz, ancak kim olduğunuzu asla bilemeyeceğimiz anlamına gelir. Simple Analytics tamamen GDPR uyumludur ve gizliliğinize saygı gösterir." + }, + "sectionTitle": "Sıkça Sorulan Sorular" + }, + "testimonials": { + "title": "Kullanıcılarımız", + "users": "Ne Diyor", + "say": "" + }, + "support": { + "title": "Çalışmamı Beğendiniz mi?", + "description": "BentoPDF, herkes için ücretsiz, özel ve güçlü bir PDF araç seti sağlamak amacıyla oluşturulmuş bir tutku projesidir. Faydalı bulduysanız, geliştirilmesini desteklemeyi düşünebilirsiniz. Her kahve yardımcı olur!", + "buyMeCoffee": "Bana Kahve Ismarla" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Tüm hakları saklıdır.", + "version": "Sürüm", + "company": "Şirket", + "aboutUs": "Hakkımızda", + "faqLink": "SSS", + "contactUs": "İletişim", + "legal": "Yasal", + "termsAndConditions": "Kullanım Koşulları", + "privacyPolicy": "Gizlilik Politikası", + "followUs": "Bizi Takip Edin" + }, + "merge": { + "title": "PDF Birleştir", + "description": "Dosyaların tamamını birleştirin veya yeni bir belge oluşturmak için belirli sayfaları seçin.", + "fileMode": "Dosya Modu", + "pageMode": "Sayfa Modu", + "howItWorks": "Nasıl Çalışır:", + "fileModeInstructions": [ + "Dosyaların sırasını değiştirmek için simgeyi tıklayıp sürükleyin.", + "Her dosya için \"Sayfalar\" kutusuna, yalnızca o sayfaları birleştirmek için aralıklar belirtebilirsiniz (örn. \"1-3, 5\").", + "Tüm sayfaları dahil etmek için \"Sayfalar\" kutusunu boş bırakın." + ], + "pageModeInstructions": [ + "Yüklediğiniz PDF'lerin tüm sayfaları aşağıda gösterilmiştir.", + "Yeni dosyanız için istediğiniz sırayı oluşturmak üzere sayfa küçük resimlerini sürükleyip bırakmanız yeterlidir." + ], + "mergePdfs": "PDF'leri Birleştir" + }, + "common": { + "page": "Sayfa", + "pages": "Sayfa", + "of": "- ", + "download": "İndir", + "cancel": "İptal", + "save": "Kaydet", + "delete": "Sil", + "edit": "Düzenle", + "add": "Ekle", + "remove": "Kaldır", + "loading": "Yükleniyor...", + "error": "Hata", + "success": "Başarılı", + "file": "Dosya", + "files": "Dosya", + "close": "Kapat" + }, + "about": { + "hero": { + "title": "PDF araçlarının", + "subtitle": "hızlı, özel ve ücretsiz olması gerektiğine inanıyoruz.", + "noCompromises": "Taviz yok." + }, + "mission": { + "title": "Misyonumuz", + "description": "Gizliliğinize saygı duyan ve asla ödeme talep etmeyen en kapsamlı PDF araç setini sağlamak. Temel belge araçlarının her yerde, herkes için erişilebilir olması gerektiğine inanıyoruz." + }, + "philosophy": { + "label": "Temel Felsefemiz", + "title": "Öncelik Her Zaman Gizlilik.", + "description": "Verinin bir meta olarak kabul edildiği bir çağda, farklı bir yaklaşım benimsiyoruz. Bentopdf araçları için tüm işlemler tarayıcınızda yerel olarak gerçekleşir. Bu, dosyalarınızın sunucularımıza asla dokunmadığı, belgelerinizi asla görmediğimiz ve ne yaptığınızı takip etmediğimiz anlamına gelir. Belgeleriniz tamamen ve kesinlikle özel kalır. Bu sadece bir özellik değil, temelimizdir." + }, + "whyBentopdf": { + "title": "Neden", + "speed": { + "title": "Hız İçin Tasarlandı", + "description": "Sunucuya yükleme veya indirme için bekleme yok. WebAssembly gibi modern web teknolojilerini kullanarak dosyaları doğrudan tarayıcınızda işleyerek, tüm araçlarımız için benzersiz bir hız sunuyoruz." + }, + "free": { + "title": "Tamamen Ücretsiz", + "description": "Deneme sürümü yok, abonelik yok, gizli ücret yok ve \"premium\" özellikler rehin alınmamış. Güçlü PDF araçlarının bir kar merkezi değil, bir kamu hizmeti olması gerektiğine inanıyoruz." + }, + "noAccount": { + "title": "Hesap Gerekmez", + "description": "Hemen herhangi bir aracı kullanmaya başlayın. E-postanıza, şifrenize veya herhangi bir kişisel bilginize ihtiyacımız yok. İş akışınız sürtünmesiz ve anonim olmalıdır." + }, + "openSource": { + "title": "Açık Kaynak Ruhu", + "description": "Şeffaflık düşünülerek oluşturuldu. PDF-lib ve PDF.js gibi inanılmaz açık kaynaklı kütüphanelerden yararlanıyoruz ve güçlü araçları herkes için erişilebilir kılmak için topluluk odaklı çabaya inanıyoruz." + } + }, + "cta": { + "title": "Başlamaya hazır mısınız?", + "description": "Günlük belge ihtiyaçları için BentoPDF'ye güvenen binlerce kullanıcıya katılın. Gizlilik ve performansın yaratabileceği farkı deneyimleyin.", + "button": "Tüm Araçları Keşfet" + } + }, + "contact": { + "title": "İletişime Geçin", + "subtitle": "Sizden haber almak isteriz. Bir sorunuz, geri bildiriminiz veya bir özellik isteğiniz varsa, lütfen bize ulaşmaktan çekinmeyin.", + "email": "Bize doğrudan şu e-posta adresinden ulaşabilirsiniz:" + }, + "licensing": { + "title": "Lisanslama", + "subtitle": "İhtiyaçlarınıza uygun lisansı seçin." + }, + "multiTool": { + "uploadPdfs": "PDF Yükle", + "upload": "Yükle", + "addBlankPage": "Boş Sayfa Ekle", + "edit": "Düzenle:", + "undo": "Geri Al", + "redo": "Yinele", + "reset": "Sıfırla", + "selection": "Seçim:", + "selectAll": "Tümünü Seç", + "deselectAll": "Seçimi Kaldır", + "rotate": "Döndür:", + "rotateLeft": "Sola", + "rotateRight": "Sağa", + "transform": "Dönüştür:", + "duplicate": "Çoğalt", + "split": "Böl", + "clear": "Temizle:", + "delete": "Sil", + "download": "İndir:", + "downloadSelected": "Seçilenleri İndir", + "exportPdf": "PDF Olarak Dışa Aktar", + "uploadPdfFiles": "PDF Dosyalarını Seçin", + "dragAndDrop": "PDF dosyalarını buraya sürükleyip bırakın veya seçmek için tıklayın", + "selectFiles": "Dosya Seç", + "renderingPages": "Sayfalar oluşturuluyor...", + "actions": { + "duplicatePage": "Bu sayfayı çoğalt", + "deletePage": "Bu sayfayı sil", + "insertPdf": "Bu sayfadan sonra PDF ekle", + "toggleSplit": "Bu sayfadan sonra bölmeyi aç/kapat" + }, + "pleaseWait": "Lütfen Bekleyin", + "pagesRendering": "Sayfalar hala oluşturuluyor. Lütfen bekleyin...", + "noPagesSelected": "Hiçbir Sayfa Seçilmedi", + "selectOnePage": "Lütfen indirmek için en az bir sayfa seçin.", + "noPages": "Sayfa Yok", + "noPagesToExport": "Dışa aktarılacak sayfa yok.", + "renderingTitle": "Sayfa önizlemeleri oluşturuluyor", + "errorRendering": "Sayfa küçük resimleri oluşturulamadı", + "error": "Hata", + "failedToLoad": "Yüklenemedi" + }, + "howItWorks": { + "title": "Nasıl Çalışır", + "step1": "Dosyanızı tıklayın veya sürükleyin", + "step2": "İşlem düğmesine tıklayın", + "step3": "İşlenmiş dosyanızı anında kaydedin" + }, + "relatedTools": { + "title": "İlgili PDF Araçları" + }, + "simpleMode": { + "title": "PDF Araçları", + "subtitle": "Başlamak için bir araç seçin" + } +} diff --git a/public/locales/tr/tools.json b/public/locales/tr/tools.json new file mode 100644 index 0000000..35a207a --- /dev/null +++ b/public/locales/tr/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "Popüler Araçlar", + "editAnnotate": "Düzenle & Açıklama Ekle", + "convertToPdf": "PDF'ye Dönüştür", + "convertFromPdf": "PDF'den Dönüştür", + "organizeManage": "Düzenle & Yönet", + "optimizeRepair": "Optimize Et & Onar", + "securePdf": "PDF Güvenliği" + }, + "pdfMultiTool": { + "name": "PDF Çoklu Araç", + "subtitle": "Birleştir, Böl, Düzenle, Sil, Döndür, Boş Sayfa Ekle, Çıkar ve Çoğalt işlemlerini tek bir arayüzde yapın." + }, + "mergePdf": { + "name": "PDF Birleştir", + "subtitle": "Birden fazla PDF'yi tek bir dosyada birleştirin. Yer imlerini korur." + }, + "splitPdf": { + "name": "PDF Böl", + "subtitle": "Sayfa aralığını yeni bir PDF olarak çıkarın." + }, + "compressPdf": { + "name": "PDF Sıkıştır", + "subtitle": "PDF dosya boyutunu küçültün.", + "algorithmLabel": "Sıkıştırma Algoritması", + "condense": "Condense (Önerilen)", + "photon": "Photon (Fotoğraf Ağırlıklı PDF'ler İçin)", + "condenseInfo": "Condense gelişmiş sıkıştırma kullanır: gereksiz verileri kaldırır, görselleri optimize eder, fontları küçültür. Çoğu PDF için idealdir.", + "photonInfo": "Photon sayfaları görsellere dönüştürür. Fotoğraf ağırlıklı/taranmış PDF'ler için kullanın.", + "photonWarning": "Uyarı: Metin seçilemez hale gelecek ve bağlantılar çalışmayacak.", + "levelLabel": "Sıkıştırma Seviyesi", + "light": "Hafif (Kaliteyi Koru)", + "balanced": "Dengeli (Önerilen)", + "aggressive": "Agresif (Daha Küçük Dosyalar)", + "extreme": "Aşırı (Maksimum Sıkıştırma)", + "grayscale": "Gri Tonlamaya Dönüştür", + "grayscaleHint": "Renk bilgisini kaldırarak dosya boyutunu küçültür", + "customSettings": "Özel Ayarlar", + "customSettingsHint": "Sıkıştırma parametrelerini ince ayarlayın:", + "outputQuality": "Çıktı Kalitesi", + "resizeImagesTo": "Görselleri Şu Boyuta Dönüştür", + "onlyProcessAbove": "Yalnızca Şunun Üzerini İşle", + "removeMetadata": "Meta verileri kaldır", + "subsetFonts": "Font alt kümesi oluştur (kullanılmayan glifleri kaldır)", + "removeThumbnails": "Gömülü küçük resimleri kaldır", + "compressButton": "PDF'yi Sıkıştır" + }, + "pdfEditor": { + "name": "PDF Düzenleyici", + "subtitle": "Açıklama ekleyin, vurgulayın, düzenleyin, yorum yapın, şekil/resim ekleyin, arama yapın ve PDF'leri görüntüleyin." + }, + "jpgToPdf": { + "name": "JPG'den PDF'ye", + "subtitle": "Bir veya daha fazla JPG görselinden PDF oluşturun." + }, + "signPdf": { + "name": "PDF İmzala", + "subtitle": "İmzanızı çizin, yazın veya yükleyin." + }, + "cropPdf": { + "name": "PDF Kırp", + "subtitle": "PDF'nizdeki her sayfanın kenar boşluklarını kırpın." + }, + "extractPages": { + "name": "Sayfaları Çıkar", + "subtitle": "Seçili sayfaları yeni dosyalar olarak kaydedin." + }, + "duplicateOrganize": { + "name": "Çoğalt & Düzenle", + "subtitle": "Sayfaları çoğaltın, yeniden sıralayın ve silin." + }, + "deletePages": { + "name": "Sayfaları Sil", + "subtitle": "Belgenizden belirli sayfaları kaldırın." + }, + "editBookmarks": { + "name": "Yer İşaretlerini Düzenle", + "subtitle": "PDF yer imlerini ekleyin, düzenleyin, içe aktarın, silin ve çıkarın." + }, + "tableOfContents": { + "name": "İçindekiler", + "subtitle": "PDF yer imlerinden bir içindekiler sayfası oluşturun." + }, + "pageNumbers": { + "name": "Sayfa Numaraları", + "subtitle": "Belgenize sayfa numaraları ekleyin." + }, + "batesNumbering": { + "name": "Bates Numaralandırma", + "subtitle": "Bir veya daha fazla PDF dosyasına sıralı Bates numaraları ekleyin." + }, + "addWatermark": { + "name": "Filigran Ekle", + "subtitle": "PDF sayfalarınızın üzerine metin veya görsel damgası ekleyin.", + "applyToAllPages": "Tüm sayfalara uygula" + }, + "headerFooter": { + "name": "Üst Bilgi & Alt Bilgi", + "subtitle": "Sayfaların üst ve alt kısmına metin ekleyin." + }, + "invertColors": { + "name": "Renkleri Ters Çevir", + "subtitle": "PDF'niz için \"karanlık mod\" sürümü oluşturun." + }, + "scannerEffect": { + "name": "Tarayıcı Efekti", + "subtitle": "PDF'nizi taranmış bir belge gibi gösterin.", + "scanSettings": "Tarama Ayarları", + "colorspace": "Renk Alanı", + "gray": "Gri", + "border": "Kenarlık", + "rotate": "Döndür", + "rotateVariance": "Döndürme Varyansı", + "brightness": "Parlaklık", + "contrast": "Kontrast", + "blur": "Bulanıklık", + "noise": "Gürültü", + "yellowish": "Sarımsı", + "resolution": "Çözünürlük", + "processButton": "Tarayıcı Efekti Uygula" + }, + "adjustColors": { + "name": "Renkleri Ayarla", + "subtitle": "PDF'inizde parlaklık, kontrast, doygunluk ve daha fazlasını ayarlayın.", + "colorSettings": "Renk Ayarları", + "brightness": "Parlaklık", + "contrast": "Kontrast", + "saturation": "Doygunluk", + "hueShift": "Ton Kaydırma", + "temperature": "Sıcaklık", + "tint": "Renk Tonu", + "gamma": "Gamma", + "sepia": "Sepya", + "processButton": "Renk Ayarlarını Uygula" + }, + "backgroundColor": { + "name": "Arka Plan Rengi", + "subtitle": "PDF'nizin arka plan rengini değiştirin." + }, + "changeTextColor": { + "name": "Metin Rengini Değiştir", + "subtitle": "PDF'nizdeki metnin rengini değiştirin." + }, + "addStamps": { + "name": "Damga Ekle", + "subtitle": "Açıklama araç çubuğunu kullanarak PDF'nize damga ekleyin.", + "usernameLabel": "Kullanıcı Adı", + "usernamePlaceholder": "Adınızı girin (damgalar için)", + "usernameHint": "Bu isim oluşturduğunuz damgalarda görünecektir." + }, + "removeAnnotations": { + "name": "Açıklamaları Kaldır", + "subtitle": "Yorumları, vurguları ve bağlantıları kaldırın." + }, + "pdfFormFiller": { + "name": "PDF Form Doldurucu", + "subtitle": "Formları doğrudan tarayıcıda doldurun. XFA formlarını da destekler." + }, + "createPdfForm": { + "name": "PDF Formu Oluştur", + "subtitle": "Sürükle-bırak metin alanları ile doldurulabilir PDF formları oluşturun." + }, + "removeBlankPages": { + "name": "Boş Sayfaları Kaldır", + "subtitle": "Boş sayfaları otomatik olarak tespit edin ve silin.", + "sensitivityHint": "Yüksek = daha katı, yalnızca tamamen boş sayfalar. Düşük = biraz içerik olan sayfalara izin verir." + }, + "imageToPdf": { + "name": "Görselden PDF'ye", + "subtitle": "JPG, PNG, WebP, BMP, TIFF, SVG, HEIC formatlarını PDF'ye dönüştürün." + }, + "pngToPdf": { + "name": "PNG'den PDF'ye", + "subtitle": "Bir veya daha fazla PNG görselinden PDF oluşturun." + }, + "webpToPdf": { + "name": "WebP'den PDF'ye", + "subtitle": "Bir veya daha fazla WebP görselinden PDF oluşturun." + }, + "svgToPdf": { + "name": "SVG'den PDF'ye", + "subtitle": "Bir veya daha fazla SVG görselinden PDF oluşturun." + }, + "bmpToPdf": { + "name": "BMP'den PDF'ye", + "subtitle": "Bir veya daha fazla BMP görselinden PDF oluşturun." + }, + "heicToPdf": { + "name": "HEIC'den PDF'ye", + "subtitle": "Bir veya daha fazla HEIC görselinden PDF oluşturun." + }, + "tiffToPdf": { + "name": "TIFF'den PDF'ye", + "subtitle": "Bir veya daha fazla TIFF görselinden PDF oluşturun." + }, + "textToPdf": { + "name": "Metinden PDF'ye", + "subtitle": "Düz metin dosyasını PDF'ye dönüştürün." + }, + "jsonToPdf": { + "name": "JSON'dan PDF'ye", + "subtitle": "JSON dosyalarını PDF formatına dönüştürün." + }, + "pdfToJpg": { + "name": "PDF'den JPG'ye", + "subtitle": "Her PDF sayfasını JPG görseline dönüştürün." + }, + "pdfToPng": { + "name": "PDF'den PNG'ye", + "subtitle": "Her PDF sayfasını PNG görseline dönüştürün." + }, + "pdfToWebp": { + "name": "PDF'den WebP'ye", + "subtitle": "Her PDF sayfasını WebP görseline dönüştürün." + }, + "pdfToBmp": { + "name": "PDF'den BMP'ye", + "subtitle": "Her PDF sayfasını BMP görseline dönüştürün." + }, + "pdfToTiff": { + "name": "PDF'den TIFF'e", + "subtitle": "Her PDF sayfasını TIFF görseline dönüştürün." + }, + "pdfToGreyscale": { + "name": "PDF'yi Gri Tonlamaya Çevir", + "subtitle": "Tüm renkleri siyah beyaza çevirin." + }, + "pdfToJson": { + "name": "PDF'den JSON'a", + "subtitle": "PDF dosyalarını JSON formatına dönüştürün." + }, + "ocrPdf": { + "name": "PDF'de OCR", + "subtitle": "PDF'yi aranabilir ve kopyalanabilir hale getirin." + }, + "alternateMix": { + "name": "Sayfaları Karıştır & Birleştir", + "subtitle": "PDF'leri her birinden sayfaları sırayla birleştirin. Yer imlerini korur." + }, + "addAttachments": { + "name": "Ek Dosya Ekle", + "subtitle": "PDF'nize bir veya daha fazla dosya ekleyin." + }, + "extractAttachments": { + "name": "Ek Dosyaları Çıkar", + "subtitle": "PDF'lerden tüm gömülü dosyaları ZIP olarak çıkarın." + }, + "editAttachments": { + "name": "Ek Dosyaları Düzenle", + "subtitle": "PDF'nizdeki ek dosyaları görüntüleyin veya kaldırın." + }, + "dividePages": { + "name": "Sayfaları Böl", + "subtitle": "Sayfaları yatay veya dikey olarak bölün." + }, + "addBlankPage": { + "name": "Boş Sayfa Ekle", + "subtitle": "PDF'nize herhangi bir yerine boş sayfa ekleyin." + }, + "reversePages": { + "name": "Sayfaları Ters Çevir", + "subtitle": "Belgenizdeki tüm sayfaların sırasını tersine çevirin." + }, + "rotatePdf": { + "name": "PDF'yi Döndür", + "subtitle": "Sayfaları 90 derecelik artışlarla döndürün." + }, + "nUpPdf": { + "name": "N'li PDF", + "subtitle": "Birden fazla sayfayı tek bir sayfaya yerleştirin." + }, + "combineToSinglePage": { + "name": "Tek Sayfada Birleştir", + "subtitle": "Tüm sayfaları tek bir sürekli kaydırılabilir sayfada birleştirin." + }, + "viewMetadata": { + "name": "Üst Veriyi Görüntüle", + "subtitle": "PDF'nizin gizli özelliklerini inceleyin." + }, + "editMetadata": { + "name": "Üst Veriyi Düzenle", + "subtitle": "Yazar, başlık ve diğer özellikleri değiştirin." + }, + "pdfsToZip": { + "name": "PDF'leri ZIP Yap", + "subtitle": "Birden fazla PDF dosyasını bir ZIP arşivinde paketleyin." + }, + "comparePdfs": { + "name": "PDF'leri Karşılaştır", + "subtitle": "İki PDF'yi yan yana karşılaştırın." + }, + "posterizePdf": { + "name": "PDF'yi Posta Boyutuna Böl", + "subtitle": "Büyük bir sayfayı birden fazla küçük sayfaya bölün." + }, + "fixPageSize": { + "name": "Sayfa Boyutunu Düzelt", + "subtitle": "Tüm sayfaları standart bir boyuta getirin." + }, + "linearizePdf": { + "name": "PDF'yi Doğrusallaştır", + "subtitle": "Hızlı web görüntüleme için PDF'yi optimize edin." + }, + "pageDimensions": { + "name": "Sayfa Boyutları", + "subtitle": "Sayfa boyutunu, yönlendirmeyi ve birimleri analiz edin." + }, + "removeRestrictions": { + "name": "Kısıtlamaları Kaldır", + "subtitle": "Dijital olarak imzalanmış PDF dosyalarıyla ilişkili şifre korumasını ve güvenlik kısıtlamalarını kaldırın." + }, + "repairPdf": { + "name": "PDF'yi Onar", + "subtitle": "Bozulmuş veya hasarlı PDF dosyalarından veri kurtarın." + }, + "encryptPdf": { + "name": "PDF'yi Şifrele", + "subtitle": "PDF'nizi şifre ekleyerek koruyun." + }, + "sanitizePdf": { + "name": "PDF'yi Temizle", + "subtitle": "Üst verileri, açıklamaları, betikleri ve daha fazlasını kaldırın." + }, + "decryptPdf": { + "name": "PDF'nin Şifresini Çöz", + "subtitle": "Şifre korumasını kaldırarak PDF'nin kilidini açın." + }, + "flattenPdf": { + "name": "PDF'yi Düzleştir", + "subtitle": "Form alanlarını ve açıklamaları düzenlenemez hale getirin." + }, + "removeMetadata": { + "name": "Üst Veriyi Kaldır", + "subtitle": "PDF'nizdeki gizli verileri temizleyin." + }, + "changePermissions": { + "name": "İzinleri Değiştir", + "subtitle": "Bir PDF üzerindeki kullanıcı izinlerini ayarlayın veya değiştirin." + }, + "emailToPdf": { + "name": "E-posta'dan PDF'ye", + "subtitle": "E-posta dosyalarını (EML, MSG) PDF formatına dönüştürün. Outlook dışa aktarmalarını ve standart e-posta formatlarını destekler.", + "acceptedFormats": "EML, MSG Dosyaları", + "convertButton": "PDF'ye Dönüştür" + }, + "fontToOutline": { + "name": "Yazı Tipi Çerçeveye Dönüştür", + "subtitle": "Tüm yazı tiplerini vektör çerçevelere dönüştürün, tüm cihazlarda tutarlı görüntü için." + }, + "deskewPdf": { + "name": "PDF Eğriliğini Düzelt", + "subtitle": "OpenCV kullanarak eğik taranmış sayfaları otomatik olarak düzeltin." + }, + "rotateCustom": { + "name": "Rotate by Custom Degrees", + "subtitle": "Rotate pages by any custom angle." + }, + "odtToPdf": { + "name": "ODT to PDF", + "subtitle": "Convert OpenDocument Text files to PDF format. Supports multiple files.", + "acceptedFormats": "ODT files", + "convertButton": "Convert to PDF" + }, + "csvToPdf": { + "name": "CSV to PDF", + "subtitle": "Convert CSV spreadsheet files to PDF format. Supports multiple files.", + "acceptedFormats": "CSV files", + "convertButton": "Convert to PDF" + }, + "rtfToPdf": { + "name": "RTF to PDF", + "subtitle": "Convert Rich Text Format documents to PDF. Supports multiple files.", + "acceptedFormats": "RTF files", + "convertButton": "Convert to PDF" + }, + "wordToPdf": { + "name": "Word to PDF", + "subtitle": "Convert Word documents (DOCX, DOC, ODT, RTF) to PDF format. Supports multiple files.", + "acceptedFormats": "DOCX, DOC, ODT, RTF files", + "convertButton": "Convert to PDF" + }, + "excelToPdf": { + "name": "Excel to PDF", + "subtitle": "Convert Excel spreadsheets (XLSX, XLS, ODS, CSV) to PDF format. Supports multiple files.", + "acceptedFormats": "XLSX, XLS, ODS, CSV files", + "convertButton": "Convert to PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint to PDF", + "subtitle": "Convert PowerPoint presentations (PPTX, PPT, ODP) to PDF format. Supports multiple files.", + "acceptedFormats": "PPTX, PPT, ODP files", + "convertButton": "Convert to PDF" + }, + "markdownToPdf": { + "name": "Markdown to PDF", + "subtitle": "Write or paste Markdown and export it as a beautifully formatted PDF.", + "paneMarkdown": "Markdown", + "panePreview": "Preview", + "btnUpload": "Upload", + "btnSyncScroll": "Sync Scroll", + "btnSettings": "Settings", + "btnExportPdf": "Export PDF", + "settingsTitle": "Markdown Settings", + "settingsPreset": "Preset", + "presetDefault": "Default (GFM-like)", + "presetCommonmark": "CommonMark (strict)", + "presetZero": "Minimal (no features)", + "settingsOptions": "Markdown Options", + "optAllowHtml": "Allow HTML tags", + "optBreaks": "Convert newlines to
", + "optLinkify": "Auto-convert URLs to links", + "optTypographer": "Typographer (smart quotes, etc.)" + }, + "pdfBooklet": { + "name": "PDF Booklet", + "subtitle": "Rearrange pages for double-sided booklet printing. Fold and staple to create a booklet.", + "howItWorks": "How it works:", + "step1": "Upload a PDF file.", + "step2": "Pages will be rearranged in booklet order.", + "step3": "Print double-sided, flip on short edge, fold and staple.", + "paperSize": "Paper Size", + "orientation": "Orientation", + "portrait": "Portrait", + "landscape": "Landscape", + "pagesPerSheet": "Pages per Sheet", + "createBooklet": "Create Booklet", + "processing": "Processing...", + "pageCount": "Page count will be padded to multiple of 4 if needed." + }, + "xpsToPdf": { + "name": "XPS to PDF", + "subtitle": "Convert XPS/OXPS documents to PDF format. Supports multiple files.", + "acceptedFormats": "XPS, OXPS files", + "convertButton": "Convert to PDF" + }, + "mobiToPdf": { + "name": "MOBI to PDF", + "subtitle": "Convert MOBI e-books to PDF format. Supports multiple files.", + "acceptedFormats": "MOBI files", + "convertButton": "Convert to PDF" + }, + "epubToPdf": { + "name": "EPUB to PDF", + "subtitle": "Convert EPUB e-books to PDF format. Supports multiple files.", + "acceptedFormats": "EPUB files", + "convertButton": "Convert to PDF" + }, + "fb2ToPdf": { + "name": "FB2 to PDF", + "subtitle": "Convert FictionBook (FB2) e-books to PDF format. Supports multiple files.", + "acceptedFormats": "FB2 files", + "convertButton": "Convert to PDF" + }, + "cbzToPdf": { + "name": "CBZ to PDF", + "subtitle": "Convert comic book archives (CBZ/CBR) to PDF format. Supports multiple files.", + "acceptedFormats": "CBZ, CBR files", + "convertButton": "Convert to PDF" + }, + "wpdToPdf": { + "name": "WPD to PDF", + "subtitle": "Convert WordPerfect documents (WPD) to PDF format. Supports multiple files.", + "acceptedFormats": "WPD files", + "convertButton": "Convert to PDF" + }, + "wpsToPdf": { + "name": "WPS to PDF", + "subtitle": "Convert WPS Office documents to PDF format. Supports multiple files.", + "acceptedFormats": "WPS files", + "convertButton": "Convert to PDF" + }, + "xmlToPdf": { + "name": "XML to PDF", + "subtitle": "Convert XML documents to PDF format. Supports multiple files.", + "acceptedFormats": "XML files", + "convertButton": "Convert to PDF" + }, + "pagesToPdf": { + "name": "Pages to PDF", + "subtitle": "Convert Apple Pages documents to PDF format. Supports multiple files.", + "acceptedFormats": "Pages files", + "convertButton": "Convert to PDF" + }, + "odgToPdf": { + "name": "ODG to PDF", + "subtitle": "Convert OpenDocument Graphics (ODG) files to PDF format. Supports multiple files.", + "acceptedFormats": "ODG files", + "convertButton": "Convert to PDF" + }, + "odsToPdf": { + "name": "ODS to PDF", + "subtitle": "Convert OpenDocument Spreadsheet (ODS) files to PDF format. Supports multiple files.", + "acceptedFormats": "ODS files", + "convertButton": "Convert to PDF" + }, + "odpToPdf": { + "name": "ODP to PDF", + "subtitle": "Convert OpenDocument Presentation (ODP) files to PDF format. Supports multiple files.", + "acceptedFormats": "ODP files", + "convertButton": "Convert to PDF" + }, + "pubToPdf": { + "name": "PUB to PDF", + "subtitle": "Convert Microsoft Publisher (PUB) files to PDF format. Supports multiple files.", + "acceptedFormats": "PUB files", + "convertButton": "Convert to PDF" + }, + "vsdToPdf": { + "name": "VSD to PDF", + "subtitle": "Convert Microsoft Visio (VSD, VSDX) files to PDF format. Supports multiple files.", + "acceptedFormats": "VSD, VSDX files", + "convertButton": "Convert to PDF" + }, + "psdToPdf": { + "name": "PSD to PDF", + "subtitle": "Convert Adobe Photoshop (PSD) files to PDF format. Supports multiple files.", + "acceptedFormats": "PSD files", + "convertButton": "Convert to PDF" + }, + "pdfToSvg": { + "name": "PDF to SVG", + "subtitle": "Convert each page of a PDF file into a scalable vector graphic (SVG) for perfect quality at any size." + }, + "extractTables": { + "name": "Extract PDF Tables", + "subtitle": "Extract tables from PDF files and export as CSV, JSON, or Markdown." + }, + "pdfToCsv": { + "name": "PDF to CSV", + "subtitle": "Extract tables from PDF and convert to CSV format." + }, + "pdfToExcel": { + "name": "PDF to Excel", + "subtitle": "Extract tables from PDF and convert to Excel (XLSX) format." + }, + "pdfToText": { + "name": "PDF to Text", + "subtitle": "Extract text from PDF files and save as plain text (.txt). Supports multiple files.", + "note": "This tool works ONLY with digitally created PDFs. For scanned documents or image-based PDFs, use our OCR PDF tool instead.", + "convertButton": "Extract Text" + }, + "digitalSignPdf": { + "name": "Digital Signature PDF", + "pageTitle": "Digital Signature PDF - Add Cryptographic Signature | BentoPDF", + "subtitle": "Add a cryptographic digital signature to your PDF using X.509 certificates. Supports PKCS#12 (.pfx, .p12) and PEM formats. Your private key never leaves your browser.", + "certificateSection": "Certificate", + "uploadCert": "Upload certificate (.pfx, .p12)", + "certPassword": "Certificate Password", + "certPasswordPlaceholder": "Enter certificate password", + "certInfo": "Certificate Information", + "certSubject": "Subject", + "certIssuer": "Issuer", + "certValidity": "Valid", + "signatureDetails": "Signature Details (Optional)", + "reason": "Reason", + "reasonPlaceholder": "e.g., I approve this document", + "location": "Location", + "locationPlaceholder": "e.g., New York, USA", + "contactInfo": "Contact Info", + "contactPlaceholder": "e.g., email@example.com", + "applySignature": "Apply Digital Signature", + "successMessage": "PDF signed successfully! The signature can be verified in any PDF reader." + }, + "validateSignaturePdf": { + "name": "Validate PDF Signature", + "pageTitle": "Validate PDF Signature - Verify Digital Signatures | BentoPDF", + "subtitle": "Verify digital signatures in your PDF files. Check certificate validity, view signer details, and confirm document integrity. All processing happens in your browser." + }, + "pdfToWord": { + "name": "PDF'den Word'e", + "subtitle": "PDF dosyalarını düzenlenebilir Word belgelerine dönüştürün." + }, + "extractImages": { + "name": "Görüntüleri Çıkar", + "subtitle": "PDF dosyalarınızdaki tüm gömülü görüntüleri çıkarın." + }, + "pdfToMarkdown": { + "name": "PDF'den Markdown'a", + "subtitle": "PDF metin ve tablolarını Markdown formatına dönüştürün." + }, + "preparePdfForAi": { + "name": "PDF'yi Yapay Zeka için Hazırla", + "subtitle": "RAG/LLM iş hatları için PDF içeriğini LlamaIndex JSON olarak çıkarın." + }, + "pdfOcg": { + "name": "PDF Katmanları (OCG)", + "subtitle": "PDF'nizdeki OCG katmanlarını görüntüleyin, değiştirin, ekleyin ve silin." + }, + "pdfToPdfa": { + "name": "PDF'den PDF/A'ya", + "subtitle": "Uzun süreli arşivleme için PDF'yi PDF/A'ya dönüştürün." + }, + "rasterizePdf": { + "name": "PDF'yi Rasterleştir", + "subtitle": "PDF'yi görüntü tabanlı PDF'ye dönüştürün. Katmanları düzleştirin ve seçilebilir metni kaldırın." + }, + "pdfWorkflow": { + "name": "PDF İş Akışı Oluşturucu", + "subtitle": "Görsel düğüm düzenleyicisi ile özel PDF işleme hatları oluşturun.", + "nodes": "Düğümler", + "searchNodes": "Düğüm ara...", + "run": "Çalıştır", + "clear": "Temizle", + "save": "Kaydet", + "load": "Yükle", + "export": "Dışa Aktar", + "import": "İçe Aktar", + "ready": "Hazır", + "settings": "Ayarlar", + "processing": "İşleniyor...", + "saveTemplate": "Şablonu Kaydet", + "templateName": "Şablon Adı", + "templatePlaceholder": "örn. Fatura İş Akışı", + "cancel": "İptal", + "loadTemplate": "Şablon Yükle", + "noTemplates": "Henüz kaydedilmiş şablon yok.", + "ok": "Tamam", + "workflowCompleted": "İş akışı tamamlandı", + "errorDuringExecution": "Yürütme sırasında hata oluştu", + "addNodeError": "İş akışını çalıştırmak için en az bir düğüm ekleyin.", + "needInputOutput": "İş akışınızın çalışması için en az bir giriş düğümü ve bir çıkış düğümü gereklidir.", + "enterName": "Lütfen bir ad girin.", + "templateExists": "Bu adla bir şablon zaten mevcut.", + "templateSaved": "\"{{name}}\" şablonu kaydedildi.", + "templateLoaded": "\"{{name}}\" şablonu yüklendi.", + "failedLoadTemplate": "Şablon yüklenemedi.", + "noSettings": "Bu düğüm için yapılandırılabilir ayar yok.", + "advancedSettings": "Gelişmiş Ayarlar" + } +} diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json index 6a12390..0a57f66 100644 --- a/public/locales/vi/common.json +++ b/public/locales/vi/common.json @@ -1,318 +1,365 @@ { - "nav": { - "home": "Trang chủ", - "about": "Giới thiệu", - "contact": "Liên hệ", - "licensing": "Giấy phép", - "allTools": "Tất cả công cụ", - "openMainMenu": "Mở menu chính", - "language": "Ngôn ngữ" + "nav": { + "home": "Trang chủ", + "about": "Giới thiệu", + "contact": "Liên hệ", + "licensing": "Giấy phép", + "allTools": "Tất cả công cụ", + "openMainMenu": "Mở menu chính", + "language": "Ngôn ngữ" + }, + "donation": { + "message": "Bạn yêu thích BentoPDF? Hãy giúp chúng tôi giữ nó miễn phí và mã nguồn mở!", + "button": "Quyên góp" + }, + "hero": { + "title": "Bộ công cụ", + "pdfToolkit": "PDF", + "builtForPrivacy": "an toàn và riêng tư", + "noSignups": "Không cần đăng ký", + "unlimitedUse": "Sử dụng không giới hạn", + "worksOffline": "Hoạt động offline", + "startUsing": "Bắt đầu sử dụng ngay" + }, + "usedBy": { + "title": "Được sử dụng bởi các công ty và những người làm việc tại" + }, + "features": { + "title": "Tại sao chọn", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "Không cần đăng ký", + "description": "Bắt đầu ngay lập tức, không cần tài khoản hay email." }, - "hero": { - "title": "Bộ công cụ", - "pdfToolkit": "PDF", - "builtForPrivacy": "an toàn và riêng tư", - "noSignups": "Không cần đăng ký", - "unlimitedUse": "Sử dụng không giới hạn", - "worksOffline": "Hoạt động offline", - "startUsing": "Bắt đầu sử dụng ngay" + "noUploads": { + "title": "Không tải lên", + "description": "100% xử lý phía máy khách, tệp của bạn không bao giờ rời khỏi thiết bị." }, - "usedBy": { - "title": "Được sử dụng bởi các công ty và những người làm việc tại" + "foreverFree": { + "title": "Miễn phí mãi mãi", + "description": "Tất cả công cụ, không dùng thử, không có tường phí." }, - "features": { - "title": "Tại sao chọn", - "bentoPdf": "BentoPDF?", - "noSignup": { - "title": "Không cần đăng ký", - "description": "Bắt đầu ngay lập tức, không cần tài khoản hay email." - }, - "noUploads": { - "title": "Không tải lên", - "description": "100% xử lý phía máy khách, tệp của bạn không bao giờ rời khỏi thiết bị." - }, - "foreverFree": { - "title": "Miễn phí mãi mãi", - "description": "Tất cả công cụ, không dùng thử, không có tường phí." - }, - "noLimits": { - "title": "Không giới hạn", - "description": "Sử dụng bao nhiêu tùy thích, không có giới hạn ẩn." - }, - "batchProcessing": { - "title": "Xử lý hàng loạt", - "description": "Xử lý không giới hạn PDF trong một lần." - }, - "lightningFast": { - "title": "Cực kỳ nhanh", - "description": "Xử lý PDF ngay lập tức, không cần chờ đợi hay trì hoãn." - } + "noLimits": { + "title": "Không giới hạn", + "description": "Sử dụng bao nhiêu tùy thích, không có giới hạn ẩn." }, - "tools": { - "title": "Bắt đầu với", - "toolsLabel": "Công cụ", - "subtitle": "Nhấp vào một công cụ để mở trình tải tệp lên", - "searchPlaceholder": "Tìm kiếm công cụ (ví dụ: 'chia', 'sắp xếp'...)", - "backToTools": "Quay lại Công cụ" + "batchProcessing": { + "title": "Xử lý hàng loạt", + "description": "Xử lý không giới hạn PDF trong một lần." }, - "upload": { - "clickToSelect": "Nhấp để chọn tệp", - "orDragAndDrop": "hoặc kéo và thả", - "pdfOrImages": "PDF hoặc Hình ảnh", - "filesNeverLeave": "Tệp của bạn không bao giờ rời khỏi thiết bị.", - "addMore": "Thêm tệp", - "clearAll": "Xóa tất cả" - }, - "loader": { - "processing": "Đang xử lý..." - }, - "alert": { - "title": "Thông báo", - "ok": "OK" - }, - "preview": { - "title": "Xem trước tài liệu", - "downloadAsPdf": "Tải xuống dưới dạng PDF", - "close": "Đóng" - }, - "settings": { - "title": "Cài đặt", - "shortcuts": "Phím tắt", - "preferences": "Tùy chọn", - "displayPreferences": "Tùy chọn hiển thị", - "searchShortcuts": "Tìm kiếm phím tắt...", - "shortcutsInfo": "Nhấn và giữ phím để đặt phím tắt. Thay đổi được lưu tự động.", - "shortcutsWarning": "⚠️ Tránh các phím tắt trình duyệt phổ biến (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N, v.v.) vì chúng có thể không hoạt động đáng tin cậy.", - "import": "Nhập", - "export": "Xuất", - "resetToDefaults": "Đặt lại về mặc định", - "fullWidthMode": "Chế độ toàn chiều rộng", - "fullWidthDescription": "Sử dụng toàn bộ chiều rộng màn hình cho tất cả công cụ thay vì container căn giữa", - "settingsAutoSaved": "Cài đặt được lưu tự động", - "clickToSet": "Nhấp để đặt", - "pressKeys": "Nhấn phím...", - "warnings": { - "alreadyInUse": "Phím tắt đã được sử dụng", - "assignedTo": "đã được gán cho:", - "chooseDifferent": "Vui lòng chọn một phím tắt khác.", - "reserved": "Cảnh báo phím tắt dành riêng", - "commonlyUsed": "thường được sử dụng cho:", - "unreliable": "Phím tắt này có thể không hoạt động đáng tin cậy hoặc có thể xung đột với hành vi trình duyệt/hệ thống.", - "useAnyway": "Bạn có muốn sử dụng nó không?", - "resetTitle": "Đặt lại phím tắt", - "resetMessage": "Bạn có chắc chắn muốn đặt lại tất cả phím tắt về mặc định?

Hành động này không thể hoàn tác.", - "importSuccessTitle": "Nhập thành công", - "importSuccessMessage": "Đã nhập phím tắt thành công!", - "importFailTitle": "Nhập thất bại", - "importFailMessage": "Không thể nhập phím tắt. Định dạng tệp không hợp lệ." - } - }, - "warning": { - "title": "Cảnh báo", - "cancel": "Hủy", - "proceed": "Tiếp tục" - }, - "compliance": { - "title": "Dữ liệu của bạn không bao giờ rời khỏi thiết bị", - "weKeep": "Chúng tôi giữ", - "yourInfoSafe": "thông tin của bạn an toàn", - "byFollowingStandards": "bằng cách tuân theo các tiêu chuẩn bảo mật toàn cầu.", - "processingLocal": "Tất cả quá trình xử lý diễn ra cục bộ trên thiết bị của bạn.", - "gdpr": { - "title": "Tuân thủ GDPR", - "description": "Bảo vệ dữ liệu cá nhân và quyền riêng tư của các cá nhân trong Liên minh Châu Âu." - }, - "ccpa": { - "title": "Tuân thủ CCPA", - "description": "Trao quyền cho cư dân California về cách thông tin cá nhân của họ được thu thập, sử dụng và chia sẻ." - }, - "hipaa": { - "title": "Tuân thủ HIPAA", - "description": "Đặt ra các biện pháp bảo vệ để xử lý thông tin sức khỏe nhạy cảm trong hệ thống chăm sóc sức khỏe Hoa Kỳ." - } - }, - "faq": { - "title": "Câu hỏi", - "questions": "Thường gặp", - "isFree": { - "question": "BentoPDF có thực sự miễn phí không?", - "answer": "Có, hoàn toàn miễn phí. Tất cả các công cụ trên BentoPDF đều 100% miễn phí sử dụng, không giới hạn tệp, không cần đăng ký và không có watermark. Chúng tôi tin rằng mọi người đều xứng đáng được tiếp cận với các công cụ PDF đơn giản, mạnh mẽ mà không có tường phí." - }, - "areFilesSecure": { - "question": "Tệp của tôi có an toàn không? Chúng được xử lý ở đâu?", - "answer": "Tệp của bạn an toàn nhất có thể vì chúng không bao giờ rời khỏi máy tính của bạn. Tất cả quá trình xử lý diễn ra trực tiếp trong trình duyệt web của bạn (phía máy khách). Chúng tôi không bao giờ tải tệp của bạn lên máy chủ, vì vậy bạn duy trì quyền riêng tư và kiểm soát hoàn toàn đối với tài liệu của mình." - }, - "platforms": { - "question": "Nó có hoạt động trên Mac, Windows và Mobile không?", - "answer": "Có! Vì BentoPDF chạy hoàn toàn trong trình duyệt của bạn, nó hoạt động trên bất kỳ hệ điều hành nào có trình duyệt web hiện đại, bao gồm Windows, macOS, Linux, iOS và Android." - }, - "gdprCompliant": { - "question": "BentoPDF có tuân thủ GDPR không?", - "answer": "Có. BentoPDF hoàn toàn tuân thủ GDPR. Vì tất cả quá trình xử lý tệp diễn ra cục bộ trong trình duyệt của bạn và chúng tôi không bao giờ thu thập hoặc truyền tệp của bạn đến bất kỳ máy chủ nào, chúng tôi không có quyền truy cập vào dữ liệu của bạn. Điều này đảm bảo bạn luôn kiểm soát tài liệu của mình." - }, - "dataStorage": { - "question": "Bạn có lưu trữ hoặc theo dõi bất kỳ tệp nào của tôi không?", - "answer": "Không. Chúng tôi không bao giờ lưu trữ, theo dõi hoặc ghi nhật ký tệp của bạn. Mọi thứ bạn làm trên BentoPDF diễn ra trong bộ nhớ trình duyệt của bạn và biến mất khi bạn đóng trang. Không có tải lên, không có nhật ký lịch sử và không có máy chủ liên quan." - }, - "different": { - "question": "Điều gì làm cho BentoPDF khác biệt so với các công cụ PDF khác?", - "answer": "Hầu hết các công cụ PDF tải tệp của bạn lên máy chủ để xử lý. BentoPDF không bao giờ làm điều đó. Chúng tôi sử dụng công nghệ web hiện đại, an toàn để xử lý tệp của bạn trực tiếp trong trình duyệt. Điều này có nghĩa là hiệu suất nhanh hơn, quyền riêng tư mạnh mẽ hơn và hoàn toàn yên tâm." - }, - "browserBased": { - "question": "Xử lý dựa trên trình duyệt giữ tôi an toàn như thế nào?", - "answer": "Bằng cách chạy hoàn toàn bên trong trình duyệt của bạn, BentoPDF đảm bảo rằng tệp của bạn không bao giờ rời khỏi thiết bị. Điều này loại bỏ các rủi ro về hack máy chủ, vi phạm dữ liệu hoặc truy cập trái phép. Tệp của bạn vẫn thuộc về bạn—luôn luôn." - }, - "analytics": { - "question": "Bạn có sử dụng cookie hoặc phân tích để theo dõi tôi không?", - "answer": "Chúng tôi quan tâm đến quyền riêng tư của bạn. BentoPDF không theo dõi thông tin cá nhân. Chúng tôi chỉ sử dụng Simple Analytics để xem số lượt truy cập ẩn danh. Điều này có nghĩa là chúng tôi có thể biết có bao nhiêu người dùng truy cập trang web của chúng tôi, nhưng chúng tôi không bao giờ biết bạn là ai. Simple Analytics hoàn toàn tuân thủ GDPR và tôn trọng quyền riêng tư của bạn." - } - }, - "testimonials": { - "title": "Người dùng", - "users": "của chúng tôi", - "say": "nói gì" - }, - "support": { - "title": "Thích công việc của tôi?", - "description": "BentoPDF là một dự án đam mê, được xây dựng để cung cấp bộ công cụ PDF miễn phí, riêng tư và mạnh mẽ cho mọi người. Nếu bạn thấy nó hữu ích, hãy cân nhắc hỗ trợ phát triển của nó. Mỗi ly cà phê đều giúp ích!", - "buyMeCoffee": "Mua cho tôi một ly cà phê" - }, - "footer": { - "copyright": "© 2025 BentoPDF. Bảo lưu mọi quyền.", - "version": "Phiên bản", - "company": "Công ty", - "aboutUs": "Về chúng tôi", - "faqLink": "FAQ", - "contactUs": "Liên hệ", - "legal": "Pháp lý", - "termsAndConditions": "Điều khoản và Điều kiện", - "privacyPolicy": "Chính sách Bảo mật", - "followUs": "Theo dõi chúng tôi" - }, - "merge": { - "title": "Gộp PDF", - "description": "Kết hợp toàn bộ tệp hoặc chọn các trang cụ thể để gộp thành tài liệu mới.", - "fileMode": "Chế độ tệp", - "pageMode": "Chế độ trang", - "howItWorks": "Cách hoạt động:", - "fileModeInstructions": [ - "Nhấp và kéo biểu tượng để thay đổi thứ tự các tệp.", - "Trong hộp \"Trang\" cho mỗi tệp, bạn có thể chỉ định phạm vi (ví dụ: \"1-3, 5\") để chỉ gộp những trang đó.", - "Để trống hộp \"Trang\" để bao gồm tất cả các trang từ tệp đó." - ], - "pageModeInstructions": [ - "Tất cả các trang từ PDF đã tải lên của bạn được hiển thị bên dưới.", - "Chỉ cần kéo và thả các hình thu nhỏ trang riêng lẻ để tạo thứ tự chính xác bạn muốn cho tệp mới của mình." - ], - "mergePdfs": "Gộp PDF" - }, - "common": { - "page": "Trang", - "pages": "Trang", - "of": "của", - "download": "Tải xuống", - "cancel": "Hủy", - "save": "Lưu", - "delete": "Xóa", - "edit": "Chỉnh sửa", - "add": "Thêm", - "remove": "Xóa", - "loading": "Đang tải...", - "error": "Lỗi", - "success": "Thành công", - "file": "Tệp", - "files": "Tệp" - }, - "about": { - "hero": { - "title": "Chúng tôi tin rằng công cụ PDF nên", - "subtitle": "nhanh, riêng tư và miễn phí.", - "noCompromises": "Không thỏa hiệp." - }, - "mission": { - "title": "Sứ mệnh của chúng tôi", - "description": "Cung cấp bộ công cụ PDF toàn diện nhất tôn trọng quyền riêng tư của bạn và không bao giờ yêu cầu thanh toán. Chúng tôi tin rằng các công cụ tài liệu thiết yếu nên có thể truy cập được cho mọi người, ở mọi nơi, không có rào cản." - }, - "philosophy": { - "label": "Triết lý cốt lõi của chúng tôi", - "title": "Quyền riêng tư trước tiên. Luôn luôn.", - "description": "Trong thời đại mà dữ liệu là hàng hóa, chúng tôi có cách tiếp cận khác. Tất cả quá trình xử lý cho các công cụ Bentopdf diễn ra cục bộ trong trình duyệt của bạn. Điều này có nghĩa là tệp của bạn không bao giờ chạm vào máy chủ của chúng tôi, chúng tôi không bao giờ thấy tài liệu của bạn và chúng tôi không theo dõi những gì bạn làm. Tài liệu của bạn vẫn hoàn toàn và rõ ràng là riêng tư. Đó không chỉ là một tính năng; đó là nền tảng của chúng tôi." - }, - "whyBentopdf": { - "title": "Tại sao chọn", - "speed": { - "title": "Được xây dựng cho tốc độ", - "description": "Không cần chờ tải lên hoặc tải xuống lên máy chủ. Bằng cách xử lý tệp trực tiếp trong trình duyệt của bạn bằng công nghệ web hiện đại như WebAssembly, chúng tôi cung cấp tốc độ vô song cho tất cả các công cụ của chúng tôi." - }, - "free": { - "title": "Hoàn toàn miễn phí", - "description": "Không dùng thử, không đăng ký, không phí ẩn và không có tính năng \"premium\" bị giữ lại. Chúng tôi tin rằng các công cụ PDF mạnh mẽ nên là tiện ích công cộng, không phải trung tâm lợi nhuận." - }, - "noAccount": { - "title": "Không cần tài khoản", - "description": "Bắt đầu sử dụng bất kỳ công cụ nào ngay lập tức. Chúng tôi không cần email, mật khẩu hoặc bất kỳ thông tin cá nhân nào của bạn. Quy trình làm việc của bạn nên không ma sát và ẩn danh." - }, - "openSource": { - "title": "Tinh thần mã nguồn mở", - "description": "Được xây dựng với tinh thần minh bạch. Chúng tôi tận dụng các thư viện mã nguồn mở tuyệt vời như PDF-lib và PDF.js, và tin vào nỗ lực do cộng đồng thúc đẩy để làm cho các công cụ mạnh mẽ có thể truy cập được cho mọi người." - } - }, - "cta": { - "title": "Sẵn sàng bắt đầu?", - "description": "Tham gia cùng hàng nghìn người dùng tin tưởng Bentopdf cho nhu cầu tài liệu hàng ngày của họ. Trải nghiệm sự khác biệt mà quyền riêng tư và hiệu suất có thể tạo ra.", - "button": "Khám phá tất cả công cụ" - } - }, - "contact": { - "title": "Liên hệ", - "subtitle": "Chúng tôi rất muốn nghe từ bạn. Cho dù bạn có câu hỏi, phản hồi hay yêu cầu tính năng, vui lòng đừng ngần ngại liên hệ.", - "email": "Bạn có thể liên hệ trực tiếp với chúng tôi qua email tại:" - }, - "licensing": { - "title": "Giấy phép cho", - "subtitle": "Chọn giấy phép phù hợp với nhu cầu của bạn." - }, - "multiTool": { - "uploadPdfs": "Tải lên PDF", - "upload": "Tải lên", - "addBlankPage": "Thêm trang trống", - "edit": "Chỉnh sửa:", - "undo": "Hoàn tác", - "redo": "Làm lại", - "reset": "Đặt lại", - "selection": "Chọn:", - "selectAll": "Chọn tất cả", - "deselectAll": "Bỏ chọn tất cả", - "rotate": "Xoay:", - "rotateLeft": "Trái", - "rotateRight": "Phải", - "transform": "Biến đổi:", - "duplicate": "Nhân bản", - "split": "Chia", - "clear": "Xóa:", - "delete": "Xóa", - "download": "Tải xuống:", - "downloadSelected": "Tải xuống đã chọn", - "exportPdf": "Xuất PDF", - "uploadPdfFiles": "Chọn tệp PDF", - "dragAndDrop": "Kéo và thả tệp PDF vào đây, hoặc nhấp để chọn", - "selectFiles": "Chọn tệp", - "renderingPages": "Đang kết xuất trang...", - "actions": { - "duplicatePage": "Nhân bản trang này", - "deletePage": "Xóa trang này", - "insertPdf": "Chèn PDF sau trang này", - "toggleSplit": "Bật/tắt chia sau trang này" - }, - "pleaseWait": "Vui lòng đợi", - "pagesRendering": "Các trang vẫn đang được kết xuất. Vui lòng đợi...", - "noPagesSelected": "Chưa chọn trang nào", - "selectOnePage": "Vui lòng chọn ít nhất một trang để tải xuống.", - "noPages": "Không có trang", - "noPagesToExport": "Không có trang nào để xuất.", - "renderingTitle": "Đang kết xuất xem trước trang", - "errorRendering": "Không thể kết xuất hình thu nhỏ trang", - "error": "Lỗi", - "failedToLoad": "Không thể tải" + "lightningFast": { + "title": "Cực kỳ nhanh", + "description": "Xử lý PDF ngay lập tức, không cần chờ đợi hay trì hoãn." } -} \ No newline at end of file + }, + "tools": { + "title": "Bắt đầu với", + "toolsLabel": "Công cụ", + "subtitle": "Nhấp vào một công cụ để mở trình tải tệp lên", + "searchPlaceholder": "Tìm kiếm công cụ (ví dụ: 'chia', 'sắp xếp'...)", + "backToTools": "Quay lại Công cụ", + "firstLoadNotice": "Lần tải đầu tiên sẽ mất một chút thời gian vì chúng tôi đang tải xuống công cụ chuyển đổi. Sau đó, mọi lần tải sẽ ngay lập tức." + }, + "upload": { + "clickToSelect": "Nhấp để chọn tệp", + "orDragAndDrop": "hoặc kéo và thả", + "pdfOrImages": "PDF hoặc Hình ảnh", + "filesNeverLeave": "Tệp của bạn không bao giờ rời khỏi thiết bị.", + "addMore": "Thêm tệp", + "clearAll": "Xóa tất cả", + "clearFiles": "Xóa tệp", + "hints": { + "singlePdf": "Một tệp PDF duy nhất", + "pdfFile": "Tệp PDF", + "multiplePdfs2": "Nhiều tệp PDF (ít nhất 2)", + "bmpImages": "Ảnh BMP", + "oneOrMorePdfs": "Một hoặc nhiều tệp PDF", + "pdfDocuments": "Tài liệu PDF", + "oneOrMoreCsv": "Một hoặc nhiều tệp CSV", + "multiplePdfsSupported": "Hỗ trợ nhiều tệp PDF", + "singleOrMultiplePdfs": "Hỗ trợ một hoặc nhiều tệp PDF", + "singlePdfFile": "Một tệp PDF", + "pdfWithForms": "Tệp PDF có trường biểu mẫu", + "heicImages": "Ảnh HEIC/HEIF", + "jpgImages": "Ảnh JPG, JPEG, JP2, JPX", + "pdfsOrImages": "PDF hoặc hình ảnh", + "oneOrMoreOdt": "Một hoặc nhiều tệp ODT", + "singlePdfOnly": "Chỉ một tệp PDF duy nhất", + "pdfFiles": "Tệp PDF", + "multiplePdfs": "Nhiều tệp PDF", + "pngImages": "Ảnh PNG", + "pdfFilesOneOrMore": "Tệp PDF (một hoặc nhiều)", + "oneOrMoreRtf": "Một hoặc nhiều tệp RTF", + "svgGraphics": "Đồ họa SVG", + "tiffImages": "Ảnh TIFF", + "webpImages": "Ảnh WebP" + } + }, + "loader": { + "processing": "Đang xử lý..." + }, + "alert": { + "title": "Thông báo", + "ok": "OK" + }, + "preview": { + "title": "Xem trước tài liệu", + "downloadAsPdf": "Tải xuống dưới dạng PDF", + "close": "Đóng" + }, + "settings": { + "title": "Cài đặt", + "shortcuts": "Phím tắt", + "preferences": "Tùy chọn", + "displayPreferences": "Tùy chọn hiển thị", + "searchShortcuts": "Tìm kiếm phím tắt...", + "shortcutsInfo": "Nhấn và giữ phím để đặt phím tắt. Thay đổi được lưu tự động.", + "shortcutsWarning": "⚠️ Tránh các phím tắt trình duyệt phổ biến (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N, v.v.) vì chúng có thể không hoạt động đáng tin cậy.", + "import": "Nhập", + "export": "Xuất", + "resetToDefaults": "Đặt lại về mặc định", + "fullWidthMode": "Chế độ toàn chiều rộng", + "fullWidthDescription": "Sử dụng toàn bộ chiều rộng màn hình cho tất cả công cụ thay vì container căn giữa", + "settingsAutoSaved": "Cài đặt được lưu tự động", + "clickToSet": "Nhấp để đặt", + "pressKeys": "Nhấn phím...", + "warnings": { + "alreadyInUse": "Phím tắt đã được sử dụng", + "assignedTo": "đã được gán cho:", + "chooseDifferent": "Vui lòng chọn một phím tắt khác.", + "reserved": "Cảnh báo phím tắt dành riêng", + "commonlyUsed": "thường được sử dụng cho:", + "unreliable": "Phím tắt này có thể không hoạt động đáng tin cậy hoặc có thể xung đột với hành vi trình duyệt/hệ thống.", + "useAnyway": "Bạn có muốn sử dụng nó không?", + "resetTitle": "Đặt lại phím tắt", + "resetMessage": "Bạn có chắc chắn muốn đặt lại tất cả phím tắt về mặc định?

Hành động này không thể hoàn tác.", + "importSuccessTitle": "Nhập thành công", + "importSuccessMessage": "Đã nhập phím tắt thành công!", + "importFailTitle": "Nhập thất bại", + "importFailMessage": "Không thể nhập phím tắt. Định dạng tệp không hợp lệ." + } + }, + "warning": { + "title": "Cảnh báo", + "cancel": "Hủy", + "proceed": "Tiếp tục" + }, + "compliance": { + "title": "Dữ liệu của bạn không bao giờ rời khỏi thiết bị", + "weKeep": "Chúng tôi giữ", + "yourInfoSafe": "thông tin của bạn an toàn", + "byFollowingStandards": "bằng cách tuân theo các tiêu chuẩn bảo mật toàn cầu.", + "processingLocal": "Tất cả quá trình xử lý diễn ra cục bộ trên thiết bị của bạn.", + "gdpr": { + "title": "Tuân thủ GDPR", + "description": "Bảo vệ dữ liệu cá nhân và quyền riêng tư của các cá nhân trong Liên minh Châu Âu." + }, + "ccpa": { + "title": "Tuân thủ CCPA", + "description": "Trao quyền cho cư dân California về cách thông tin cá nhân của họ được thu thập, sử dụng và chia sẻ." + }, + "hipaa": { + "title": "Tuân thủ HIPAA", + "description": "Đặt ra các biện pháp bảo vệ để xử lý thông tin sức khỏe nhạy cảm trong hệ thống chăm sóc sức khỏe Hoa Kỳ." + } + }, + "faq": { + "title": "Câu hỏi", + "questions": "Thường gặp", + "isFree": { + "question": "BentoPDF có thực sự miễn phí không?", + "answer": "Có, hoàn toàn miễn phí. Tất cả các công cụ trên BentoPDF đều 100% miễn phí sử dụng, không giới hạn tệp, không cần đăng ký và không có watermark. Chúng tôi tin rằng mọi người đều xứng đáng được tiếp cận với các công cụ PDF đơn giản, mạnh mẽ mà không có tường phí." + }, + "areFilesSecure": { + "question": "Tệp của tôi có an toàn không? Chúng được xử lý ở đâu?", + "answer": "Tệp của bạn an toàn nhất có thể vì chúng không bao giờ rời khỏi máy tính của bạn. Tất cả quá trình xử lý diễn ra trực tiếp trong trình duyệt web của bạn (phía máy khách). Chúng tôi không bao giờ tải tệp của bạn lên máy chủ, vì vậy bạn duy trì quyền riêng tư và kiểm soát hoàn toàn đối với tài liệu của mình." + }, + "platforms": { + "question": "Nó có hoạt động trên Mac, Windows và Mobile không?", + "answer": "Có! Vì BentoPDF chạy hoàn toàn trong trình duyệt của bạn, nó hoạt động trên bất kỳ hệ điều hành nào có trình duyệt web hiện đại, bao gồm Windows, macOS, Linux, iOS và Android." + }, + "gdprCompliant": { + "question": "BentoPDF có tuân thủ GDPR không?", + "answer": "Có. BentoPDF hoàn toàn tuân thủ GDPR. Vì tất cả quá trình xử lý tệp diễn ra cục bộ trong trình duyệt của bạn và chúng tôi không bao giờ thu thập hoặc truyền tệp của bạn đến bất kỳ máy chủ nào, chúng tôi không có quyền truy cập vào dữ liệu của bạn. Điều này đảm bảo bạn luôn kiểm soát tài liệu của mình." + }, + "dataStorage": { + "question": "Bạn có lưu trữ hoặc theo dõi bất kỳ tệp nào của tôi không?", + "answer": "Không. Chúng tôi không bao giờ lưu trữ, theo dõi hoặc ghi nhật ký tệp của bạn. Mọi thứ bạn làm trên BentoPDF diễn ra trong bộ nhớ trình duyệt của bạn và biến mất khi bạn đóng trang. Không có tải lên, không có nhật ký lịch sử và không có máy chủ liên quan." + }, + "different": { + "question": "Điều gì làm cho BentoPDF khác biệt so với các công cụ PDF khác?", + "answer": "Hầu hết các công cụ PDF tải tệp của bạn lên máy chủ để xử lý. BentoPDF không bao giờ làm điều đó. Chúng tôi sử dụng công nghệ web hiện đại, an toàn để xử lý tệp của bạn trực tiếp trong trình duyệt. Điều này có nghĩa là hiệu suất nhanh hơn, quyền riêng tư mạnh mẽ hơn và hoàn toàn yên tâm." + }, + "browserBased": { + "question": "Xử lý dựa trên trình duyệt giữ tôi an toàn như thế nào?", + "answer": "Bằng cách chạy hoàn toàn bên trong trình duyệt của bạn, BentoPDF đảm bảo rằng tệp của bạn không bao giờ rời khỏi thiết bị. Điều này loại bỏ các rủi ro về hack máy chủ, vi phạm dữ liệu hoặc truy cập trái phép. Tệp của bạn vẫn thuộc về bạn—luôn luôn." + }, + "analytics": { + "question": "Bạn có sử dụng cookie hoặc phân tích để theo dõi tôi không?", + "answer": "Chúng tôi quan tâm đến quyền riêng tư của bạn. BentoPDF không theo dõi thông tin cá nhân. Chúng tôi chỉ sử dụng Simple Analytics để xem số lượt truy cập ẩn danh. Điều này có nghĩa là chúng tôi có thể biết có bao nhiêu người dùng truy cập trang web của chúng tôi, nhưng chúng tôi không bao giờ biết bạn là ai. Simple Analytics hoàn toàn tuân thủ GDPR và tôn trọng quyền riêng tư của bạn." + }, + "sectionTitle": "Câu hỏi thường gặp" + }, + "testimonials": { + "title": "Người dùng", + "users": "của chúng tôi", + "say": "nói gì" + }, + "support": { + "title": "Thích công việc của tôi?", + "description": "BentoPDF là một dự án đam mê, được xây dựng để cung cấp bộ công cụ PDF miễn phí, riêng tư và mạnh mẽ cho mọi người. Nếu bạn thấy nó hữu ích, hãy cân nhắc hỗ trợ phát triển của nó. Mỗi ly cà phê đều giúp ích!", + "buyMeCoffee": "Mua cho tôi một ly cà phê" + }, + "footer": { + "copyright": "© 2026 BentoPDF. Bảo lưu mọi quyền.", + "version": "Phiên bản", + "company": "Công ty", + "aboutUs": "Về chúng tôi", + "faqLink": "FAQ", + "contactUs": "Liên hệ", + "legal": "Pháp lý", + "termsAndConditions": "Điều khoản và Điều kiện", + "privacyPolicy": "Chính sách Bảo mật", + "followUs": "Theo dõi chúng tôi" + }, + "merge": { + "title": "Gộp PDF", + "description": "Kết hợp toàn bộ tệp hoặc chọn các trang cụ thể để gộp thành tài liệu mới.", + "fileMode": "Chế độ tệp", + "pageMode": "Chế độ trang", + "howItWorks": "Cách hoạt động:", + "fileModeInstructions": [ + "Nhấp và kéo biểu tượng để thay đổi thứ tự các tệp.", + "Trong hộp \"Trang\" cho mỗi tệp, bạn có thể chỉ định phạm vi (ví dụ: \"1-3, 5\") để chỉ gộp những trang đó.", + "Để trống hộp \"Trang\" để bao gồm tất cả các trang từ tệp đó." + ], + "pageModeInstructions": [ + "Tất cả các trang từ PDF đã tải lên của bạn được hiển thị bên dưới.", + "Chỉ cần kéo và thả các hình thu nhỏ trang riêng lẻ để tạo thứ tự chính xác bạn muốn cho tệp mới của mình." + ], + "mergePdfs": "Gộp PDF" + }, + "common": { + "page": "Trang", + "pages": "Trang", + "of": "của", + "download": "Tải xuống", + "cancel": "Hủy", + "save": "Lưu", + "delete": "Xóa", + "edit": "Chỉnh sửa", + "add": "Thêm", + "remove": "Xóa", + "loading": "Đang tải...", + "error": "Lỗi", + "success": "Thành công", + "file": "Tệp", + "files": "Tệp", + "close": "Đóng" + }, + "about": { + "hero": { + "title": "Chúng tôi tin rằng công cụ PDF nên", + "subtitle": "nhanh, riêng tư và miễn phí.", + "noCompromises": "Không thỏa hiệp." + }, + "mission": { + "title": "Sứ mệnh của chúng tôi", + "description": "Cung cấp bộ công cụ PDF toàn diện nhất tôn trọng quyền riêng tư của bạn và không bao giờ yêu cầu thanh toán. Chúng tôi tin rằng các công cụ tài liệu thiết yếu nên có thể truy cập được cho mọi người, ở mọi nơi, không có rào cản." + }, + "philosophy": { + "label": "Triết lý cốt lõi của chúng tôi", + "title": "Quyền riêng tư trước tiên. Luôn luôn.", + "description": "Trong thời đại mà dữ liệu là hàng hóa, chúng tôi có cách tiếp cận khác. Tất cả quá trình xử lý cho các công cụ Bentopdf diễn ra cục bộ trong trình duyệt của bạn. Điều này có nghĩa là tệp của bạn không bao giờ chạm vào máy chủ của chúng tôi, chúng tôi không bao giờ thấy tài liệu của bạn và chúng tôi không theo dõi những gì bạn làm. Tài liệu của bạn vẫn hoàn toàn và rõ ràng là riêng tư. Đó không chỉ là một tính năng; đó là nền tảng của chúng tôi." + }, + "whyBentopdf": { + "title": "Tại sao chọn", + "speed": { + "title": "Được xây dựng cho tốc độ", + "description": "Không cần chờ tải lên hoặc tải xuống lên máy chủ. Bằng cách xử lý tệp trực tiếp trong trình duyệt của bạn bằng công nghệ web hiện đại như WebAssembly, chúng tôi cung cấp tốc độ vô song cho tất cả các công cụ của chúng tôi." + }, + "free": { + "title": "Hoàn toàn miễn phí", + "description": "Không dùng thử, không đăng ký, không phí ẩn và không có tính năng \"premium\" bị giữ lại. Chúng tôi tin rằng các công cụ PDF mạnh mẽ nên là tiện ích công cộng, không phải trung tâm lợi nhuận." + }, + "noAccount": { + "title": "Không cần tài khoản", + "description": "Bắt đầu sử dụng bất kỳ công cụ nào ngay lập tức. Chúng tôi không cần email, mật khẩu hoặc bất kỳ thông tin cá nhân nào của bạn. Quy trình làm việc của bạn nên không ma sát và ẩn danh." + }, + "openSource": { + "title": "Tinh thần mã nguồn mở", + "description": "Được xây dựng với tinh thần minh bạch. Chúng tôi tận dụng các thư viện mã nguồn mở tuyệt vời như PDF-lib và PDF.js, và tin vào nỗ lực do cộng đồng thúc đẩy để làm cho các công cụ mạnh mẽ có thể truy cập được cho mọi người." + } + }, + "cta": { + "title": "Sẵn sàng bắt đầu?", + "description": "Tham gia cùng hàng nghìn người dùng tin tưởng Bentopdf cho nhu cầu tài liệu hàng ngày của họ. Trải nghiệm sự khác biệt mà quyền riêng tư và hiệu suất có thể tạo ra.", + "button": "Khám phá tất cả công cụ" + } + }, + "contact": { + "title": "Liên hệ", + "subtitle": "Chúng tôi rất muốn nghe từ bạn. Cho dù bạn có câu hỏi, phản hồi hay yêu cầu tính năng, vui lòng đừng ngần ngại liên hệ.", + "email": "Bạn có thể liên hệ trực tiếp với chúng tôi qua email tại:" + }, + "licensing": { + "title": "Giấy phép cho", + "subtitle": "Chọn giấy phép phù hợp với nhu cầu của bạn." + }, + "multiTool": { + "uploadPdfs": "Tải lên PDF", + "upload": "Tải lên", + "addBlankPage": "Thêm trang trống", + "edit": "Chỉnh sửa:", + "undo": "Hoàn tác", + "redo": "Làm lại", + "reset": "Đặt lại", + "selection": "Chọn:", + "selectAll": "Chọn tất cả", + "deselectAll": "Bỏ chọn tất cả", + "rotate": "Xoay:", + "rotateLeft": "Trái", + "rotateRight": "Phải", + "transform": "Biến đổi:", + "duplicate": "Nhân bản", + "split": "Chia", + "clear": "Xóa:", + "delete": "Xóa", + "download": "Tải xuống:", + "downloadSelected": "Tải xuống đã chọn", + "exportPdf": "Xuất PDF", + "uploadPdfFiles": "Chọn tệp PDF", + "dragAndDrop": "Kéo và thả tệp PDF vào đây, hoặc nhấp để chọn", + "selectFiles": "Chọn tệp", + "renderingPages": "Đang kết xuất trang...", + "actions": { + "duplicatePage": "Nhân bản trang này", + "deletePage": "Xóa trang này", + "insertPdf": "Chèn PDF sau trang này", + "toggleSplit": "Bật/tắt chia sau trang này" + }, + "pleaseWait": "Vui lòng đợi", + "pagesRendering": "Các trang vẫn đang được kết xuất. Vui lòng đợi...", + "noPagesSelected": "Chưa chọn trang nào", + "selectOnePage": "Vui lòng chọn ít nhất một trang để tải xuống.", + "noPages": "Không có trang", + "noPagesToExport": "Không có trang nào để xuất.", + "renderingTitle": "Đang kết xuất xem trước trang", + "errorRendering": "Không thể kết xuất hình thu nhỏ trang", + "error": "Lỗi", + "failedToLoad": "Không thể tải" + }, + "howItWorks": { + "title": "Cách thức hoạt động", + "step1": "Nhấp hoặc kéo tệp của bạn vào đây", + "step2": "Nhấp vào nút xử lý để bắt đầu", + "step3": "Lưu tệp đã xử lý ngay lập tức" + }, + "relatedTools": { + "title": "Công cụ PDF liên quan" + }, + "simpleMode": { + "title": "Công cụ PDF", + "subtitle": "Chọn một công cụ để bắt đầu" + } +} diff --git a/public/locales/vi/tools.json b/public/locales/vi/tools.json index 894c316..fcdd960 100644 --- a/public/locales/vi/tools.json +++ b/public/locales/vi/tools.json @@ -1,282 +1,631 @@ { - "categories": { - "popularTools": "Công cụ phổ biến", - "editAnnotate": "Chỉnh sửa & Ghi chú", - "convertToPdf": "Chuyển đổi sang PDF", - "convertFromPdf": "Chuyển đổi từ PDF", - "organizeManage": "Sắp xếp & Quản lý", - "optimizeRepair": "Tối ưu hóa & Sửa chữa", - "securePdf": "Bảo mật PDF" - }, - "pdfMultiTool": { - "name": "Công cụ đa năng PDF", - "subtitle": "Gộp, Chia, Sắp xếp, Xóa, Xoay, Thêm trang trống, Trích xuất và Nhân đôi trong một giao diện thống nhất." - }, - "mergePdf": { - "name": "Gộp PDF", - "subtitle": "Kết hợp nhiều PDF thành một tệp. Giữ nguyên Bookmark." - }, - "splitPdf": { - "name": "Chia PDF", - "subtitle": "Trích xuất một phạm vi trang thành PDF mới." - }, - "compressPdf": { - "name": "Nén PDF", - "subtitle": "Giảm kích thước tệp PDF của bạn." - }, - "pdfEditor": { - "name": "Trình chỉnh sửa PDF", - "subtitle": "Ghi chú, tô sáng, chỉnh sửa, bình luận, thêm hình dạng/hình ảnh, tìm kiếm và xem PDF." - }, - "jpgToPdf": { - "name": "JPG sang PDF", - "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh JPG." - }, - "signPdf": { - "name": "Ký PDF", - "subtitle": "Vẽ, gõ hoặc tải lên chữ ký của bạn." - }, - "cropPdf": { - "name": "Cắt PDF", - "subtitle": "Cắt lề của mọi trang trong PDF của bạn." - }, - "extractPages": { - "name": "Trích xuất trang", - "subtitle": "Lưu một lựa chọn trang dưới dạng tệp mới." - }, - "duplicateOrganize": { - "name": "Nhân đôi & Sắp xếp", - "subtitle": "Nhân đôi, sắp xếp lại và xóa trang." - }, - "deletePages": { - "name": "Xóa trang", - "subtitle": "Xóa các trang cụ thể khỏi tài liệu của bạn." - }, - "editBookmarks": { - "name": "Chỉnh sửa Bookmark", - "subtitle": "Thêm, chỉnh sửa, nhập, xóa và trích xuất bookmark PDF." - }, - "tableOfContents": { - "name": "Mục lục", - "subtitle": "Tạo trang mục lục từ bookmark PDF." - }, - "pageNumbers": { - "name": "Số trang", - "subtitle": "Chèn số trang vào tài liệu của bạn." - }, - "addWatermark": { - "name": "Thêm Watermark", - "subtitle": "Đóng dấu văn bản hoặc hình ảnh lên các trang PDF của bạn." - }, - "headerFooter": { - "name": "Đầu trang & Chân trang", - "subtitle": "Thêm văn bản vào đầu và cuối trang." - }, - "invertColors": { - "name": "Đảo ngược màu", - "subtitle": "Tạo phiên bản \"chế độ tối\" cho PDF của bạn." - }, - "backgroundColor": { - "name": "Màu nền", - "subtitle": "Thay đổi màu nền của PDF của bạn." - }, - "changeTextColor": { - "name": "Thay đổi màu văn bản", - "subtitle": "Thay đổi màu văn bản trong PDF của bạn." - }, - "addStamps": { - "name": "Thêm tem", - "subtitle": "Thêm tem hình ảnh vào PDF của bạn bằng thanh công cụ ghi chú.", - "usernameLabel": "Tên người dùng tem", - "usernamePlaceholder": "Nhập tên của bạn (cho tem)", - "usernameHint": "Tên này sẽ xuất hiện trên các tem bạn tạo." - }, - "removeAnnotations": { - "name": "Xóa ghi chú", - "subtitle": "Loại bỏ bình luận, tô sáng và liên kết." - }, - "pdfFormFiller": { - "name": "Điền form PDF", - "subtitle": "Điền form trực tiếp trong trình duyệt. Cũng hỗ trợ form XFA." - }, - "createPdfForm": { - "name": "Tạo form PDF", - "subtitle": "Tạo form PDF có thể điền với các trường văn bản kéo và thả." - }, - "removeBlankPages": { - "name": "Xóa trang trống", - "subtitle": "Tự động phát hiện và xóa trang trống." - }, - "imageToPdf": { - "name": "Hình ảnh sang PDF", - "subtitle": "Chuyển đổi JPG, PNG, WebP, BMP, TIFF, SVG, HEIC sang PDF." - }, - "pngToPdf": { - "name": "PNG sang PDF", - "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh PNG." - }, - "webpToPdf": { - "name": "WebP sang PDF", - "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh WebP." - }, - "svgToPdf": { - "name": "SVG sang PDF", - "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh SVG." - }, - "bmpToPdf": { - "name": "BMP sang PDF", - "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh BMP." - }, - "heicToPdf": { - "name": "HEIC sang PDF", - "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh HEIC." - }, - "tiffToPdf": { - "name": "TIFF sang PDF", - "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh TIFF." - }, - "textToPdf": { - "name": "Văn bản sang PDF", - "subtitle": "Chuyển đổi tệp văn bản thuần túy thành PDF." - }, - "jsonToPdf": { - "name": "JSON sang PDF", - "subtitle": "Chuyển đổi tệp JSON sang định dạng PDF." - }, - "pdfToJpg": { - "name": "PDF sang JPG", - "subtitle": "Chuyển đổi mỗi trang PDF thành hình ảnh JPG." - }, - "pdfToPng": { - "name": "PDF sang PNG", - "subtitle": "Chuyển đổi mỗi trang PDF thành hình ảnh PNG." - }, - "pdfToWebp": { - "name": "PDF sang WebP", - "subtitle": "Chuyển đổi mỗi trang PDF thành hình ảnh WebP." - }, - "pdfToBmp": { - "name": "PDF sang BMP", - "subtitle": "Chuyển đổi mỗi trang PDF thành hình ảnh BMP." - }, - "pdfToTiff": { - "name": "PDF sang TIFF", - "subtitle": "Chuyển đổi mỗi trang PDF thành hình ảnh TIFF." - }, - "pdfToGreyscale": { - "name": "PDF sang thang xám", - "subtitle": "Chuyển đổi tất cả màu sắc sang đen trắng." - }, - "pdfToJson": { - "name": "PDF sang JSON", - "subtitle": "Chuyển đổi tệp PDF sang định dạng JSON." - }, - "ocrPdf": { - "name": "OCR PDF", - "subtitle": "Làm cho PDF có thể tìm kiếm và sao chép được." - }, - "alternateMix": { - "name": "Xen kẽ & Trộn trang", - "subtitle": "Gộp PDF bằng cách xen kẽ trang từ mỗi PDF. Giữ nguyên Bookmark." - }, - "addAttachments": { - "name": "Thêm tệp đính kèm", - "subtitle": "Nhúng một hoặc nhiều tệp vào PDF của bạn." - }, - "extractAttachments": { - "name": "Trích xuất tệp đính kèm", - "subtitle": "Trích xuất tất cả tệp được nhúng từ PDF thành ZIP." - }, - "editAttachments": { - "name": "Chỉnh sửa tệp đính kèm", - "subtitle": "Xem hoặc xóa tệp đính kèm trong PDF của bạn." - }, - "dividePages": { - "name": "Chia trang", - "subtitle": "Chia trang theo chiều ngang hoặc chiều dọc." - }, - "addBlankPage": { - "name": "Thêm trang trống", - "subtitle": "Chèn trang trống ở bất kỳ đâu trong PDF của bạn." - }, - "reversePages": { - "name": "Đảo ngược trang", - "subtitle": "Lật ngược thứ tự tất cả các trang trong tài liệu của bạn." - }, - "rotatePdf": { - "name": "Xoay PDF", - "subtitle": "Xoay trang theo bội số 90 độ." - }, - "nUpPdf": { - "name": "N-Up PDF", - "subtitle": "Sắp xếp nhiều trang lên một tờ." - }, - "combineToSinglePage": { - "name": "Kết hợp thành một trang", - "subtitle": "Ghép tất cả các trang thành một cuộn liên tục." - }, - "viewMetadata": { - "name": "Xem Metadata", - "subtitle": "Kiểm tra các thuộc tính ẩn của PDF của bạn." - }, - "editMetadata": { - "name": "Chỉnh sửa Metadata", - "subtitle": "Thay đổi tác giả, tiêu đề và các thuộc tính khác." - }, - "pdfsToZip": { - "name": "PDF sang ZIP", - "subtitle": "Đóng gói nhiều tệp PDF thành kho lưu trữ ZIP." - }, - "comparePdfs": { - "name": "So sánh PDF", - "subtitle": "So sánh hai PDF cạnh nhau." - }, - "posterizePdf": { - "name": "Posterize PDF", - "subtitle": "Chia một trang lớn thành nhiều trang nhỏ hơn." - }, - "fixPageSize": { - "name": "Sửa kích thước trang", - "subtitle": "Chuẩn hóa tất cả các trang về cùng một kích thước." - }, - "linearizePdf": { - "name": "Tuyến tính hóa PDF", - "subtitle": "Tối ưu hóa PDF để xem web nhanh." - }, - "pageDimensions": { - "name": "Kích thước trang", - "subtitle": "Phân tích kích thước trang, hướng và đơn vị." - }, - "removeRestrictions": { - "name": "Xóa hạn chế", - "subtitle": "Xóa bảo vệ mật khẩu và hạn chế bảo mật liên quan đến tệp PDF được ký số." - }, - "repairPdf": { - "name": "Sửa chữa PDF", - "subtitle": "Khôi phục dữ liệu từ tệp PDF bị hỏng hoặc hư hỏng." - }, - "encryptPdf": { - "name": "Mã hóa PDF", - "subtitle": "Khóa PDF của bạn bằng cách thêm mật khẩu." - }, - "sanitizePdf": { - "name": "Làm sạch PDF", - "subtitle": "Xóa metadata, ghi chú, script và nhiều hơn nữa." - }, - "decryptPdf": { - "name": "Giải mã PDF", - "subtitle": "Mở khóa PDF bằng cách xóa bảo vệ mật khẩu." - }, - "flattenPdf": { - "name": "Làm phẳng PDF", - "subtitle": "Làm cho trường form và ghi chú không thể chỉnh sửa." - }, - "removeMetadata": { - "name": "Xóa Metadata", - "subtitle": "Loại bỏ dữ liệu ẩn khỏi PDF của bạn." - }, - "changePermissions": { - "name": "Thay đổi quyền", - "subtitle": "Đặt hoặc thay đổi quyền người dùng trên PDF." - } -} \ No newline at end of file + "categories": { + "popularTools": "Công cụ phổ biến", + "editAnnotate": "Chỉnh sửa & Ghi chú", + "convertToPdf": "Chuyển đổi sang PDF", + "convertFromPdf": "Chuyển đổi từ PDF", + "organizeManage": "Sắp xếp & Quản lý", + "optimizeRepair": "Tối ưu hóa & Sửa chữa", + "securePdf": "Bảo mật PDF" + }, + "pdfMultiTool": { + "name": "Công cụ đa năng PDF", + "subtitle": "Gộp, Chia, Sắp xếp, Xóa, Xoay, Thêm trang trống, Trích xuất và Nhân đôi trong một giao diện thống nhất." + }, + "mergePdf": { + "name": "Gộp PDF", + "subtitle": "Kết hợp nhiều PDF thành một tệp. Giữ nguyên Bookmark." + }, + "splitPdf": { + "name": "Chia PDF", + "subtitle": "Trích xuất một phạm vi trang thành PDF mới." + }, + "compressPdf": { + "name": "Nén PDF", + "subtitle": "Giảm kích thước tệp PDF của bạn.", + "algorithmLabel": "Thuật toán nén", + "condense": "Condense (Khuyến nghị)", + "photon": "Photon (Dành cho PDF nhiều ảnh)", + "condenseInfo": "Condense sử dụng nén nâng cao: loại bỏ dữ liệu thừa, tối ưu hóa hình ảnh, gọn phông chữ. Phù hợp với hầu hết PDF.", + "photonInfo": "Photon chuyển đổi trang thành hình ảnh. Dùng cho PDF nhiều ảnh/quét.", + "photonWarning": "Cảnh báo: Văn bản sẽ không thể chọn được và liên kết sẽ không hoạt động.", + "levelLabel": "Mức độ nén", + "light": "Nhẹ (Giữ chất lượng)", + "balanced": "Cân bằng (Khuyến nghị)", + "aggressive": "Mạnh (Tệp nhỏ hơn)", + "extreme": "Cực đoan (Nén tối đa)", + "grayscale": "Chuyển sang thang xám", + "grayscaleHint": "Giảm kích thước tệp bằng cách loại bỏ thông tin màu", + "customSettings": "Cài đặt tùy chỉnh", + "customSettingsHint": "Tinh chỉnh các thông số nén:", + "outputQuality": "Chất lượng đầu ra", + "resizeImagesTo": "Thay đổi kích thước ảnh thành", + "onlyProcessAbove": "Chỉ xử lý khi trên", + "removeMetadata": "Xóa siêu dữ liệu", + "subsetFonts": "Gọn phông chữ (xóa ký tự không dùng)", + "removeThumbnails": "Xóa hình thu nhỏ nhúng", + "compressButton": "Nén PDF" + }, + "pdfEditor": { + "name": "Trình chỉnh sửa PDF", + "subtitle": "Ghi chú, tô sáng, chỉnh sửa, bình luận, thêm hình dạng/hình ảnh, tìm kiếm và xem PDF." + }, + "jpgToPdf": { + "name": "JPG sang PDF", + "subtitle": "Tạo PDF từ hình ảnh JPG, JPEG và JPEG2000 (JP2/JPX)." + }, + "signPdf": { + "name": "Ký PDF", + "subtitle": "Vẽ, gõ hoặc tải lên chữ ký của bạn." + }, + "cropPdf": { + "name": "Cắt PDF", + "subtitle": "Cắt lề của mọi trang trong PDF của bạn." + }, + "extractPages": { + "name": "Trích xuất trang", + "subtitle": "Lưu một lựa chọn trang dưới dạng tệp mới." + }, + "duplicateOrganize": { + "name": "Nhân đôi & Sắp xếp", + "subtitle": "Nhân đôi, sắp xếp lại và xóa trang." + }, + "deletePages": { + "name": "Xóa trang", + "subtitle": "Xóa các trang cụ thể khỏi tài liệu của bạn." + }, + "editBookmarks": { + "name": "Chỉnh sửa Bookmark", + "subtitle": "Thêm, chỉnh sửa, nhập, xóa và trích xuất bookmark PDF." + }, + "tableOfContents": { + "name": "Mục lục", + "subtitle": "Tạo trang mục lục từ bookmark PDF." + }, + "pageNumbers": { + "name": "Số trang", + "subtitle": "Chèn số trang vào tài liệu của bạn." + }, + "batesNumbering": { + "name": "Đánh số Bates", + "subtitle": "Thêm số Bates tuần tự trên một hoặc nhiều tệp PDF." + }, + "addWatermark": { + "name": "Thêm Watermark", + "subtitle": "Đóng dấu văn bản hoặc hình ảnh lên các trang PDF của bạn.", + "applyToAllPages": "Áp dụng cho tất cả các trang" + }, + "headerFooter": { + "name": "Đầu trang & Chân trang", + "subtitle": "Thêm văn bản vào đầu và cuối trang." + }, + "invertColors": { + "name": "Đảo ngược màu", + "subtitle": "Tạo phiên bản \"chế độ tối\" cho PDF của bạn." + }, + "scannerEffect": { + "name": "Hiệu ứng máy quét", + "subtitle": "Làm cho PDF trông giống như tài liệu đã quét.", + "scanSettings": "Cài đặt quét", + "colorspace": "Không gian màu", + "gray": "Thang xám", + "border": "Viền", + "rotate": "Xoay", + "rotateVariance": "Biên độ xoay", + "brightness": "Độ sáng", + "contrast": "Độ tương phản", + "blur": "Làm mờ", + "noise": "Nhiễu", + "yellowish": "Ngả vàng", + "resolution": "Độ phân giải", + "processButton": "Áp dụng hiệu ứng máy quét" + }, + "adjustColors": { + "name": "Điều chỉnh màu sắc", + "subtitle": "Tinh chỉnh độ sáng, tương phản, độ bão hòa và nhiều hơn nữa.", + "colorSettings": "Cài đặt màu sắc", + "brightness": "Độ sáng", + "contrast": "Tương phản", + "saturation": "Độ bão hòa", + "hueShift": "Dịch chuyển sắc độ", + "temperature": "Nhiệt độ màu", + "tint": "Sắc thái", + "gamma": "Gamma", + "sepia": "Sepia", + "processButton": "Áp dụng điều chỉnh màu" + }, + "backgroundColor": { + "name": "Màu nền", + "subtitle": "Thay đổi màu nền của PDF của bạn." + }, + "changeTextColor": { + "name": "Thay đổi màu văn bản", + "subtitle": "Thay đổi màu văn bản trong PDF của bạn." + }, + "addStamps": { + "name": "Thêm tem", + "subtitle": "Thêm tem hình ảnh vào PDF của bạn bằng thanh công cụ ghi chú.", + "usernameLabel": "Tên người dùng tem", + "usernamePlaceholder": "Nhập tên của bạn (cho tem)", + "usernameHint": "Tên này sẽ xuất hiện trên các tem bạn tạo." + }, + "removeAnnotations": { + "name": "Xóa ghi chú", + "subtitle": "Loại bỏ bình luận, tô sáng và liên kết." + }, + "pdfFormFiller": { + "name": "Điền form PDF", + "subtitle": "Điền form trực tiếp trong trình duyệt. Cũng hỗ trợ form XFA." + }, + "createPdfForm": { + "name": "Tạo form PDF", + "subtitle": "Tạo form PDF có thể điền với các trường văn bản kéo và thả." + }, + "removeBlankPages": { + "name": "Xóa trang trống", + "subtitle": "Tự động phát hiện và xóa trang trống.", + "sensitivityHint": "Cao hơn = nghiêm ngặt hơn, chỉ các trang hoàn toàn trống. Thấp hơn = cho phép trang có một số nội dung." + }, + "imageToPdf": { + "name": "Hình ảnh sang PDF", + "subtitle": "Chuyển đổi JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP sang PDF." + }, + "pngToPdf": { + "name": "PNG sang PDF", + "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh PNG." + }, + "webpToPdf": { + "name": "WebP sang PDF", + "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh WebP." + }, + "svgToPdf": { + "name": "SVG sang PDF", + "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh SVG." + }, + "bmpToPdf": { + "name": "BMP sang PDF", + "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh BMP." + }, + "heicToPdf": { + "name": "HEIC sang PDF", + "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh HEIC." + }, + "tiffToPdf": { + "name": "TIFF sang PDF", + "subtitle": "Tạo PDF từ một hoặc nhiều hình ảnh TIFF." + }, + "textToPdf": { + "name": "Văn bản sang PDF", + "subtitle": "Chuyển đổi tệp văn bản thuần túy thành PDF." + }, + "jsonToPdf": { + "name": "JSON sang PDF", + "subtitle": "Chuyển đổi tệp JSON sang định dạng PDF." + }, + "pdfToJpg": { + "name": "PDF sang JPG", + "subtitle": "Chuyển đổi mỗi trang PDF thành hình ảnh JPG." + }, + "pdfToPng": { + "name": "PDF sang PNG", + "subtitle": "Chuyển đổi mỗi trang PDF thành hình ảnh PNG." + }, + "pdfToWebp": { + "name": "PDF sang WebP", + "subtitle": "Chuyển đổi mỗi trang PDF thành hình ảnh WebP." + }, + "pdfToBmp": { + "name": "PDF sang BMP", + "subtitle": "Chuyển đổi mỗi trang PDF thành hình ảnh BMP." + }, + "pdfToTiff": { + "name": "PDF sang TIFF", + "subtitle": "Chuyển đổi mỗi trang PDF thành hình ảnh TIFF." + }, + "pdfToGreyscale": { + "name": "PDF sang thang xám", + "subtitle": "Chuyển đổi tất cả màu sắc sang đen trắng." + }, + "pdfToJson": { + "name": "PDF sang JSON", + "subtitle": "Chuyển đổi tệp PDF sang định dạng JSON." + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "Làm cho PDF có thể tìm kiếm và sao chép được." + }, + "alternateMix": { + "name": "Xen kẽ & Trộn trang", + "subtitle": "Gộp PDF bằng cách xen kẽ trang từ mỗi PDF. Giữ nguyên Bookmark." + }, + "addAttachments": { + "name": "Thêm tệp đính kèm", + "subtitle": "Nhúng một hoặc nhiều tệp vào PDF của bạn." + }, + "extractAttachments": { + "name": "Trích xuất tệp đính kèm", + "subtitle": "Trích xuất tất cả tệp được nhúng từ PDF thành ZIP." + }, + "editAttachments": { + "name": "Chỉnh sửa tệp đính kèm", + "subtitle": "Xem hoặc xóa tệp đính kèm trong PDF của bạn." + }, + "dividePages": { + "name": "Chia trang", + "subtitle": "Chia trang theo chiều ngang hoặc chiều dọc." + }, + "addBlankPage": { + "name": "Thêm trang trống", + "subtitle": "Chèn trang trống ở bất kỳ đâu trong PDF của bạn." + }, + "reversePages": { + "name": "Đảo ngược trang", + "subtitle": "Lật ngược thứ tự tất cả các trang trong tài liệu của bạn." + }, + "rotatePdf": { + "name": "Xoay PDF", + "subtitle": "Xoay trang theo bội số 90 độ." + }, + "rotateCustom": { + "name": "Xoay theo độ tùy chỉnh", + "subtitle": "Xoay trang theo bất kỳ góc độ tùy chỉnh nào." + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "Sắp xếp nhiều trang lên một tờ." + }, + "combineToSinglePage": { + "name": "Kết hợp thành một trang", + "subtitle": "Ghép tất cả các trang thành một cuộn liên tục." + }, + "viewMetadata": { + "name": "Xem Metadata", + "subtitle": "Kiểm tra các thuộc tính ẩn của PDF của bạn." + }, + "editMetadata": { + "name": "Chỉnh sửa Metadata", + "subtitle": "Thay đổi tác giả, tiêu đề và các thuộc tính khác." + }, + "pdfsToZip": { + "name": "PDF sang ZIP", + "subtitle": "Đóng gói nhiều tệp PDF thành kho lưu trữ ZIP." + }, + "comparePdfs": { + "name": "So sánh PDF", + "subtitle": "So sánh hai PDF cạnh nhau." + }, + "posterizePdf": { + "name": "Posterize PDF", + "subtitle": "Chia một trang lớn thành nhiều trang nhỏ hơn." + }, + "fixPageSize": { + "name": "Sửa kích thước trang", + "subtitle": "Chuẩn hóa tất cả các trang về cùng một kích thước." + }, + "linearizePdf": { + "name": "Tuyến tính hóa PDF", + "subtitle": "Tối ưu hóa PDF để xem web nhanh." + }, + "pageDimensions": { + "name": "Kích thước trang", + "subtitle": "Phân tích kích thước trang, hướng và đơn vị." + }, + "removeRestrictions": { + "name": "Xóa hạn chế", + "subtitle": "Xóa bảo vệ mật khẩu và hạn chế bảo mật liên quan đến tệp PDF được ký số." + }, + "repairPdf": { + "name": "Sửa chữa PDF", + "subtitle": "Khôi phục dữ liệu từ tệp PDF bị hỏng hoặc hư hỏng." + }, + "encryptPdf": { + "name": "Mã hóa PDF", + "subtitle": "Khóa PDF của bạn bằng cách thêm mật khẩu." + }, + "sanitizePdf": { + "name": "Làm sạch PDF", + "subtitle": "Xóa metadata, ghi chú, script và nhiều hơn nữa." + }, + "decryptPdf": { + "name": "Giải mã PDF", + "subtitle": "Mở khóa PDF bằng cách xóa bảo vệ mật khẩu." + }, + "flattenPdf": { + "name": "Làm phẳng PDF", + "subtitle": "Làm cho trường form và ghi chú không thể chỉnh sửa." + }, + "removeMetadata": { + "name": "Xóa Metadata", + "subtitle": "Loại bỏ dữ liệu ẩn khỏi PDF của bạn." + }, + "changePermissions": { + "name": "Thay đổi quyền", + "subtitle": "Đặt hoặc thay đổi quyền người dùng trên PDF." + }, + "odtToPdf": { + "name": "ODT sang PDF", + "subtitle": "Chuyển đổi tệp OpenDocument Text sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp ODT", + "convertButton": "Chuyển đổi sang PDF" + }, + "csvToPdf": { + "name": "CSV sang PDF", + "subtitle": "Chuyển đổi tệp bảng tính CSV sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp CSV", + "convertButton": "Chuyển đổi sang PDF" + }, + "rtfToPdf": { + "name": "RTF sang PDF", + "subtitle": "Chuyển đổi tài liệu Rich Text Format sang PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp RTF", + "convertButton": "Chuyển đổi sang PDF" + }, + "wordToPdf": { + "name": "Word sang PDF", + "subtitle": "Chuyển đổi tài liệu Word (DOCX, DOC, ODT, RTF) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp DOCX, DOC, ODT, RTF", + "convertButton": "Chuyển đổi sang PDF" + }, + "excelToPdf": { + "name": "Excel sang PDF", + "subtitle": "Chuyển đổi bảng tính Excel (XLSX, XLS, ODS, CSV) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp XLSX, XLS, ODS, CSV", + "convertButton": "Chuyển đổi sang PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint sang PDF", + "subtitle": "Chuyển đổi bài thuyết trình PowerPoint (PPTX, PPT, ODP) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp PPTX, PPT, ODP", + "convertButton": "Chuyển đổi sang PDF" + }, + "markdownToPdf": { + "name": "Markdown sang PDF", + "subtitle": "Viết hoặc dán Markdown và xuất nó thành PDF được định dạng đẹp.", + "paneMarkdown": "Markdown", + "panePreview": "Xem trước", + "btnUpload": "Tải lên", + "btnSyncScroll": "Cuộn đồng bộ", + "btnSettings": "Cài đặt", + "btnExportPdf": "Xuất PDF", + "settingsTitle": "Cài đặt Markdown", + "settingsPreset": "Cài đặt sẵn", + "presetDefault": "Mặc định (kiểu GFM)", + "presetCommonmark": "CommonMark (nghiêm ngặt)", + "presetZero": "Tối thiểu (không có tính năng)", + "settingsOptions": "Tùy chọn Markdown", + "optAllowHtml": "Cho phép thẻ HTML", + "optBreaks": "Chuyển đổi xuống dòng thành
", + "optLinkify": "Tự động chuyển URL thành liên kết", + "optTypographer": "Trình sắp chữ (dấu ngoặc thông minh, v.v.)" + }, + "pdfBooklet": { + "name": "Sách nhỏ PDF", + "subtitle": "Sắp xếp lại các trang để in sách nhỏ hai mặt. Gấp và đóng ghim để tạo sách nhỏ.", + "howItWorks": "Cách hoạt động:", + "step1": "Tải lên tệp PDF.", + "step2": "Các trang sẽ được sắp xếp lại theo thứ tự sách nhỏ.", + "step3": "In hai mặt, lật cạnh ngắn, gấp và đóng ghim.", + "paperSize": "Kích thước giấy", + "orientation": "Hướng", + "portrait": "Dọc", + "landscape": "Ngang", + "pagesPerSheet": "Số trang mỗi tờ", + "createBooklet": "Tạo sách nhỏ", + "processing": "Đang xử lý...", + "pageCount": "Số trang sẽ được bổ sung lên bội số của 4 nếu cần." + }, + "xpsToPdf": { + "name": "XPS sang PDF", + "subtitle": "Chuyển đổi tài liệu XPS/OXPS sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp XPS, OXPS", + "convertButton": "Chuyển đổi sang PDF" + }, + "mobiToPdf": { + "name": "MOBI sang PDF", + "subtitle": "Chuyển đổi sách điện tử MOBI sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp MOBI", + "convertButton": "Chuyển đổi sang PDF" + }, + "epubToPdf": { + "name": "EPUB sang PDF", + "subtitle": "Chuyển đổi sách điện tử EPUB sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp EPUB", + "convertButton": "Chuyển đổi sang PDF" + }, + "fb2ToPdf": { + "name": "FB2 sang PDF", + "subtitle": "Chuyển đổi sách điện tử FictionBook (FB2) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp FB2", + "convertButton": "Chuyển đổi sang PDF" + }, + "cbzToPdf": { + "name": "CBZ sang PDF", + "subtitle": "Chuyển đổi kho lưu trữ truyện tranh (CBZ/CBR) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp CBZ, CBR", + "convertButton": "Chuyển đổi sang PDF" + }, + "wpdToPdf": { + "name": "WPD sang PDF", + "subtitle": "Chuyển đổi tài liệu WordPerfect (WPD) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp WPD", + "convertButton": "Chuyển đổi sang PDF" + }, + "wpsToPdf": { + "name": "WPS sang PDF", + "subtitle": "Chuyển đổi tài liệu WPS Office sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp WPS", + "convertButton": "Chuyển đổi sang PDF" + }, + "xmlToPdf": { + "name": "XML sang PDF", + "subtitle": "Chuyển đổi tài liệu XML sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp XML", + "convertButton": "Chuyển đổi sang PDF" + }, + "pagesToPdf": { + "name": "Pages sang PDF", + "subtitle": "Chuyển đổi tài liệu Apple Pages sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp Pages", + "convertButton": "Chuyển đổi sang PDF" + }, + "odgToPdf": { + "name": "ODG sang PDF", + "subtitle": "Chuyển đổi tệp OpenDocument Graphics (ODG) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp ODG", + "convertButton": "Chuyển đổi sang PDF" + }, + "odsToPdf": { + "name": "ODS sang PDF", + "subtitle": "Chuyển đổi tệp OpenDocument Spreadsheet (ODS) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp ODS", + "convertButton": "Chuyển đổi sang PDF" + }, + "odpToPdf": { + "name": "ODP sang PDF", + "subtitle": "Chuyển đổi tệp OpenDocument Presentation (ODP) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp ODP", + "convertButton": "Chuyển đổi sang PDF" + }, + "pubToPdf": { + "name": "PUB sang PDF", + "subtitle": "Chuyển đổi tệp Microsoft Publisher (PUB) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp PUB", + "convertButton": "Chuyển đổi sang PDF" + }, + "vsdToPdf": { + "name": "VSD sang PDF", + "subtitle": "Chuyển đổi tệp Microsoft Visio (VSD, VSDX) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp VSD, VSDX", + "convertButton": "Chuyển đổi sang PDF" + }, + "psdToPdf": { + "name": "PSD sang PDF", + "subtitle": "Chuyển đổi tệp Adobe Photoshop (PSD) sang định dạng PDF. Hỗ trợ nhiều tệp.", + "acceptedFormats": "Tệp PSD", + "convertButton": "Chuyển đổi sang PDF" + }, + "pdfToSvg": { + "name": "PDF sang SVG", + "subtitle": "Chuyển đổi mỗi trang PDF thành đồ họa vector có thể mở rộng (SVG) với chất lượng hoàn hảo ở mọi kích thước." + }, + "extractTables": { + "name": "Trích xuất bảng PDF", + "subtitle": "Trích xuất bảng từ tệp PDF và xuất dưới dạng CSV, JSON hoặc Markdown." + }, + "pdfToCsv": { + "name": "PDF sang CSV", + "subtitle": "Trích xuất bảng từ PDF và chuyển đổi sang định dạng CSV." + }, + "pdfToExcel": { + "name": "PDF sang Excel", + "subtitle": "Trích xuất bảng từ PDF và chuyển đổi sang định dạng Excel (XLSX)." + }, + "pdfToText": { + "name": "PDF sang Văn bản", + "subtitle": "Trích xuất văn bản từ tệp PDF và lưu dưới dạng tệp văn bản (.txt). Hỗ trợ nhiều tệp.", + "note": "Công cụ này CHỈ hoạt động với các tệp PDF được tạo kỹ thuật số. Đối với tài liệu quét hoặc PDF dựa trên hình ảnh, hãy sử dụng công cụ OCR PDF của chúng tôi.", + "convertButton": "Trích xuất văn bản" + }, + "digitalSignPdf": { + "name": "Chữ ký số PDF", + "pageTitle": "Chữ ký số PDF - Thêm chữ ký mật mã | BentoPDF", + "subtitle": "Thêm chữ ký số mật mã vào PDF của bạn bằng chứng chỉ X.509. Hỗ trợ định dạng PKCS#12 (.pfx, .p12) và PEM. Khóa riêng của bạn không bao giờ rời khỏi trình duyệt.", + "certificateSection": "Chứng chỉ", + "uploadCert": "Tải lên chứng chỉ (.pfx, .p12)", + "certPassword": "Mật khẩu chứng chỉ", + "certPasswordPlaceholder": "Nhập mật khẩu chứng chỉ", + "certInfo": "Thông tin chứng chỉ", + "certSubject": "Chủ thể", + "certIssuer": "Nhà phát hành", + "certValidity": "Hiệu lực", + "signatureDetails": "Chi tiết chữ ký (Tùy chọn)", + "reason": "Lý do", + "reasonPlaceholder": "ví dụ: Tôi phê duyệt tài liệu này", + "location": "Địa điểm", + "locationPlaceholder": "ví dụ: Hà Nội, Việt Nam", + "contactInfo": "Thông tin liên hệ", + "contactPlaceholder": "ví dụ: email@example.com", + "applySignature": "Áp dụng chữ ký số", + "successMessage": "Ký PDF thành công! Chữ ký có thể được xác minh trong bất kỳ trình đọc PDF nào." + }, + "validateSignaturePdf": { + "name": "Xác minh chữ ký PDF", + "pageTitle": "Xác minh chữ ký PDF - Xác thực chữ ký số | BentoPDF", + "subtitle": "Xác minh chữ ký số trong tệp PDF của bạn. Kiểm tra hiệu lực chứng chỉ, xem thông tin người ký và xác nhận tính toàn vẹn tài liệu. Tất cả xử lý diễn ra trong trình duyệt của bạn." + }, + "emailToPdf": { + "name": "Email sang PDF", + "subtitle": "Chuyển đổi tệp email (EML, MSG) sang định dạng PDF. Hỗ trợ xuất Outlook và định dạng email tiêu chuẩn.", + "acceptedFormats": "Tệp EML, MSG", + "convertButton": "Chuyển đổi sang PDF" + }, + "fontToOutline": { + "name": "Phông chữ thành đường viền", + "subtitle": "Chuyển đổi tất cả phông chữ thành đường viền vector để hiển thị nhất quán trên mọi thiết bị." + }, + "deskewPdf": { + "name": "Chỉnh nghiêng PDF", + "subtitle": "Tự động làm thẳng các trang quét bị nghiêng bằng OpenCV." + }, + "pdfToWord": { + "name": "PDF sang Word", + "subtitle": "Chuyển đổi tệp PDF thành tài liệu Word có thể chỉnh sửa." + }, + "extractImages": { + "name": "Trích xuất hình ảnh", + "subtitle": "Trích xuất tất cả hình ảnh nhúng từ tệp PDF của bạn." + }, + "pdfToMarkdown": { + "name": "PDF sang Markdown", + "subtitle": "Chuyển đổi văn bản và bảng PDF sang định dạng Markdown." + }, + "preparePdfForAi": { + "name": "Chuẩn bị PDF cho AI", + "subtitle": "Trích xuất nội dung PDF dưới dạng JSON LlamaIndex cho quy trình RAG/LLM." + }, + "pdfOcg": { + "name": "Lớp PDF (OCG)", + "subtitle": "Xem, chuyển đổi, thêm và xóa các lớp OCG trong PDF của bạn." + }, + "pdfToPdfa": { + "name": "PDF sang PDF/A", + "subtitle": "Chuyển đổi PDF sang PDF/A để lưu trữ lâu dài." + }, + "rasterizePdf": { + "name": "Rasterize PDF", + "subtitle": "Chuyển đổi PDF thành PDF dựa trên hình ảnh. Làm phẳng các lớp và xóa văn bản có thể chọn." + }, + "pdfWorkflow": { + "name": "Trình xây dựng quy trình PDF", + "subtitle": "Xây dựng quy trình xử lý PDF tùy chỉnh bằng trình chỉnh sửa nút trực quan.", + "nodes": "Nút", + "searchNodes": "Tìm kiếm nút...", + "run": "Chạy", + "clear": "Xóa", + "save": "Lưu", + "load": "Tải", + "export": "Xuất", + "import": "Nhập", + "ready": "Sẵn sàng", + "settings": "Cài đặt", + "processing": "Đang xử lý...", + "saveTemplate": "Lưu mẫu", + "templateName": "Tên mẫu", + "templatePlaceholder": "ví dụ: Quy trình hóa đơn", + "cancel": "Hủy", + "loadTemplate": "Tải mẫu", + "noTemplates": "Chưa có mẫu nào được lưu.", + "ok": "OK", + "workflowCompleted": "Quy trình đã hoàn tất", + "errorDuringExecution": "Lỗi trong quá trình thực thi", + "addNodeError": "Thêm ít nhất một nút để chạy quy trình.", + "needInputOutput": "Quy trình của bạn cần ít nhất một nút đầu vào và một nút đầu ra để chạy.", + "enterName": "Vui lòng nhập tên.", + "templateExists": "Đã tồn tại mẫu có tên này.", + "templateSaved": "Đã lưu mẫu \"{{name}}\".", + "templateLoaded": "Đã tải mẫu \"{{name}}\".", + "failedLoadTemplate": "Không thể tải mẫu.", + "noSettings": "Không có cài đặt nào có thể cấu hình cho nút này.", + "advancedSettings": "Cài đặt nâng cao" + } +} diff --git a/public/locales/zh-TW/common.json b/public/locales/zh-TW/common.json new file mode 100644 index 0000000..7b10dc4 --- /dev/null +++ b/public/locales/zh-TW/common.json @@ -0,0 +1,365 @@ +{ + "nav": { + "home": "首頁", + "about": "關於我們", + "contact": "聯絡我們", + "licensing": "產品授權", + "allTools": "所有工具", + "openMainMenu": "開啟主選單", + "language": "語言" + }, + "donation": { + "message": "喜歡 BentoPDF?幫助我們保持免費和開源!", + "button": "捐贈" + }, + "hero": { + "title": "專為隱私打造的", + "pdfToolkit": "PDF 工具箱", + "builtForPrivacy": " ", + "noSignups": "不須註冊", + "unlimitedUse": "無限使用", + "worksOffline": "離線可用", + "startUsing": "立刻開始使用" + }, + "usedBy": { + "title": "被下列公司及其員工採用" + }, + "features": { + "title": "為何你該選擇", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "不須註冊", + "description": "立即可用,不須帳號或電子郵件。" + }, + "noUploads": { + "title": "不須上傳", + "description": "所有文件都在用戶端處理,永遠不會離開你的裝置。" + }, + "foreverFree": { + "title": "永遠免費", + "description": "所有工具免費使用,沒有試用期,也沒有付費牆。" + }, + "noLimits": { + "title": "沒有限制", + "description": "隨心所欲的使用,沒有任何隱藏限制。" + }, + "batchProcessing": { + "title": "批量處理", + "description": "一次處理無限量的 PDF 檔案。" + }, + "lightningFast": { + "title": "快如閃電", + "description": "瞬間處理 PDF,無須忍受任何等待或延遲。" + } + }, + "tools": { + "title": "開始使用", + "toolsLabel": "工具", + "subtitle": "點擊任意工具以開始上傳檔案", + "searchPlaceholder": "搜尋工具(例如「合併」或「分割」...)", + "backToTools": "返回工具列表", + "firstLoadNotice": "首次載入需要一點時間,因為我們正在下載轉換引擎。之後所有載入將即時完成。" + }, + "upload": { + "clickToSelect": "點擊以選擇檔案", + "orDragAndDrop": "或將檔案拖放到此處", + "pdfOrImages": "PDF 或圖片", + "filesNeverLeave": "你的檔案永遠不會離開你的裝置。", + "addMore": "添加更多檔案", + "clearAll": "清除全部", + "clearFiles": "清除檔案", + "hints": { + "singlePdf": "單個 PDF 檔案", + "pdfFile": "PDF 檔案", + "multiplePdfs2": "多個 PDF 檔案(至少 2 個)", + "bmpImages": "BMP 圖片", + "oneOrMorePdfs": "一個或多個 PDF 檔案", + "pdfDocuments": "PDF 文件", + "oneOrMoreCsv": "一個或多個 CSV 檔案", + "multiplePdfsSupported": "支援多個 PDF 檔案", + "singleOrMultiplePdfs": "支援單個或多個 PDF 檔案", + "singlePdfFile": "單個 PDF 檔案", + "pdfWithForms": "含有表單欄位的 PDF 檔案", + "heicImages": "HEIC/HEIF 圖片", + "jpgImages": "JPG、JPEG、JP2、JPX 圖片", + "pdfsOrImages": "PDF 或圖片", + "oneOrMoreOdt": "一個或多個 ODT 檔案", + "singlePdfOnly": "僅限單個 PDF 檔案", + "pdfFiles": "PDF 檔案", + "multiplePdfs": "多個 PDF 檔案", + "pngImages": "PNG 圖片", + "pdfFilesOneOrMore": "PDF 檔案(一個或多個)", + "oneOrMoreRtf": "一個或多個 RTF 檔案", + "svgGraphics": "SVG 圖形", + "tiffImages": "TIFF 圖片", + "webpImages": "WebP 圖片" + } + }, + "loader": { + "processing": "正在處理..." + }, + "alert": { + "title": "提示", + "ok": "確認" + }, + "preview": { + "title": "文件預覽", + "downloadAsPdf": "下載為 PDF", + "close": "關閉" + }, + "settings": { + "title": "設定", + "shortcuts": "快捷鍵", + "preferences": "偏好設定", + "displayPreferences": "顯示設定", + "searchShortcuts": "搜尋快捷鍵...", + "shortcutsInfo": "按下並按住按鍵以設定快捷鍵。變更將自動儲存。", + "shortcutsWarning": "⚠️ 避免使用瀏覽器常用快捷鍵(Cmd/Ctrl+W、Cmd/Ctrl+T、Cmd/Ctrl+N 等),它們可能無法穩定運作。", + "import": "匯入", + "export": "匯出", + "resetToDefaults": "恢復預設值", + "fullWidthMode": "全寬模式", + "fullWidthDescription": "使用全螢幕寬度而非置中容器顯示所有工具", + "settingsAutoSaved": "設定會自動儲存", + "clickToSet": "點擊以設定", + "pressKeys": "按下按鍵...", + "warnings": { + "alreadyInUse": "快捷鍵已被占用", + "assignedTo": "已被指定為:", + "chooseDifferent": "請選擇一個不同的快捷鍵。", + "reserved": "保留快捷鍵警告", + "commonlyUsed": "常被用於:", + "unreliable": "這個快捷鍵可能與系統/瀏覽器行為衝突或無法穩定運作。", + "useAnyway": "仍要使用嗎?", + "resetTitle": "重設快捷鍵", + "resetMessage": "確定要將所有快捷鍵恢復為預設值嗎?

這個操作無法被撤回。", + "importSuccessTitle": "匯入成功", + "importSuccessMessage": "快捷鍵匯入成功!", + "importFailTitle": "匯入失敗", + "importFailMessage": "匯入快捷鍵失敗。無效的檔案格式。" + } + }, + "warning": { + "title": "警告", + "cancel": "取消", + "proceed": "繼續" + }, + "compliance": { + "title": "你的資料永遠不會離開你的裝置", + "weKeep": "我們確保", + "yourInfoSafe": "你的資訊安全", + "byFollowingStandards": "遵循全球安全標準。", + "processingLocal": "所有處理過程都在你的裝置上進行。", + "gdpr": { + "title": "符合 GDPR 規範", + "description": "保護歐盟境內個人的數據及隱私。" + }, + "ccpa": { + "title": "符合 CCPA 規範", + "description": "賦予加州居民對其個人資訊如何被蒐集、使用及分享的權利。" + }, + "hipaa": { + "title": "符合 HIPAA 規範", + "description": "制定處理美國健保系統中敏感健康資訊的規範。" + } + }, + "faq": { + "title": "常見", + "questions": "問題", + "isFree": { + "question": "BentoPDF 真的是免費的嗎?", + "answer": "沒錯,完全免費。BentoPDF 上的所有工具均為 100% 免費使用,並且沒有檔案限制、無須註冊且無浮水印。我們相信每個人都值得免費使用簡單且強大的 PDF 工具。" + }, + "areFilesSecure": { + "question": "我的檔案都是安全的嗎?它們都在哪裡被處理?", + "answer": "你的檔案都非常安全,因為它們從未離開你的電腦。所有處理過程都直接在你的網頁瀏覽器中進行(用戶端)。我們永遠不會將你的檔案上傳到伺服器,因此你對你的文件保有完全的隱私與控制權。" + }, + "platforms": { + "question": "我能在 Mac、Windows 和行動裝置上使用嗎?", + "answer": "可以!由於 BentoPDF 完全在你的瀏覽器中運作,它在任何有著現代網頁瀏覽器的系統中都能運行,包含 Windows、macOS、Linux、iOS 和 Android。" + }, + "gdprCompliant": { + "question": "BentoPDF 符合 GDPR 規範嗎?", + "answer": "是的。BentoPDF 完全符合 GDPR 規範。由於所有檔案處理都在你的瀏覽器本地發生且我們永不蒐集或傳輸你的檔案至任何伺服器,我們無法存取你的資料。這確保你的文件永遠都在你的控制之中。" + }, + "dataStorage": { + "question": "你會保存或追蹤我的檔案嗎?", + "answer": "不。我們永不儲存、追蹤或記錄你的檔案。你在 BentoPDF 上進行的任何操作都發生在你的瀏覽器記憶體中,並且會在你關閉頁面後立即消失。沒有上傳、沒有歷史紀錄且無伺服器參與。" + }, + "different": { + "question": "BentoPDF 跟其他的 PDF 工具有何不同之處?", + "answer": "大多數 PDF 工具都透過將你的檔案上傳至伺服器好進行處理。BentoPDF 永遠不會那麼做。我們使用安全且現代的網頁科技以在你的瀏覽器中直接處理檔案。這意味著更快的性能、更強的隱私與完全的安心。" + }, + "browserBased": { + "question": "瀏覽器端處理如何保障我的安全?", + "answer": "透過完全在你的瀏覽器內運作,BentoPDF 確保你的文件從未離開你的裝置。這消除了伺服器遭駭、資料外洩與未授權訪問的風險。你的檔案永遠都屬於你。" + }, + "analytics": { + "question": "你會使用 Cookies 或網站分析來追蹤我嗎?", + "answer": "我們在乎你的隱私。BentoPDF 並不追蹤個人資訊。我們僅使用 Simple Analytics 來查看匿名訪問次數。這代表我們能知道有多少使用者造訪過我們的網站,但我們永遠都不會知道你是誰。Simple Analytics 完全符合 GDPR 規範且尊重你的隱私。" + }, + "sectionTitle": "常見問題" + }, + "testimonials": { + "title": "看看我們的", + "users": "使用者", + "say": "怎麼說" + }, + "support": { + "title": "喜歡我的作品嗎?", + "description": "BentoPDF 是一個出於熱情開發的專案,旨在為每個人提供一個免費、注重隱私且強大的 PDF 工具組。如果有幫上你的忙,請考慮支持它的開發。每杯咖啡都意義重大!", + "buyMeCoffee": "買杯咖啡給我" + }, + "footer": { + "copyright": "© 2026 BentoPDF。版權所有。", + "version": "版本", + "company": "公司", + "aboutUs": "關於我們", + "faqLink": "常見問題", + "contactUs": "聯絡我們", + "legal": "法律", + "termsAndConditions": "服務條款", + "privacyPolicy": "隱私政策", + "followUs": "關注我們" + }, + "merge": { + "title": "合併 PDF", + "description": "合併整個檔案,或選擇特定頁面合併為新文件。", + "fileMode": "檔案模式", + "pageMode": "頁面模式", + "howItWorks": "使用說明:", + "fileModeInstructions": [ + "點擊並抓取圖標來改變檔案順序。", + "在每個文件的「頁碼」框中,你可以僅指定想要合併的頁面範圍(例如「1-3, 5」)。", + "將「頁碼」框留空以包含該檔案的所有頁面。" + ], + "pageModeInstructions": [ + "下列是你上傳的 PDF 中的所有頁面。", + "只要將個別頁面縮圖拖放到指定位置,即可為新檔案建立您想要的精確排序。" + ], + "mergePdfs": "合併 PDF" + }, + "common": { + "page": "頁", + "pages": "頁", + "of": " / ", + "download": "下載", + "cancel": "取消", + "save": "儲存", + "delete": "刪除", + "edit": "編輯", + "add": "添加", + "remove": "移除", + "loading": "載入中...", + "error": "錯誤", + "success": "成功", + "file": "檔案", + "files": "檔案", + "close": "關閉" + }, + "about": { + "hero": { + "title": "我們相信 PDF 工具應該", + "subtitle": "快速、私密且免費。", + "noCompromises": "絕不妥協。" + }, + "mission": { + "title": "我們的任務", + "description": "在尊重你的隱私且從不要求收費的同時提供最全面的 PDF 工具箱。我們相信核心文件工具應讓任何人隨時隨地不受限的使用。" + }, + "philosophy": { + "label": "我們的核心理念", + "title": "永遠以隱私為重。", + "description": "在數據被商品化的時代,我們採取截然不同的做法。所有 BentoPDF 工具的處理流程皆在你的瀏覽器本地完成。這意味著你的檔案絕不觸及我們的伺服器,我們從未看見你的文件內容,更不會追蹤你的行為。你的文件將始終保持無可置疑的私密性。這不僅是功能,更是我們的立身之本。" + }, + "whyBentopdf": { + "title": "為何選擇", + "speed": { + "title": "生來迅捷", + "description": "無需等待與伺服器間的上傳和下載。透過在你的瀏覽器中使用 WebAssembly 等現代網路科技處理檔案,我們得以為所有工具提供無與倫比的速度。" + }, + "free": { + "title": "完全免費", + "description": "沒有試用期、訂閱、隱藏費用與所謂的「高級」功能。我們相信強大的 PDF 工具應該是一種公共設施,而非以營利為重。" + }, + "noAccount": { + "title": "無須帳號", + "description": "立即開始使用任何工具。我們不需要你的電子郵件、密碼或任何個人資訊。你的工作流程應當匿名且不受阻礙。" + }, + "openSource": { + "title": "開源精神", + "description": "將透明性視為核心打造。我們使用了如 PDF-lib 和 PDF.js 等優秀的開源庫,並且相信社群驅動力能讓強大的工具惠及每一個人。" + } + }, + "cta": { + "title": "準備好開始了嗎?", + "description": "加入成千上萬信任 BentoPDF 能勝任他們日常文件需求的使用者們。體驗隱私與性能所帶來的差距。", + "button": "探索所有工具" + } + }, + "contact": { + "title": "保持聯絡", + "subtitle": "我們很樂意收到你的訊息。無論你想提出的是問題、回饋或功能請求,都請隨時聯繫我們。", + "email": "你可以直接透過電子郵件聯繫我們:" + }, + "licensing": { + "title": "授權使用", + "subtitle": "選擇適合需求的產品授權。" + }, + "multiTool": { + "uploadPdfs": "上傳 PDF", + "upload": "上傳", + "addBlankPage": "添加空白頁面", + "edit": "編輯:", + "undo": "復原", + "redo": "取消復原", + "reset": "重設", + "selection": "選取:", + "selectAll": "選取全部", + "deselectAll": "取消選取全部", + "rotate": "旋轉:", + "rotateLeft": "左", + "rotateRight": "右", + "transform": "變換:", + "duplicate": "複製", + "split": "分割", + "clear": "清除:", + "delete": "刪除", + "download": "下載:", + "downloadSelected": "下載選取的項目", + "exportPdf": "匯出 PDF", + "uploadPdfFiles": "選擇 PDF 檔案", + "dragAndDrop": "拖放 PDF 檔案至此處,或是點擊以選取", + "selectFiles": "選擇檔案", + "renderingPages": "渲染頁面...", + "actions": { + "duplicatePage": "複製此頁", + "deletePage": "刪除此頁", + "insertPdf": "在此頁後插入 PDF", + "toggleSplit": "在此頁後切換分割" + }, + "pleaseWait": "請稍後", + "pagesRendering": "正在渲染頁面。請稍後...", + "noPagesSelected": "未選擇頁面", + "selectOnePage": "請至少選擇一頁以開始下載。", + "noPages": "無頁面", + "noPagesToExport": "無可匯出的頁面。", + "renderingTitle": "正在渲染頁面預覽", + "errorRendering": "無法渲染頁面縮圖", + "error": "錯誤", + "failedToLoad": "載入失敗" + }, + "howItWorks": { + "title": "使用方式", + "step1": "點擊或拖放您的檔案到此處", + "step2": "點擊處理按鈕開始", + "step3": "立即儲存處理後的檔案" + }, + "relatedTools": { + "title": "相關 PDF 工具" + }, + "simpleMode": { + "title": "PDF 工具", + "subtitle": "選擇一個工具開始使用" + } +} diff --git a/public/locales/zh-TW/tools.json b/public/locales/zh-TW/tools.json new file mode 100644 index 0000000..b129178 --- /dev/null +++ b/public/locales/zh-TW/tools.json @@ -0,0 +1,631 @@ +{ + "categories": { + "popularTools": "熱門工具", + "editAnnotate": "編輯與註解", + "convertToPdf": "轉換為 PDF", + "convertFromPdf": "從 PDF 轉換", + "organizeManage": "組織與管理", + "optimizeRepair": "優化與修復", + "securePdf": "安全 PDF" + }, + "pdfMultiTool": { + "name": "PDF 多功能工具", + "subtitle": "在統一的頁面中合併、分割、組織、刪除、旋轉、添加空白頁面、提取與複製。" + }, + "mergePdf": { + "name": "合併 PDF", + "subtitle": "將多個 PDF 合併為一個檔案。保留書籤。" + }, + "splitPdf": { + "name": "分割 PDF", + "subtitle": "將指定範圍的頁面提取為新的 PDF。" + }, + "compressPdf": { + "name": "壓縮 PDF", + "subtitle": "降低你的 PDF 檔案大小。", + "algorithmLabel": "壓縮演算法", + "condense": "Condense(推薦)", + "photon": "Photon(適用於圖片較多的 PDF)", + "condenseInfo": "Condense 使用高級壓縮:移除冗餘資料、優化圖片、精簡字型。適用於大多數 PDF。", + "photonInfo": "Photon 將頁面轉換為圖片。適用於圖片較多/掃描的 PDF。", + "photonWarning": "警告:文字將無法選取,連結將失效。", + "levelLabel": "壓縮等級", + "light": "輕度(保持品質)", + "balanced": "平衡(推薦)", + "aggressive": "積極(更小檔案)", + "extreme": "極限(最大壓縮)", + "grayscale": "轉換為灰階", + "grayscaleHint": "透過移除色彩資訊來縮小檔案大小", + "customSettings": "自訂設定", + "customSettingsHint": "微調壓縮參數:", + "outputQuality": "輸出品質", + "resizeImagesTo": "調整圖片至", + "onlyProcessAbove": "僅處理高於", + "removeMetadata": "移除中繼資料", + "subsetFonts": "精簡字型(移除未使用的字符)", + "removeThumbnails": "移除嵌入的縮圖", + "compressButton": "壓縮 PDF" + }, + "pdfEditor": { + "name": "PDF 編輯器", + "subtitle": "註解、螢光、塗黑、評論、添加圖形或圖片、搜尋與查看 PDF。" + }, + "jpgToPdf": { + "name": "JPG 轉 PDF", + "subtitle": "從一張或多張 JPG 圖片建立 PDF。" + }, + "signPdf": { + "name": "簽署 PDF", + "subtitle": "繪製、輸入或上傳你的簽名。" + }, + "cropPdf": { + "name": "裁切 PDF", + "subtitle": "修剪你的 PDF 中所有頁面的邊界。" + }, + "extractPages": { + "name": "提取頁面", + "subtitle": "將選取的頁面保存為新的檔案。" + }, + "duplicateOrganize": { + "name": "複製與組織", + "subtitle": "複製、重新排序與刪除頁面。" + }, + "deletePages": { + "name": "刪除頁面", + "subtitle": "移除你的文件中的特定頁面。" + }, + "editBookmarks": { + "name": "編輯書籤", + "subtitle": "添加、編輯、匯入、刪除與提取 PDF 書籤。" + }, + "tableOfContents": { + "name": "目錄", + "subtitle": "從 PDF 書籤生成目錄頁。" + }, + "pageNumbers": { + "name": "頁碼", + "subtitle": "在你的文件中插入頁碼。" + }, + "batesNumbering": { + "name": "Bates編號", + "subtitle": "在一個或多個PDF檔案中新增連續的Bates編號。" + }, + "addWatermark": { + "name": "添加浮水印", + "subtitle": "在你的 PDF 頁面上壓印文字或圖片。", + "applyToAllPages": "套用至所有頁面" + }, + "headerFooter": { + "name": "頁首與頁尾", + "subtitle": "在頁面的頂部與底部新增文字。" + }, + "invertColors": { + "name": "反轉顏色", + "subtitle": "為你的 PDF 建立深色版本。" + }, + "scannerEffect": { + "name": "掃描效果", + "subtitle": "讓你的 PDF 看起來像掃描文件。", + "scanSettings": "掃描設定", + "colorspace": "色彩空間", + "gray": "灰階", + "border": "邊框", + "rotate": "旋轉", + "rotateVariance": "旋轉變異", + "brightness": "亮度", + "contrast": "對比度", + "blur": "模糊", + "noise": "雜訊", + "yellowish": "泛黃", + "resolution": "解析度", + "processButton": "套用掃描效果" + }, + "adjustColors": { + "name": "調整顏色", + "subtitle": "微調 PDF 的亮度、對比度、飽和度等。", + "colorSettings": "顏色設定", + "brightness": "亮度", + "contrast": "對比度", + "saturation": "飽和度", + "hueShift": "色相偏移", + "temperature": "色溫", + "tint": "色調", + "gamma": "Gamma", + "sepia": "復古色", + "processButton": "套用顏色調整" + }, + "backgroundColor": { + "name": "背景顏色", + "subtitle": "更改你的 PDF 的背景顏色。" + }, + "changeTextColor": { + "name": "更改文字顏色", + "subtitle": "更改你的 PDF 中的文字顏色。" + }, + "addStamps": { + "name": "添加印章", + "subtitle": "使用註解工具列在你的 PDF 中添加圖片印章。", + "usernameLabel": "印章使用者名稱", + "usernamePlaceholder": "輸入你的名稱(印章用)", + "usernameHint": "該名稱會出現在你建立的印章上。" + }, + "removeAnnotations": { + "name": "移除註解", + "subtitle": "去除留言、螢光與連結。" + }, + "pdfFormFiller": { + "name": "PDF 表單填寫器", + "subtitle": "直接在你的瀏覽器中填寫表單。支援 XFA 表單。" + }, + "createPdfForm": { + "name": "建立 PDF 表單", + "subtitle": "透過拖放文字框建立可填寫的 PDF 表單。" + }, + "removeBlankPages": { + "name": "移除空白頁面", + "subtitle": "自動偵測並刪除空白頁面。", + "sensitivityHint": "越高 = 越嚴格,僅偵測純空白頁面。越低 = 允許包含少量內容的頁面。" + }, + "imageToPdf": { + "name": "圖片轉 PDF", + "subtitle": "將 JPG、PNG、WebP、BMP、TIFF、SVG 與 HEIC 轉換為 PDF。" + }, + "pngToPdf": { + "name": "PNG 轉 PDF", + "subtitle": "從一張或多張 PNG 圖片建立 PDF。" + }, + "webpToPdf": { + "name": "WebP 轉 PDF", + "subtitle": "從一張或多張 WebP 圖片建立 PDF。" + }, + "svgToPdf": { + "name": "SVG 轉 PDF", + "subtitle": "從一張或多張 SVG 圖片建立 PDF。" + }, + "bmpToPdf": { + "name": "BMP 轉 PDF", + "subtitle": "從一張或多張 BMP 圖片建立 PDF。" + }, + "heicToPdf": { + "name": "HEIC 轉 PDF", + "subtitle": "從一張或多張 HEIC 圖片建立 PDF。" + }, + "tiffToPdf": { + "name": "TIFF 轉 PDF", + "subtitle": "從一張或多張 TIFF 圖片建立 PDF。" + }, + "textToPdf": { + "name": "Text 轉 PDF", + "subtitle": "將純文字檔案轉換為 PDF。" + }, + "jsonToPdf": { + "name": "JSON 轉 PDF", + "subtitle": "將 JSON 檔案轉換為 PDF 格式。" + }, + "pdfToJpg": { + "name": "PDF 轉 JPG", + "subtitle": "將每個 PDF 頁面轉換為 JPG 圖片。" + }, + "pdfToPng": { + "name": "PDF 轉 PNG", + "subtitle": "將每個 PDF 頁面轉換為 PNG 圖片。" + }, + "pdfToWebp": { + "name": "PDF 轉 WebP", + "subtitle": "將每個 PDF 頁面轉換為 WebP 圖片。" + }, + "pdfToBmp": { + "name": "PDF 轉 BMP", + "subtitle": "將每個 PDF 頁面轉換為 BMP 圖片。" + }, + "pdfToTiff": { + "name": "PDF 轉 TIFF", + "subtitle": "將每個 PDF 頁面轉換為 TIFF 圖片。" + }, + "pdfToGreyscale": { + "name": "PDF 轉灰階", + "subtitle": "將所有顏色轉換為黑白。" + }, + "pdfToJson": { + "name": "PDF 轉 JSON", + "subtitle": "將 PDF 檔案轉換為 JSON 格式。" + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "使 PDF 可搜尋且可複製。" + }, + "alternateMix": { + "name": "交錯混合頁面", + "subtitle": "將每個 PDF 的頁面交錯合併。保留書籤。" + }, + "addAttachments": { + "name": "添加附件", + "subtitle": "嵌入一個或多個檔案至你的 PDF 中。" + }, + "extractAttachments": { + "name": "提取附件", + "subtitle": "從 PDF 中提取所有嵌入的檔案為 ZIP。" + }, + "editAttachments": { + "name": "編輯附件", + "subtitle": "查看或移除你的 PDF 中的附件。" + }, + "dividePages": { + "name": "分割頁面", + "subtitle": "垂直或水平分割頁面。" + }, + "addBlankPage": { + "name": "添加空白頁面", + "subtitle": "在你的 PDF 中的任一位置插入空白頁面。" + }, + "reversePages": { + "name": "反轉頁面", + "subtitle": "反轉你的文件中所有頁面的順序。" + }, + "rotatePdf": { + "name": "旋轉 PDF", + "subtitle": "以 90 度增量旋轉頁面。" + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "將多個頁面排列在單張紙上。" + }, + "combineToSinglePage": { + "name": "合併為單一頁面", + "subtitle": "將所有頁面縫合為一個單一且連續的滾動頁面。" + }, + "viewMetadata": { + "name": "查看元資料", + "subtitle": "檢視你的 PDF 中的隱藏屬性。" + }, + "editMetadata": { + "name": "編輯元資料", + "subtitle": "更改作者、標題和其他屬性。" + }, + "pdfsToZip": { + "name": "PDF 轉 ZIP", + "subtitle": "將多個 PDF 檔案打包為 ZIP 壓縮檔。" + }, + "comparePdfs": { + "name": "比較 PDF", + "subtitle": "並排比較兩個 PDF。" + }, + "posterizePdf": { + "name": "海報化 PDF", + "subtitle": "將大頁面分割為多個較小的頁面。" + }, + "fixPageSize": { + "name": "修復頁面大小", + "subtitle": "將所有頁面標準化為統一尺寸。" + }, + "linearizePdf": { + "name": "線性化 PDF", + "subtitle": "為快速網頁瀏覽優化 PDF。" + }, + "pageDimensions": { + "name": "頁面尺寸", + "subtitle": "分析頁面大小、方向和單位。" + }, + "removeRestrictions": { + "name": "移除限制", + "subtitle": "移除與數位簽名的 PDF 檔案相關的密碼保護與安全限制。" + }, + "repairPdf": { + "name": "修復 PDF", + "subtitle": "從受損的 PDF 檔案中復原資料。" + }, + "encryptPdf": { + "name": "加密 PDF", + "subtitle": "透過添加密碼為你的 PDF 上鎖。" + }, + "sanitizePdf": { + "name": "清理 PDF", + "subtitle": "移除元資料、註解、腳本與其他資料。" + }, + "decryptPdf": { + "name": "解密 PDF", + "subtitle": "透過移除密碼保護解鎖 PDF。" + }, + "flattenPdf": { + "name": "平面化 PDF", + "subtitle": "使表單欄位和註解不可編輯。" + }, + "removeMetadata": { + "name": "移除元資料", + "subtitle": "除去你的 PDF 中的隱藏資料。" + }, + "changePermissions": { + "name": "更改權限", + "subtitle": "設定或變更 PDF 上的使用者權限。" + }, + "emailToPdf": { + "name": "Email 轉 PDF", + "subtitle": "將電子郵件檔案 (EML, MSG) 轉換為 PDF 格式。支援 Outlook 匯出和標準電子郵件格式。", + "acceptedFormats": "EML, MSG 檔案", + "convertButton": "轉換為 PDF" + }, + "fontToOutline": { + "name": "字型轉外框", + "subtitle": "將所有字型轉換為向量外框,確保在所有裝置上呈現一致。" + }, + "deskewPdf": { + "name": "PDF 歪斜修正", + "subtitle": "使用 OpenCV 自動調正傾斜的掃描頁面。" + }, + "rotateCustom": { + "name": "Rotate by Custom Degrees", + "subtitle": "Rotate pages by any custom angle." + }, + "odtToPdf": { + "name": "ODT to PDF", + "subtitle": "Convert OpenDocument Text files to PDF format. Supports multiple files.", + "acceptedFormats": "ODT files", + "convertButton": "Convert to PDF" + }, + "csvToPdf": { + "name": "CSV to PDF", + "subtitle": "Convert CSV spreadsheet files to PDF format. Supports multiple files.", + "acceptedFormats": "CSV files", + "convertButton": "Convert to PDF" + }, + "rtfToPdf": { + "name": "RTF to PDF", + "subtitle": "Convert Rich Text Format documents to PDF. Supports multiple files.", + "acceptedFormats": "RTF files", + "convertButton": "Convert to PDF" + }, + "wordToPdf": { + "name": "Word to PDF", + "subtitle": "Convert Word documents (DOCX, DOC, ODT, RTF) to PDF format. Supports multiple files.", + "acceptedFormats": "DOCX, DOC, ODT, RTF files", + "convertButton": "Convert to PDF" + }, + "excelToPdf": { + "name": "Excel to PDF", + "subtitle": "Convert Excel spreadsheets (XLSX, XLS, ODS, CSV) to PDF format. Supports multiple files.", + "acceptedFormats": "XLSX, XLS, ODS, CSV files", + "convertButton": "Convert to PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint to PDF", + "subtitle": "Convert PowerPoint presentations (PPTX, PPT, ODP) to PDF format. Supports multiple files.", + "acceptedFormats": "PPTX, PPT, ODP files", + "convertButton": "Convert to PDF" + }, + "markdownToPdf": { + "name": "Markdown to PDF", + "subtitle": "Write or paste Markdown and export it as a beautifully formatted PDF.", + "paneMarkdown": "Markdown", + "panePreview": "Preview", + "btnUpload": "Upload", + "btnSyncScroll": "Sync Scroll", + "btnSettings": "Settings", + "btnExportPdf": "Export PDF", + "settingsTitle": "Markdown Settings", + "settingsPreset": "Preset", + "presetDefault": "Default (GFM-like)", + "presetCommonmark": "CommonMark (strict)", + "presetZero": "Minimal (no features)", + "settingsOptions": "Markdown Options", + "optAllowHtml": "Allow HTML tags", + "optBreaks": "Convert newlines to
", + "optLinkify": "Auto-convert URLs to links", + "optTypographer": "Typographer (smart quotes, etc.)" + }, + "pdfBooklet": { + "name": "PDF Booklet", + "subtitle": "Rearrange pages for double-sided booklet printing. Fold and staple to create a booklet.", + "howItWorks": "How it works:", + "step1": "Upload a PDF file.", + "step2": "Pages will be rearranged in booklet order.", + "step3": "Print double-sided, flip on short edge, fold and staple.", + "paperSize": "Paper Size", + "orientation": "Orientation", + "portrait": "Portrait", + "landscape": "Landscape", + "pagesPerSheet": "Pages per Sheet", + "createBooklet": "Create Booklet", + "processing": "Processing...", + "pageCount": "Page count will be padded to multiple of 4 if needed." + }, + "xpsToPdf": { + "name": "XPS to PDF", + "subtitle": "Convert XPS/OXPS documents to PDF format. Supports multiple files.", + "acceptedFormats": "XPS, OXPS files", + "convertButton": "Convert to PDF" + }, + "mobiToPdf": { + "name": "MOBI to PDF", + "subtitle": "Convert MOBI e-books to PDF format. Supports multiple files.", + "acceptedFormats": "MOBI files", + "convertButton": "Convert to PDF" + }, + "epubToPdf": { + "name": "EPUB to PDF", + "subtitle": "Convert EPUB e-books to PDF format. Supports multiple files.", + "acceptedFormats": "EPUB files", + "convertButton": "Convert to PDF" + }, + "fb2ToPdf": { + "name": "FB2 to PDF", + "subtitle": "Convert FictionBook (FB2) e-books to PDF format. Supports multiple files.", + "acceptedFormats": "FB2 files", + "convertButton": "Convert to PDF" + }, + "cbzToPdf": { + "name": "CBZ to PDF", + "subtitle": "Convert comic book archives (CBZ/CBR) to PDF format. Supports multiple files.", + "acceptedFormats": "CBZ, CBR files", + "convertButton": "Convert to PDF" + }, + "wpdToPdf": { + "name": "WPD to PDF", + "subtitle": "Convert WordPerfect documents (WPD) to PDF format. Supports multiple files.", + "acceptedFormats": "WPD files", + "convertButton": "Convert to PDF" + }, + "wpsToPdf": { + "name": "WPS to PDF", + "subtitle": "Convert WPS Office documents to PDF format. Supports multiple files.", + "acceptedFormats": "WPS files", + "convertButton": "Convert to PDF" + }, + "xmlToPdf": { + "name": "XML to PDF", + "subtitle": "Convert XML documents to PDF format. Supports multiple files.", + "acceptedFormats": "XML files", + "convertButton": "Convert to PDF" + }, + "pagesToPdf": { + "name": "Pages to PDF", + "subtitle": "Convert Apple Pages documents to PDF format. Supports multiple files.", + "acceptedFormats": "Pages files", + "convertButton": "Convert to PDF" + }, + "odgToPdf": { + "name": "ODG to PDF", + "subtitle": "Convert OpenDocument Graphics (ODG) files to PDF format. Supports multiple files.", + "acceptedFormats": "ODG files", + "convertButton": "Convert to PDF" + }, + "odsToPdf": { + "name": "ODS to PDF", + "subtitle": "Convert OpenDocument Spreadsheet (ODS) files to PDF format. Supports multiple files.", + "acceptedFormats": "ODS files", + "convertButton": "Convert to PDF" + }, + "odpToPdf": { + "name": "ODP to PDF", + "subtitle": "Convert OpenDocument Presentation (ODP) files to PDF format. Supports multiple files.", + "acceptedFormats": "ODP files", + "convertButton": "Convert to PDF" + }, + "pubToPdf": { + "name": "PUB to PDF", + "subtitle": "Convert Microsoft Publisher (PUB) files to PDF format. Supports multiple files.", + "acceptedFormats": "PUB files", + "convertButton": "Convert to PDF" + }, + "vsdToPdf": { + "name": "VSD to PDF", + "subtitle": "Convert Microsoft Visio (VSD, VSDX) files to PDF format. Supports multiple files.", + "acceptedFormats": "VSD, VSDX files", + "convertButton": "Convert to PDF" + }, + "psdToPdf": { + "name": "PSD to PDF", + "subtitle": "Convert Adobe Photoshop (PSD) files to PDF format. Supports multiple files.", + "acceptedFormats": "PSD files", + "convertButton": "Convert to PDF" + }, + "pdfToSvg": { + "name": "PDF to SVG", + "subtitle": "Convert each page of a PDF file into a scalable vector graphic (SVG) for perfect quality at any size." + }, + "extractTables": { + "name": "Extract PDF Tables", + "subtitle": "Extract tables from PDF files and export as CSV, JSON, or Markdown." + }, + "pdfToCsv": { + "name": "PDF to CSV", + "subtitle": "Extract tables from PDF and convert to CSV format." + }, + "pdfToExcel": { + "name": "PDF to Excel", + "subtitle": "Extract tables from PDF and convert to Excel (XLSX) format." + }, + "pdfToText": { + "name": "PDF to Text", + "subtitle": "Extract text from PDF files and save as plain text (.txt). Supports multiple files.", + "note": "This tool works ONLY with digitally created PDFs. For scanned documents or image-based PDFs, use our OCR PDF tool instead.", + "convertButton": "Extract Text" + }, + "digitalSignPdf": { + "name": "Digital Signature PDF", + "pageTitle": "Digital Signature PDF - Add Cryptographic Signature | BentoPDF", + "subtitle": "Add a cryptographic digital signature to your PDF using X.509 certificates. Supports PKCS#12 (.pfx, .p12) and PEM formats. Your private key never leaves your browser.", + "certificateSection": "Certificate", + "uploadCert": "Upload certificate (.pfx, .p12)", + "certPassword": "Certificate Password", + "certPasswordPlaceholder": "Enter certificate password", + "certInfo": "Certificate Information", + "certSubject": "Subject", + "certIssuer": "Issuer", + "certValidity": "Valid", + "signatureDetails": "Signature Details (Optional)", + "reason": "Reason", + "reasonPlaceholder": "e.g., I approve this document", + "location": "Location", + "locationPlaceholder": "e.g., New York, USA", + "contactInfo": "Contact Info", + "contactPlaceholder": "e.g., email@example.com", + "applySignature": "Apply Digital Signature", + "successMessage": "PDF signed successfully! The signature can be verified in any PDF reader." + }, + "validateSignaturePdf": { + "name": "Validate PDF Signature", + "pageTitle": "Validate PDF Signature - Verify Digital Signatures | BentoPDF", + "subtitle": "Verify digital signatures in your PDF files. Check certificate validity, view signer details, and confirm document integrity. All processing happens in your browser." + }, + "pdfToWord": { + "name": "PDF 轉 Word", + "subtitle": "將 PDF 檔案轉換為可編輯的 Word 文件。" + }, + "extractImages": { + "name": "擷取圖片", + "subtitle": "從 PDF 檔案中擷取所有嵌入的圖片。" + }, + "pdfToMarkdown": { + "name": "PDF 轉 Markdown", + "subtitle": "將 PDF 文字和表格轉換為 Markdown 格式。" + }, + "preparePdfForAi": { + "name": "為 AI 準備 PDF", + "subtitle": "將 PDF 內容擷取為 LlamaIndex JSON,用於 RAG/LLM 管線。" + }, + "pdfOcg": { + "name": "PDF 圖層 (OCG)", + "subtitle": "檢視、切換、新增和刪除 PDF 中的 OCG 圖層。" + }, + "pdfToPdfa": { + "name": "PDF 轉 PDF/A", + "subtitle": "將 PDF 轉換為 PDF/A 格式以進行長期歸檔。" + }, + "rasterizePdf": { + "name": "柵格化 PDF", + "subtitle": "將 PDF 轉換為基於影像的 PDF。展平圖層並移除可選取的文字。" + }, + "pdfWorkflow": { + "name": "PDF 工作流程建構器", + "subtitle": "使用視覺化節點編輯器建構自訂 PDF 處理管線。", + "nodes": "節點", + "searchNodes": "搜尋節點...", + "run": "執行", + "clear": "清除", + "save": "儲存", + "load": "載入", + "export": "匯出", + "import": "匯入", + "ready": "就緒", + "settings": "設定", + "processing": "處理中...", + "saveTemplate": "儲存範本", + "templateName": "範本名稱", + "templatePlaceholder": "例如:發票處理工作流程", + "cancel": "取消", + "loadTemplate": "載入範本", + "noTemplates": "目前沒有已儲存的範本。", + "ok": "確定", + "workflowCompleted": "工作流程已完成", + "errorDuringExecution": "執行過程中發生錯誤", + "addNodeError": "請至少新增一個節點以執行工作流程。", + "needInputOutput": "您的工作流程至少需要一個輸入節點和一個輸出節點才能執行。", + "enterName": "請輸入名稱。", + "templateExists": "已存在相同名稱的範本。", + "templateSaved": "範本「{{name}}」已儲存。", + "templateLoaded": "範本「{{name}}」已載入。", + "failedLoadTemplate": "載入範本失敗。", + "noSettings": "此節點沒有可設定的選項。", + "advancedSettings": "進階設定" + } +} diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index a65fed9..a3b76bd 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -1,318 +1,365 @@ { - "nav": { - "home": "首页", - "about": "关于我们", - "contact": "联系我们", - "licensing": "许可", - "allTools": "所有工具", - "openMainMenu": "打开主菜单", - "language": "语言" + "nav": { + "home": "首页", + "about": "关于我们", + "contact": "联系我们", + "licensing": "许可", + "allTools": "所有工具", + "openMainMenu": "打开主菜单", + "language": "语言" + }, + "donation": { + "message": "喜欢 BentoPDF?帮助我们保持免费和开源!", + "button": "捐赠" + }, + "hero": { + "title": "专为隐私打造的", + "pdfToolkit": "PDF 工具箱", + "builtForPrivacy": " ", + "noSignups": "无需注册", + "unlimitedUse": "无限使用", + "worksOffline": "离线可用", + "startUsing": "立即开始" + }, + "usedBy": { + "title": "被众多公司和个人信赖,包括" + }, + "features": { + "title": "为什么选择", + "bentoPdf": "BentoPDF?", + "noSignup": { + "title": "无需注册", + "description": "即刻开始,无需账户或电子邮件。" }, - "hero": { - "title": "专为隐私打造的", - "pdfToolkit": "PDF 工具箱", - "builtForPrivacy": " ", - "noSignups": "无需注册", - "unlimitedUse": "无限使用", - "worksOffline": "离线可用", - "startUsing": "立即开始" + "noUploads": { + "title": "无需上传", + "description": "100% 客户端处理,您的文件从未离开您的设备。" }, - "usedBy": { - "title": "被众多公司和个人信赖,包括" + "foreverFree": { + "title": "永久免费", + "description": "所有工具免费,无试用期,无付费墙。" }, - "features": { - "title": "为什么选择", - "bentoPdf": "BentoPDF?", - "noSignup": { - "title": "无需注册", - "description": "即刻开始,无需账户或电子邮件。" - }, - "noUploads": { - "title": "无需上传", - "description": "100% 客户端处理,您的文件从未离开您的设备。" - }, - "foreverFree": { - "title": "永久免费", - "description": "所有工具免费,无试用期,无付费墙。" - }, - "noLimits": { - "title": "无限制", - "description": "随心使用,无任何隐形限制。" - }, - "batchProcessing": { - "title": "批量处理", - "description": "一次处理无限数量的 PDF 文件。" - }, - "lightningFast": { - "title": "极速处理", - "description": "瞬间处理 PDF, 无需等待。" - } + "noLimits": { + "title": "无限制", + "description": "随心使用,无任何隐形限制。" }, - "tools": { - "title": "开始使用", - "toolsLabel": "工具", - "subtitle": "点击工具以打开文件上传", - "searchPlaceholder": "搜索工具 (例如 '合并', '分割'...)", - "backToTools": "返回工具列表" + "batchProcessing": { + "title": "批量处理", + "description": "一次处理无限数量的 PDF 文件。" }, - "upload": { - "clickToSelect": "点击选择文件", - "orDragAndDrop": "或将文件拖放到此处", - "pdfOrImages": "PDF 或图片", - "filesNeverLeave": "您的文件从未离开您的设备。", - "addMore": "添加更多文件", - "clearAll": "清空所有" - }, - "loader": { - "processing": "处理中..." - }, - "alert": { - "title": "提示", - "ok": "确定" - }, - "preview": { - "title": "文档预览", - "downloadAsPdf": "下载 PDF", - "close": "关闭" - }, - "settings": { - "title": "设置", - "shortcuts": "快捷键", - "preferences": "偏好设置", - "displayPreferences": "显示设置", - "searchShortcuts": "搜索快捷键...", - "shortcutsInfo": "按下并按住按键以设置快捷键。更改将自动保存。", - "shortcutsWarning": "⚠️ 避免使用通用的浏览器快捷键 (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N 等),因为它们可能无法稳定工作。", - "import": "导入", - "export": "导出", - "resetToDefaults": "恢复默认", - "fullWidthMode": "全宽模式", - "fullWidthDescription": "使用全屏宽度展示所有工具,而非居中显示", - "settingsAutoSaved": "设置已自动保存", - "clickToSet": "点击设置", - "pressKeys": "按下按键...", - "warnings": { - "alreadyInUse": "快捷键已被使用", - "assignedTo": "已分配给:", - "chooseDifferent": "请选择其他快捷键。", - "reserved": "保留快捷键警告", - "commonlyUsed": "通常用于:", - "unreliable": "此快捷键可能无法稳定工作,或与浏览器/系统快捷键冲突。", - "useAnyway": "仍要使用吗?", - "resetTitle": "重置快捷键", - "resetMessage": "确定要将所有快捷键重置为默认值吗?

此操作无法撤销。", - "importSuccessTitle": "导入成功", - "importSuccessMessage": "快捷键导入成功!", - "importFailTitle": "导入失败", - "importFailMessage": "导入快捷键失败。文件格式无效。" - } - }, - "warning": { - "title": "警告", - "cancel": "取消", - "proceed": "继续" - }, - "compliance": { - "title": "您的数据从未离开您的设备", - "weKeep": "我们会保持", - "yourInfoSafe": "您的信息安全", - "byFollowingStandards": ",遵循全球安全标准。", - "processingLocal": "所有处理都在您的设备上本地进行。", - "gdpr": { - "title": "GDPR 合规", - "description": "保护欧盟境内个人的个人数据和隐私。" - }, - "ccpa": { - "title": "CCPA 合规", - "description": "赋予加州居民对其个人信息的收集、使用和共享的权利。" - }, - "hipaa": { - "title": "HIPAA 合规", - "description": "制定美国医疗系统中处理敏感健康信息的保障措施。" - } - }, - "faq": { - "title": "常见", - "questions": "问题", - "isFree": { - "question": "BentoPDF 真的免费吗?", - "answer": "是的,完全免费。BentoPDF 上的所有工具均可 100% 免费使用,没有文件限制,无需注册,也没有水印。我们相信每个人都应该能够使用简单、强大的 PDF 工具,而无需通过付费墙。" - }, - "areFilesSecure": { - "question": "我的文件安全吗?它们在哪里处理?", - "answer": "您的文件非常安全,因为它们从未离开您的电脑。所有处理都直接在您的网络浏览器(客户端)中进行。我们从不将您的文件上传到服务器,因此您可以对文档保持完全的隐私和控制。" - }, - "platforms": { - "question": "它适用于 Mac、Windows 和手机吗?", - "answer": "是的!由于 BentoPDF 完全在您的浏览器中运行,因此它适用于任何带有现代网络浏览器的操作系统,包括 Windows、macOS、Linux、iOS 和 Android。" - }, - "gdprCompliant": { - "question": "BentoPDF 符合 GDPR 吗?", - "answer": "是的。BentoPDF 完全符合 GDPR。由于所有文件处理都在您的浏览器中本地进行,并且我们从不收集或将您的文件传输到任何服务器,因此我们无法访问您的数据。这确保您始终掌控您的文档。" - }, - "dataStorage": { - "question": "你们会存储或跟踪我的任何文件吗?", - "answer": "不。我们从不存储、跟踪或记录您的文件。您在 BentoPDF 上所做的所有操作都在您的浏览器内存中进行,并在您关闭页面后消失。没有上传,没有历史记录,也没有涉及服务器。" - }, - "different": { - "question": "BentoPDF 与其他 PDF 工具有何不同?", - "answer": "大多数 PDF 工具将您的文件上传到服务器进行处理。BentoPDF 从不这样做。我们使用安全、现代的网络技术直接在您的浏览器中处理您的文件。这意味着更快的性能、更强的隐私和完全的安心。" - }, - "browserBased": { - "question": "基于浏览器的处理如何保护我的安全?", - "answer": "通过完全在您的浏览器内部运行,BentoPDF 确保您的文件从未离开您的设备。这消除了服务器黑客攻击、数据泄露或未经授权访问的风险。您的文件始终属于您。" - }, - "analytics": { - "question": "你们使用 Cookie 或分析来跟踪我吗?", - "answer": "我们关心您的隐私。BentoPDF 不会跟踪个人信息。我们仅使用 Simple Analytics 查看匿名访问计数。这意味着我们可以知道有多少用户访问我们的网站,但我们永远不知道您是谁。Simple Analytics 完全符合 GDPR 并尊重您的隐私。" - } - }, - "testimonials": { - "title": "我们的", - "users": "用户", - "say": "评价" - }, - "support": { - "title": "喜欢这个项目?", - "description": "BentoPDF 是一个充满激情的项目,旨在为每个人提供免费、私密且强大的 PDF 工具箱。如果您觉得它有用,请考虑支持它的开发。每一杯咖啡都是莫大的支持!", - "buyMeCoffee": "请我喝杯咖啡" - }, - "footer": { - "copyright": "© 2025 BentoPDF. 保留所有权利。", - "version": "版本", - "company": "公司", - "aboutUs": "关于我们", - "faqLink": "常见问题", - "contactUs": "联系我们", - "legal": "法律", - "termsAndConditions": "服务条款", - "privacyPolicy": "隐私政策", - "followUs": "关注我们" - }, - "merge": { - "title": "合并 PDF", - "description": "合并整个文件,或选择特定页面合并到新文档中。", - "fileMode": "文件模式", - "pageMode": "页面模式", - "howItWorks": "使用说明:", - "fileModeInstructions": [ - "点击并拖动图标以更改文件的顺序。", - "在每个文件的 '页码' 框中,您可以指定范围(例如 '1-3, 5')以仅合并这些页面。", - "将 '页码' 框留空以包含该文件的所有页面。" - ], - "pageModeInstructions": [ - "您上传的 PDF 的所有页面显示在下方。", - "只需拖放单个页面缩略图,即可为您新文件对页面进行排序。" - ], - "mergePdfs": "合并 PDF" - }, - "common": { - "page": "页", - "pages": "页", - "of": " / ", - "download": "下载", - "cancel": "取消", - "save": "保存", - "delete": "删除", - "edit": "编辑", - "add": "添加", - "remove": "移除", - "loading": "加载中...", - "error": "错误", - "success": "成功", - "file": "文件", - "files": "文件" - }, - "about": { - "hero": { - "title": "我们相信 PDF 工具应该是", - "subtitle": "快速、私密且免费的。", - "noCompromises": "绝不妥协。" - }, - "mission": { - "title": "我们的使命", - "description": "提供最全面的 PDF 工具箱,尊重您的隐私,且永不收费。我们相信基本的文档工具应该对所有人、在任何地方都触手可及,没有任何障碍。" - }, - "philosophy": { - "label": "我们的核心理念", - "title": "隐私至上。始终如一。", - "description": "在数据被商品化的时代,我们采取不同的方式。BentoPDF 工具的所有处理都在您的浏览器本地进行。这意味着您的文件从未接触我们的服务器,我们从不查看您的文档,也不跟踪您的操作。您的文档保持绝对的私密性。这不仅是一项功能;这是我们的基石。" - }, - "whyBentopdf": { - "title": "为什么选择", - "speed": { - "title": "为速度而生", - "description": "无需等待上传或从服务器下载。通过使用 WebAssembly 等现代网络技术直接在您的浏览器中处理文件,我们为所有工具提供了无与伦比的速度。" - }, - "free": { - "title": "完全免费", - "description": "无试用,无订阅,无隐藏费用,也没有被锁定的 '高级' 功能。我们相信强大的 PDF 工具应该是一种公共设施,而不是盈利中心。" - }, - "noAccount": { - "title": "无需账户", - "description": "立即开始使用任何工具。我们不需要您的电子邮件、密码或任何个人信息。您的工作流程应该是无摩擦且匿名的。" - }, - "openSource": { - "title": "开源精神", - "description": "以透明度为核心构建。我们利用了像 PDF-lib 和 PDF.js 这样优秀的开源库,并相信社区驱动的力量能使强大的工具惠及每一个人。" - } - }, - "cta": { - "title": "准备好开始了吗?", - "description": "加入成千上万信任 BentoPDF 满足日常文档需求的用户。体验隐私和性能带来的不同。", - "button": "探索所有工具" - } - }, - "contact": { - "title": "联系我们", - "subtitle": "我们很乐意听到您的声音。无论您有问题、反馈还是功能请求,请随时联系我们。", - "email": "您可以直接通过电子邮件联系我们:" - }, - "licensing": { - "title": "许可适用", - "subtitle": "选择适合您需求的许可。" - }, - "multiTool": { - "uploadPdfs": "上传 PDF", - "upload": "上传", - "addBlankPage": "添加空白页", - "edit": "编辑:", - "undo": "撤销", - "redo": "重做", - "reset": "重置", - "selection": "选择:", - "selectAll": "全选", - "deselectAll": "取消全选", - "rotate": "旋转:", - "rotateLeft": "向左", - "rotateRight": "向右", - "transform": "变换:", - "duplicate": "复制", - "split": "拆分", - "clear": "清除:", - "delete": "删除", - "download": "下载:", - "downloadSelected": "下载选中", - "exportPdf": "导出 PDF", - "uploadPdfFiles": "选择 PDF 文件", - "dragAndDrop": "将 PDF 文件拖放到此处,或点击选择", - "selectFiles": "选择文件", - "renderingPages": "正在渲染页面...", - "actions": { - "duplicatePage": "复制此页", - "deletePage": "删除此页", - "insertPdf": "在此页后插入 PDF", - "toggleSplit": "在此页后切换拆分" - }, - "pleaseWait": "请稍候", - "pagesRendering": "页面正在渲染中,请稍候...", - "noPagesSelected": "未选择页面", - "selectOnePage": "请至少选择一页以进行下载。", - "noPages": "没有页面", - "noPagesToExport": "没有可导出的页面。", - "renderingTitle": "正在渲染页面预览", - "errorRendering": "渲染页面缩略图失败", - "error": "错误", - "failedToLoad": "加载失败" + "lightningFast": { + "title": "极速处理", + "description": "瞬间处理 PDF, 无需等待。" } -} \ No newline at end of file + }, + "tools": { + "title": "开始使用", + "toolsLabel": "工具", + "subtitle": "点击工具以打开文件上传", + "searchPlaceholder": "搜索工具 (例如 '合并', '分割'...)", + "backToTools": "返回工具列表", + "firstLoadNotice": "首次加载需要一点时间,因为我们正在下载转换引擎。之后所有加载将即时完成。" + }, + "upload": { + "clickToSelect": "点击选择文件", + "orDragAndDrop": "或将文件拖放到此处", + "pdfOrImages": "PDF 或图片", + "filesNeverLeave": "您的文件从未离开您的设备。", + "addMore": "添加更多文件", + "clearAll": "清空所有", + "clearFiles": "清除文件", + "hints": { + "singlePdf": "单个 PDF 文件", + "pdfFile": "PDF 文件", + "multiplePdfs2": "多个 PDF 文件(至少 2 个)", + "bmpImages": "BMP 图片", + "oneOrMorePdfs": "一个或多个 PDF 文件", + "pdfDocuments": "PDF 文档", + "oneOrMoreCsv": "一个或多个 CSV 文件", + "multiplePdfsSupported": "支持多个 PDF 文件", + "singleOrMultiplePdfs": "支持单个或多个 PDF 文件", + "singlePdfFile": "单个 PDF 文件", + "pdfWithForms": "带有表单字段的 PDF 文件", + "heicImages": "HEIC/HEIF 图片", + "jpgImages": "JPG、JPEG、JP2、JPX 图片", + "pdfsOrImages": "PDF 或图片", + "oneOrMoreOdt": "一个或多个 ODT 文件", + "singlePdfOnly": "仅限单个 PDF 文件", + "pdfFiles": "PDF 文件", + "multiplePdfs": "多个 PDF 文件", + "pngImages": "PNG 图片", + "pdfFilesOneOrMore": "PDF 文件(一个或多个)", + "oneOrMoreRtf": "一个或多个 RTF 文件", + "svgGraphics": "SVG 图形", + "tiffImages": "TIFF 图片", + "webpImages": "WebP 图片" + } + }, + "loader": { + "processing": "处理中..." + }, + "alert": { + "title": "提示", + "ok": "确定" + }, + "preview": { + "title": "文档预览", + "downloadAsPdf": "下载 PDF", + "close": "关闭" + }, + "settings": { + "title": "设置", + "shortcuts": "快捷键", + "preferences": "偏好设置", + "displayPreferences": "显示设置", + "searchShortcuts": "搜索快捷键...", + "shortcutsInfo": "按下并按住按键以设置快捷键。更改将自动保存。", + "shortcutsWarning": "⚠️ 避免使用通用的浏览器快捷键 (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N 等),因为它们可能无法稳定工作。", + "import": "导入", + "export": "导出", + "resetToDefaults": "恢复默认", + "fullWidthMode": "全宽模式", + "fullWidthDescription": "使用全屏宽度展示所有工具,而非居中显示", + "settingsAutoSaved": "设置已自动保存", + "clickToSet": "点击设置", + "pressKeys": "按下按键...", + "warnings": { + "alreadyInUse": "快捷键已被使用", + "assignedTo": "已分配给:", + "chooseDifferent": "请选择其他快捷键。", + "reserved": "保留快捷键警告", + "commonlyUsed": "通常用于:", + "unreliable": "此快捷键可能无法稳定工作,或与浏览器/系统快捷键冲突。", + "useAnyway": "仍要使用吗?", + "resetTitle": "重置快捷键", + "resetMessage": "确定要将所有快捷键重置为默认值吗?

此操作无法撤销。", + "importSuccessTitle": "导入成功", + "importSuccessMessage": "快捷键导入成功!", + "importFailTitle": "导入失败", + "importFailMessage": "导入快捷键失败。文件格式无效。" + } + }, + "warning": { + "title": "警告", + "cancel": "取消", + "proceed": "继续" + }, + "compliance": { + "title": "您的数据从未离开您的设备", + "weKeep": "我们会保持", + "yourInfoSafe": "您的信息安全", + "byFollowingStandards": ",遵循全球安全标准。", + "processingLocal": "所有处理都在您的设备上本地进行。", + "gdpr": { + "title": "GDPR 合规", + "description": "保护欧盟境内个人的个人数据和隐私。" + }, + "ccpa": { + "title": "CCPA 合规", + "description": "赋予加州居民对其个人信息的收集、使用和共享的权利。" + }, + "hipaa": { + "title": "HIPAA 合规", + "description": "制定美国医疗系统中处理敏感健康信息的保障措施。" + } + }, + "faq": { + "title": "常见", + "questions": "问题", + "isFree": { + "question": "BentoPDF 真的免费吗?", + "answer": "是的,完全免费。BentoPDF 上的所有工具均可 100% 免费使用,没有文件限制,无需注册,也没有水印。我们相信每个人都应该能够使用简单、强大的 PDF 工具,而无需通过付费墙。" + }, + "areFilesSecure": { + "question": "我的文件安全吗?它们在哪里处理?", + "answer": "您的文件非常安全,因为它们从未离开您的电脑。所有处理都直接在您的网络浏览器(客户端)中进行。我们从不将您的文件上传到服务器,因此您可以对文档保持完全的隐私和控制。" + }, + "platforms": { + "question": "它适用于 Mac、Windows 和手机吗?", + "answer": "是的!由于 BentoPDF 完全在您的浏览器中运行,因此它适用于任何带有现代网络浏览器的操作系统,包括 Windows、macOS、Linux、iOS 和 Android。" + }, + "gdprCompliant": { + "question": "BentoPDF 符合 GDPR 吗?", + "answer": "是的。BentoPDF 完全符合 GDPR。由于所有文件处理都在您的浏览器中本地进行,并且我们从不收集或将您的文件传输到任何服务器,因此我们无法访问您的数据。这确保您始终掌控您的文档。" + }, + "dataStorage": { + "question": "你们会存储或跟踪我的任何文件吗?", + "answer": "不。我们从不存储、跟踪或记录您的文件。您在 BentoPDF 上所做的所有操作都在您的浏览器内存中进行,并在您关闭页面后消失。没有上传,没有历史记录,也没有涉及服务器。" + }, + "different": { + "question": "BentoPDF 与其他 PDF 工具有何不同?", + "answer": "大多数 PDF 工具将您的文件上传到服务器进行处理。BentoPDF 从不这样做。我们使用安全、现代的网络技术直接在您的浏览器中处理您的文件。这意味着更快的性能、更强的隐私和完全的安心。" + }, + "browserBased": { + "question": "基于浏览器的处理如何保护我的安全?", + "answer": "通过完全在您的浏览器内部运行,BentoPDF 确保您的文件从未离开您的设备。这消除了服务器黑客攻击、数据泄露或未经授权访问的风险。您的文件始终属于您。" + }, + "analytics": { + "question": "你们使用 Cookie 或分析来跟踪我吗?", + "answer": "我们关心您的隐私。BentoPDF 不会跟踪个人信息。我们仅使用 Simple Analytics 查看匿名访问计数。这意味着我们可以知道有多少用户访问我们的网站,但我们永远不知道您是谁。Simple Analytics 完全符合 GDPR 并尊重您的隐私。" + }, + "sectionTitle": "常见问题" + }, + "testimonials": { + "title": "我们的", + "users": "用户", + "say": "评价" + }, + "support": { + "title": "喜欢这个项目?", + "description": "BentoPDF 是一个充满激情的项目,旨在为每个人提供免费、私密且强大的 PDF 工具箱。如果您觉得它有用,请考虑支持它的开发。每一杯咖啡都是莫大的支持!", + "buyMeCoffee": "请我喝杯咖啡" + }, + "footer": { + "copyright": "© 2026 BentoPDF. 保留所有权利。", + "version": "版本", + "company": "公司", + "aboutUs": "关于我们", + "faqLink": "常见问题", + "contactUs": "联系我们", + "legal": "法律", + "termsAndConditions": "服务条款", + "privacyPolicy": "隐私政策", + "followUs": "关注我们" + }, + "merge": { + "title": "合并 PDF", + "description": "合并整个文件,或选择特定页面合并到新文档中。", + "fileMode": "文件模式", + "pageMode": "页面模式", + "howItWorks": "使用说明:", + "fileModeInstructions": [ + "点击并拖动图标以更改文件的顺序。", + "在每个文件的 '页码' 框中,您可以指定范围(例如 '1-3, 5')以仅合并这些页面。", + "将 '页码' 框留空以包含该文件的所有页面。" + ], + "pageModeInstructions": [ + "您上传的 PDF 的所有页面显示在下方。", + "只需拖放单个页面缩略图,即可为您新文件对页面进行排序。" + ], + "mergePdfs": "合并 PDF" + }, + "common": { + "page": "页", + "pages": "页", + "of": " / ", + "download": "下载", + "cancel": "取消", + "save": "保存", + "delete": "删除", + "edit": "编辑", + "add": "添加", + "remove": "移除", + "loading": "加载中...", + "error": "错误", + "success": "成功", + "file": "文件", + "files": "文件", + "close": "关闭" + }, + "about": { + "hero": { + "title": "我们相信 PDF 工具应该是", + "subtitle": "快速、私密且免费的。", + "noCompromises": "绝不妥协。" + }, + "mission": { + "title": "我们的使命", + "description": "提供最全面的 PDF 工具箱,尊重您的隐私,且永不收费。我们相信基本的文档工具应该对所有人、在任何地方都触手可及,没有任何障碍。" + }, + "philosophy": { + "label": "我们的核心理念", + "title": "隐私至上。始终如一。", + "description": "在数据被商品化的时代,我们采取不同的方式。BentoPDF 工具的所有处理都在您的浏览器本地进行。这意味着您的文件从未接触我们的服务器,我们从不查看您的文档,也不跟踪您的操作。您的文档保持绝对的私密性。这不仅是一项功能;这是我们的基石。" + }, + "whyBentopdf": { + "title": "为什么选择", + "speed": { + "title": "为速度而生", + "description": "无需等待上传或从服务器下载。通过使用 WebAssembly 等现代网络技术直接在您的浏览器中处理文件,我们为所有工具提供了无与伦比的速度。" + }, + "free": { + "title": "完全免费", + "description": "无试用,无订阅,无隐藏费用,也没有被锁定的 '高级' 功能。我们相信强大的 PDF 工具应该是一种公共设施,而不是盈利中心。" + }, + "noAccount": { + "title": "无需账户", + "description": "立即开始使用任何工具。我们不需要您的电子邮件、密码或任何个人信息。您的工作流程应该是无摩擦且匿名的。" + }, + "openSource": { + "title": "开源精神", + "description": "以透明度为核心构建。我们利用了像 PDF-lib 和 PDF.js 这样优秀的开源库,并相信社区驱动的力量能使强大的工具惠及每一个人。" + } + }, + "cta": { + "title": "准备好开始了吗?", + "description": "加入成千上万信任 BentoPDF 满足日常文档需求的用户。体验隐私和性能带来的不同。", + "button": "探索所有工具" + } + }, + "contact": { + "title": "联系我们", + "subtitle": "我们很乐意听到您的声音。无论您有问题、反馈还是功能请求,请随时联系我们。", + "email": "您可以直接通过电子邮件联系我们:" + }, + "licensing": { + "title": "许可适用", + "subtitle": "选择适合您需求的许可。" + }, + "multiTool": { + "uploadPdfs": "上传 PDF", + "upload": "上传", + "addBlankPage": "添加空白页", + "edit": "编辑:", + "undo": "撤销", + "redo": "重做", + "reset": "重置", + "selection": "选择:", + "selectAll": "全选", + "deselectAll": "取消全选", + "rotate": "旋转:", + "rotateLeft": "向左", + "rotateRight": "向右", + "transform": "变换:", + "duplicate": "复制", + "split": "拆分", + "clear": "清除:", + "delete": "删除", + "download": "下载:", + "downloadSelected": "下载选中", + "exportPdf": "导出 PDF", + "uploadPdfFiles": "选择 PDF 文件", + "dragAndDrop": "将 PDF 文件拖放到此处,或点击选择", + "selectFiles": "选择文件", + "renderingPages": "正在渲染页面...", + "actions": { + "duplicatePage": "复制此页", + "deletePage": "删除此页", + "insertPdf": "在此页后插入 PDF", + "toggleSplit": "在此页后切换拆分" + }, + "pleaseWait": "请稍候", + "pagesRendering": "页面正在渲染中,请稍候...", + "noPagesSelected": "未选择页面", + "selectOnePage": "请至少选择一页以进行下载。", + "noPages": "没有页面", + "noPagesToExport": "没有可导出的页面。", + "renderingTitle": "正在渲染页面预览", + "errorRendering": "渲染页面缩略图失败", + "error": "错误", + "failedToLoad": "加载失败" + }, + "howItWorks": { + "title": "使用方法", + "step1": "点击或拖放您的文件到此处", + "step2": "点击处理按钮开始", + "step3": "立即保存处理后的文件" + }, + "relatedTools": { + "title": "相关 PDF 工具" + }, + "simpleMode": { + "title": "PDF 工具", + "subtitle": "选择一个工具开始使用" + } +} diff --git a/public/locales/zh/tools.json b/public/locales/zh/tools.json index a64ed0f..d867a35 100644 --- a/public/locales/zh/tools.json +++ b/public/locales/zh/tools.json @@ -1,279 +1,631 @@ { - "categories": { - "popularTools": "热门工具", - "editAnnotate": "编辑与注释", - "convertToPdf": "转换为 PDF", - "convertFromPdf": "从 PDF 转换", - "organizeManage": "组织与管理", - "optimizeRepair": "优化与修复", - "securePdf": "安全 PDF" - }, - "pdfMultiTool": { - "name": "PDF 多功能工具", - "subtitle": "在一个统一的界面中合并、分割、组织、删除、旋转、添加空白页、提取和复制。" - }, - "mergePdf": { - "name": "合并 PDF", - "subtitle": "将多个 PDF 合并为一个文件。保留书签。" - }, - "splitPdf": { - "name": "分割 PDF", - "subtitle": "将指定范围的页面提取到新 PDF 中。" - }, - "compressPdf": { - "name": "压缩 PDF", - "subtitle": "减小您的 PDF 文件大小。" - }, - "pdfEditor": { - "name": "PDF 编辑器", - "subtitle": "注释、高亮、涂黑、评论、添加形状/图片、搜索和查看 PDF。" - }, - "jpgToPdf": { - "name": "JPG 转 PDF", - "subtitle": "从一张或多张 JPG 图片创建 PDF。" - }, - "signPdf": { - "name": "签署 PDF", - "subtitle": "绘制、键入或上传您的签名。" - }, - "cropPdf": { - "name": "裁剪 PDF", - "subtitle": "修剪 PDF 中每一页的边距。" - }, - "extractPages": { - "name": "提取页面", - "subtitle": "将选定的页面保存为新文件。" - }, - "duplicateOrganize": { - "name": "复制与组织", - "subtitle": "复制、重新排序和删除页面。" - }, - "deletePages": { - "name": "删除页面", - "subtitle": "自您的文档中移除特定页面。" - }, - "editBookmarks": { - "name": "编辑书签", - "subtitle": "添加、编辑、导入、删除和提取 PDF 书签。" - }, - "tableOfContents": { - "name": "目录", - "subtitle": "根据 PDF 书签生成目录页。" - }, - "pageNumbers": { - "name": "页码", - "subtitle": "将页码插入到您的文档中。" - }, - "addWatermark": { - "name": "添加水印", - "subtitle": "在您的 PDF 页面上添加文字或图片水印。" - }, - "headerFooter": { - "name": "页眉和页脚", - "subtitle": "在页面顶部和底部添加文字。" - }, - "invertColors": { - "name": "反转颜色", - "subtitle": "创建您的 PDF 的“暗黑模式”版本。" - }, - "backgroundColor": { - "name": "背景颜色", - "subtitle": "更改您的 PDF 的背景颜色。" - }, - "changeTextColor": { - "name": "更改文本颜色", - "subtitle": "更改您 PDF 中文本的颜色。" - }, - "addStamps": { - "name": "添加印章", - "subtitle": "使用注释工具栏向您的 PDF 添加图片印章。" - }, - "removeAnnotations": { - "name": "移除注释", - "subtitle": "移除评论、高亮和链接。" - }, - "pdfFormFiller": { - "name": "PDF 表单填写器", - "subtitle": "直接在浏览器中填写表单。也支持 XFA 表单。" - }, - "createPdfForm": { - "name": "创建 PDF 表单", - "subtitle": "使用拖放文本字段创建可填写的 PDF 表单。" - }, - "removeBlankPages": { - "name": "移除空白页", - "subtitle": "自动检测并删除空白页。" - }, - "imageToPdf": { - "name": "图片转 PDF", - "subtitle": "将 JPG, PNG, WebP, BMP, TIFF, SVG, HEIC 转换为 PDF。" - }, - "pngToPdf": { - "name": "PNG 转 PDF", - "subtitle": "从一张或多张 PNG 图片创建 PDF。" - }, - "webpToPdf": { - "name": "WebP 转 PDF", - "subtitle": "从一张或多张 WebP 图片创建 PDF。" - }, - "svgToPdf": { - "name": "SVG 转 PDF", - "subtitle": "从一张或多张 SVG 图片创建 PDF。" - }, - "bmpToPdf": { - "name": "BMP 转 PDF", - "subtitle": "从一张或多张 BMP 图片创建 PDF。" - }, - "heicToPdf": { - "name": "HEIC 转 PDF", - "subtitle": "从一张或多张 HEIC 图片创建 PDF。" - }, - "tiffToPdf": { - "name": "TIFF 转 PDF", - "subtitle": "从一张或多张 TIFF 图片创建 PDF。" - }, - "textToPdf": { - "name": "文本转 PDF", - "subtitle": "将纯文本文件转换为 PDF。" - }, - "jsonToPdf": { - "name": "JSON 转 PDF", - "subtitle": "将 JSON 文件转换为 PDF 格式。" - }, - "pdfToJpg": { - "name": "PDF 转 JPG", - "subtitle": "将每一页 PDF 转换为 JPG 图片。" - }, - "pdfToPng": { - "name": "PDF 转 PNG", - "subtitle": "将每一页 PDF 转换为 PNG 图片。" - }, - "pdfToWebp": { - "name": "PDF 转 WebP", - "subtitle": "将每一页 PDF 转换为 WebP 图片。" - }, - "pdfToBmp": { - "name": "PDF 转 BMP", - "subtitle": "将每一页 PDF 转换为 BMP 图片。" - }, - "pdfToTiff": { - "name": "PDF 转 TIFF", - "subtitle": "将每一页 PDF 转换为 TIFF 图片。" - }, - "pdfToGreyscale": { - "name": "PDF 转 灰度", - "subtitle": "将所有颜色转换为黑白。" - }, - "pdfToJson": { - "name": "PDF 转 JSON", - "subtitle": "将 PDF 文件转换为 JSON 格式。" - }, - "ocrPdf": { - "name": "OCR PDF", - "subtitle": "使 PDF 可搜索和可复制。" - }, - "alternateMix": { - "name": "交替混合页面", - "subtitle": "通过交替每个 PDF 的页面来合并 PDF。保留书签。" - }, - "addAttachments": { - "name": "添加附件", - "subtitle": "将一个或多个文件嵌入到您的 PDF 中。" - }, - "extractAttachments": { - "name": "提取附件", - "subtitle": "从 PDF 中提取所有嵌入的文件为 ZIP。" - }, - "editAttachments": { - "name": "编辑附件", - "subtitle": "查看或移除 PDF 中的附件。" - }, - "dividePages": { - "name": "分割页面", - "subtitle": "水平或垂直分割页面。" - }, - "addBlankPage": { - "name": "添加空白页", - "subtitle": "在 PDF 的任意位置插入空白页。" - }, - "reversePages": { - "name": "反转页面", - "subtitle": "反转文档中所有页面的顺序。" - }, - "rotatePdf": { - "name": "旋转 PDF", - "subtitle": "以 90 度增量旋转页面。" - }, - "nUpPdf": { - "name": "N-Up PDF", - "subtitle": "将多页排列在单张纸上。" - }, - "combineToSinglePage": { - "name": "合并为单页", - "subtitle": "将所有页面拼接成一个连续的滚动页面。" - }, - "viewMetadata": { - "name": "查看元数据", - "subtitle": "检查 PDF 的隐藏属性。" - }, - "editMetadata": { - "name": "编辑元数据", - "subtitle": "更改作者、标题和其他属性。" - }, - "pdfsToZip": { - "name": "PDF 转 ZIP", - "subtitle": "将多个 PDF 文件打包成一个 ZIP 归档。" - }, - "comparePdfs": { - "name": "比较 PDF", - "subtitle": "并排比较两个 PDF。" - }, - "posterizePdf": { - "name": "海报化 PDF", - "subtitle": "将大页面分割成多个小页面。" - }, - "fixPageSize": { - "name": "修复页面尺寸", - "subtitle": "将所有页面标准化为统一尺寸。" - }, - "linearizePdf": { - "name": "线性化 PDF", - "subtitle": "优化 PDF 以便快速网络查看。" - }, - "pageDimensions": { - "name": "页面尺寸", - "subtitle": "分析页面大小、方向和单位。" - }, - "removeRestrictions": { - "name": "移除限制", - "subtitle": "移除与数字签名 PDF 文件相关的密码保护和安全限制。" - }, - "repairPdf": { - "name": "修复 PDF", - "subtitle": "从损坏的 PDF 文件中恢复数据。" - }, - "encryptPdf": { - "name": "加密 PDF", - "subtitle": "通过添加密码锁定您的 PDF。" - }, - "sanitizePdf": { - "name": "清理 PDF", - "subtitle": "移除元数据、注释、脚本等。" - }, - "decryptPdf": { - "name": "解密 PDF", - "subtitle": "通过移除密码保护解锁 PDF。" - }, - "flattenPdf": { - "name": "扁平化 PDF", - "subtitle": "使表单字段和注释不可编辑。" - }, - "removeMetadata": { - "name": "移除元数据", - "subtitle": "从 PDF 中剥离隐藏数据。" - }, - "changePermissions": { - "name": "更改权限", - "subtitle": "设置或更改 PDF 上的用户权限。" - } -} \ No newline at end of file + "categories": { + "popularTools": "热门工具", + "editAnnotate": "编辑与注释", + "convertToPdf": "转换为 PDF", + "convertFromPdf": "从 PDF 转换", + "organizeManage": "组织与管理", + "optimizeRepair": "优化与修复", + "securePdf": "安全 PDF" + }, + "pdfMultiTool": { + "name": "PDF 多功能工具", + "subtitle": "在一个统一的界面中合并、分割、组织、删除、旋转、添加空白页、提取和复制。" + }, + "mergePdf": { + "name": "合并 PDF", + "subtitle": "将多个 PDF 合并为一个文件。保留书签。" + }, + "splitPdf": { + "name": "分割 PDF", + "subtitle": "将指定范围的页面提取到新 PDF 中。" + }, + "compressPdf": { + "name": "压缩 PDF", + "subtitle": "减小您的 PDF 文件大小。", + "algorithmLabel": "压缩算法", + "condense": "Condense(推荐)", + "photon": "Photon(适用于图片较多的 PDF)", + "condenseInfo": "Condense 使用高级压缩:移除冗余数据、优化图片、精简字体。适用于大多数 PDF。", + "photonInfo": "Photon 将页面转换为图片。适用于图片较多/扫描的 PDF。", + "photonWarning": "警告:文本将无法选择,链接将失效。", + "levelLabel": "压缩级别", + "light": "轻度(保持质量)", + "balanced": "平衡(推荐)", + "aggressive": "积极(更小文件)", + "extreme": "极限(最大压缩)", + "grayscale": "转换为灰度", + "grayscaleHint": "通过移除颜色信息来减小文件大小", + "customSettings": "自定义设置", + "customSettingsHint": "微调压缩参数:", + "outputQuality": "输出质量", + "resizeImagesTo": "调整图片至", + "onlyProcessAbove": "仅处理高于", + "removeMetadata": "移除元数据", + "subsetFonts": "精简字体(移除未使用的字符)", + "removeThumbnails": "移除嵌入的缩略图", + "compressButton": "压缩 PDF" + }, + "pdfEditor": { + "name": "PDF 编辑器", + "subtitle": "注释、高亮、涂黑、评论、添加形状/图片、搜索和查看 PDF。" + }, + "jpgToPdf": { + "name": "JPG 转 PDF", + "subtitle": "从 JPG、JPEG 和 JPEG2000 (JP2/JPX) 图片创建 PDF。" + }, + "signPdf": { + "name": "签署 PDF", + "subtitle": "绘制、键入或上传您的签名。" + }, + "cropPdf": { + "name": "裁剪 PDF", + "subtitle": "修剪 PDF 中每一页的边距。" + }, + "extractPages": { + "name": "提取页面", + "subtitle": "将选定的页面保存为新文件。" + }, + "duplicateOrganize": { + "name": "复制与组织", + "subtitle": "复制、重新排序和删除页面。" + }, + "deletePages": { + "name": "删除页面", + "subtitle": "自您的文档中移除特定页面。" + }, + "editBookmarks": { + "name": "编辑书签", + "subtitle": "添加、编辑、导入、删除和提取 PDF 书签。" + }, + "tableOfContents": { + "name": "目录", + "subtitle": "根据 PDF 书签生成目录页。" + }, + "pageNumbers": { + "name": "页码", + "subtitle": "将页码插入到您的文档中。" + }, + "batesNumbering": { + "name": "Bates编号", + "subtitle": "在一个或多个PDF文件中添加连续的Bates编号。" + }, + "addWatermark": { + "name": "添加水印", + "subtitle": "在您的 PDF 页面上添加文字或图片水印。", + "applyToAllPages": "应用到所有页面" + }, + "headerFooter": { + "name": "页眉和页脚", + "subtitle": "在页面顶部和底部添加文字。" + }, + "invertColors": { + "name": "反转颜色", + "subtitle": "创建您的 PDF 的“暗黑模式”版本。" + }, + "scannerEffect": { + "name": "扫描效果", + "subtitle": "让您的 PDF 看起来像扫描文件。", + "scanSettings": "扫描设置", + "colorspace": "色彩空间", + "gray": "灰度", + "border": "边框", + "rotate": "旋转", + "rotateVariance": "旋转偏差", + "brightness": "亮度", + "contrast": "对比度", + "blur": "模糊", + "noise": "噪点", + "yellowish": "泛黄", + "resolution": "分辨率", + "processButton": "应用扫描效果" + }, + "adjustColors": { + "name": "调整颜色", + "subtitle": "微调 PDF 的亮度、对比度、饱和度等。", + "colorSettings": "颜色设置", + "brightness": "亮度", + "contrast": "对比度", + "saturation": "饱和度", + "hueShift": "色相偏移", + "temperature": "色温", + "tint": "色调", + "gamma": "Gamma", + "sepia": "复古色", + "processButton": "应用颜色调整" + }, + "backgroundColor": { + "name": "背景颜色", + "subtitle": "更改您的 PDF 的背景颜色。" + }, + "changeTextColor": { + "name": "更改文本颜色", + "subtitle": "更改您 PDF 中文本的颜色。" + }, + "addStamps": { + "name": "添加印章", + "subtitle": "使用注释工具栏向您的 PDF 添加图片印章。", + "usernameLabel": "印章用户名", + "usernamePlaceholder": "输入您的姓名(用于印章)", + "usernameHint": "此名称将显示在您创建的印章上。" + }, + "removeAnnotations": { + "name": "移除注释", + "subtitle": "移除评论、高亮和链接。" + }, + "pdfFormFiller": { + "name": "PDF 表单填写器", + "subtitle": "直接在浏览器中填写表单。也支持 XFA 表单。" + }, + "createPdfForm": { + "name": "创建 PDF 表单", + "subtitle": "使用拖放文本字段创建可填写的 PDF 表单。" + }, + "removeBlankPages": { + "name": "移除空白页", + "subtitle": "自动检测并删除空白页。", + "sensitivityHint": "越高 = 越严格,仅检测纯空白页。越低 = 允许包含少量内容的页面。" + }, + "imageToPdf": { + "name": "图片转 PDF", + "subtitle": "将 JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP 转换为 PDF。" + }, + "pngToPdf": { + "name": "PNG 转 PDF", + "subtitle": "从一张或多张 PNG 图片创建 PDF。" + }, + "webpToPdf": { + "name": "WebP 转 PDF", + "subtitle": "从一张或多张 WebP 图片创建 PDF。" + }, + "svgToPdf": { + "name": "SVG 转 PDF", + "subtitle": "从一张或多张 SVG 图片创建 PDF。" + }, + "bmpToPdf": { + "name": "BMP 转 PDF", + "subtitle": "从一张或多张 BMP 图片创建 PDF。" + }, + "heicToPdf": { + "name": "HEIC 转 PDF", + "subtitle": "从一张或多张 HEIC 图片创建 PDF。" + }, + "tiffToPdf": { + "name": "TIFF 转 PDF", + "subtitle": "从一张或多张 TIFF 图片创建 PDF。" + }, + "textToPdf": { + "name": "文本转 PDF", + "subtitle": "将纯文本文件转换为 PDF。" + }, + "jsonToPdf": { + "name": "JSON 转 PDF", + "subtitle": "将 JSON 文件转换为 PDF 格式。" + }, + "pdfToJpg": { + "name": "PDF 转 JPG", + "subtitle": "将每一页 PDF 转换为 JPG 图片。" + }, + "pdfToPng": { + "name": "PDF 转 PNG", + "subtitle": "将每一页 PDF 转换为 PNG 图片。" + }, + "pdfToWebp": { + "name": "PDF 转 WebP", + "subtitle": "将每一页 PDF 转换为 WebP 图片。" + }, + "pdfToBmp": { + "name": "PDF 转 BMP", + "subtitle": "将每一页 PDF 转换为 BMP 图片。" + }, + "pdfToTiff": { + "name": "PDF 转 TIFF", + "subtitle": "将每一页 PDF 转换为 TIFF 图片。" + }, + "pdfToGreyscale": { + "name": "PDF 转 灰度", + "subtitle": "将所有颜色转换为黑白。" + }, + "pdfToJson": { + "name": "PDF 转 JSON", + "subtitle": "将 PDF 文件转换为 JSON 格式。" + }, + "ocrPdf": { + "name": "OCR PDF", + "subtitle": "使 PDF 可搜索和可复制。" + }, + "alternateMix": { + "name": "交替混合页面", + "subtitle": "通过交替每个 PDF 的页面来合并 PDF。保留书签。" + }, + "addAttachments": { + "name": "添加附件", + "subtitle": "将一个或多个文件嵌入到您的 PDF 中。" + }, + "extractAttachments": { + "name": "提取附件", + "subtitle": "从 PDF 中提取所有嵌入的文件为 ZIP。" + }, + "editAttachments": { + "name": "编辑附件", + "subtitle": "查看或移除 PDF 中的附件。" + }, + "dividePages": { + "name": "分割页面", + "subtitle": "水平或垂直分割页面。" + }, + "addBlankPage": { + "name": "添加空白页", + "subtitle": "在 PDF 的任意位置插入空白页。" + }, + "reversePages": { + "name": "反转页面", + "subtitle": "反转文档中所有页面的顺序。" + }, + "rotatePdf": { + "name": "旋转 PDF", + "subtitle": "以 90 度增量旋转页面。" + }, + "rotateCustom": { + "name": "按自定义角度旋转", + "subtitle": "按任意自定义角度旋转页面。" + }, + "nUpPdf": { + "name": "N-Up PDF", + "subtitle": "将多页排列在单张纸上。" + }, + "combineToSinglePage": { + "name": "合并为单页", + "subtitle": "将所有页面拼接成一个连续的滚动页面。" + }, + "viewMetadata": { + "name": "查看元数据", + "subtitle": "检查 PDF 的隐藏属性。" + }, + "editMetadata": { + "name": "编辑元数据", + "subtitle": "更改作者、标题和其他属性。" + }, + "pdfsToZip": { + "name": "PDF 转 ZIP", + "subtitle": "将多个 PDF 文件打包成一个 ZIP 归档。" + }, + "comparePdfs": { + "name": "比较 PDF", + "subtitle": "并排比较两个 PDF。" + }, + "posterizePdf": { + "name": "海报化 PDF", + "subtitle": "将大页面分割成多个小页面。" + }, + "fixPageSize": { + "name": "修复页面尺寸", + "subtitle": "将所有页面标准化为统一尺寸。" + }, + "linearizePdf": { + "name": "线性化 PDF", + "subtitle": "优化 PDF 以便快速网络查看。" + }, + "pageDimensions": { + "name": "页面尺寸", + "subtitle": "分析页面大小、方向和单位。" + }, + "removeRestrictions": { + "name": "移除限制", + "subtitle": "移除与数字签名 PDF 文件相关的密码保护和安全限制。" + }, + "repairPdf": { + "name": "修复 PDF", + "subtitle": "从损坏的 PDF 文件中恢复数据。" + }, + "encryptPdf": { + "name": "加密 PDF", + "subtitle": "通过添加密码锁定您的 PDF。" + }, + "sanitizePdf": { + "name": "清理 PDF", + "subtitle": "移除元数据、注释、脚本等。" + }, + "decryptPdf": { + "name": "解密 PDF", + "subtitle": "通过移除密码保护解锁 PDF。" + }, + "flattenPdf": { + "name": "扁平化 PDF", + "subtitle": "使表单字段和注释不可编辑。" + }, + "removeMetadata": { + "name": "移除元数据", + "subtitle": "从 PDF 中剥离隐藏数据。" + }, + "changePermissions": { + "name": "更改权限", + "subtitle": "设置或更改 PDF 上的用户权限。" + }, + "odtToPdf": { + "name": "ODT 转 PDF", + "subtitle": "将 OpenDocument 文本文件转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "ODT 文件", + "convertButton": "转换为 PDF" + }, + "csvToPdf": { + "name": "CSV 转 PDF", + "subtitle": "将 CSV 电子表格文件转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "CSV 件", + "convertButton": "转换为 PDF" + }, + "rtfToPdf": { + "name": "RTF 转 PDF", + "subtitle": "将富文本格式文档转换为 PDF。支持多个文件。", + "acceptedFormats": "RTF 文件", + "convertButton": "转换为 PDF" + }, + "wordToPdf": { + "name": "Word 转 PDF", + "subtitle": "将 Word 文档 (DOCX, DOC, ODT, RTF) 转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "DOCX, DOC, ODT, RTF 文件", + "convertButton": "转换为 PDF" + }, + "excelToPdf": { + "name": "Excel 转 PDF", + "subtitle": "将 Excel 电子表格 (XLSX, XLS, ODS, CSV) 转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "XLSX, XLS, ODS, CSV 文件", + "convertButton": "转换为 PDF" + }, + "powerpointToPdf": { + "name": "PowerPoint 转 PDF", + "subtitle": "将 PowerPoint 演示文稿 (PPTX, PPT, ODP) 转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "PPTX, PPT, ODP 文件", + "convertButton": "转换为 PDF" + }, + "markdownToPdf": { + "name": "Markdown 转 PDF", + "subtitle": "编写或粘贴 Markdown 并将其导出为精美格式的 PDF。", + "paneMarkdown": "Markdown", + "panePreview": "预览", + "btnUpload": "上传", + "btnSyncScroll": "同步滚动", + "btnSettings": "设置", + "btnExportPdf": "导出 PDF", + "settingsTitle": "Markdown 设置", + "settingsPreset": "预设", + "presetDefault": "默认 (GFM 风格)", + "presetCommonmark": "CommonMark (严格)", + "presetZero": "最小 (无功能)", + "settingsOptions": "Markdown 选项", + "optAllowHtml": "允许 HTML 标签", + "optBreaks": "将换行转换为
", + "optLinkify": "自动将 URL 转换为链接", + "optTypographer": "排版器 (智能引号等)" + }, + "pdfBooklet": { + "name": "PDF 小册子", + "subtitle": "重新排列页面用于双面小册子打印。折叠并装订以创建小册子。", + "howItWorks": "工作原理:", + "step1": "上传 PDF 文件。", + "step2": "页面将按小册子顺序重新排列。", + "step3": "双面打印,短边翻转,折叠并装订。", + "paperSize": "纸张大小", + "orientation": "方向", + "portrait": "纵向", + "landscape": "横向", + "pagesPerSheet": "每张页数", + "createBooklet": "创建小册子", + "processing": "处理中...", + "pageCount": "如需要,页数将补齐为 4 的倍数。" + }, + "xpsToPdf": { + "name": "XPS 转 PDF", + "subtitle": "将 XPS/OXPS 文档转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "XPS, OXPS 文件", + "convertButton": "转换为 PDF" + }, + "mobiToPdf": { + "name": "MOBI 转 PDF", + "subtitle": "将 MOBI 电子书转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "MOBI 文件", + "convertButton": "转换为 PDF" + }, + "epubToPdf": { + "name": "EPUB 转 PDF", + "subtitle": "将 EPUB 电子书转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "EPUB 文件", + "convertButton": "转换为 PDF" + }, + "fb2ToPdf": { + "name": "FB2 转 PDF", + "subtitle": "将 FictionBook (FB2) 电子书转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "FB2 文件", + "convertButton": "转换为 PDF" + }, + "cbzToPdf": { + "name": "CBZ 转 PDF", + "subtitle": "将漫画档案 (CBZ/CBR) 转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "CBZ, CBR 文件", + "convertButton": "转换为 PDF" + }, + "wpdToPdf": { + "name": "WPD 转 PDF", + "subtitle": "将 WordPerfect 文档 (WPD) 转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "WPD 文件", + "convertButton": "转换为 PDF" + }, + "wpsToPdf": { + "name": "WPS 转 PDF", + "subtitle": "将 WPS Office 文档转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "WPS 文件", + "convertButton": "转换为 PDF" + }, + "xmlToPdf": { + "name": "XML 转 PDF", + "subtitle": "将 XML 文档转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "XML 文件", + "convertButton": "转换为 PDF" + }, + "pagesToPdf": { + "name": "Pages 转 PDF", + "subtitle": "将 Apple Pages 文档转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "Pages 文件", + "convertButton": "转换为 PDF" + }, + "odgToPdf": { + "name": "ODG 转 PDF", + "subtitle": "将 OpenDocument Graphics (ODG) 文件转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "ODG 文件", + "convertButton": "转换为 PDF" + }, + "odsToPdf": { + "name": "ODS 转 PDF", + "subtitle": "将 OpenDocument Spreadsheet (ODS) 文件转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "ODS 文件", + "convertButton": "转换为 PDF" + }, + "odpToPdf": { + "name": "ODP 转 PDF", + "subtitle": "将 OpenDocument Presentation (ODP) 文件转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "ODP 文件", + "convertButton": "转换为 PDF" + }, + "pubToPdf": { + "name": "PUB 转 PDF", + "subtitle": "将 Microsoft Publisher (PUB) 文件转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "PUB 文件", + "convertButton": "转换为 PDF" + }, + "vsdToPdf": { + "name": "VSD 转 PDF", + "subtitle": "将 Microsoft Visio (VSD, VSDX) 文件转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "VSD, VSDX 文件", + "convertButton": "转换为 PDF" + }, + "psdToPdf": { + "name": "PSD 转 PDF", + "subtitle": "将 Adobe Photoshop (PSD) 文件转换为 PDF 格式。支持多个文件。", + "acceptedFormats": "PSD 文件", + "convertButton": "转换为 PDF" + }, + "pdfToSvg": { + "name": "PDF 转 SVG", + "subtitle": "将 PDF 文件的每一页转换为可缩放矢量图形 (SVG),在任何尺寸下都能保持完美质量。" + }, + "extractTables": { + "name": "提取 PDF 表格", + "subtitle": "从 PDF 文件中提取表格,并导出为 CSV、JSON 或 Markdown 格式。" + }, + "pdfToCsv": { + "name": "PDF 转 CSV", + "subtitle": "从 PDF 中提取表格并转换为 CSV 格式。" + }, + "pdfToExcel": { + "name": "PDF 转 Excel", + "subtitle": "从 PDF 中提取表格并转换为 Excel (XLSX) 格式。" + }, + "pdfToText": { + "name": "PDF 转 文本", + "subtitle": "从 PDF 文件中提取文本并保存为纯文本文件 (.txt)。支持多个文件。", + "note": "此工具仅适用于数字创建的 PDF。对于扫描文档或基于图像的 PDF,请使用我们的 OCR PDF 工具。", + "convertButton": "提取文本" + }, + "digitalSignPdf": { + "name": "PDF 数字签名", + "pageTitle": "PDF 数字签名 - 添加加密签名 | BentoPDF", + "subtitle": "使用 X.509 证书为您的 PDF 添加加密数字签名。支持 PKCS#12 (.pfx, .p12) 和 PEM 格式。您的私钥永远不会离开您的浏览器。", + "certificateSection": "证书", + "uploadCert": "上传证书 (.pfx, .p12)", + "certPassword": "证书密码", + "certPasswordPlaceholder": "输入证书密码", + "certInfo": "证书信息", + "certSubject": "主体", + "certIssuer": "颁发者", + "certValidity": "有效期", + "signatureDetails": "签名详情(可选)", + "reason": "原因", + "reasonPlaceholder": "例如:我批准此文档", + "location": "位置", + "locationPlaceholder": "例如:北京,中国", + "contactInfo": "联系信息", + "contactPlaceholder": "例如:email@example.com", + "applySignature": "应用数字签名", + "successMessage": "PDF 签名成功!签名可在任何 PDF 阅读器中验证。" + }, + "validateSignaturePdf": { + "name": "验证 PDF 签名", + "pageTitle": "验证 PDF 签名 - 验证数字签名 | BentoPDF", + "subtitle": "验证您的 PDF 文件中的数字签名。检查证书有效性、查看签名者详情并确认文档完整性。所有处理都在您的浏览器中进行。" + }, + "emailToPdf": { + "name": "邮件转 PDF", + "subtitle": "将电子邮件文件 (EML, MSG) 转换为 PDF 格式。支持 Outlook 导出和标准邮件格式。", + "acceptedFormats": "EML, MSG 文件", + "convertButton": "转换为 PDF" + }, + "fontToOutline": { + "name": "字体转轮廓", + "subtitle": "将所有字体转换为矢量轮廓,确保在所有设备上一致呈现。" + }, + "deskewPdf": { + "name": "校正 PDF", + "subtitle": "使用 OpenCV 自动校正倾斜的扫描页面。" + }, + "pdfToWord": { + "name": "PDF 转 Word", + "subtitle": "将 PDF 文件转换为可编辑的 Word 文档。" + }, + "extractImages": { + "name": "提取图片", + "subtitle": "从 PDF 文件中提取所有嵌入的图片。" + }, + "pdfToMarkdown": { + "name": "PDF 转 Markdown", + "subtitle": "将 PDF 文本和表格转换为 Markdown 格式。" + }, + "preparePdfForAi": { + "name": "为 AI 准备 PDF", + "subtitle": "将 PDF 内容提取为 LlamaIndex JSON,用于 RAG/LLM 管道。" + }, + "pdfOcg": { + "name": "PDF 图层 (OCG)", + "subtitle": "查看、切换、添加和删除 PDF 中的 OCG 图层。" + }, + "pdfToPdfa": { + "name": "PDF 转 PDF/A", + "subtitle": "将 PDF 转换为 PDF/A 格式以进行长期存档。" + }, + "rasterizePdf": { + "name": "栅格化 PDF", + "subtitle": "将 PDF 转换为基于图像的 PDF。展平图层并移除可选择的文本。" + }, + "pdfWorkflow": { + "name": "PDF 工作流构建器", + "subtitle": "使用可视化节点编辑器构建自定义 PDF 处理流水线。", + "nodes": "节点", + "searchNodes": "搜索节点...", + "run": "运行", + "clear": "清除", + "save": "保存", + "load": "加载", + "export": "导出", + "import": "导入", + "ready": "就绪", + "settings": "设置", + "processing": "处理中...", + "saveTemplate": "保存模板", + "templateName": "模板名称", + "templatePlaceholder": "例如:发票处理工作流", + "cancel": "取消", + "loadTemplate": "加载模板", + "noTemplates": "暂无已保存的模板。", + "ok": "确定", + "workflowCompleted": "工作流已完成", + "errorDuringExecution": "执行过程中出错", + "addNodeError": "请至少添加一个节点以运行工作流。", + "needInputOutput": "工作流至少需要一个输入节点和一个输出节点才能运行。", + "enterName": "请输入名称。", + "templateExists": "已存在同名模板。", + "templateSaved": "模板「{{name}}」已保存。", + "templateLoaded": "模板「{{name}}」已加载。", + "failedLoadTemplate": "加载模板失败。", + "noSettings": "此节点没有可配置的设置。", + "advancedSettings": "高级设置" + } +} diff --git a/public/pdfjs-annotation-viewer/web/viewer.html b/public/pdfjs-annotation-viewer/web/viewer.html index d3a5a53..70d97ce 100644 --- a/public/pdfjs-annotation-viewer/web/viewer.html +++ b/public/pdfjs-annotation-viewer/web/viewer.html @@ -1,4 +1,4 @@ - + - - - + + + PDF.js viewer - - - + + + - + - - - + + +
-
-
- - - -
@@ -84,302 +133,895 @@ See https://github.com/adobe-type-tools/cmap-resources
-
-
-
- - - +
+ + +
-
+ +
+
+ - - ` - } else if (field.type === 'signature') { - specificProps = ` + `; + } else if (field.type === 'signature') { + specificProps = `
Signature fields are AcroForm signature fields and would only be visible in an advanced PDF viewer.
- ` - } else if (field.type === 'date') { - const formats = ['mm/dd/yyyy', 'dd/mm/yyyy', 'mm/yy', 'dd/mm/yy', 'yyyy/mm/dd', 'mmm d, yyyy', 'd-mmm-yy', 'yy-mm-dd'] - specificProps = ` + `; + } else if (field.type === 'date') { + const formats = [ + 'm/d', + 'm/d/yy', + 'm/d/yyyy', + 'mm/dd/yy', + 'mm/dd/yyyy', + 'mm/yy', + 'mm/yyyy', + 'd-mmm', + 'd-mmm-yy', + 'd-mmm-yyyy', + 'dd-mmm-yy', + 'dd-mmm-yyyy', + 'yy-mm-dd', + 'yyyy-mm-dd', + 'mmm-yy', + 'mmm-yyyy', + 'mmm d, yyyy', + 'mmmm-yy', + 'mmmm-yyyy', + 'mmmm d, yyyy', + 'dd/mm/yy', + 'dd/mm/yyyy', + 'yyyy/mm/dd', + 'dd.mm.yy', + 'dd.mm.yyyy', + 'm/d/yy h:MM tt', + 'm/d/yyyy h:MM tt', + 'm/d/yy HH:MM', + 'm/d/yyyy HH:MM', + 'yyyy-mm', + 'yyyy', + ]; + const isCustom = !formats.includes(field.dateFormat || 'mm/dd/yyyy'); + specificProps = `
-
- The selected format will be enforced when the user types or picks a date. +
+ + +
+
+ Example of current format: +
-

- - Browser Note: Firefox and Chrome may show their native date picker format during selection. The correct format will apply when you finish entering the date. This is normal browser behavior and not an issue. +

+ + Browser Note: Firefox and Chrome may show their native date picker format during selection. The correct format will apply when you finish entering the date.

- ` - } else if (field.type === 'image') { - specificProps = ` + `; + } else if (field.type === 'image') { + specificProps = `
@@ -930,27 +1159,67 @@ function showProperties(field: FormField): void {
Clicking this field in the PDF will open a file picker to upload an image.
- ` - } + `; + } else if (field.type === 'barcode') { + specificProps = ` +
+ + +
+
+ + +
+
+ `; + } - propertiesPanel.innerHTML = ` + propertiesPanel.innerHTML = `
- ${field.type === 'radio' && (existingRadioGroups.size > 0 || fields.some(f => f.type === 'radio' && f.id !== field.id)) ? ` + ${ + field.type === 'radio' && + (existingRadioGroups.size > 0 || + fields.some((f) => f.type === 'radio' && f.id !== field.id)) + ? `

Select to add this button to an existing group

- ` : ''} + ` + : '' + } ${specificProps}
@@ -966,7 +1235,7 @@ function showProperties(field: FormField): void {
- +
@@ -976,1113 +1245,1428 @@ function showProperties(field: FormField): void { Delete Field
- ` + `; - // Common listeners - const propName = document.getElementById('propName') as HTMLInputElement - const nameError = document.getElementById('nameError') as HTMLDivElement - const propTooltip = document.getElementById('propTooltip') as HTMLInputElement - const propRequired = document.getElementById('propRequired') as HTMLInputElement - const propReadOnly = document.getElementById('propReadOnly') as HTMLInputElement - const deleteBtn = document.getElementById('deleteBtn') as HTMLButtonElement + // Common listeners + const propName = document.getElementById('propName') as HTMLInputElement; + const nameError = document.getElementById('nameError') as HTMLDivElement; + const propTooltip = document.getElementById( + 'propTooltip' + ) as HTMLInputElement; + const propRequired = document.getElementById( + 'propRequired' + ) as HTMLInputElement; + const propReadOnly = document.getElementById( + 'propReadOnly' + ) as HTMLInputElement; + const deleteBtn = document.getElementById('deleteBtn') as HTMLButtonElement; - const validateName = (newName: string): boolean => { - if (!newName) { - nameError.textContent = 'Field name cannot be empty' - nameError.classList.remove('hidden') - propName.classList.add('border-red-500') - return false - } - - if (field.type === 'radio') { - nameError.classList.add('hidden') - propName.classList.remove('border-red-500') - return true - } - - const isDuplicateInFields = fields.some(f => f.id !== field.id && f.name === newName) - const isDuplicateInPdf = existingFieldNames.has(newName) - - if (isDuplicateInFields || isDuplicateInPdf) { - nameError.textContent = `Field name "${newName}" already exists in this ${isDuplicateInPdf ? 'PDF' : 'form'}. Please try using a unique name.` - nameError.classList.remove('hidden') - propName.classList.add('border-red-500') - return false - } - - nameError.classList.add('hidden') - propName.classList.remove('border-red-500') - return true + const validateName = (newName: string): boolean => { + if (!newName) { + nameError.textContent = 'Field name cannot be empty'; + nameError.classList.remove('hidden'); + propName.classList.add('border-red-500'); + return false; } - propName.addEventListener('input', (e) => { - const newName = (e.target as HTMLInputElement).value.trim() - validateName(newName) - }) - - propName.addEventListener('change', (e) => { - const newName = (e.target as HTMLInputElement).value.trim() - - if (!validateName(newName)) { - (e.target as HTMLInputElement).value = field.name - return - } - - field.name = newName - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const label = fieldWrapper.querySelector('.field-label') as HTMLElement - if (label) label.textContent = field.name - } - }) - - propTooltip.addEventListener('input', (e) => { - field.tooltip = (e.target as HTMLInputElement).value - }) - if (field.type === 'radio') { - const existingGroupsSelect = document.getElementById('existingGroups') as HTMLSelectElement - if (existingGroupsSelect) { - existingGroupsSelect.addEventListener('change', (e) => { - const selectedGroup = (e.target as HTMLSelectElement).value - if (selectedGroup) { - propName.value = selectedGroup - field.name = selectedGroup - validateName(selectedGroup) - - // Update field label - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const label = fieldWrapper.querySelector('.field-label') as HTMLElement - if (label) label.textContent = field.name - } - } - }) - } + nameError.classList.add('hidden'); + propName.classList.remove('border-red-500'); + return true; } - propRequired.addEventListener('change', (e) => { - field.required = (e.target as HTMLInputElement).checked - }) + const isDuplicateInFields = fields.some( + (f) => f.id !== field.id && f.name === newName + ); + const isDuplicateInPdf = existingFieldNames.has(newName); - propReadOnly.addEventListener('change', (e) => { - field.readOnly = (e.target as HTMLInputElement).checked - }) - - const propBorderColor = document.getElementById('propBorderColor') as HTMLInputElement - const propHideBorder = document.getElementById('propHideBorder') as HTMLInputElement - - propBorderColor.addEventListener('input', (e) => { - field.borderColor = (e.target as HTMLInputElement).value - }) - - propHideBorder.addEventListener('change', (e) => { - field.hideBorder = (e.target as HTMLInputElement).checked - }) - - deleteBtn.addEventListener('click', () => { - deleteField(field) - }) - - // Specific listeners - if (field.type === 'text') { - const propValue = document.getElementById('propValue') as HTMLInputElement - const propMaxLength = document.getElementById('propMaxLength') as HTMLInputElement - const propComb = document.getElementById('propComb') as HTMLInputElement - const propFontSize = document.getElementById('propFontSize') as HTMLInputElement - const propTextColor = document.getElementById('propTextColor') as HTMLInputElement - const propAlignment = document.getElementById('propAlignment') as HTMLSelectElement - - propValue.addEventListener('input', (e) => { - field.defaultValue = (e.target as HTMLInputElement).value - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement - if (textEl) textEl.textContent = field.defaultValue - } - }) - - propMaxLength.addEventListener('input', (e) => { - const val = parseInt((e.target as HTMLInputElement).value) - field.maxLength = isNaN(val) ? 0 : Math.max(0, val) - if (field.maxLength > 0) { - propValue.maxLength = field.maxLength - if (field.defaultValue.length > field.maxLength) { - field.defaultValue = field.defaultValue.substring(0, field.maxLength) - propValue.value = field.defaultValue - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement - if (textEl) textEl.textContent = field.defaultValue - } - } - } else { - propValue.removeAttribute('maxLength') - } - }) - - propComb.addEventListener('input', (e) => { - const val = parseInt((e.target as HTMLInputElement).value) - field.combCells = isNaN(val) ? 0 : Math.max(0, val) - - if (field.combCells > 0) { - propValue.maxLength = field.combCells - propMaxLength.value = field.combCells.toString() - propMaxLength.disabled = true - field.maxLength = field.combCells - - if (field.defaultValue.length > field.combCells) { - field.defaultValue = field.defaultValue.substring(0, field.combCells) - propValue.value = field.defaultValue - } - } else { - propMaxLength.disabled = false - propValue.removeAttribute('maxLength') - if (field.maxLength > 0) { - propValue.maxLength = field.maxLength - } - } - - // Re-render field visual only, NOT the properties panel - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - // Update text content - const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement - if (textEl) { - textEl.textContent = field.defaultValue - if (field.combCells > 0) { - textEl.style.backgroundImage = `repeating-linear-gradient(90deg, transparent, transparent calc((100% / ${field.combCells}) - 1px), #e5e7eb calc((100% / ${field.combCells}) - 1px), #e5e7eb calc(100% / ${field.combCells}))` - textEl.style.fontFamily = 'monospace' - textEl.style.letterSpacing = `calc(${field.width / field.combCells}px - 1ch)` - textEl.style.paddingLeft = `calc((${field.width / field.combCells}px - 1ch) / 2)` - textEl.style.overflow = 'hidden' - textEl.style.textAlign = 'left' - textEl.style.justifyContent = 'flex-start' - } else { - textEl.style.backgroundImage = 'none' - textEl.style.fontFamily = 'inherit' - textEl.style.letterSpacing = 'normal' - textEl.style.textAlign = field.alignment - textEl.style.justifyContent = field.alignment === 'left' ? 'flex-start' : field.alignment === 'right' ? 'flex-end' : 'center' - } - } - } - }) - - propFontSize.addEventListener('input', (e) => { - field.fontSize = parseInt((e.target as HTMLInputElement).value) - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement - if (textEl) textEl.style.fontSize = field.fontSize + 'px' - } - }) - - propTextColor.addEventListener('input', (e) => { - field.textColor = (e.target as HTMLInputElement).value - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement - if (textEl) textEl.style.color = field.textColor - } - }) - - propAlignment.addEventListener('change', (e) => { - field.alignment = (e.target as HTMLSelectElement).value as 'left' | 'center' | 'right' - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement - if (textEl) { - textEl.style.textAlign = field.alignment - textEl.style.justifyContent = field.alignment === 'left' ? 'flex-start' : field.alignment === 'right' ? 'flex-end' : 'center' - } - } - }) - - const propMultilineBtn = document.getElementById('propMultilineBtn') as HTMLButtonElement - if (propMultilineBtn) { - propMultilineBtn.addEventListener('click', () => { - field.multiline = !field.multiline - - // Update Toggle Button UI - const span = propMultilineBtn.querySelector('span') - if (field.multiline) { - propMultilineBtn.classList.remove('bg-gray-500') - propMultilineBtn.classList.add('bg-indigo-600') - span?.classList.remove('translate-x-0') - span?.classList.add('translate-x-6') - } else { - propMultilineBtn.classList.remove('bg-indigo-600') - propMultilineBtn.classList.add('bg-gray-500') - span?.classList.remove('translate-x-6') - span?.classList.add('translate-x-0') - } - - // Update Canvas UI - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement - if (textEl) { - if (field.multiline) { - textEl.style.whiteSpace = 'pre-wrap' - textEl.style.alignItems = 'flex-start' - textEl.style.overflow = 'hidden' - } else { - textEl.style.whiteSpace = 'nowrap' - textEl.style.alignItems = 'center' - textEl.style.overflow = 'hidden' - } - } - } - }) - } - } else if (field.type === 'checkbox' || field.type === 'radio') { - const propCheckedBtn = document.getElementById('propCheckedBtn') as HTMLButtonElement - - propCheckedBtn.addEventListener('click', () => { - field.checked = !field.checked - - // Update Toggle Button UI - const span = propCheckedBtn.querySelector('span') - if (field.checked) { - propCheckedBtn.classList.remove('bg-gray-500') - propCheckedBtn.classList.add('bg-indigo-600') - span?.classList.remove('translate-x-0') - span?.classList.add('translate-x-6') - } else { - propCheckedBtn.classList.remove('bg-indigo-600') - propCheckedBtn.classList.add('bg-gray-500') - span?.classList.remove('translate-x-6') - span?.classList.add('translate-x-0') - } - - // Update Canvas UI - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const contentEl = fieldWrapper.querySelector('.field-content') as HTMLElement - if (contentEl) { - if (field.type === 'checkbox') { - contentEl.innerHTML = field.checked ? '' : '' - } else { - contentEl.innerHTML = field.checked ? '
' : '' - } - } - } - }) - - if (field.type === 'radio') { - const propGroupName = document.getElementById('propGroupName') as HTMLInputElement - const propExportValue = document.getElementById('propExportValue') as HTMLInputElement - - propGroupName.addEventListener('input', (e) => { - field.groupName = (e.target as HTMLInputElement).value - }) - propExportValue.addEventListener('input', (e) => { - field.exportValue = (e.target as HTMLInputElement).value - }) - } - } else if (field.type === 'dropdown' || field.type === 'optionlist') { - const propOptions = document.getElementById('propOptions') as HTMLTextAreaElement - propOptions.addEventListener('input', (e) => { - // We split by newline OR comma for the actual options array - const val = (e.target as HTMLTextAreaElement).value - field.options = val.split(/[\n,]/).map(s => s.trim()).filter(s => s.length > 0) - - const propSelectedOption = document.getElementById('propSelectedOption') as HTMLSelectElement - if (propSelectedOption) { - const currentVal = field.defaultValue - propSelectedOption.innerHTML = '' + - field.options?.map(opt => ``).join('') - - if (currentVal && field.options && !field.options.includes(currentVal)) { - field.defaultValue = '' - propSelectedOption.value = '' - } - } - - renderField(field) - }) - - const propSelectedOption = document.getElementById('propSelectedOption') as HTMLSelectElement - propSelectedOption.addEventListener('change', (e) => { - field.defaultValue = (e.target as HTMLSelectElement).value - - // Update visual on canvas - renderField(field) - }) - } else if (field.type === 'button') { - const propLabel = document.getElementById('propLabel') as HTMLInputElement - propLabel.addEventListener('input', (e) => { - field.label = (e.target as HTMLInputElement).value - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const contentEl = fieldWrapper.querySelector('.field-content') as HTMLElement - if (contentEl) contentEl.textContent = field.label || 'Button' - } - }) - - const propAction = document.getElementById('propAction') as HTMLSelectElement - const propUrlContainer = document.getElementById('propUrlContainer') as HTMLDivElement - const propJsContainer = document.getElementById('propJsContainer') as HTMLDivElement - const propShowHideContainer = document.getElementById('propShowHideContainer') as HTMLDivElement - - propAction.addEventListener('change', (e) => { - field.action = (e.target as HTMLSelectElement).value as any - - // Show/hide containers - propUrlContainer.classList.add('hidden') - propJsContainer.classList.add('hidden') - propShowHideContainer.classList.add('hidden') - - if (field.action === 'url') { - propUrlContainer.classList.remove('hidden') - } else if (field.action === 'js') { - propJsContainer.classList.remove('hidden') - } else if (field.action === 'showHide') { - propShowHideContainer.classList.remove('hidden') - } - }) - - const propActionUrl = document.getElementById('propActionUrl') as HTMLInputElement - propActionUrl.addEventListener('input', (e) => { - field.actionUrl = (e.target as HTMLInputElement).value - }) - - const propJsScript = document.getElementById('propJsScript') as HTMLTextAreaElement - if (propJsScript) { - propJsScript.addEventListener('input', (e) => { - field.jsScript = (e.target as HTMLTextAreaElement).value - }) - } - - const propTargetField = document.getElementById('propTargetField') as HTMLSelectElement - if (propTargetField) { - propTargetField.addEventListener('change', (e) => { - field.targetFieldName = (e.target as HTMLSelectElement).value - }) - } - - const propVisibilityAction = document.getElementById('propVisibilityAction') as HTMLSelectElement - if (propVisibilityAction) { - propVisibilityAction.addEventListener('change', (e) => { - field.visibilityAction = (e.target as HTMLSelectElement).value as any - }) - } - } else if (field.type === 'signature') { - // No specific listeners for signature fields yet - } else if (field.type === 'date') { - const propDateFormat = document.getElementById('propDateFormat') as HTMLSelectElement - if (propDateFormat) { - propDateFormat.addEventListener('change', (e) => { - field.dateFormat = (e.target as HTMLSelectElement).value - // Update canvas preview - const fieldWrapper = document.getElementById(field.id) - if (fieldWrapper) { - const textSpan = fieldWrapper.querySelector('.date-format-text') as HTMLElement - if (textSpan) { - textSpan.textContent = field.dateFormat - } - } - // Re-initialize lucide icons in the properties panel - setTimeout(() => (window as any).lucide?.createIcons(), 0) - }) - } - } else if (field.type === 'image') { - const propLabel = document.getElementById('propLabel') as HTMLInputElement - propLabel.addEventListener('input', (e) => { - field.label = (e.target as HTMLInputElement).value - renderField(field) - }) + if (isDuplicateInFields || isDuplicateInPdf) { + nameError.textContent = `Field name "${newName}" already exists in this ${isDuplicateInPdf ? 'PDF' : 'form'}. Please try using a unique name.`; + nameError.classList.remove('hidden'); + propName.classList.add('border-red-500'); + return false; } + + nameError.classList.add('hidden'); + propName.classList.remove('border-red-500'); + return true; + }; + + propName.addEventListener('input', (e) => { + const newName = (e.target as HTMLInputElement).value.trim(); + validateName(newName); + }); + + propName.addEventListener('change', (e) => { + const newName = (e.target as HTMLInputElement).value.trim(); + + if (!validateName(newName)) { + (e.target as HTMLInputElement).value = field.name; + return; + } + + field.name = newName; + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const label = fieldWrapper.querySelector('.field-label') as HTMLElement; + if (label) label.textContent = field.name; + } + }); + + propTooltip.addEventListener('input', (e) => { + field.tooltip = (e.target as HTMLInputElement).value; + }); + + if (field.type === 'radio') { + const existingGroupsSelect = document.getElementById( + 'existingGroups' + ) as HTMLSelectElement; + if (existingGroupsSelect) { + existingGroupsSelect.addEventListener('change', (e) => { + const selectedGroup = (e.target as HTMLSelectElement).value; + if (selectedGroup) { + propName.value = selectedGroup; + field.name = selectedGroup; + validateName(selectedGroup); + + // Update field label + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const label = fieldWrapper.querySelector( + '.field-label' + ) as HTMLElement; + if (label) label.textContent = field.name; + } + } + }); + } + } + + propRequired.addEventListener('change', (e) => { + field.required = (e.target as HTMLInputElement).checked; + }); + + propReadOnly.addEventListener('change', (e) => { + field.readOnly = (e.target as HTMLInputElement).checked; + }); + + const propBorderColor = document.getElementById( + 'propBorderColor' + ) as HTMLInputElement; + const propHideBorder = document.getElementById( + 'propHideBorder' + ) as HTMLInputElement; + + propBorderColor.addEventListener('input', (e) => { + field.borderColor = (e.target as HTMLInputElement).value; + }); + + propHideBorder.addEventListener('change', (e) => { + field.hideBorder = (e.target as HTMLInputElement).checked; + }); + + deleteBtn.addEventListener('click', () => { + deleteField(field); + }); + + // Specific listeners + if (field.type === 'text') { + const propValue = document.getElementById('propValue') as HTMLInputElement; + const propMaxLength = document.getElementById( + 'propMaxLength' + ) as HTMLInputElement; + const propComb = document.getElementById('propComb') as HTMLInputElement; + const propFontSize = document.getElementById( + 'propFontSize' + ) as HTMLInputElement; + const propTextColor = document.getElementById( + 'propTextColor' + ) as HTMLInputElement; + const propAlignment = document.getElementById( + 'propAlignment' + ) as HTMLSelectElement; + + propValue.addEventListener('input', (e) => { + field.defaultValue = (e.target as HTMLInputElement).value; + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement; + if (textEl) textEl.textContent = field.defaultValue; + } + }); + + propMaxLength.addEventListener('input', (e) => { + const val = parseInt((e.target as HTMLInputElement).value); + field.maxLength = isNaN(val) ? 0 : Math.max(0, val); + if (field.maxLength > 0) { + propValue.maxLength = field.maxLength; + if (field.defaultValue.length > field.maxLength) { + field.defaultValue = field.defaultValue.substring(0, field.maxLength); + propValue.value = field.defaultValue; + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const textEl = fieldWrapper.querySelector( + '.field-text' + ) as HTMLElement; + if (textEl) textEl.textContent = field.defaultValue; + } + } + } else { + propValue.removeAttribute('maxLength'); + } + }); + + propComb.addEventListener('input', (e) => { + const val = parseInt((e.target as HTMLInputElement).value); + field.combCells = isNaN(val) ? 0 : Math.max(0, val); + + if (field.combCells > 0) { + propValue.maxLength = field.combCells; + propMaxLength.value = field.combCells.toString(); + propMaxLength.disabled = true; + field.maxLength = field.combCells; + + if (field.defaultValue.length > field.combCells) { + field.defaultValue = field.defaultValue.substring(0, field.combCells); + propValue.value = field.defaultValue; + } + } else { + propMaxLength.disabled = false; + propValue.removeAttribute('maxLength'); + if (field.maxLength > 0) { + propValue.maxLength = field.maxLength; + } + } + + // Re-render field visual only, NOT the properties panel + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + // Update text content + const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement; + if (textEl) { + textEl.textContent = field.defaultValue; + if (field.combCells > 0) { + textEl.style.backgroundImage = `repeating-linear-gradient(90deg, transparent, transparent calc((100% / ${field.combCells}) - 1px), #e5e7eb calc((100% / ${field.combCells}) - 1px), #e5e7eb calc(100% / ${field.combCells}))`; + textEl.style.fontFamily = 'monospace'; + textEl.style.letterSpacing = `calc(${field.width / field.combCells}px - 1ch)`; + textEl.style.paddingLeft = `calc((${field.width / field.combCells}px - 1ch) / 2)`; + textEl.style.overflow = 'hidden'; + textEl.style.textAlign = 'left'; + textEl.style.justifyContent = 'flex-start'; + } else { + textEl.style.backgroundImage = 'none'; + textEl.style.fontFamily = 'inherit'; + textEl.style.letterSpacing = 'normal'; + textEl.style.textAlign = field.alignment; + textEl.style.justifyContent = + field.alignment === 'left' + ? 'flex-start' + : field.alignment === 'right' + ? 'flex-end' + : 'center'; + } + } + } + }); + + propFontSize.addEventListener('input', (e) => { + field.fontSize = parseInt((e.target as HTMLInputElement).value); + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement; + if (textEl) textEl.style.fontSize = field.fontSize + 'px'; + } + }); + + propTextColor.addEventListener('input', (e) => { + field.textColor = (e.target as HTMLInputElement).value; + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement; + if (textEl) textEl.style.color = field.textColor; + } + }); + + propAlignment.addEventListener('change', (e) => { + field.alignment = (e.target as HTMLSelectElement).value as + | 'left' + | 'center' + | 'right'; + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const textEl = fieldWrapper.querySelector('.field-text') as HTMLElement; + if (textEl) { + textEl.style.textAlign = field.alignment; + textEl.style.justifyContent = + field.alignment === 'left' + ? 'flex-start' + : field.alignment === 'right' + ? 'flex-end' + : 'center'; + } + } + }); + + const propMultilineBtn = document.getElementById( + 'propMultilineBtn' + ) as HTMLButtonElement; + if (propMultilineBtn) { + propMultilineBtn.addEventListener('click', () => { + field.multiline = !field.multiline; + + // Update Toggle Button UI + const span = propMultilineBtn.querySelector('span'); + if (field.multiline) { + propMultilineBtn.classList.remove('bg-gray-500'); + propMultilineBtn.classList.add('bg-indigo-600'); + span?.classList.remove('translate-x-0'); + span?.classList.add('translate-x-6'); + } else { + propMultilineBtn.classList.remove('bg-indigo-600'); + propMultilineBtn.classList.add('bg-gray-500'); + span?.classList.remove('translate-x-6'); + span?.classList.add('translate-x-0'); + } + + // Update Canvas UI + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const textEl = fieldWrapper.querySelector( + '.field-text' + ) as HTMLElement; + if (textEl) { + if (field.multiline) { + textEl.style.whiteSpace = 'pre-wrap'; + textEl.style.alignItems = 'flex-start'; + textEl.style.overflow = 'hidden'; + } else { + textEl.style.whiteSpace = 'nowrap'; + textEl.style.alignItems = 'center'; + textEl.style.overflow = 'hidden'; + } + } + } + }); + } + } else if (field.type === 'checkbox' || field.type === 'radio') { + const propCheckedBtn = document.getElementById( + 'propCheckedBtn' + ) as HTMLButtonElement; + + propCheckedBtn.addEventListener('click', () => { + field.checked = !field.checked; + + // Update Toggle Button UI + const span = propCheckedBtn.querySelector('span'); + if (field.checked) { + propCheckedBtn.classList.remove('bg-gray-500'); + propCheckedBtn.classList.add('bg-indigo-600'); + span?.classList.remove('translate-x-0'); + span?.classList.add('translate-x-6'); + } else { + propCheckedBtn.classList.remove('bg-indigo-600'); + propCheckedBtn.classList.add('bg-gray-500'); + span?.classList.remove('translate-x-6'); + span?.classList.add('translate-x-0'); + } + + // Update Canvas UI + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const contentEl = fieldWrapper.querySelector( + '.field-content' + ) as HTMLElement; + if (contentEl) { + if (field.type === 'checkbox') { + contentEl.innerHTML = field.checked + ? '' + : ''; + } else { + contentEl.innerHTML = field.checked + ? '
' + : ''; + } + } + } + }); + + if (field.type === 'radio') { + const propGroupName = document.getElementById( + 'propGroupName' + ) as HTMLInputElement; + const propExportValue = document.getElementById( + 'propExportValue' + ) as HTMLInputElement; + + propGroupName.addEventListener('input', (e) => { + field.groupName = (e.target as HTMLInputElement).value; + }); + propExportValue.addEventListener('input', (e) => { + field.exportValue = (e.target as HTMLInputElement).value; + }); + } + } else if (field.type === 'dropdown' || field.type === 'optionlist') { + const propOptions = document.getElementById( + 'propOptions' + ) as HTMLTextAreaElement; + propOptions.addEventListener('input', (e) => { + // We split by newline OR comma for the actual options array + const val = (e.target as HTMLTextAreaElement).value; + field.options = val + .split(/[\n,]/) + .map((s) => s.trim()) + .filter((s) => s.length > 0); + + const propSelectedOption = document.getElementById( + 'propSelectedOption' + ) as HTMLSelectElement; + if (propSelectedOption) { + const currentVal = field.defaultValue; + propSelectedOption.innerHTML = + '' + + field.options + ?.map( + (opt) => + `` + ) + .join(''); + + if ( + currentVal && + field.options && + !field.options.includes(currentVal) + ) { + field.defaultValue = ''; + propSelectedOption.value = ''; + } + } + + renderField(field); + }); + + const propSelectedOption = document.getElementById( + 'propSelectedOption' + ) as HTMLSelectElement; + propSelectedOption.addEventListener('change', (e) => { + field.defaultValue = (e.target as HTMLSelectElement).value; + + // Update visual on canvas + renderField(field); + }); + } else if (field.type === 'button') { + const propLabel = document.getElementById('propLabel') as HTMLInputElement; + propLabel.addEventListener('input', (e) => { + field.label = (e.target as HTMLInputElement).value; + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const contentEl = fieldWrapper.querySelector( + '.field-content' + ) as HTMLElement; + if (contentEl) contentEl.textContent = field.label || 'Button'; + } + }); + + const propAction = document.getElementById( + 'propAction' + ) as HTMLSelectElement; + const propUrlContainer = document.getElementById( + 'propUrlContainer' + ) as HTMLDivElement; + const propJsContainer = document.getElementById( + 'propJsContainer' + ) as HTMLDivElement; + const propShowHideContainer = document.getElementById( + 'propShowHideContainer' + ) as HTMLDivElement; + + propAction.addEventListener('change', (e) => { + field.action = (e.target as HTMLSelectElement).value as any; + + // Show/hide containers + propUrlContainer.classList.add('hidden'); + propJsContainer.classList.add('hidden'); + propShowHideContainer.classList.add('hidden'); + + if (field.action === 'url') { + propUrlContainer.classList.remove('hidden'); + } else if (field.action === 'js') { + propJsContainer.classList.remove('hidden'); + } else if (field.action === 'showHide') { + propShowHideContainer.classList.remove('hidden'); + } + }); + + const propActionUrl = document.getElementById( + 'propActionUrl' + ) as HTMLInputElement; + propActionUrl.addEventListener('input', (e) => { + field.actionUrl = (e.target as HTMLInputElement).value; + }); + + const propJsScript = document.getElementById( + 'propJsScript' + ) as HTMLTextAreaElement; + if (propJsScript) { + propJsScript.addEventListener('input', (e) => { + field.jsScript = (e.target as HTMLTextAreaElement).value; + }); + } + + const propTargetField = document.getElementById( + 'propTargetField' + ) as HTMLSelectElement; + if (propTargetField) { + propTargetField.addEventListener('change', (e) => { + field.targetFieldName = (e.target as HTMLSelectElement).value; + }); + } + + const propVisibilityAction = document.getElementById( + 'propVisibilityAction' + ) as HTMLSelectElement; + if (propVisibilityAction) { + propVisibilityAction.addEventListener('change', (e) => { + field.visibilityAction = (e.target as HTMLSelectElement).value as any; + }); + } + } else if (field.type === 'signature') { + // No specific listeners for signature fields yet + } else if (field.type === 'date') { + const propDateFormat = document.getElementById( + 'propDateFormat' + ) as HTMLSelectElement; + const customFormatContainer = document.getElementById( + 'customFormatContainer' + ) as HTMLDivElement; + const propCustomFormat = document.getElementById( + 'propCustomFormat' + ) as HTMLInputElement; + const dateFormatExample = document.getElementById( + 'dateFormatExample' + ) as HTMLSpanElement; + + const formatDateExample = (format: string): string => { + const now = new Date(); + const d = now.getDate(); + const dd = d.toString().padStart(2, '0'); + const m = now.getMonth() + 1; + const mm = m.toString().padStart(2, '0'); + const yy = now.getFullYear().toString().slice(-2); + const yyyy = now.getFullYear().toString(); + const h = now.getHours() % 12 || 12; + const HH = now.getHours().toString().padStart(2, '0'); + const MM = now.getMinutes().toString().padStart(2, '0'); + const tt = now.getHours() >= 12 ? 'PM' : 'AM'; + const monthNames = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + const monthNamesFull = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + const mmm = monthNames[now.getMonth()]; + const mmmm = monthNamesFull[now.getMonth()]; + + return format + .replace(/mmmm/g, mmmm) + .replace(/mmm/g, mmm) + .replace(/mm/g, mm) + .replace(/m/g, m.toString()) + .replace(/dddd/g, dd) + .replace(/dd/g, dd) + .replace(/d/g, d.toString()) + .replace(/yyyy/g, yyyy) + .replace(/yy/g, yy) + .replace(/HH/g, HH) + .replace(/h/g, h.toString()) + .replace(/MM/g, MM) + .replace(/tt/g, tt); + }; + + const updateExample = () => { + if (dateFormatExample) { + dateFormatExample.textContent = formatDateExample( + field.dateFormat || 'mm/dd/yyyy' + ); + } + }; + + updateExample(); + + if (propDateFormat) { + propDateFormat.addEventListener('change', (e) => { + const value = (e.target as HTMLSelectElement).value; + if (value === 'custom') { + customFormatContainer?.classList.remove('hidden'); + if (propCustomFormat && propCustomFormat.value) { + field.dateFormat = propCustomFormat.value; + } + } else { + customFormatContainer?.classList.add('hidden'); + field.dateFormat = value; + } + updateExample(); + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const textSpan = fieldWrapper.querySelector( + '.date-format-text' + ) as HTMLElement; + if (textSpan) { + textSpan.textContent = field.dateFormat; + } + } + setTimeout(() => (window as any).lucide?.createIcons(), 0); + }); + } + + if (propCustomFormat) { + propCustomFormat.addEventListener('input', (e) => { + field.dateFormat = (e.target as HTMLInputElement).value || 'mm/dd/yyyy'; + updateExample(); + const fieldWrapper = document.getElementById(field.id); + if (fieldWrapper) { + const textSpan = fieldWrapper.querySelector( + '.date-format-text' + ) as HTMLElement; + if (textSpan) { + textSpan.textContent = field.dateFormat; + } + } + }); + } + } else if (field.type === 'image') { + const propLabel = document.getElementById('propLabel') as HTMLInputElement; + propLabel.addEventListener('input', (e) => { + field.label = (e.target as HTMLInputElement).value; + renderField(field); + }); + } else if (field.type === 'barcode') { + const propBarcodeFormat = document.getElementById( + 'propBarcodeFormat' + ) as HTMLSelectElement; + const propBarcodeValue = document.getElementById( + 'propBarcodeValue' + ) as HTMLInputElement; + + const barcodeSampleValues: Record = { + qrcode: 'https://example.com', + code128: 'ABC-123', + code39: 'ABC123', + ean13: '590123412345', + upca: '01234567890', + datamatrix: 'https://example.com', + pdf417: 'https://example.com', + }; + + const barcodeFormatHints: Record = { + qrcode: 'Any text, URL, or data', + code128: 'ASCII characters (letters, numbers, symbols)', + code39: 'Uppercase A-Z, digits 0-9, and - . $ / + % SPACE', + ean13: 'Exactly 12 or 13 digits', + upca: 'Exactly 11 or 12 digits', + datamatrix: 'Any text, URL, or data', + pdf417: 'Any text, URL, or data', + }; + + const hintEl = document.getElementById('barcodeFormatHint'); + if (hintEl) + hintEl.textContent = + barcodeFormatHints[field.barcodeFormat || 'qrcode'] || ''; + + if (propBarcodeFormat) { + propBarcodeFormat.addEventListener('change', (e) => { + const newFormat = (e.target as HTMLSelectElement).value; + field.barcodeFormat = newFormat; + field.barcodeValue = barcodeSampleValues[newFormat] || 'hello'; + if (propBarcodeValue) propBarcodeValue.value = field.barcodeValue; + if (hintEl) hintEl.textContent = barcodeFormatHints[newFormat] || ''; + renderField(field); + }); + } + + if (propBarcodeValue) { + propBarcodeValue.addEventListener('input', (e) => { + field.barcodeValue = (e.target as HTMLInputElement).value; + renderField(field); + }); + } + } } // Hide properties panel function hideProperties(): void { - propertiesPanel.innerHTML = '

Select a field to edit properties

' + propertiesPanel.innerHTML = + '

Select a field to edit properties

'; } // Delete field function deleteField(field: FormField): void { - const fieldEl = document.getElementById(field.id) - if (fieldEl) { - fieldEl.remove() - } - fields = fields.filter((f) => f.id !== field.id) - deselectAll() - updateFieldCount() + const fieldEl = document.getElementById(field.id); + if (fieldEl) { + fieldEl.remove(); + } + fields = fields.filter((f) => f.id !== field.id); + deselectAll(); + updateFieldCount(); } // Delete key handler document.addEventListener('keydown', (e) => { - if (e.key === 'Delete' && selectedField) { - deleteField(selectedField) - } else if (e.key === 'Escape' && selectedToolType) { - // Cancel tool selection - toolItems.forEach(item => item.classList.remove('ring-2', 'ring-indigo-400', 'bg-indigo-600')) - selectedToolType = null - canvas.style.cursor = 'default' - } -}) + if (e.key === 'Delete' && selectedField) { + deleteField(selectedField); + } else if (e.key === 'Escape' && selectedToolType) { + // Cancel tool selection + toolItems.forEach((item) => + item.classList.remove('ring-2', 'ring-indigo-400', 'bg-indigo-600') + ); + selectedToolType = null; + canvas.style.cursor = 'default'; + } +}); // Update field count function updateFieldCount(): void { - fieldCountDisplay.textContent = fields.length.toString() + fieldCountDisplay.textContent = fields.length.toString(); } // Download PDF downloadBtn.addEventListener('click', async () => { - // Check for duplicate field names before generating PDF - const nameCount = new Map() - const duplicates: string[] = [] - const conflictsWithPdf: string[] = [] + // Check for duplicate field names before generating PDF + const nameCount = new Map(); + const duplicates: string[] = []; + const conflictsWithPdf: string[] = []; - fields.forEach(field => { - const count = nameCount.get(field.name) || 0 - nameCount.set(field.name, count + 1) + fields.forEach((field) => { + const count = nameCount.get(field.name) || 0; + nameCount.set(field.name, count + 1); - if (existingFieldNames.has(field.name)) { - if (field.type === 'radio' && existingRadioGroups.has(field.name)) { - } else { - conflictsWithPdf.push(field.name) - } + if (existingFieldNames.has(field.name)) { + if (field.type === 'radio' && existingRadioGroups.has(field.name)) { + } else { + conflictsWithPdf.push(field.name); + } + } + }); + + nameCount.forEach((count, name) => { + if (count > 1) { + const fieldsWithName = fields.filter((f) => f.name === name); + const allRadio = fieldsWithName.every((f) => f.type === 'radio'); + + if (!allRadio) { + duplicates.push(name); + } + } + }); + + if (conflictsWithPdf.length > 0) { + const conflictList = [...new Set(conflictsWithPdf)] + .map((name) => `"${name}"`) + .join(', '); + showModal( + 'Field Name Conflict', + `The following field names already exist in the uploaded PDF: ${conflictList}. Please rename these fields before downloading.`, + 'error' + ); + return; + } + + if (duplicates.length > 0) { + const duplicateList = duplicates.map((name) => `"${name}"`).join(', '); + showModal( + 'Duplicate Field Names', + `The following field names are used more than once: ${duplicateList}. Please rename these fields to use unique names before downloading.`, + 'error' + ); + return; + } + + if (fields.length === 0) { + alert('Please add at least one field before downloading.'); + return; + } + + if (pages.length === 0) { + alert('No pages found. Please create a blank PDF or upload one.'); + return; + } + + try { + let pdfDoc: PDFDocument; + + if (uploadedPdfDoc) { + pdfDoc = uploadedPdfDoc; + } else { + pdfDoc = await PDFDocument.create(); + + for (const pageData of pages) { + pdfDoc.addPage([pageData.width, pageData.height]); + } + } + + const form = pdfDoc.getForm(); + + if (extractedFieldNames.size > 0) { + for (const fieldName of extractedFieldNames) { + try { + const existingField = form.getFieldMaybe(fieldName); + if (existingField) { + form.removeField(existingField); + } + } catch (e) { + console.warn(`Failed to remove existing field "${fieldName}":`, e); } - }) - - nameCount.forEach((count, name) => { - if (count > 1) { - const fieldsWithName = fields.filter(f => f.name === name) - const allRadio = fieldsWithName.every(f => f.type === 'radio') - - if (!allRadio) { - duplicates.push(name) - } - } - }) - - if (conflictsWithPdf.length > 0) { - const conflictList = [...new Set(conflictsWithPdf)].map(name => `"${name}"`).join(', ') - showModal( - 'Field Name Conflict', - `The following field names already exist in the uploaded PDF: ${conflictList}. Please rename these fields before downloading.`, - 'error' - ) - return + } } - if (duplicates.length > 0) { - const duplicateList = duplicates.map(name => `"${name}"`).join(', ') - showModal( - 'Duplicate Field Names', - `The following field names are used more than once: ${duplicateList}. Please rename these fields to use unique names before downloading.`, - 'error' - ) - return - } + const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica); - if (fields.length === 0) { - alert('Please add at least one field before downloading.') - return - } + // Set document metadata for accessibility + pdfDoc.setTitle('Fillable Form'); + pdfDoc.setAuthor('BentoPDF'); + pdfDoc.setLanguage('en-US'); - if (pages.length === 0) { - alert('No pages found. Please create a blank PDF or upload one.') - return - } + const radioGroups = new Map(); // Track created radio groups - try { - let pdfDoc: PDFDocument + for (const field of fields) { + const pageData = pages[field.pageIndex]; + if (!pageData) continue; - if (uploadedPdfDoc) { - pdfDoc = uploadedPdfDoc + const pdfPage = pdfDoc.getPage(field.pageIndex); + const { height: pageHeight } = pdfPage.getSize(); + + const scaleX = 1 / pdfViewerScale; + const scaleY = 1 / pdfViewerScale; + + const adjustedX = field.x - pdfViewerOffset.x; + const adjustedY = field.y - pdfViewerOffset.y; + + const x = adjustedX * scaleX; + const y = pageHeight - adjustedY * scaleY - field.height * scaleY; + const width = field.width * scaleX; + const height = field.height * scaleY; + + console.log(`Field "${field.name}":`, { + screenPos: { x: field.x, y: field.y }, + adjustedPos: { x: adjustedX, y: adjustedY }, + pdfPos: { x, y, width, height }, + metrics: { offset: pdfViewerOffset, scale: pdfViewerScale }, + }); + + if (field.type === 'text') { + const textField = form.createTextField(field.name); + const rgbColor = hexToRgb(field.textColor); + const borderRgb = hexToRgb(field.borderColor || '#000000'); + + textField.addToPage(pdfPage, { + x: x, + y: y, + width: width, + height: height, + borderWidth: field.hideBorder ? 0 : 1, + borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), + backgroundColor: rgb(1, 1, 1), + textColor: rgb(rgbColor.r, rgbColor.g, rgbColor.b), + }); + + textField.setText(field.defaultValue); + textField.setFontSize(field.fontSize); + + // Set alignment + if (field.alignment === 'center') { + textField.setAlignment(TextAlignment.Center); + } else if (field.alignment === 'right') { + textField.setAlignment(TextAlignment.Right); } else { - pdfDoc = await PDFDocument.create() - - for (const pageData of pages) { - pdfDoc.addPage([pageData.width, pageData.height]) - } + textField.setAlignment(TextAlignment.Left); } - const form = pdfDoc.getForm() - - const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica) - - // Set document metadata for accessibility - pdfDoc.setTitle('Fillable Form') - pdfDoc.setAuthor('BentoPDF') - pdfDoc.setLanguage('en-US') - - const radioGroups = new Map() // Track created radio groups - - for (const field of fields) { - const pageData = pages[field.pageIndex] - if (!pageData) continue - - const pdfPage = pdfDoc.getPage(field.pageIndex) - const { height: pageHeight } = pdfPage.getSize() - - const scaleX = 1 / pdfViewerScale - const scaleY = 1 / pdfViewerScale - - const adjustedX = field.x - pdfViewerOffset.x - const adjustedY = field.y - pdfViewerOffset.y - - const x = adjustedX * scaleX - const y = pageHeight - (adjustedY * scaleY) - (field.height * scaleY) - const width = field.width * scaleX - const height = field.height * scaleY - - console.log(`Field "${field.name}":`, { - screenPos: { x: field.x, y: field.y }, - adjustedPos: { x: adjustedX, y: adjustedY }, - pdfPos: { x, y, width, height }, - metrics: { offset: pdfViewerOffset, scale: pdfViewerScale } - }) - - if (field.type === 'text') { - const textField = form.createTextField(field.name) - const rgbColor = hexToRgb(field.textColor) - const borderRgb = hexToRgb(field.borderColor || '#000000') - - textField.addToPage(pdfPage, { - x: x, - y: y, - width: width, - height: height, - borderWidth: field.hideBorder ? 0 : 1, - borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), - backgroundColor: rgb(1, 1, 1), - textColor: rgb(rgbColor.r, rgbColor.g, rgbColor.b), - }) - - textField.setText(field.defaultValue) - textField.setFontSize(field.fontSize) - - // Set alignment - if (field.alignment === 'center') { - textField.setAlignment(TextAlignment.Center) - } else if (field.alignment === 'right') { - textField.setAlignment(TextAlignment.Right) - } else { - textField.setAlignment(TextAlignment.Left) - } - - // Handle combing - if (field.combCells > 0) { - textField.setMaxLength(field.combCells) - textField.enableCombing() - } else if (field.maxLength > 0) { - textField.setMaxLength(field.maxLength) - } - - // Disable multiline to prevent RTL issues (unless explicitly enabled) - if (!field.multiline) { - textField.disableMultiline() - } else { - textField.enableMultiline() - } - - // Common properties - if (field.required) textField.enableRequired() - if (field.readOnly) textField.enableReadOnly() - if (field.tooltip) { - textField.acroField.getWidgets().forEach(widget => { - widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)) - }) - } - - } else if (field.type === 'checkbox') { - const checkBox = form.createCheckBox(field.name) - const borderRgb = hexToRgb(field.borderColor || '#000000') - checkBox.addToPage(pdfPage, { - x: x, - y: y, - width: width, - height: height, - borderWidth: field.hideBorder ? 0 : 1, - borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), - backgroundColor: rgb(1, 1, 1), - }) - if (field.checked) checkBox.check() - if (field.required) checkBox.enableRequired() - if (field.readOnly) checkBox.enableReadOnly() - if (field.tooltip) { - checkBox.acroField.getWidgets().forEach(widget => { - widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)) - }) - } - - } else if (field.type === 'radio') { - const groupName = field.name - let radioGroup - - if (radioGroups.has(groupName)) { - radioGroup = radioGroups.get(groupName) - } else { - const existingField = form.getFieldMaybe(groupName) - - if (existingField) { - radioGroup = existingField - radioGroups.set(groupName, radioGroup) - console.log(`Using existing radio group from PDF: ${groupName}`) - } else { - radioGroup = form.createRadioGroup(groupName) - radioGroups.set(groupName, radioGroup) - console.log(`Created new radio group: ${groupName}`) - } - } - - const borderRgb = hexToRgb(field.borderColor || '#000000') - radioGroup.addOptionToPage(field.exportValue || 'Yes', pdfPage as any, { - x: x, - y: y, - width: width, - height: height, - borderWidth: field.hideBorder ? 0 : 1, - borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), - backgroundColor: rgb(1, 1, 1), - }) - if (field.checked) radioGroup.select(field.exportValue || 'Yes') - if (field.required) radioGroup.enableRequired() - if (field.readOnly) radioGroup.enableReadOnly() - if (field.tooltip) { - radioGroup.acroField.getWidgets().forEach((widget: any) => { - widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)) - }) - } - - } else if (field.type === 'dropdown') { - const dropdown = form.createDropdown(field.name) - const borderRgb = hexToRgb(field.borderColor || '#000000') - dropdown.addToPage(pdfPage, { - x: x, - y: y, - width: width, - height: height, - borderWidth: field.hideBorder ? 0 : 1, - borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), - backgroundColor: rgb(1, 1, 1), // Light blue not supported in standard PDF appearance easily without streams - }) - if (field.options) dropdown.setOptions(field.options) - if (field.defaultValue && field.options?.includes(field.defaultValue)) dropdown.select(field.defaultValue) - else if (field.options && field.options.length > 0) dropdown.select(field.options[0]) - - const rgbColor = hexToRgb(field.textColor) - dropdown.acroField.setFontSize(field.fontSize) - dropdown.acroField.setDefaultAppearance( - `0 0 0 rg /Helv ${field.fontSize} Tf` - ) - - if (field.required) dropdown.enableRequired() - if (field.readOnly) dropdown.enableReadOnly() - if (field.tooltip) { - dropdown.acroField.getWidgets().forEach(widget => { - widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)) - }) - } - - } else if (field.type === 'optionlist') { - const optionList = form.createOptionList(field.name) - const borderRgb = hexToRgb(field.borderColor || '#000000') - optionList.addToPage(pdfPage, { - x: x, - y: y, - width: width, - height: height, - borderWidth: field.hideBorder ? 0 : 1, - borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), - backgroundColor: rgb(1, 1, 1), - }) - if (field.options) optionList.setOptions(field.options) - if (field.defaultValue && field.options?.includes(field.defaultValue)) optionList.select(field.defaultValue) - else if (field.options && field.options.length > 0) optionList.select(field.options[0]) - - const rgbColor = hexToRgb(field.textColor) - optionList.acroField.setFontSize(field.fontSize) - optionList.acroField.setDefaultAppearance( - `0 0 0 rg /Helv ${field.fontSize} Tf` - ) - - if (field.required) optionList.enableRequired() - if (field.readOnly) optionList.enableReadOnly() - if (field.tooltip) { - optionList.acroField.getWidgets().forEach(widget => { - widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)) - }) - } - - } else if (field.type === 'button') { - const button = form.createButton(field.name) - const borderRgb = hexToRgb(field.borderColor || '#000000') - button.addToPage(field.label || 'Button', pdfPage, { - x: x, - y: y, - width: width, - height: height, - borderWidth: field.hideBorder ? 0 : 1, - borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), - backgroundColor: rgb(0.8, 0.8, 0.8), // Light gray - }) - - // Add Action - if (field.action && field.action !== 'none') { - const widgets = button.acroField.getWidgets() - - widgets.forEach(widget => { - let actionDict: any - - if (field.action === 'reset') { - actionDict = pdfDoc.context.obj({ - Type: 'Action', - S: 'ResetForm' - }) - } else if (field.action === 'print') { - // Print action using JavaScript - actionDict = pdfDoc.context.obj({ - Type: 'Action', - S: 'JavaScript', - JS: 'print();' - }) - } else if (field.action === 'url' && field.actionUrl) { - // Validate URL - let url = field.actionUrl.trim() - if (!url.startsWith('http://') && !url.startsWith('https://')) { - url = 'https://' + url - } - - // Encode URL to handle special characters (RFC3986) - try { - url = encodeURI(url) - } catch (e) { - console.warn('Failed to encode URL:', e) - } - - actionDict = pdfDoc.context.obj({ - Type: 'Action', - S: 'URI', - URI: PDFString.of(url) - }) - } else if (field.action === 'js' && field.jsScript) { - actionDict = pdfDoc.context.obj({ - Type: 'Action', - S: 'JavaScript', - JS: field.jsScript - }) - } else if (field.action === 'showHide' && field.targetFieldName) { - const target = field.targetFieldName - let script = '' - - if (field.visibilityAction === 'show') { - script = `var f = this.getField("${target}"); if(f) f.display = display.visible;` - } else if (field.visibilityAction === 'hide') { - script = `var f = this.getField("${target}"); if(f) f.display = display.hidden;` - } else { - // Toggle - script = `var f = this.getField("${target}"); if(f) f.display = (f.display === display.visible) ? display.hidden : display.visible;` - } - - actionDict = pdfDoc.context.obj({ - Type: 'Action', - S: 'JavaScript', - JS: script - }) - } - - if (actionDict) { - widget.dict.set(PDFName.of('A'), actionDict) - } - }) - } - - if (field.tooltip) { - button.acroField.getWidgets().forEach(widget => { - widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)) - }) - } - } else if (field.type === 'date') { - const dateField = form.createTextField(field.name) - dateField.addToPage(pdfPage, { - x: x, - y: y, - width: width, - height: height, - borderWidth: 1, - borderColor: rgb(0, 0, 0), - backgroundColor: rgb(1, 1, 1), - }) - - // Add Date Format and Keystroke Actions to the FIELD (not widget) - const dateFormat = field.dateFormat || 'mm/dd/yyyy' - - const formatAction = pdfDoc.context.obj({ - Type: 'Action', - S: 'JavaScript', - JS: PDFString.of(`AFDate_FormatEx("${dateFormat}");`) - }) - - const keystrokeAction = pdfDoc.context.obj({ - Type: 'Action', - S: 'JavaScript', - JS: PDFString.of(`AFDate_KeystrokeEx("${dateFormat}");`) - }) - - // Attach AA (Additional Actions) to the field dictionary - const additionalActions = pdfDoc.context.obj({ - F: formatAction, - K: keystrokeAction - }) - dateField.acroField.dict.set(PDFName.of('AA'), additionalActions) - - if (field.required) dateField.enableRequired() - if (field.readOnly) dateField.enableReadOnly() - if (field.tooltip) { - dateField.acroField.getWidgets().forEach(widget => { - widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)) - }) - } - } else if (field.type === 'image') { - const imageBtn = form.createButton(field.name) - imageBtn.addToPage(field.label || 'Click to Upload Image', pdfPage, { - x: x, - y: y, - width: width, - height: height, - borderWidth: 1, - borderColor: rgb(0, 0, 0), - backgroundColor: rgb(0.9, 0.9, 0.9), - }) - - // Add Import Icon Action - const widgets = imageBtn.acroField.getWidgets() - widgets.forEach(widget => { - const actionDict = pdfDoc.context.obj({ - Type: 'Action', - S: 'JavaScript', - JS: 'event.target.buttonImportIcon();' - }) - widget.dict.set(PDFName.of('A'), actionDict) - - // Set Appearance Characteristics (MK) -> Text Position (TP) = 1 (Icon Only) - // This ensures the image replaces the text when uploaded - // IF (Icon Fit) -> SW: A (Always Scale), S: A (Anamorphic/Fill) - const mkDict = pdfDoc.context.obj({ - TP: 1, - BG: [0.9, 0.9, 0.9], // Background color (Light Gray) - BC: [0, 0, 0], // Border color (Black) - IF: { - SW: PDFName.of('A'), - S: PDFName.of('A'), - FB: true - } - }) - widget.dict.set(PDFName.of('MK'), mkDict) - }) - - if (field.tooltip) { - imageBtn.acroField.getWidgets().forEach(widget => { - widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)) - }) - } - } else if (field.type === 'signature') { - const context = pdfDoc.context - - // Create the signature field dictionary with FT = Sig - const sigDict = context.obj({ - FT: PDFName.of('Sig'), - T: PDFString.of(field.name), - Kids: [], - }) as PDFDict - const sigRef = context.register(sigDict) - - // Create the widget annotation for the signature field - const widgetDict = context.obj({ - Type: PDFName.of('Annot'), - Subtype: PDFName.of('Widget'), - Rect: [x, y, x + width, y + height], - F: 4, // Print flag - P: pdfPage.ref, - Parent: sigRef, - }) as PDFDict - - // Add border and background appearance - const borderStyle = context.obj({ - W: 1, // Border width - S: PDFName.of('S'), // Solid border - }) as PDFDict - widgetDict.set(PDFName.of('BS'), borderStyle) - widgetDict.set(PDFName.of('BC'), context.obj([0, 0, 0])) // Border color (black) - widgetDict.set(PDFName.of('BG'), context.obj([0.95, 0.95, 0.95])) // Background color - - const widgetRef = context.register(widgetDict) - - const kidsArray = sigDict.get(PDFName.of('Kids')) as PDFArray - kidsArray.push(widgetRef) - - pdfPage.node.addAnnot(widgetRef) - - const acroForm = form.acroForm - acroForm.addField(sigRef) - - // Add tooltip if specified - if (field.tooltip) { - widgetDict.set(PDFName.of('TU'), PDFString.of(field.tooltip)) - } - } + // Handle combing + if (field.combCells > 0) { + textField.setMaxLength(field.combCells); + textField.enableCombing(); + } else if (field.maxLength > 0) { + textField.setMaxLength(field.maxLength); } - form.updateFieldAppearances(helveticaFont) - - const pdfBytes = await pdfDoc.save() - const blob = new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }) - downloadFile(blob, 'fillable-form.pdf') - showModal('Success', 'Your PDF has been downloaded successfully.', 'info', () => { - resetToInitial() - }, 'Okay') - } catch (error) { - console.error('Error generating PDF:', error) - const errorMessage = (error as Error).message - - // Check if it's a duplicate field name error - if (errorMessage.includes('A field already exists with the specified name')) { - // Extract the field name from the error message - const match = errorMessage.match(/A field already exists with the specified name: "(.+?)"/) - const fieldName = match ? match[1] : 'unknown' - - if (existingRadioGroups.has(fieldName)) { - console.log(`Adding to existing radio group: ${fieldName}`) - } else { - showModal('Duplicate Field Name', `A field named "${fieldName}" already exists. Please rename this field to use a unique name before downloading.`, 'error') - } + // Disable multiline to prevent RTL issues (unless explicitly enabled) + if (!field.multiline) { + textField.disableMultiline(); } else { - showModal('Error', 'Error generating PDF: ' + errorMessage, 'error') + textField.enableMultiline(); } + + // Common properties + if (field.required) textField.enableRequired(); + if (field.readOnly) textField.enableReadOnly(); + if (field.tooltip) { + textField.acroField.getWidgets().forEach((widget) => { + widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)); + }); + } + } else if (field.type === 'checkbox') { + const checkBox = form.createCheckBox(field.name); + const borderRgb = hexToRgb(field.borderColor || '#000000'); + checkBox.addToPage(pdfPage, { + x: x, + y: y, + width: width, + height: height, + borderWidth: field.hideBorder ? 0 : 1, + borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), + backgroundColor: rgb(1, 1, 1), + }); + if (field.checked) checkBox.check(); + if (field.required) checkBox.enableRequired(); + if (field.readOnly) checkBox.enableReadOnly(); + if (field.tooltip) { + checkBox.acroField.getWidgets().forEach((widget) => { + widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)); + }); + } + } else if (field.type === 'radio') { + const groupName = field.name; + let radioGroup; + + if (radioGroups.has(groupName)) { + radioGroup = radioGroups.get(groupName); + } else { + const existingField = form.getFieldMaybe(groupName); + + if (existingField) { + radioGroup = existingField; + radioGroups.set(groupName, radioGroup); + console.log(`Using existing radio group from PDF: ${groupName}`); + } else { + radioGroup = form.createRadioGroup(groupName); + radioGroups.set(groupName, radioGroup); + console.log(`Created new radio group: ${groupName}`); + } + } + + const borderRgb = hexToRgb(field.borderColor || '#000000'); + radioGroup.addOptionToPage(field.exportValue || 'Yes', pdfPage as any, { + x: x, + y: y, + width: width, + height: height, + borderWidth: field.hideBorder ? 0 : 1, + borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), + backgroundColor: rgb(1, 1, 1), + }); + if (field.checked) radioGroup.select(field.exportValue || 'Yes'); + if (field.required) radioGroup.enableRequired(); + if (field.readOnly) radioGroup.enableReadOnly(); + if (field.tooltip) { + radioGroup.acroField.getWidgets().forEach((widget: any) => { + widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)); + }); + } + } else if (field.type === 'dropdown') { + const dropdown = form.createDropdown(field.name); + const borderRgb = hexToRgb(field.borderColor || '#000000'); + dropdown.addToPage(pdfPage, { + x: x, + y: y, + width: width, + height: height, + borderWidth: field.hideBorder ? 0 : 1, + borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), + backgroundColor: rgb(1, 1, 1), // Light blue not supported in standard PDF appearance easily without streams + }); + if (field.options) dropdown.setOptions(field.options); + if (field.defaultValue && field.options?.includes(field.defaultValue)) + dropdown.select(field.defaultValue); + else if (field.options && field.options.length > 0) + dropdown.select(field.options[0]); + + const rgbColor = hexToRgb(field.textColor); + dropdown.acroField.setFontSize(field.fontSize); + dropdown.acroField.setDefaultAppearance( + `0 0 0 rg /Helv ${field.fontSize} Tf` + ); + + if (field.required) dropdown.enableRequired(); + if (field.readOnly) dropdown.enableReadOnly(); + if (field.tooltip) { + dropdown.acroField.getWidgets().forEach((widget) => { + widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)); + }); + } + } else if (field.type === 'optionlist') { + const optionList = form.createOptionList(field.name); + const borderRgb = hexToRgb(field.borderColor || '#000000'); + optionList.addToPage(pdfPage, { + x: x, + y: y, + width: width, + height: height, + borderWidth: field.hideBorder ? 0 : 1, + borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), + backgroundColor: rgb(1, 1, 1), + }); + if (field.options) optionList.setOptions(field.options); + if (field.defaultValue && field.options?.includes(field.defaultValue)) + optionList.select(field.defaultValue); + else if (field.options && field.options.length > 0) + optionList.select(field.options[0]); + + const rgbColor = hexToRgb(field.textColor); + optionList.acroField.setFontSize(field.fontSize); + optionList.acroField.setDefaultAppearance( + `0 0 0 rg /Helv ${field.fontSize} Tf` + ); + + if (field.required) optionList.enableRequired(); + if (field.readOnly) optionList.enableReadOnly(); + if (field.tooltip) { + optionList.acroField.getWidgets().forEach((widget) => { + widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)); + }); + } + } else if (field.type === 'button') { + const button = form.createButton(field.name); + const borderRgb = hexToRgb(field.borderColor || '#000000'); + button.addToPage(field.label || 'Button', pdfPage, { + x: x, + y: y, + width: width, + height: height, + borderWidth: field.hideBorder ? 0 : 1, + borderColor: rgb(borderRgb.r, borderRgb.g, borderRgb.b), + backgroundColor: rgb(0.8, 0.8, 0.8), // Light gray + }); + + // Add Action + if (field.action && field.action !== 'none') { + const widgets = button.acroField.getWidgets(); + + widgets.forEach((widget) => { + let actionDict: any; + + if (field.action === 'reset') { + actionDict = pdfDoc.context.obj({ + Type: 'Action', + S: 'ResetForm', + }); + } else if (field.action === 'print') { + // Print action using JavaScript + actionDict = pdfDoc.context.obj({ + Type: 'Action', + S: 'JavaScript', + JS: 'print();', + }); + } else if (field.action === 'url' && field.actionUrl) { + // Validate URL + let url = field.actionUrl.trim(); + if (!url.startsWith('http://') && !url.startsWith('https://')) { + url = 'https://' + url; + } + + // Encode URL to handle special characters (RFC3986) + try { + url = encodeURI(url); + } catch (e) { + console.warn('Failed to encode URL:', e); + } + + actionDict = pdfDoc.context.obj({ + Type: 'Action', + S: 'URI', + URI: PDFString.of(url), + }); + } else if (field.action === 'js' && field.jsScript) { + actionDict = pdfDoc.context.obj({ + Type: 'Action', + S: 'JavaScript', + JS: field.jsScript, + }); + } else if (field.action === 'showHide' && field.targetFieldName) { + const target = field.targetFieldName; + let script = ''; + + if (field.visibilityAction === 'show') { + script = `var f = this.getField("${target}"); if(f) f.display = display.visible;`; + } else if (field.visibilityAction === 'hide') { + script = `var f = this.getField("${target}"); if(f) f.display = display.hidden;`; + } else { + // Toggle + script = `var f = this.getField("${target}"); if(f) f.display = (f.display === display.visible) ? display.hidden : display.visible;`; + } + + actionDict = pdfDoc.context.obj({ + Type: 'Action', + S: 'JavaScript', + JS: script, + }); + } + + if (actionDict) { + widget.dict.set(PDFName.of('A'), actionDict); + } + }); + } + + if (field.tooltip) { + button.acroField.getWidgets().forEach((widget) => { + widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)); + }); + } + } else if (field.type === 'date') { + const dateField = form.createTextField(field.name); + dateField.addToPage(pdfPage, { + x: x, + y: y, + width: width, + height: height, + borderWidth: 1, + borderColor: rgb(0, 0, 0), + backgroundColor: rgb(1, 1, 1), + }); + + // Add Date Format and Keystroke Actions to the FIELD (not widget) + const dateFormat = field.dateFormat || 'mm/dd/yyyy'; + + const formatAction = pdfDoc.context.obj({ + Type: 'Action', + S: 'JavaScript', + JS: PDFString.of(`AFDate_FormatEx("${dateFormat}");`), + }); + + const keystrokeAction = pdfDoc.context.obj({ + Type: 'Action', + S: 'JavaScript', + JS: PDFString.of(`AFDate_KeystrokeEx("${dateFormat}");`), + }); + + // Attach AA (Additional Actions) to the field dictionary + const additionalActions = pdfDoc.context.obj({ + F: formatAction, + K: keystrokeAction, + }); + dateField.acroField.dict.set(PDFName.of('AA'), additionalActions); + + if (field.required) dateField.enableRequired(); + if (field.readOnly) dateField.enableReadOnly(); + if (field.tooltip) { + dateField.acroField.getWidgets().forEach((widget) => { + widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)); + }); + } + } else if (field.type === 'image') { + const imageBtn = form.createButton(field.name); + imageBtn.addToPage(field.label || 'Click to Upload Image', pdfPage, { + x: x, + y: y, + width: width, + height: height, + borderWidth: 1, + borderColor: rgb(0, 0, 0), + backgroundColor: rgb(0.9, 0.9, 0.9), + }); + + // Add Import Icon Action + const widgets = imageBtn.acroField.getWidgets(); + widgets.forEach((widget) => { + const actionDict = pdfDoc.context.obj({ + Type: 'Action', + S: 'JavaScript', + JS: 'event.target.buttonImportIcon();', + }); + widget.dict.set(PDFName.of('A'), actionDict); + + // Set Appearance Characteristics (MK) -> Text Position (TP) = 1 (Icon Only) + // This ensures the image replaces the text when uploaded + // IF (Icon Fit) -> SW: A (Always Scale), S: A (Anamorphic/Fill) + const mkDict = pdfDoc.context.obj({ + TP: 1, + BG: [0.9, 0.9, 0.9], // Background color (Light Gray) + BC: [0, 0, 0], // Border color (Black) + IF: { + SW: PDFName.of('A'), + S: PDFName.of('A'), + FB: true, + }, + }); + widget.dict.set(PDFName.of('MK'), mkDict); + }); + + if (field.tooltip) { + imageBtn.acroField.getWidgets().forEach((widget) => { + widget.dict.set(PDFName.of('TU'), PDFString.of(field.tooltip)); + }); + } + } else if (field.type === 'signature') { + const context = pdfDoc.context; + + // Create the signature field dictionary with FT = Sig + const sigDict = context.obj({ + FT: PDFName.of('Sig'), + T: PDFString.of(field.name), + Kids: [], + }) as PDFDict; + const sigRef = context.register(sigDict); + + // Create the widget annotation for the signature field + const widgetDict = context.obj({ + Type: PDFName.of('Annot'), + Subtype: PDFName.of('Widget'), + Rect: [x, y, x + width, y + height], + F: 4, // Print flag + P: pdfPage.ref, + Parent: sigRef, + }) as PDFDict; + + // Add border and background appearance + const borderStyle = context.obj({ + W: 1, // Border width + S: PDFName.of('S'), // Solid border + }) as PDFDict; + widgetDict.set(PDFName.of('BS'), borderStyle); + widgetDict.set(PDFName.of('BC'), context.obj([0, 0, 0])); // Border color (black) + widgetDict.set(PDFName.of('BG'), context.obj([0.95, 0.95, 0.95])); // Background color + + const widgetRef = context.register(widgetDict); + + const kidsArray = sigDict.get(PDFName.of('Kids')) as PDFArray; + kidsArray.push(widgetRef); + + pdfPage.node.addAnnot(widgetRef); + + const acroForm = form.acroForm; + acroForm.addField(sigRef); + + // Add tooltip if specified + if (field.tooltip) { + widgetDict.set(PDFName.of('TU'), PDFString.of(field.tooltip)); + } + } else if (field.type === 'barcode') { + if (field.barcodeValue) { + try { + const offscreen = document.createElement('canvas'); + bwipjs.toCanvas(offscreen, { + bcid: field.barcodeFormat || 'qrcode', + text: field.barcodeValue, + scale: 3, + includetext: + field.barcodeFormat !== 'qrcode' && + field.barcodeFormat !== 'datamatrix', + }); + const dataUrl = offscreen.toDataURL('image/png'); + const base64 = dataUrl.split(',')[1]; + const pngBytes = Uint8Array.from(atob(base64), (c) => + c.charCodeAt(0) + ); + const pngImage = await pdfDoc.embedPng(pngBytes); + pdfPage.drawImage(pngImage, { x, y, width, height }); + } catch (e) { + console.warn( + `Failed to generate barcode for field "${field.name}":`, + e + ); + } + } + } } -}) + + form.updateFieldAppearances(helveticaFont); + + const pdfBytes = await pdfDoc.save(); + const blob = new Blob([new Uint8Array(pdfBytes)], { + type: 'application/pdf', + }); + downloadFile(blob, 'fillable-form.pdf'); + showModal( + 'Success', + 'Your PDF has been downloaded successfully.', + 'info', + () => { + resetToInitial(); + }, + 'Okay' + ); + } catch (error) { + console.error('Error generating PDF:', error); + const errorMessage = (error as Error).message; + + // Check if it's a duplicate field name error + if ( + errorMessage.includes('A field already exists with the specified name') + ) { + // Extract the field name from the error message + const match = errorMessage.match( + /A field already exists with the specified name: "(.+?)"/ + ); + const fieldName = match ? match[1] : 'unknown'; + + if (existingRadioGroups.has(fieldName)) { + console.log(`Adding to existing radio group: ${fieldName}`); + } else { + showModal( + 'Duplicate Field Name', + `A field named "${fieldName}" already exists. Please rename this field to use a unique name before downloading.`, + 'error' + ); + } + } else { + showModal('Error', 'Error generating PDF: ' + errorMessage, 'error'); + } + } +}); // Back to tools button -const backToToolsBtns = document.querySelectorAll('[id^="back-to-tools"]') as NodeListOf -backToToolsBtns.forEach(btn => { - btn.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL - }) -}) +const backToToolsBtns = document.querySelectorAll( + '[id^="back-to-tools"]' +) as NodeListOf; +backToToolsBtns.forEach((btn) => { + btn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); +}); function getPageDimensions(size: string): { width: number; height: number } { - let dimensions: [number, number] - switch (size) { - case 'letter': - dimensions = PageSizes.Letter - break - case 'a4': - dimensions = PageSizes.A4 - break - case 'a5': - dimensions = PageSizes.A5 - break - case 'legal': - dimensions = PageSizes.Legal - break - case 'tabloid': - dimensions = PageSizes.Tabloid - break - case 'a3': - dimensions = PageSizes.A3 - break - case 'custom': - // Get custom dimensions from inputs - const width = parseInt(customWidth.value) || 612 - const height = parseInt(customHeight.value) || 792 - return { width, height } - default: - dimensions = PageSizes.Letter - } - return { width: dimensions[0], height: dimensions[1] } + let dimensions: [number, number]; + switch (size) { + case 'letter': + dimensions = PageSizes.Letter; + break; + case 'a4': + dimensions = PageSizes.A4; + break; + case 'a5': + dimensions = PageSizes.A5; + break; + case 'legal': + dimensions = PageSizes.Legal; + break; + case 'tabloid': + dimensions = PageSizes.Tabloid; + break; + case 'a3': + dimensions = PageSizes.A3; + break; + case 'custom': + // Get custom dimensions from inputs + const width = parseInt(customWidth.value) || 612; + const height = parseInt(customHeight.value) || 792; + return { width, height }; + default: + dimensions = PageSizes.Letter; + } + return { width: dimensions[0], height: dimensions[1] }; } // Reset to initial state function resetToInitial(): void { - fields = [] - pages = [] - currentPageIndex = 0 - uploadedPdfDoc = null - selectedField = null + fields = []; + pages = []; + currentPageIndex = 0; + uploadedPdfDoc = null; + selectedField = null; + extractedFieldNames.clear(); + pendingFieldExtraction = false; - canvas.innerHTML = '' + canvas.innerHTML = ''; - propertiesPanel.innerHTML = '

Select a field to edit properties

' + propertiesPanel.innerHTML = + '

Select a field to edit properties

'; - updateFieldCount() + updateFieldCount(); - // Show upload area and hide tool container - uploadArea.classList.remove('hidden') - toolContainer.classList.add('hidden') - pageSizeSelector.classList.add('hidden') - setTimeout(() => createIcons({ icons }), 100) + // Show upload area and hide tool container + uploadArea.classList.remove('hidden'); + toolContainer.classList.add('hidden'); + pageSizeSelector.classList.add('hidden'); + setTimeout(() => createIcons({ icons }), 100); } function createBlankPage(): void { - pages.push({ - index: pages.length, - width: pageSize.width, - height: pageSize.height - }) - updatePageNavigation() + pages.push({ + index: pages.length, + width: pageSize.width, + height: pageSize.height, + }); + updatePageNavigation(); } function switchToPage(pageIndex: number): void { - if (pageIndex < 0 || pageIndex >= pages.length) return + if (pageIndex < 0 || pageIndex >= pages.length) return; - currentPageIndex = pageIndex - renderCanvas() - updatePageNavigation() + currentPageIndex = pageIndex; + renderCanvas(); + updatePageNavigation(); - // Deselect any selected field when switching pages - deselectAll() + // Deselect any selected field when switching pages + deselectAll(); } // Render the canvas for the current page async function renderCanvas(): Promise { - const currentPage = pages[currentPageIndex] - if (!currentPage) return + const currentPage = pages[currentPageIndex]; + if (!currentPage) return; - // Fixed scale for better visibility - let scale = 1.333 + // Fixed scale for better visibility + const scale = 1.333; - currentScale = scale + currentScale = scale; - // Use actual PDF page dimensions (not scaled) - const canvasWidth = currentPage.width * scale - const canvasHeight = currentPage.height * scale + // Use actual PDF page dimensions (not scaled) + const canvasWidth = currentPage.width * scale; + const canvasHeight = currentPage.height * scale; - canvas.style.width = `${canvasWidth}px` - canvas.style.height = `${canvasHeight}px` + canvas.style.width = `${canvasWidth}px`; + canvas.style.height = `${canvasHeight}px`; - canvas.innerHTML = '' + canvas.innerHTML = ''; - if (uploadedPdfDoc) { + if (uploadedPdfDoc) { + try { + const arrayBuffer = await uploadedPdfDoc.save(); + const blob = new Blob([arrayBuffer.buffer as ArrayBuffer], { + type: 'application/pdf', + }); + const blobUrl = URL.createObjectURL(blob); + + const iframe = document.createElement('iframe'); + iframe.src = `${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html?file=${encodeURIComponent(blobUrl)}#page=${currentPageIndex + 1}&toolbar=0`; + iframe.style.width = '100%'; + iframe.style.height = `${canvasHeight}px`; + iframe.style.border = 'none'; + iframe.style.position = 'absolute'; + iframe.style.top = '0'; + iframe.style.left = '0'; + iframe.style.pointerEvents = 'none'; + iframe.style.opacity = '0.8'; + + iframe.onload = () => { try { - const arrayBuffer = await uploadedPdfDoc.save() - const blob = new Blob([arrayBuffer.buffer as ArrayBuffer], { type: 'application/pdf' }) - const blobUrl = URL.createObjectURL(blob) + const viewerWindow = iframe.contentWindow as any; + if (viewerWindow && viewerWindow.PDFViewerApplication) { + const app = viewerWindow.PDFViewerApplication; - const iframe = document.createElement('iframe') - iframe.src = `${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html?file=${encodeURIComponent(blobUrl)}#page=${currentPageIndex + 1}&toolbar=0` - iframe.style.width = '100%' - iframe.style.height = `${canvasHeight}px` - iframe.style.border = 'none' - iframe.style.position = 'absolute' - iframe.style.top = '0' - iframe.style.left = '0' - iframe.style.pointerEvents = 'none' - iframe.style.opacity = '0.8' - - iframe.onload = () => { - try { - const viewerWindow = iframe.contentWindow as any - if (viewerWindow && viewerWindow.PDFViewerApplication) { - const app = viewerWindow.PDFViewerApplication - - const style = viewerWindow.document.createElement('style') - style.textContent = ` + const style = viewerWindow.document.createElement('style'); + style.textContent = ` * { margin: 0 !important; padding: 0 !important; @@ -2128,268 +2712,559 @@ async function renderCanvas(): Promise { border: none !important; box-shadow: none !important; } - ` - viewerWindow.document.head.appendChild(style) + `; + viewerWindow.document.head.appendChild(style); - const checkRender = setInterval(() => { - if (app.pdfViewer && app.pdfViewer.pagesCount > 0) { - clearInterval(checkRender) + const checkRender = setInterval(() => { + if (app.pdfViewer && app.pdfViewer.pagesCount > 0) { + clearInterval(checkRender); - const pageContainer = viewerWindow.document.querySelector('.page') - if (pageContainer) { - const initialRect = pageContainer.getBoundingClientRect() + const pageContainer = + viewerWindow.document.querySelector('.page'); + if (pageContainer) { + const initialRect = pageContainer.getBoundingClientRect(); - const offsetX = -initialRect.left - const offsetY = -initialRect.top - pageContainer.style.transform = `translate(${offsetX}px, ${offsetY}px)` + const offsetX = -initialRect.left; + const offsetY = -initialRect.top; + pageContainer.style.transform = `translate(${offsetX}px, ${offsetY}px)`; - setTimeout(() => { - const rect = pageContainer.getBoundingClientRect() - const style = viewerWindow.getComputedStyle(pageContainer) + setTimeout(() => { + const rect = pageContainer.getBoundingClientRect(); + const style = viewerWindow.getComputedStyle(pageContainer); - const borderLeft = parseFloat(style.borderLeftWidth) || 0 - const borderTop = parseFloat(style.borderTopWidth) || 0 - const borderRight = parseFloat(style.borderRightWidth) || 0 + const borderLeft = parseFloat(style.borderLeftWidth) || 0; + const borderTop = parseFloat(style.borderTopWidth) || 0; + const borderRight = parseFloat(style.borderRightWidth) || 0; - pdfViewerOffset = { - x: rect.left + borderLeft, - y: rect.top + borderTop - } + pdfViewerOffset = { + x: rect.left + borderLeft, + y: rect.top + borderTop, + }; - const contentWidth = rect.width - borderLeft - borderRight - pdfViewerScale = contentWidth / currentPage.width + const contentWidth = rect.width - borderLeft - borderRight; + pdfViewerScale = contentWidth / currentPage.width; - console.log('📏 Calibrated Metrics (force positioned):', { - initialPosition: { left: initialRect.left, top: initialRect.top }, - appliedTransform: { x: offsetX, y: offsetY }, - finalRect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height }, - computedBorders: { left: borderLeft, top: borderTop, right: borderRight }, - finalOffset: pdfViewerOffset, - finalScale: pdfViewerScale, - pdfDimensions: { width: currentPage.width, height: currentPage.height } - }) - }, 50) - } - } - }, 100) + console.log('📏 Calibrated Metrics (force positioned):', { + initialPosition: { + left: initialRect.left, + top: initialRect.top, + }, + appliedTransform: { x: offsetX, y: offsetY }, + finalRect: { + left: rect.left, + top: rect.top, + width: rect.width, + height: rect.height, + }, + computedBorders: { + left: borderLeft, + top: borderTop, + right: borderRight, + }, + finalOffset: pdfViewerOffset, + finalScale: pdfViewerScale, + pdfDimensions: { + width: currentPage.width, + height: currentPage.height, + }, + }); + + if (pendingFieldExtraction && uploadedPdfDoc) { + pendingFieldExtraction = false; + extractExistingFields(uploadedPdfDoc); + extractedFieldNames.forEach((name) => + existingFieldNames.delete(name) + ); + + try { + const form = uploadedPdfDoc.getForm(); + for (const name of extractedFieldNames) { + try { + const f = form.getFieldMaybe(name); + if (f) form.removeField(f); + } catch {} + } + } catch {} + + renderCanvas(); + updateFieldCount(); } - } catch (e) { - console.error('Error accessing iframe content:', e) + }, 50); } - } - - canvas.appendChild(iframe) - - console.log('Canvas dimensions:', { width: canvasWidth, height: canvasHeight, scale: currentScale }) - console.log('PDF page dimensions:', { width: currentPage.width, height: currentPage.height }) - } catch (error) { - console.error('Error rendering PDF:', error) + } + }, 100); + } + } catch (e) { + console.error('Error accessing iframe content:', e); } - } + }; - fields.filter(f => f.pageIndex === currentPageIndex).forEach(field => { - renderField(field) - }) + canvas.appendChild(iframe); + + console.log('Canvas dimensions:', { + width: canvasWidth, + height: canvasHeight, + scale: currentScale, + }); + console.log('PDF page dimensions:', { + width: currentPage.width, + height: currentPage.height, + }); + } catch (error) { + console.error('Error rendering PDF:', error); + } + } + + fields + .filter((f) => f.pageIndex === currentPageIndex) + .forEach((field) => { + renderField(field); + }); } function updatePageNavigation(): void { - pageIndicator.textContent = `Page ${currentPageIndex + 1} of ${pages.length}` - prevPageBtn.disabled = currentPageIndex === 0 - nextPageBtn.disabled = currentPageIndex === pages.length - 1 + pageIndicator.textContent = `Page ${currentPageIndex + 1} of ${pages.length}`; + prevPageBtn.disabled = currentPageIndex === 0; + nextPageBtn.disabled = currentPageIndex === pages.length - 1; } // Drag and drop handlers for upload area dropZone.addEventListener('dragover', (e) => { - e.preventDefault() - dropZone.classList.add('border-indigo-500', 'bg-gray-600') -}) + e.preventDefault(); + dropZone.classList.add('border-indigo-500', 'bg-gray-600'); +}); dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('border-indigo-500', 'bg-gray-600') -}) + dropZone.classList.remove('border-indigo-500', 'bg-gray-600'); +}); dropZone.addEventListener('drop', (e) => { - e.preventDefault() - dropZone.classList.remove('border-indigo-500', 'bg-gray-600') - const files = e.dataTransfer?.files - if (files && files.length > 0 && files[0].type === 'application/pdf') { - handlePdfUpload(files[0]) - } -}) + e.preventDefault(); + dropZone.classList.remove('border-indigo-500', 'bg-gray-600'); + const files = e.dataTransfer?.files; + if (files && files.length > 0 && files[0].type === 'application/pdf') { + handlePdfUpload(files[0]); + } +}); pdfFileInput.addEventListener('change', async (e) => { - const file = (e.target as HTMLInputElement).files?.[0] - if (file) { - handlePdfUpload(file) - } -}) + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + handlePdfUpload(file); + } +}); blankPdfBtn.addEventListener('click', () => { - pageSizeSelector.classList.remove('hidden') -}) + pageSizeSelector.classList.remove('hidden'); +}); pageSizeSelect.addEventListener('change', () => { - if (pageSizeSelect.value === 'custom') { - customDimensionsInput.classList.remove('hidden') - } else { - customDimensionsInput.classList.add('hidden') - } -}) + if (pageSizeSelect.value === 'custom') { + customDimensionsInput.classList.remove('hidden'); + } else { + customDimensionsInput.classList.add('hidden'); + } +}); confirmBlankBtn.addEventListener('click', () => { - const selectedSize = pageSizeSelect.value - pageSize = getPageDimensions(selectedSize) + const selectedSize = pageSizeSelect.value; + pageSize = getPageDimensions(selectedSize); - createBlankPage() - switchToPage(0) + createBlankPage(); + switchToPage(0); - // Hide upload area and show tool container - uploadArea.classList.add('hidden') - toolContainer.classList.remove('hidden') - setTimeout(() => createIcons({ icons }), 100) -}) + // Hide upload area and show tool container + uploadArea.classList.add('hidden'); + toolContainer.classList.remove('hidden'); + setTimeout(() => createIcons({ icons }), 100); +}); + +const extractedFieldNames: Set = new Set(); + +function extractExistingFields(pdfDoc: PDFDocument): void { + try { + const form = pdfDoc.getForm(); + const pdfFields = form.getFields(); + const pdfPages = pdfDoc.getPages(); + + const pageRefToIndex = new Map(); + pdfPages.forEach((page, index) => { + pageRefToIndex.set(page.ref, index); + }); + + for (const pdfField of pdfFields) { + const name = pdfField.getName(); + const widgets = pdfField.acroField.getWidgets(); + + if (widgets.length === 0) continue; + + let fieldType: FormField['type']; + if (pdfField instanceof PDFTextField) { + fieldType = 'text'; + } else if (pdfField instanceof PDFCheckBox) { + fieldType = 'checkbox'; + } else if (pdfField instanceof PDFRadioGroup) { + fieldType = 'radio'; + } else if (pdfField instanceof PDFDropdown) { + fieldType = 'dropdown'; + } else if (pdfField instanceof PDFOptionList) { + fieldType = 'optionlist'; + } else if (pdfField instanceof PDFButton) { + fieldType = 'button'; + } else if (pdfField instanceof PDFSignature) { + fieldType = 'signature'; + } else { + continue; + } + + if (fieldType === 'radio') { + const radioField = pdfField as PDFRadioGroup; + const options = radioField.getOptions(); + + for (let wi = 0; wi < widgets.length; wi++) { + const widget = widgets[wi]; + const rect = widget.getRectangle(); + + const pageRef = widget.dict.get(PDFName.of('P')); + let pageIndex = 0; + if (pageRef) { + for (let pi = 0; pi < pdfPages.length; pi++) { + if (pdfPages[pi].ref === pageRef) { + pageIndex = pi; + break; + } + } + } + + const page = pdfPages[pageIndex]; + const { height: pageHeight } = page.getSize(); + + const canvasX = rect.x * pdfViewerScale + pdfViewerOffset.x; + const canvasY = + (pageHeight - rect.y - rect.height) * pdfViewerScale + + pdfViewerOffset.y; + const canvasWidth = rect.width * pdfViewerScale; + const canvasHeight = rect.height * pdfViewerScale; + + fieldCounter++; + const exportValue = options[wi] || 'Yes'; + + let tooltip = ''; + try { + const tu = widget.dict.get(PDFName.of('TU')); + if (tu instanceof PDFString) { + tooltip = tu.decodeText(); + } + } catch {} + + const formField: FormField = { + id: `field_${fieldCounter}`, + type: 'radio', + x: canvasX, + y: canvasY, + width: canvasWidth, + height: canvasHeight, + name: name, + defaultValue: '', + fontSize: 12, + alignment: 'left', + textColor: '#000000', + required: radioField.isRequired(), + readOnly: radioField.isReadOnly(), + tooltip: tooltip, + combCells: 0, + maxLength: 0, + checked: false, + exportValue: exportValue, + groupName: name, + pageIndex: pageIndex, + borderColor: '#000000', + hideBorder: false, + }; + + fields.push(formField); + } + + extractedFieldNames.add(name); + continue; + } + + const widget = widgets[0]; + const rect = widget.getRectangle(); + + const pageRef = widget.dict.get(PDFName.of('P')); + let pageIndex = 0; + if (pageRef) { + for (let pi = 0; pi < pdfPages.length; pi++) { + if (pdfPages[pi].ref === pageRef) { + pageIndex = pi; + break; + } + } + } + + const page = pdfPages[pageIndex]; + const { height: pageHeight } = page.getSize(); + + const canvasX = rect.x * pdfViewerScale + pdfViewerOffset.x; + const canvasY = + (pageHeight - rect.y - rect.height) * pdfViewerScale + + pdfViewerOffset.y; + const canvasWidth = rect.width * pdfViewerScale; + const canvasHeight = rect.height * pdfViewerScale; + + let tooltip = ''; + try { + const tu = widget.dict.get(PDFName.of('TU')); + if (tu instanceof PDFString) { + tooltip = tu.decodeText(); + } + } catch {} + + fieldCounter++; + + const formField: FormField = { + id: `field_${fieldCounter}`, + type: fieldType, + x: canvasX, + y: canvasY, + width: canvasWidth, + height: canvasHeight, + name: name, + defaultValue: '', + fontSize: 12, + alignment: 'left', + textColor: '#000000', + required: pdfField.isRequired(), + readOnly: pdfField.isReadOnly(), + tooltip: tooltip, + combCells: 0, + maxLength: 0, + pageIndex: pageIndex, + borderColor: '#000000', + hideBorder: false, + }; + + if (pdfField instanceof PDFTextField) { + try { + formField.defaultValue = pdfField.getText() || ''; + } catch {} + try { + formField.multiline = pdfField.isMultiline(); + } catch {} + try { + const maxLen = pdfField.getMaxLength(); + if (maxLen !== undefined) { + if (pdfField.isCombed()) { + formField.combCells = maxLen; + } else { + formField.maxLength = maxLen; + } + } + } catch {} + try { + const alignment = pdfField.getAlignment(); + if (alignment === TextAlignment.Center) + formField.alignment = 'center'; + else if (alignment === TextAlignment.Right) + formField.alignment = 'right'; + else formField.alignment = 'left'; + } catch {} + } else if (pdfField instanceof PDFCheckBox) { + try { + formField.checked = pdfField.isChecked(); + } catch {} + formField.exportValue = 'Yes'; + } else if (pdfField instanceof PDFDropdown) { + try { + formField.options = pdfField.getOptions(); + } catch {} + try { + const selected = pdfField.getSelected(); + if (selected.length > 0) formField.defaultValue = selected[0]; + } catch {} + } else if (pdfField instanceof PDFOptionList) { + try { + formField.options = pdfField.getOptions(); + } catch {} + try { + const selected = pdfField.getSelected(); + if (selected.length > 0) formField.defaultValue = selected[0]; + } catch {} + } else if (pdfField instanceof PDFButton) { + formField.label = 'Button'; + formField.action = 'none'; + } + + fields.push(formField); + extractedFieldNames.add(name); + } + + console.log( + `Extracted ${extractedFieldNames.size} existing fields for editing` + ); + } catch (e) { + console.warn('Error extracting existing fields:', e); + } +} async function handlePdfUpload(file: File) { + try { + const arrayBuffer = await file.arrayBuffer(); + uploadedPdfDoc = await PDFDocument.load(arrayBuffer); + + // Check for existing fields and update counter + existingFieldNames.clear(); try { - const arrayBuffer = await file.arrayBuffer() - uploadedPdfDoc = await PDFDocument.load(arrayBuffer) + const form = uploadedPdfDoc.getForm(); + const pdfFields = form.getFields(); - // Check for existing fields and update counter - existingFieldNames.clear() - try { - const form = uploadedPdfDoc.getForm() - const pdfFields = form.getFields() + // console.log('📋 Found', pdfFields.length, 'existing fields in uploaded PDF') - // console.log('📋 Found', pdfFields.length, 'existing fields in uploaded PDF') + pdfFields.forEach((field) => { + const name = field.getName(); + existingFieldNames.add(name); // Track all existing field names - pdfFields.forEach(field => { - const name = field.getName() - existingFieldNames.add(name) // Track all existing field names - - if (field instanceof PDFRadioGroup) { - existingRadioGroups.add(name) - } - - // console.log(' Field:', name, '| Type:', field.constructor.name) - - const match = name.match(/([a-zA-Z]+)_(\d+)/) - if (match) { - const num = parseInt(match[2]) - if (!isNaN(num) && num > fieldCounter) { - fieldCounter = num - console.log(' → Updated field counter to:', fieldCounter) - } - } - }) - - // TODO@ALAM: DEBUGGER - // console.log('Field counter after upload:', fieldCounter) - // console.log('Existing field names:', Array.from(existingFieldNames)) - } catch (e) { - console.log('No form fields found or error reading fields:', e) + if (field instanceof PDFRadioGroup) { + existingRadioGroups.add(name); } - uploadedPdfjsDoc = await getPDFDocument({ data: arrayBuffer }).promise + // console.log(' Field:', name, '| Type:', field.constructor.name) - const pageCount = uploadedPdfDoc.getPageCount() - pages = [] - - for (let i = 0; i < pageCount; i++) { - const page = uploadedPdfDoc.getPage(i) - const { width, height } = page.getSize() - - pages.push({ - index: i, - width, - height, - pdfPageData: undefined - }) + const match = name.match(/([a-zA-Z]+)_(\d+)/); + if (match) { + const num = parseInt(match[2]); + if (!isNaN(num) && num > fieldCounter) { + fieldCounter = num; + console.log(' → Updated field counter to:', fieldCounter); + } } + }); - currentPageIndex = 0 - renderCanvas() - updatePageNavigation() - - // Hide upload area and show tool container - uploadArea.classList.add('hidden') - toolContainer.classList.remove('hidden') - - // Init icons - setTimeout(() => createIcons({ icons }), 100) - } catch (error) { - console.error('Error loading PDF:', error) - showModal('Error', 'Error loading PDF file. Please try again with a valid PDF.', 'error') + // TODO@ALAM: DEBUGGER + // console.log('Field counter after upload:', fieldCounter) + // console.log('Existing field names:', Array.from(existingFieldNames)) + } catch (e) { + console.log('No form fields found or error reading fields:', e); } + + uploadedPdfjsDoc = await getPDFDocument({ data: arrayBuffer }).promise; + + const pageCount = uploadedPdfDoc.getPageCount(); + pages = []; + + for (let i = 0; i < pageCount; i++) { + const page = uploadedPdfDoc.getPage(i); + const { width, height } = page.getSize(); + + pages.push({ + index: i, + width, + height, + pdfPageData: undefined, + }); + } + + currentPageIndex = 0; + + pendingFieldExtraction = true; + + renderCanvas(); + updatePageNavigation(); + + // Hide upload area and show tool container + uploadArea.classList.add('hidden'); + toolContainer.classList.remove('hidden'); + + // Init icons + setTimeout(() => createIcons({ icons }), 100); + } catch (error) { + console.error('Error loading PDF:', error); + showModal( + 'Error', + 'Error loading PDF file. Please try again with a valid PDF.', + 'error' + ); + } } // Page navigation prevPageBtn.addEventListener('click', () => { - if (currentPageIndex > 0) { - switchToPage(currentPageIndex - 1) - } -}) + if (currentPageIndex > 0) { + switchToPage(currentPageIndex - 1); + } +}); nextPageBtn.addEventListener('click', () => { - if (currentPageIndex < pages.length - 1) { - switchToPage(currentPageIndex + 1) - } -}) + if (currentPageIndex < pages.length - 1) { + switchToPage(currentPageIndex + 1); + } +}); addPageBtn.addEventListener('click', () => { - createBlankPage() - switchToPage(pages.length - 1) -}) + createBlankPage(); + switchToPage(pages.length - 1); +}); resetBtn.addEventListener('click', () => { - if (fields.length > 0 || pages.length > 0) { - if (confirm('Are you sure you want to reset? All your work will be lost.')) { - resetToInitial() - } - } else { - resetToInitial() + if (fields.length > 0 || pages.length > 0) { + if ( + confirm('Are you sure you want to reset? All your work will be lost.') + ) { + resetToInitial(); } -}) + } else { + resetToInitial(); + } +}); // Custom Modal Logic -const errorModal = document.getElementById('errorModal') -const errorModalTitle = document.getElementById('errorModalTitle') -const errorModalMessage = document.getElementById('errorModalMessage') -const errorModalClose = document.getElementById('errorModalClose') +const errorModal = document.getElementById('errorModal'); +const errorModalTitle = document.getElementById('errorModalTitle'); +const errorModalMessage = document.getElementById('errorModalMessage'); +const errorModalClose = document.getElementById('errorModalClose'); -let modalCloseCallback: (() => void) | null = null +let modalCloseCallback: (() => void) | null = null; -function showModal(title: string, message: string, type: 'error' | 'warning' | 'info' = 'error', onClose?: () => void, buttonText: string = 'Close') { - if (!errorModal || !errorModalTitle || !errorModalMessage || !errorModalClose) return +function showModal( + title: string, + message: string, + type: 'error' | 'warning' | 'info' = 'error', + onClose?: () => void, + buttonText: string = 'Close' +) { + if (!errorModal || !errorModalTitle || !errorModalMessage || !errorModalClose) + return; - errorModalTitle.textContent = title - errorModalMessage.textContent = message - errorModalClose.textContent = buttonText + errorModalTitle.textContent = title; + errorModalMessage.textContent = message; + errorModalClose.textContent = buttonText; - modalCloseCallback = onClose || null - errorModal.classList.remove('hidden') + modalCloseCallback = onClose || null; + errorModal.classList.remove('hidden'); } if (errorModalClose) { - errorModalClose.addEventListener('click', () => { - errorModal?.classList.add('hidden') - if (modalCloseCallback) { - modalCloseCallback() - modalCloseCallback = null - } - }) + errorModalClose.addEventListener('click', () => { + errorModal?.classList.add('hidden'); + if (modalCloseCallback) { + modalCloseCallback(); + modalCloseCallback = null; + } + }); } // Close modal on backdrop click if (errorModal) { - errorModal.addEventListener('click', (e) => { - if (e.target === errorModal) { - errorModal.classList.add('hidden') - if (modalCloseCallback) { - modalCloseCallback() - modalCloseCallback = null - } - } - }) + errorModal.addEventListener('click', (e) => { + if (e.target === errorModal) { + errorModal.classList.add('hidden'); + if (modalCloseCallback) { + modalCloseCallback(); + modalCloseCallback = null; + } + } + }); } -initializeGlobalShortcuts() +initializeGlobalShortcuts(); diff --git a/src/js/logic/form-filler-page.ts b/src/js/logic/form-filler-page.ts index 1deb9d1..e7c58af 100644 --- a/src/js/logic/form-filler-page.ts +++ b/src/js/logic/form-filler-page.ts @@ -85,7 +85,7 @@ function resetState() { } const toolUploader = document.getElementById('tool-uploader'); - const isFullWidth = localStorage.getItem('fullWidthMode') === 'true'; + const isFullWidth = localStorage.getItem('fullWidthMode') !== 'false'; if (toolUploader && !isFullWidth) { toolUploader.classList.remove('max-w-6xl'); toolUploader.classList.add('max-w-2xl'); @@ -139,7 +139,8 @@ async function setupFormViewer() { } const toolUploader = document.getElementById('tool-uploader'); - const isFullWidth = localStorage.getItem('fullWidthMode') === 'true'; + // Default to true if not set + const isFullWidth = localStorage.getItem('fullWidthMode') !== 'false'; if (toolUploader && !isFullWidth) { toolUploader.classList.remove('max-w-2xl'); toolUploader.classList.add('max-w-6xl'); diff --git a/src/js/logic/header-footer-page.ts b/src/js/logic/header-footer-page.ts index 5113af9..c2d0dc3 100644 --- a/src/js/logic/header-footer-page.ts +++ b/src/js/logic/header-footer-page.ts @@ -2,9 +2,9 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; import { downloadFile, hexToRgb, formatBytes, parsePageRanges } from '../utils/helpers.js'; import { PDFDocument as PDFLibDocument, rgb, StandardFonts } from 'pdf-lib'; +import { HeaderFooterState } from '@/types'; -interface PageState { file: File | null; pdfDoc: PDFLibDocument | null; } -const pageState: PageState = { file: null, pdfDoc: null }; +const pageState: HeaderFooterState = { file: null, pdfDoc: null }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializePage); diff --git a/src/js/logic/image-to-pdf-page.ts b/src/js/logic/image-to-pdf-page.ts index ba20e44..3cef7e4 100644 --- a/src/js/logic/image-to-pdf-page.ts +++ b/src/js/logic/image-to-pdf-page.ts @@ -1,321 +1,299 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; -import { downloadFile, readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js'; -import { PDFDocument as PDFLibDocument } from 'pdf-lib'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; +import heic2any from 'heic2any'; +import { + getSelectedQuality, + compressImageFile, +} from '../utils/image-compress.js'; + +const SUPPORTED_FORMATS = + '.jpg,.jpeg,.png,.bmp,.gif,.tiff,.tif,.pnm,.pgm,.pbm,.ppm,.pam,.jxr,.jpx,.jp2,.psd,.svg,.heic,.heif,.webp'; +const SUPPORTED_FORMATS_DISPLAY = + 'JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP'; let files: File[] = []; +let pymupdf: any = null; if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } function initializePage() { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const addMoreBtn = document.getElementById('add-more-btn'); - const clearFilesBtn = document.getElementById('clear-files-btn'); - const processBtn = document.getElementById('process-btn'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const processBtn = document.getElementById('process-btn'); + const formatDisplay = document.getElementById('supported-formats'); - if (fileInput) { - fileInput.addEventListener('change', handleFileUpload); - } + if (formatDisplay) { + formatDisplay.textContent = SUPPORTED_FORMATS_DISPLAY; + } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); + if (fileInput) { + fileInput.accept = SUPPORTED_FORMATS; + fileInput.addEventListener('change', handleFileUpload); + } - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handleFiles(droppedFiles); - } - }); - - // Clear value on click to allow re-selecting the same file - fileInput?.addEventListener('click', () => { - if (fileInput) fileInput.value = ''; - }); - } - - if (addMoreBtn) { - addMoreBtn.addEventListener('click', () => { - fileInput?.click(); - }); - } - - if (clearFilesBtn) { - clearFilesBtn.addEventListener('click', () => { - files = []; - updateUI(); - }); - } - - if (processBtn) { - processBtn.addEventListener('click', convertToPdf); - } - - document.getElementById('back-to-tools')?.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleFiles(droppedFiles); + } + }); + + fileInput?.addEventListener('click', () => { + if (fileInput) fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput?.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + files = []; + updateUI(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } + + document.getElementById('back-to-tools')?.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); } function handleFileUpload(e: Event) { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handleFiles(input.files); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleFiles(input.files); + } +} + +function getFileExtension(filename: string): string { + return '.' + filename.split('.').pop()?.toLowerCase() || ''; +} + +function isValidImageFile(file: File): boolean { + const ext = getFileExtension(file.name); + const validExtensions = SUPPORTED_FORMATS.split(','); + return validExtensions.includes(ext) || file.type.startsWith('image/'); } function handleFiles(newFiles: FileList) { - const validFiles = Array.from(newFiles).filter(file => - file.type.startsWith('image/') + const validFiles = Array.from(newFiles).filter(isValidImageFile); + + if (validFiles.length < newFiles.length) { + showAlert( + 'Invalid Files', + 'Some files were skipped. Only supported image formats are allowed.' ); + } - if (validFiles.length < newFiles.length) { - showAlert('Invalid Files', 'Some files were skipped. Only image files are allowed.'); - } - - if (validFiles.length > 0) { - files = [...files, ...validFiles]; - updateUI(); - } + if (validFiles.length > 0) { + files = [...files, ...validFiles]; + updateUI(); + } } const resetState = () => { - files = []; - updateUI(); + files = []; + updateUI(); }; function updateUI() { - const fileDisplayArea = document.getElementById('file-display-area'); - const fileControls = document.getElementById('file-controls'); - const optionsDiv = document.getElementById('jpg-to-pdf-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const optionsDiv = document.getElementById('jpg-to-pdf-options'); - if (!fileDisplayArea || !fileControls || !optionsDiv) return; + if (!fileDisplayArea || !fileControls || !optionsDiv) return; - fileDisplayArea.innerHTML = ''; + fileDisplayArea.innerHTML = ''; - if (files.length > 0) { - fileControls.classList.remove('hidden'); - optionsDiv.classList.remove('hidden'); + if (files.length > 0) { + fileControls.classList.remove('hidden'); + optionsDiv.classList.remove('hidden'); - files.forEach((file, index) => { - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + files.forEach((file, index) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex items-center gap-2 overflow-hidden'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex items-center gap-2 overflow-hidden'; - const nameSpan = document.createElement('span'); - nameSpan.className = 'truncate font-medium text-gray-200'; - nameSpan.textContent = file.name; + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; - const sizeSpan = document.createElement('span'); - sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; - sizeSpan.textContent = `(${formatBytes(file.size)})`; + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; + sizeSpan.textContent = `(${formatBytes(file.size)})`; - infoContainer.append(nameSpan, sizeSpan); + infoContainer.append(nameSpan, sizeSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - files = files.filter((_, i) => i !== index); - updateUI(); - }; + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + files = files.filter((_, i) => i !== index); + updateUI(); + }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - }); - createIcons({ icons }); - } else { - fileControls.classList.add('hidden'); - optionsDiv.classList.add('hidden'); - } -} - -function sanitizeImageAsJpeg(imageBytes: any) { - return new Promise((resolve, reject) => { - const blob = new Blob([imageBytes]); - const imageUrl = URL.createObjectURL(blob); - const img = new Image(); - - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.naturalWidth; - canvas.height = img.naturalHeight; - const ctx = canvas.getContext('2d'); - if (!ctx) { - URL.revokeObjectURL(imageUrl); - return reject(new Error('Could not get canvas context')); - } - ctx.drawImage(img, 0, 0); - - canvas.toBlob( - async (jpegBlob) => { - if (!jpegBlob) { - return reject(new Error('Canvas toBlob conversion failed.')); - } - const arrayBuffer = await jpegBlob.arrayBuffer(); - resolve(new Uint8Array(arrayBuffer)); - }, - 'image/jpeg', - 0.9 - ); - URL.revokeObjectURL(imageUrl); - }; - - img.onerror = () => { - URL.revokeObjectURL(imageUrl); - reject( - new Error( - 'The provided file could not be loaded as an image. It may be corrupted.' - ) - ); - }; - - img.src = imageUrl; + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); }); + createIcons({ icons }); + } else { + fileControls.classList.add('hidden'); + optionsDiv.classList.add('hidden'); + } } -// Special handler for SVG files - must read as text -function svgToPng(svgText: string): Promise { - return new Promise((resolve, reject) => { +async function ensurePyMuPDF(): Promise { + if (!pymupdf) { + pymupdf = await loadPyMuPDF(); + } + return pymupdf; +} + +async function preprocessFile(file: File): Promise { + const ext = getFileExtension(file.name); + + if (ext === '.heic' || ext === '.heif') { + try { + const conversionResult = await heic2any({ + blob: file, + toType: 'image/png', + quality: 0.9, + }); + + const blob = Array.isArray(conversionResult) + ? conversionResult[0] + : conversionResult; + return new File([blob], file.name.replace(/\.(heic|heif)$/i, '.png'), { + type: 'image/png', + }); + } catch (e) { + console.error(`Failed to convert HEIC: ${file.name}`, e); + throw new Error(`Failed to process HEIC file: ${file.name}`); + } + } + + if (ext === '.webp') { + try { + return await new Promise((resolve, reject) => { const img = new Image(); - const svgBlob = new Blob([svgText], { type: 'image/svg+xml;charset=utf-8' }); - const url = URL.createObjectURL(svgBlob); + const url = URL.createObjectURL(file); img.onload = () => { - const canvas = document.createElement('canvas'); - const width = img.naturalWidth || img.width || 800; - const height = img.naturalHeight || img.height || 600; - - canvas.width = width; - canvas.height = height; - - const ctx = canvas.getContext('2d'); - if (!ctx) { - URL.revokeObjectURL(url); - return reject(new Error('Could not get canvas context')); + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + URL.revokeObjectURL(url); + reject(new Error('Canvas context failed')); + return; + } + ctx.drawImage(img, 0, 0); + canvas.toBlob((blob) => { + URL.revokeObjectURL(url); + if (blob) { + resolve( + new File([blob], file.name.replace(/\.webp$/i, '.png'), { + type: 'image/png', + }) + ); + } else { + reject(new Error('Canvas toBlob failed')); } - - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, width, height); - ctx.drawImage(img, 0, 0, width, height); - - canvas.toBlob( - async (pngBlob) => { - URL.revokeObjectURL(url); - if (!pngBlob) { - return reject(new Error('Canvas toBlob conversion failed.')); - } - const arrayBuffer = await pngBlob.arrayBuffer(); - resolve(new Uint8Array(arrayBuffer)); - }, - 'image/png' - ); + }, 'image/png'); }; img.onerror = () => { - URL.revokeObjectURL(url); - reject(new Error('Failed to load SVG image')); + URL.revokeObjectURL(url); + reject(new Error('Failed to load WebP image')); }; img.src = url; - }); + }); + } catch (e) { + console.error(`Failed to convert WebP: ${file.name}`, e); + throw new Error(`Failed to process WebP file: ${file.name}`); + } + } + + return file; } async function convertToPdf() { - if (files.length === 0) { - showAlert('No Files', 'Please select at least one image file.'); - return; + if (files.length === 0) { + showAlert('No Files', 'Please select at least one image file.'); + return; + } + + showLoader('Processing images...'); + + try { + const quality = getSelectedQuality(); + const processedFiles: File[] = []; + for (const file of files) { + try { + const processed = await preprocessFile(file); + const compressed = await compressImageFile(processed, quality); + processedFiles.push(compressed); + } catch (error: any) { + console.warn(error); + throw error; + } } - showLoader('Creating PDF from images...'); + showLoader('Loading engine...'); + const mupdf = await ensurePyMuPDF(); - try { - const pdfDoc = await PDFLibDocument.create(); + showLoader('Converting images to PDF...'); + const pdfBlob = await mupdf.imagesToPdf(processedFiles); - for (const file of files) { - try { - const isSvg = file.type === 'image/svg+xml' || file.name.toLowerCase().endsWith('.svg'); + downloadFile(pdfBlob, 'images_to_pdf.pdf'); - if (isSvg) { - // Handle SVG files - read as text - const svgText = await file.text(); - const pngBytes = await svgToPng(svgText); - const pngImage = await pdfDoc.embedPng(pngBytes); - - const page = pdfDoc.addPage([pngImage.width, pngImage.height]); - page.drawImage(pngImage, { - x: 0, - y: 0, - width: pngImage.width, - height: pngImage.height, - }); - } else if (file.type === 'image/png') { - // Handle PNG files - const originalBytes = await readFileAsArrayBuffer(file); - const pngImage = await pdfDoc.embedPng(originalBytes as Uint8Array); - - const page = pdfDoc.addPage([pngImage.width, pngImage.height]); - page.drawImage(pngImage, { - x: 0, - y: 0, - width: pngImage.width, - height: pngImage.height, - }); - } else { - // Handle JPG/other raster images - const originalBytes = await readFileAsArrayBuffer(file); - let jpgImage; - - try { - jpgImage = await pdfDoc.embedJpg(originalBytes as Uint8Array); - } catch (e) { - // Fallback: convert to JPEG via canvas - const sanitizedBytes = await sanitizeImageAsJpeg(originalBytes); - jpgImage = await pdfDoc.embedJpg(sanitizedBytes as Uint8Array); - } - - const page = pdfDoc.addPage([jpgImage.width, jpgImage.height]); - page.drawImage(jpgImage, { - x: 0, - y: 0, - width: jpgImage.width, - height: jpgImage.height, - }); - } - } catch (error) { - console.error(`Failed to process ${file.name}:`, error); - throw new Error(`Could not process "${file.name}". The file may be corrupted.`); - } - } - - const pdfBytes = await pdfDoc.save(); - downloadFile( - new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), - 'from_images.pdf' - ); - showAlert('Success', 'PDF created successfully!', 'success', () => { - resetState(); - }); - } catch (e: any) { - console.error(e); - showAlert('Conversion Error', e.message); - } finally { - hideLoader(); - } + showAlert('Success', 'PDF created successfully!', 'success', () => { + resetState(); + }); + } catch (e: any) { + console.error('[ImageToPDF]', e); + showAlert( + 'Conversion Error', + e.message || 'Failed to convert images to PDF.' + ); + } finally { + hideLoader(); + } } diff --git a/src/js/logic/invert-colors-page.ts b/src/js/logic/invert-colors-page.ts index 1ae6971..cea6048 100644 --- a/src/js/logic/invert-colors-page.ts +++ b/src/js/logic/invert-colors-page.ts @@ -2,130 +2,173 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; import { downloadFile, formatBytes, getPDFDocument } from '../utils/helpers.js'; import { PDFDocument as PDFLibDocument } from 'pdf-lib'; +import { applyInvertColors } from '../utils/image-effects.js'; import * as pdfjsLib from 'pdfjs-dist'; +import { InvertColorsState } from '@/types'; -pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); -interface PageState { file: File | null; pdfDoc: PDFLibDocument | null; } -const pageState: PageState = { file: null, pdfDoc: null }; +const pageState: InvertColorsState = { file: null, pdfDoc: null }; -if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializePage); } -else { initializePage(); } - -function initializePage() { - createIcons({ icons }); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const backBtn = document.getElementById('back-to-tools'); - const processBtn = document.getElementById('process-btn'); - - if (fileInput) { - fileInput.addEventListener('change', handleFileUpload); - fileInput.addEventListener('click', () => { fileInput.value = ''; }); - } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('border-indigo-500'); }); - dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('border-indigo-500'); }); - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); dropZone.classList.remove('border-indigo-500'); - if (e.dataTransfer?.files.length) handleFiles(e.dataTransfer.files); - }); - } - if (backBtn) backBtn.addEventListener('click', () => { window.location.href = import.meta.env.BASE_URL; }); - if (processBtn) processBtn.addEventListener('click', invertColors); +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializePage); +} else { + initializePage(); } -function handleFileUpload(e: Event) { const input = e.target as HTMLInputElement; if (input.files?.length) handleFiles(input.files); } +function initializePage() { + createIcons({ icons }); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (fileInput) { + fileInput.addEventListener('change', handleFileUpload); + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('border-indigo-500'); + }); + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('border-indigo-500'); + }); + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('border-indigo-500'); + if (e.dataTransfer?.files.length) handleFiles(e.dataTransfer.files); + }); + } + if (backBtn) + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + if (processBtn) processBtn.addEventListener('click', invertColors); +} + +function handleFileUpload(e: Event) { + const input = e.target as HTMLInputElement; + if (input.files?.length) handleFiles(input.files); +} async function handleFiles(files: FileList) { - const file = files[0]; - if (!file || file.type !== 'application/pdf') { showAlert('Invalid File', 'Please upload a valid PDF file.'); return; } - showLoader('Loading PDF...'); - try { - const arrayBuffer = await file.arrayBuffer(); - pageState.pdfDoc = await PDFLibDocument.load(arrayBuffer); - pageState.file = file; - updateFileDisplay(); - document.getElementById('options-panel')?.classList.remove('hidden'); - } catch (error) { console.error(error); showAlert('Error', 'Failed to load PDF file.'); } - finally { hideLoader(); } + const file = files[0]; + if (!file || file.type !== 'application/pdf') { + showAlert('Invalid File', 'Please upload a valid PDF file.'); + return; + } + showLoader('Loading PDF...'); + try { + const arrayBuffer = await file.arrayBuffer(); + pageState.pdfDoc = await PDFLibDocument.load(arrayBuffer); + pageState.file = file; + updateFileDisplay(); + document.getElementById('options-panel')?.classList.remove('hidden'); + } catch (error) { + console.error(error); + showAlert('Error', 'Failed to load PDF file.'); + } finally { + hideLoader(); + } } function updateFileDisplay() { - const fileDisplayArea = document.getElementById('file-display-area'); - if (!fileDisplayArea || !pageState.file || !pageState.pdfDoc) return; - fileDisplayArea.innerHTML = ''; - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col flex-1 min-w-0'; - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = pageState.file.name; - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageState.pdfDoc.getPageCount()} pages`; - infoContainer.append(nameSpan, metaSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = resetState; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); + const fileDisplayArea = document.getElementById('file-display-area'); + if (!fileDisplayArea || !pageState.file || !pageState.pdfDoc) return; + fileDisplayArea.innerHTML = ''; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = pageState.file.name; + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageState.pdfDoc.getPageCount()} pages`; + infoContainer.append(nameSpan, metaSpan); + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = resetState; + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); } function resetState() { - pageState.file = null; pageState.pdfDoc = null; - const fileDisplayArea = document.getElementById('file-display-area'); - if (fileDisplayArea) fileDisplayArea.innerHTML = ''; - document.getElementById('options-panel')?.classList.add('hidden'); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - if (fileInput) fileInput.value = ''; + pageState.file = null; + pageState.pdfDoc = null; + const fileDisplayArea = document.getElementById('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + document.getElementById('options-panel')?.classList.add('hidden'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; } async function invertColors() { - if (!pageState.pdfDoc || !pageState.file) { showAlert('Error', 'Please upload a PDF file first.'); return; } - showLoader('Inverting PDF colors...'); - try { - const newPdfDoc = await PDFLibDocument.create(); - const pdfBytes = await pageState.pdfDoc.save(); - const pdfjsDoc = await getPDFDocument({ data: pdfBytes }).promise; + if (!pageState.pdfDoc || !pageState.file) { + showAlert('Error', 'Please upload a PDF file first.'); + return; + } + showLoader('Inverting PDF colors...'); + try { + const newPdfDoc = await PDFLibDocument.create(); + const pdfBytes = await pageState.pdfDoc.save(); + const pdfjsDoc = await getPDFDocument({ data: pdfBytes }).promise; - for (let i = 1; i <= pdfjsDoc.numPages; i++) { - showLoader(`Processing page ${i} of ${pdfjsDoc.numPages}...`); - const page = await pdfjsDoc.getPage(i); - const viewport = page.getViewport({ scale: 1.5 }); - const canvas = document.createElement('canvas'); - canvas.width = viewport.width; - canvas.height = viewport.height; - const ctx = canvas.getContext('2d')!; - await page.render({ canvasContext: ctx, viewport, canvas }).promise; + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + showLoader(`Processing page ${i} of ${pdfjsDoc.numPages}...`); + const page = await pdfjsDoc.getPage(i); + const viewport = page.getViewport({ scale: 1.5 }); + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + const ctx = canvas.getContext('2d')!; + await page.render({ canvasContext: ctx, viewport, canvas }).promise; - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const data = imageData.data; - for (let j = 0; j < data.length; j += 4) { - data[j] = 255 - data[j]; - data[j + 1] = 255 - data[j + 1]; - data[j + 2] = 255 - data[j + 2]; - } - ctx.putImageData(imageData, 0, 0); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + applyInvertColors(imageData); + ctx.putImageData(imageData, 0, 0); - const pngImageBytes = await new Promise((resolve) => - canvas.toBlob((blob) => { - const reader = new FileReader(); - reader.onload = () => resolve(new Uint8Array(reader.result as ArrayBuffer)); - reader.readAsArrayBuffer(blob!); - }, 'image/png') - ); + const pngImageBytes = await new Promise((resolve) => + canvas.toBlob((blob) => { + const reader = new FileReader(); + reader.onload = () => + resolve(new Uint8Array(reader.result as ArrayBuffer)); + reader.readAsArrayBuffer(blob!); + }, 'image/png') + ); - const image = await newPdfDoc.embedPng(pngImageBytes); - const newPage = newPdfDoc.addPage([image.width, image.height]); - newPage.drawImage(image, { x: 0, y: 0, width: image.width, height: image.height }); - } - const newPdfBytes = await newPdfDoc.save(); - downloadFile(new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }), 'inverted.pdf'); - showAlert('Success', 'Colors inverted successfully!', 'success', () => { resetState(); }); - } catch (e) { console.error(e); showAlert('Error', 'Could not invert PDF colors.'); } - finally { hideLoader(); } + const image = await newPdfDoc.embedPng(pngImageBytes); + const newPage = newPdfDoc.addPage([image.width, image.height]); + newPage.drawImage(image, { + x: 0, + y: 0, + width: image.width, + height: image.height, + }); + } + const newPdfBytes = await newPdfDoc.save(); + downloadFile( + new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }), + 'inverted.pdf' + ); + showAlert('Success', 'Colors inverted successfully!', 'success', () => { + resetState(); + }); + } catch (e) { + console.error(e); + showAlert('Error', 'Could not invert PDF colors.'); + } finally { + hideLoader(); + } } diff --git a/src/js/logic/jpg-to-pdf-page.ts b/src/js/logic/jpg-to-pdf-page.ts index f657c80..e4e37ba 100644 --- a/src/js/logic/jpg-to-pdf-page.ts +++ b/src/js/logic/jpg-to-pdf-page.ts @@ -1,248 +1,212 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; -import { downloadFile, readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js'; -import { PDFDocument as PDFLibDocument } from 'pdf-lib'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { loadPyMuPDF } from '../utils/pymupdf-loader.js'; +import { + getSelectedQuality, + compressImageFile, +} from '../utils/image-compress.js'; + +const SUPPORTED_FORMATS = '.jpg,.jpeg,.jp2,.jpx'; +const SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/jpg', 'image/jp2']; let files: File[] = []; +let pymupdf: any = null; if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } function initializePage() { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const addMoreBtn = document.getElementById('add-more-btn'); - const clearFilesBtn = document.getElementById('clear-files-btn'); - const processBtn = document.getElementById('process-btn'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const processBtn = document.getElementById('process-btn'); - if (fileInput) { - fileInput.addEventListener('change', handleFileUpload); - } + if (fileInput) { + fileInput.addEventListener('change', handleFileUpload); + } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); - - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handleFiles(droppedFiles); - } - }); - - // Clear value on click to allow re-selecting the same file - fileInput?.addEventListener('click', () => { - if (fileInput) fileInput.value = ''; - }); - } - - if (addMoreBtn) { - addMoreBtn.addEventListener('click', () => { - fileInput?.click(); - }); - } - - if (clearFilesBtn) { - clearFilesBtn.addEventListener('click', () => { - files = []; - updateUI(); - }); - } - - if (processBtn) { - processBtn.addEventListener('click', convertToPdf); - } - - document.getElementById('back-to-tools')?.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleFiles(droppedFiles); + } + }); + + fileInput?.addEventListener('click', () => { + if (fileInput) fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput?.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + files = []; + updateUI(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } + + document.getElementById('back-to-tools')?.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); } function handleFileUpload(e: Event) { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handleFiles(input.files); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleFiles(input.files); + } +} + +function getFileExtension(filename: string): string { + return '.' + (filename.split('.').pop()?.toLowerCase() || ''); +} + +function isValidImageFile(file: File): boolean { + const ext = getFileExtension(file.name); + const validExtensions = SUPPORTED_FORMATS.split(','); + return ( + validExtensions.includes(ext) || SUPPORTED_MIME_TYPES.includes(file.type) + ); } function handleFiles(newFiles: FileList) { - const validFiles = Array.from(newFiles).filter(file => - file.type === 'image/jpeg' || file.type === 'image/jpg' || file.name.toLowerCase().endsWith('.jpg') || file.name.toLowerCase().endsWith('.jpeg') + const validFiles = Array.from(newFiles).filter(isValidImageFile); + + if (validFiles.length < newFiles.length) { + showAlert( + 'Invalid Files', + 'Some files were skipped. Only JPG, JPEG, JP2, and JPX files are allowed.' ); + } - if (validFiles.length < newFiles.length) { - showAlert('Invalid Files', 'Some files were skipped. Only JPG/JPEG images are allowed.'); - } - - if (validFiles.length > 0) { - files = [...files, ...validFiles]; - updateUI(); - } + if (validFiles.length > 0) { + files = [...files, ...validFiles]; + updateUI(); + } } const resetState = () => { - files = []; - updateUI(); + files = []; + updateUI(); }; function updateUI() { - const fileDisplayArea = document.getElementById('file-display-area'); - const fileControls = document.getElementById('file-controls'); - const optionsDiv = document.getElementById('jpg-to-pdf-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const optionsDiv = document.getElementById('jpg-to-pdf-options'); - if (!fileDisplayArea || !fileControls || !optionsDiv) return; + if (!fileDisplayArea || !fileControls || !optionsDiv) return; - fileDisplayArea.innerHTML = ''; + fileDisplayArea.innerHTML = ''; - if (files.length > 0) { - fileControls.classList.remove('hidden'); - optionsDiv.classList.remove('hidden'); + if (files.length > 0) { + fileControls.classList.remove('hidden'); + optionsDiv.classList.remove('hidden'); - files.forEach((file, index) => { - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + files.forEach((file, index) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex items-center gap-2 overflow-hidden'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex items-center gap-2 overflow-hidden'; - const nameSpan = document.createElement('span'); - nameSpan.className = 'truncate font-medium text-gray-200'; - nameSpan.textContent = file.name; + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; - const sizeSpan = document.createElement('span'); - sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; - sizeSpan.textContent = `(${formatBytes(file.size)})`; + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; + sizeSpan.textContent = `(${formatBytes(file.size)})`; - infoContainer.append(nameSpan, sizeSpan); + infoContainer.append(nameSpan, sizeSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - files = files.filter((_, i) => i !== index); - updateUI(); - }; + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + files = files.filter((_, i) => i !== index); + updateUI(); + }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - }); - createIcons({ icons }); - } else { - fileControls.classList.add('hidden'); - optionsDiv.classList.add('hidden'); - } + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + }); + createIcons({ icons }); + } else { + fileControls.classList.add('hidden'); + optionsDiv.classList.add('hidden'); + } } -function sanitizeImageAsJpeg(imageBytes: any) { - return new Promise((resolve, reject) => { - const blob = new Blob([imageBytes]); - const imageUrl = URL.createObjectURL(blob); - const img = new Image(); - - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.naturalWidth; - canvas.height = img.naturalHeight; - const ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0); - - canvas.toBlob( - async (jpegBlob) => { - if (!jpegBlob) { - return reject(new Error('Canvas toBlob conversion failed.')); - } - const arrayBuffer = await jpegBlob.arrayBuffer(); - resolve(new Uint8Array(arrayBuffer)); - }, - 'image/jpeg', - 0.9 - ); - URL.revokeObjectURL(imageUrl); - }; - - img.onerror = () => { - URL.revokeObjectURL(imageUrl); - reject( - new Error( - 'The provided file could not be loaded as an image. It may be corrupted.' - ) - ); - }; - - img.src = imageUrl; - }); +async function ensurePyMuPDF(): Promise { + if (!pymupdf) { + pymupdf = await loadPyMuPDF(); + } + return pymupdf; } async function convertToPdf() { - if (files.length === 0) { - showAlert('No Files', 'Please select at least one JPG file.'); - return; + if (files.length === 0) { + showAlert('No Files', 'Please select at least one JPG or JPEG2000 image.'); + return; + } + + showLoader('Loading engine...'); + + try { + const mupdf = await ensurePyMuPDF(); + + showLoader('Converting images to PDF...'); + const quality = getSelectedQuality(); + const compressedFiles: File[] = []; + for (const file of files) { + compressedFiles.push(await compressImageFile(file, quality)); } - showLoader('Creating PDF from JPGs...'); + const pdfBlob = await mupdf.imagesToPdf(compressedFiles); - try { - const pdfDoc = await PDFLibDocument.create(); + downloadFile(pdfBlob, 'from_jpgs.pdf'); - for (const file of files) { - const originalBytes = await readFileAsArrayBuffer(file); - let jpgImage; - - try { - jpgImage = await pdfDoc.embedJpg(originalBytes as Uint8Array); - } catch (e) { - showAlert( - 'Warning', - `Direct JPG embedding failed for ${file.name}, attempting to sanitize...` - ); - try { - const sanitizedBytes = await sanitizeImageAsJpeg(originalBytes); - jpgImage = await pdfDoc.embedJpg(sanitizedBytes as Uint8Array); - } catch (fallbackError) { - console.error( - `Failed to process ${file.name} after sanitization:`, - fallbackError - ); - throw new Error( - `Could not process "${file.name}". The file may be corrupted.` - ); - } - } - - const page = pdfDoc.addPage([jpgImage.width, jpgImage.height]); - page.drawImage(jpgImage, { - x: 0, - y: 0, - width: jpgImage.width, - height: jpgImage.height, - }); - } - - const pdfBytes = await pdfDoc.save(); - downloadFile( - new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), - 'from_jpgs.pdf' - ); - showAlert('Success', 'PDF created successfully!', 'success', () => { - resetState(); - }); - } catch (e: any) { - console.error(e); - showAlert('Conversion Error', e.message); - } finally { - hideLoader(); - } + showAlert('Success', 'PDF created successfully!', 'success', () => { + resetState(); + }); + } catch (e: any) { + console.error('[JpgToPdf]', e); + showAlert( + 'Conversion Error', + e.message || 'Failed to convert images to PDF.' + ); + } finally { + hideLoader(); + } } diff --git a/src/js/logic/json-to-pdf.ts b/src/js/logic/json-to-pdf.ts index e483781..0c9735d 100644 --- a/src/js/logic/json-to-pdf.ts +++ b/src/js/logic/json-to-pdf.ts @@ -1,101 +1,133 @@ -import JSZip from 'jszip' -import { downloadFile, formatBytes, readFileAsArrayBuffer } from '../utils/helpers'; +import JSZip from 'jszip'; +import { + downloadFile, + formatBytes, + readFileAsArrayBuffer, +} from '../utils/helpers'; import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'; +import { isCpdfAvailable } from '../utils/cpdf-helper.js'; +import { + showWasmRequiredDialog, + WasmProvider, +} from '../utils/wasm-provider.js'; -const worker = new Worker(import.meta.env.BASE_URL + 'workers/json-to-pdf.worker.js'); +const worker = new Worker( + import.meta.env.BASE_URL + 'workers/json-to-pdf.worker.js' +); -let selectedFiles: File[] = [] +let selectedFiles: File[] = []; -const jsonFilesInput = document.getElementById('jsonFiles') as HTMLInputElement -const convertBtn = document.getElementById('convertBtn') as HTMLButtonElement -const statusMessage = document.getElementById('status-message') as HTMLDivElement -const fileListDiv = document.getElementById('fileList') as HTMLDivElement -const backToToolsBtn = document.getElementById('back-to-tools') as HTMLButtonElement +const jsonFilesInput = document.getElementById('jsonFiles') as HTMLInputElement; +const convertBtn = document.getElementById('convertBtn') as HTMLButtonElement; +const statusMessage = document.getElementById( + 'status-message' +) as HTMLDivElement; +const fileListDiv = document.getElementById('fileList') as HTMLDivElement; +const backToToolsBtn = document.getElementById( + 'back-to-tools' +) as HTMLButtonElement; function showStatus( message: string, type: 'success' | 'error' | 'info' = 'info' ) { - statusMessage.textContent = message - statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${type === 'success' - ? 'bg-green-900 text-green-200' - : type === 'error' - ? 'bg-red-900 text-red-200' - : 'bg-blue-900 text-blue-200' - }` - statusMessage.classList.remove('hidden') + statusMessage.textContent = message; + statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${ + type === 'success' + ? 'bg-green-900 text-green-200' + : type === 'error' + ? 'bg-red-900 text-red-200' + : 'bg-blue-900 text-blue-200' + }`; + statusMessage.classList.remove('hidden'); } function hideStatus() { - statusMessage.classList.add('hidden') + statusMessage.classList.add('hidden'); } function updateFileList() { - fileListDiv.innerHTML = '' + fileListDiv.innerHTML = ''; if (selectedFiles.length === 0) { - fileListDiv.classList.add('hidden') - return + fileListDiv.classList.add('hidden'); + return; } - fileListDiv.classList.remove('hidden') + fileListDiv.classList.remove('hidden'); selectedFiles.forEach((file) => { - const fileDiv = document.createElement('div') - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm mb-2' + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm mb-2'; - const nameSpan = document.createElement('span') - nameSpan.className = 'truncate font-medium text-gray-200' - nameSpan.textContent = file.name + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; - const sizeSpan = document.createElement('span') - sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400' - sizeSpan.textContent = formatBytes(file.size) + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400'; + sizeSpan.textContent = formatBytes(file.size); - fileDiv.append(nameSpan, sizeSpan) - fileListDiv.appendChild(fileDiv) - }) + fileDiv.append(nameSpan, sizeSpan); + fileListDiv.appendChild(fileDiv); + }); } jsonFilesInput.addEventListener('change', (e) => { - const target = e.target as HTMLInputElement + const target = e.target as HTMLInputElement; if (target.files && target.files.length > 0) { - selectedFiles = Array.from(target.files) - convertBtn.disabled = selectedFiles.length === 0 - updateFileList() + selectedFiles = Array.from(target.files); + convertBtn.disabled = selectedFiles.length === 0; + updateFileList(); if (selectedFiles.length === 0) { - showStatus('Please select at least 1 JSON file', 'info') + showStatus('Please select at least 1 JSON file', 'info'); } else { - showStatus(`${selectedFiles.length} file(s) selected. Ready to convert!`, 'info') + showStatus( + `${selectedFiles.length} file(s) selected. Ready to convert!`, + 'info' + ); } } -}) +}); async function convertJSONsToPDF() { if (selectedFiles.length === 0) { - showStatus('Please select at least 1 JSON file', 'error') - return + showStatus('Please select at least 1 JSON file', 'error'); + return; + } + + // Check if CPDF is configured + if (!isCpdfAvailable()) { + showWasmRequiredDialog('cpdf'); + return; } try { - convertBtn.disabled = true - showStatus('Reading files (Main Thread)...', 'info') + convertBtn.disabled = true; + showStatus('Reading files (Main Thread)...', 'info'); const fileBuffers = await Promise.all( - selectedFiles.map(file => readFileAsArrayBuffer(file)) - ) + selectedFiles.map((file) => readFileAsArrayBuffer(file)) + ); - showStatus('Converting JSONs to PDFs...', 'info') - - worker.postMessage({ - command: 'convert', - fileBuffers: fileBuffers, - fileNames: selectedFiles.map(f => f.name) - }, fileBuffers); + showStatus('Converting JSONs to PDFs...', 'info'); + worker.postMessage( + { + command: 'convert', + fileBuffers: fileBuffers, + fileNames: selectedFiles.map((f) => f.name), + cpdfUrl: WasmProvider.getUrl('cpdf')! + 'coherentpdf.browser.min.js', + }, + fileBuffers + ); } catch (error) { - console.error('Error reading files:', error) - showStatus(`❌ Error reading files: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error') - convertBtn.disabled = false + console.error('Error reading files:', error); + showStatus( + `❌ Error reading files: ${error instanceof Error ? error.message : 'Unknown error'}`, + 'error' + ); + convertBtn.disabled = false; } } @@ -103,42 +135,49 @@ worker.onmessage = async (e: MessageEvent) => { convertBtn.disabled = false; if (e.data.status === 'success') { - const pdfFiles = e.data.pdfFiles as Array<{ name: string, data: ArrayBuffer }>; + const pdfFiles = e.data.pdfFiles as Array<{ + name: string; + data: ArrayBuffer; + }>; try { - showStatus('Creating ZIP file...', 'info') + showStatus('Creating ZIP file...', 'info'); - const zip = new JSZip() + const zip = new JSZip(); pdfFiles.forEach(({ name, data }) => { - const pdfName = name.replace(/\.json$/i, '.pdf') - const uint8Array = new Uint8Array(data) - zip.file(pdfName, uint8Array) - }) + const pdfName = name.replace(/\.json$/i, '.pdf'); + const uint8Array = new Uint8Array(data); + zip.file(pdfName, uint8Array); + }); - const zipBlob = await zip.generateAsync({ type: 'blob' }) - const url = URL.createObjectURL(zipBlob) - const a = document.createElement('a') - a.href = url - a.download = 'jsons-to-pdf.zip' - downloadFile(zipBlob, 'jsons-to-pdf.zip') + const zipBlob = await zip.generateAsync({ type: 'blob' }); + const url = URL.createObjectURL(zipBlob); + const a = document.createElement('a'); + a.href = url; + a.download = 'jsons-to-pdf.zip'; + downloadFile(zipBlob, 'jsons-to-pdf.zip'); - showStatus('✅ JSONs converted to PDF successfully! ZIP download started.', 'success') + showStatus( + '✅ JSONs converted to PDF successfully! ZIP download started.', + 'success' + ); - selectedFiles = [] - jsonFilesInput.value = '' - fileListDiv.innerHTML = '' - fileListDiv.classList.add('hidden') - convertBtn.disabled = true + selectedFiles = []; + jsonFilesInput.value = ''; + fileListDiv.innerHTML = ''; + fileListDiv.classList.add('hidden'); + convertBtn.disabled = true; setTimeout(() => { - hideStatus() - }, 3000) - + hideStatus(); + }, 3000); } catch (error) { - console.error('Error creating ZIP:', error) - showStatus(`❌ Error creating ZIP: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error') + console.error('Error creating ZIP:', error); + showStatus( + `❌ Error creating ZIP: ${error instanceof Error ? error.message : 'Unknown error'}`, + 'error' + ); } - } else if (e.data.status === 'error') { const errorMessage = e.data.message || 'Unknown error occurred in worker.'; console.error('Worker Error:', errorMessage); @@ -148,12 +187,12 @@ worker.onmessage = async (e: MessageEvent) => { if (backToToolsBtn) { backToToolsBtn.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL - }) + window.location.href = import.meta.env.BASE_URL; + }); } -convertBtn.addEventListener('click', convertJSONsToPDF) +convertBtn.addEventListener('click', convertJSONsToPDF); // Initialize -showStatus('Select JSON files to get started', 'info') -initializeGlobalShortcuts() +showStatus('Select JSON files to get started', 'info'); +initializeGlobalShortcuts(); diff --git a/src/js/logic/linearize-pdf-page.ts b/src/js/logic/linearize-pdf-page.ts index d6d5121..f9db90f 100644 --- a/src/js/logic/linearize-pdf-page.ts +++ b/src/js/logic/linearize-pdf-page.ts @@ -2,12 +2,9 @@ import { showAlert } from '../ui.js'; import { downloadFile, formatBytes, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js'; import { icons, createIcons } from 'lucide'; import JSZip from 'jszip'; +import { LinearizePdfState } from '@/types'; -interface PageState { - files: File[]; -} - -const pageState: PageState = { +const pageState: LinearizePdfState = { files: [], }; diff --git a/src/js/logic/markdown-to-pdf-page.ts b/src/js/logic/markdown-to-pdf-page.ts new file mode 100644 index 0000000..e104c98 --- /dev/null +++ b/src/js/logic/markdown-to-pdf-page.ts @@ -0,0 +1,21 @@ +import { MarkdownEditor } from '../utils/markdown-editor.js'; + +document.addEventListener('DOMContentLoaded', () => { + const container = document.getElementById('markdown-editor-container'); + + if (!container) { + console.error('Markdown editor container not found'); + return; + } + + const editor = new MarkdownEditor(container, {}); + + console.log('Markdown editor initialized'); + + const backButton = document.getElementById('back-to-tools'); + if (backButton) { + backButton.addEventListener('click', () => { + window.location.href = '/'; + }); + } +}); diff --git a/src/js/logic/merge-pdf-page.ts b/src/js/logic/merge-pdf-page.ts index 85c6a23..714aa04 100644 --- a/src/js/logic/merge-pdf-page.ts +++ b/src/js/logic/merge-pdf-page.ts @@ -1,629 +1,676 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js'; +import { + downloadFile, + readFileAsArrayBuffer, + getPDFDocument, +} from '../utils/helpers.js'; import { state } from '../state.js'; -import { renderPagesProgressively, cleanupLazyRendering } from '../utils/render-utils.js'; +import { + renderPagesProgressively, + cleanupLazyRendering, +} from '../utils/render-utils.js'; +import { initPagePreview } from '../utils/page-preview.js'; +import { isCpdfAvailable } from '../utils/cpdf-helper.js'; +import { + showWasmRequiredDialog, + WasmProvider, +} from '../utils/wasm-provider.js'; import { createIcons, icons } from 'lucide'; import * as pdfjsLib from 'pdfjs-dist'; import Sortable from 'sortablejs'; // @ts-ignore -pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); interface MergeState { - pdfDocs: Record; - pdfBytes: Record; - activeMode: 'file' | 'page'; - sortableInstances: { - fileList?: Sortable; - pageThumbnails?: Sortable; - }; - isRendering: boolean; - cachedThumbnails: boolean | null; - lastFileHash: string | null; - mergeSuccess: boolean; + pdfDocs: Record; + pdfBytes: Record; + activeMode: 'file' | 'page'; + sortableInstances: { + fileList?: Sortable; + pageThumbnails?: Sortable; + }; + isRendering: boolean; + cachedThumbnails: boolean | null; + lastFileHash: string | null; + mergeSuccess: boolean; } const mergeState: MergeState = { - pdfDocs: {}, - pdfBytes: {}, - activeMode: 'file', - sortableInstances: {}, - isRendering: false, - cachedThumbnails: null, - lastFileHash: null, - mergeSuccess: false, + pdfDocs: {}, + pdfBytes: {}, + activeMode: 'file', + sortableInstances: {}, + isRendering: false, + cachedThumbnails: null, + lastFileHash: null, + mergeSuccess: false, }; -const mergeWorker = new Worker(import.meta.env.BASE_URL + 'workers/merge.worker.js'); +const mergeWorker = new Worker( + import.meta.env.BASE_URL + 'workers/merge.worker.js' +); function initializeFileListSortable() { - const fileList = document.getElementById('file-list'); - if (!fileList) return; + const fileList = document.getElementById('file-list'); + if (!fileList) return; - if (mergeState.sortableInstances.fileList) { - mergeState.sortableInstances.fileList.destroy(); - } + if (mergeState.sortableInstances.fileList) { + mergeState.sortableInstances.fileList.destroy(); + } - mergeState.sortableInstances.fileList = Sortable.create(fileList, { - handle: '.drag-handle', - animation: 150, - ghostClass: 'sortable-ghost', - chosenClass: 'sortable-chosen', - dragClass: 'sortable-drag', - onStart: function (evt: any) { - evt.item.style.opacity = '0.5'; - }, - onEnd: function (evt: any) { - evt.item.style.opacity = '1'; - }, - }); + mergeState.sortableInstances.fileList = Sortable.create(fileList, { + handle: '.drag-handle', + animation: 150, + ghostClass: 'sortable-ghost', + chosenClass: 'sortable-chosen', + dragClass: 'sortable-drag', + onStart: function (evt: any) { + evt.item.style.opacity = '0.5'; + }, + onEnd: function (evt: any) { + evt.item.style.opacity = '1'; + }, + }); } function initializePageThumbnailsSortable() { - const container = document.getElementById('page-merge-preview'); - if (!container) return; + const container = document.getElementById('page-merge-preview'); + if (!container) return; - if (mergeState.sortableInstances.pageThumbnails) { - mergeState.sortableInstances.pageThumbnails.destroy(); - } + if (mergeState.sortableInstances.pageThumbnails) { + mergeState.sortableInstances.pageThumbnails.destroy(); + } - mergeState.sortableInstances.pageThumbnails = Sortable.create(container, { - animation: 150, - ghostClass: 'sortable-ghost', - chosenClass: 'sortable-chosen', - dragClass: 'sortable-drag', - onStart: function (evt: any) { - evt.item.style.opacity = '0.5'; - }, - onEnd: function (evt: any) { - evt.item.style.opacity = '1'; - }, - }); + mergeState.sortableInstances.pageThumbnails = Sortable.create(container, { + animation: 150, + ghostClass: 'sortable-ghost', + chosenClass: 'sortable-chosen', + dragClass: 'sortable-drag', + onStart: function (evt: any) { + evt.item.style.opacity = '0.5'; + }, + onEnd: function (evt: any) { + evt.item.style.opacity = '1'; + }, + }); } function generateFileHash() { - return (state.files as File[]) - .map((f) => `${f.name}-${f.size}-${f.lastModified}`) - .join('|'); + return (state.files as File[]) + .map((f) => `${f.name}-${f.size}-${f.lastModified}`) + .join('|'); } async function renderPageMergeThumbnails() { - const container = document.getElementById('page-merge-preview'); - if (!container) return; + const container = document.getElementById('page-merge-preview'); + if (!container) return; - const currentFileHash = generateFileHash(); - const filesChanged = currentFileHash !== mergeState.lastFileHash; + const currentFileHash = generateFileHash(); + const filesChanged = currentFileHash !== mergeState.lastFileHash; - if (!filesChanged && mergeState.cachedThumbnails !== null) { - // Simple check to see if it's already rendered to avoid flicker. - if (container.firstChild) { - initializePageThumbnailsSortable(); - return; - } + if (!filesChanged && mergeState.cachedThumbnails !== null) { + // Simple check to see if it's already rendered to avoid flicker. + if (container.firstChild) { + initializePageThumbnailsSortable(); + return; } + } - if (mergeState.isRendering) { - return; - } + if (mergeState.isRendering) { + return; + } - mergeState.isRendering = true; - container.textContent = ''; + mergeState.isRendering = true; + container.textContent = ''; - cleanupLazyRendering(); + cleanupLazyRendering(); - let totalPages = 0; + let totalPages = 0; + for (const file of state.files) { + const doc = mergeState.pdfDocs[file.name]; + if (doc) totalPages += doc.numPages; + } + + try { + let currentPageNumber = 0; + + // Function to create wrapper element for each page + const createWrapper = ( + canvas: HTMLCanvasElement, + pageNumber: number, + fileName?: string + ) => { + const wrapper = document.createElement('div'); + wrapper.className = + 'page-thumbnail relative cursor-move flex flex-col items-center gap-1 p-2 border-2 border-gray-600 hover:border-indigo-500 rounded-lg bg-gray-700 transition-colors'; + wrapper.dataset.fileName = fileName || ''; + wrapper.dataset.pageIndex = (pageNumber - 1).toString(); + + const imgContainer = document.createElement('div'); + imgContainer.className = 'relative'; + + const img = document.createElement('img'); + img.src = canvas.toDataURL(); + img.className = 'rounded-md shadow-md max-w-full h-auto'; + + const pageNumDiv = document.createElement('div'); + pageNumDiv.className = + 'absolute top-1 left-1 bg-indigo-600 text-white text-xs px-2 py-1 rounded-md font-semibold shadow-lg'; + pageNumDiv.textContent = pageNumber.toString(); + + imgContainer.append(img, pageNumDiv); + + const fileNamePara = document.createElement('p'); + fileNamePara.className = + 'text-xs text-gray-400 truncate w-full text-center'; + const fullTitle = fileName + ? `${fileName} (page ${pageNumber})` + : `Page ${pageNumber}`; + fileNamePara.title = fullTitle; + fileNamePara.textContent = fileName + ? `${fileName.substring(0, 10)}... (p${pageNumber})` + : `Page ${pageNumber}`; + + wrapper.append(imgContainer, fileNamePara); + return wrapper; + }; + + // Render pages from all files progressively for (const file of state.files) { - const doc = mergeState.pdfDocs[file.name]; - if (doc) totalPages += doc.numPages; - } + const pdfjsDoc = mergeState.pdfDocs[file.name]; + if (!pdfjsDoc) continue; - try { - let currentPageNumber = 0; + // Create a wrapper function that includes the file name + const createWrapperWithFileName = ( + canvas: HTMLCanvasElement, + pageNumber: number + ) => { + return createWrapper(canvas, pageNumber, file.name); + }; - // Function to create wrapper element for each page - const createWrapper = (canvas: HTMLCanvasElement, pageNumber: number, fileName?: string) => { - const wrapper = document.createElement('div'); - wrapper.className = - 'page-thumbnail relative cursor-move flex flex-col items-center gap-1 p-2 border-2 border-gray-600 hover:border-indigo-500 rounded-lg bg-gray-700 transition-colors'; - wrapper.dataset.fileName = fileName || ''; - wrapper.dataset.pageIndex = (pageNumber - 1).toString(); - - const imgContainer = document.createElement('div'); - imgContainer.className = 'relative'; - - const img = document.createElement('img'); - img.src = canvas.toDataURL(); - img.className = 'rounded-md shadow-md max-w-full h-auto'; - - const pageNumDiv = document.createElement('div'); - pageNumDiv.className = - 'absolute top-1 left-1 bg-indigo-600 text-white text-xs px-2 py-1 rounded-md font-semibold shadow-lg'; - pageNumDiv.textContent = pageNumber.toString(); - - imgContainer.append(img, pageNumDiv); - - const fileNamePara = document.createElement('p'); - fileNamePara.className = - 'text-xs text-gray-400 truncate w-full text-center'; - const fullTitle = fileName ? `${fileName} (page ${pageNumber})` : `Page ${pageNumber}`; - fileNamePara.title = fullTitle; - fileNamePara.textContent = fileName - ? `${fileName.substring(0, 10)}... (p${pageNumber})` - : `Page ${pageNumber}`; - - wrapper.append(imgContainer, fileNamePara); - return wrapper; - }; - - // Render pages from all files progressively - for (const file of state.files) { - const pdfjsDoc = mergeState.pdfDocs[file.name]; - if (!pdfjsDoc) continue; - - // Create a wrapper function that includes the file name - const createWrapperWithFileName = (canvas: HTMLCanvasElement, pageNumber: number) => { - return createWrapper(canvas, pageNumber, file.name); - }; - - // Render pages progressively with lazy loading - await renderPagesProgressively( - pdfjsDoc, - container, - createWrapperWithFileName, - { - batchSize: 8, - useLazyLoading: true, - lazyLoadMargin: '300px', - onProgress: (current, total) => { - currentPageNumber++; - showLoader( - `Rendering page previews...` - ); - }, - onBatchComplete: () => { - createIcons({ icons }); - } - } - ); + // Render pages progressively with lazy loading + await renderPagesProgressively( + pdfjsDoc, + container, + createWrapperWithFileName, + { + batchSize: 8, + useLazyLoading: true, + lazyLoadMargin: '300px', + onProgress: (current, total) => { + currentPageNumber++; + showLoader(`Rendering page previews...`); + }, + onBatchComplete: () => { + createIcons({ icons }); + }, } + ); - mergeState.cachedThumbnails = true; - mergeState.lastFileHash = currentFileHash; - - initializePageThumbnailsSortable(); - } catch (error) { - console.error('Error rendering page thumbnails:', error); - showAlert('Error', 'Failed to render page thumbnails'); - } finally { - hideLoader(); - mergeState.isRendering = false; + initPagePreview(container, pdfjsDoc); } + + mergeState.cachedThumbnails = true; + mergeState.lastFileHash = currentFileHash; + + initializePageThumbnailsSortable(); + } catch (error) { + console.error('Error rendering page thumbnails:', error); + showAlert('Error', 'Failed to render page thumbnails'); + } finally { + hideLoader(); + mergeState.isRendering = false; + } } const updateUI = async () => { - const fileControls = document.getElementById('file-controls'); - const mergeOptions = document.getElementById('merge-options'); + const fileControls = document.getElementById('file-controls'); + const mergeOptions = document.getElementById('merge-options'); - if (state.files.length > 0) { - if (fileControls) fileControls.classList.remove('hidden'); - if (mergeOptions) mergeOptions.classList.remove('hidden'); - await refreshMergeUI(); - } else { - if (fileControls) fileControls.classList.add('hidden'); - if (mergeOptions) mergeOptions.classList.add('hidden'); - // Clear file list UI - const fileList = document.getElementById('file-list'); - if (fileList) fileList.innerHTML = ''; - } + if (state.files.length > 0) { + if (fileControls) fileControls.classList.remove('hidden'); + if (mergeOptions) mergeOptions.classList.remove('hidden'); + await refreshMergeUI(); + } else { + if (fileControls) fileControls.classList.add('hidden'); + if (mergeOptions) mergeOptions.classList.add('hidden'); + // Clear file list UI + const fileList = document.getElementById('file-list'); + if (fileList) fileList.innerHTML = ''; + } }; const resetState = async () => { - state.files = []; - state.pdfDoc = null; + state.files = []; + state.pdfDoc = null; - mergeState.pdfDocs = {}; - mergeState.pdfBytes = {}; - mergeState.activeMode = 'file'; - mergeState.cachedThumbnails = null; - mergeState.lastFileHash = null; - mergeState.mergeSuccess = false; + mergeState.pdfDocs = {}; + mergeState.pdfBytes = {}; + mergeState.activeMode = 'file'; + mergeState.cachedThumbnails = null; + mergeState.lastFileHash = null; + mergeState.mergeSuccess = false; - const fileList = document.getElementById('file-list'); - if (fileList) fileList.innerHTML = ''; + const fileList = document.getElementById('file-list'); + if (fileList) fileList.innerHTML = ''; - const pageMergePreview = document.getElementById('page-merge-preview'); - if (pageMergePreview) pageMergePreview.innerHTML = ''; + const pageMergePreview = document.getElementById('page-merge-preview'); + if (pageMergePreview) pageMergePreview.innerHTML = ''; - const fileModeBtn = document.getElementById('file-mode-btn'); - const pageModeBtn = document.getElementById('page-mode-btn'); - const filePanel = document.getElementById('file-mode-panel'); - const pagePanel = document.getElementById('page-mode-panel'); + const fileModeBtn = document.getElementById('file-mode-btn'); + const pageModeBtn = document.getElementById('page-mode-btn'); + const filePanel = document.getElementById('file-mode-panel'); + const pagePanel = document.getElementById('page-mode-panel'); - if (fileModeBtn && pageModeBtn && filePanel && pagePanel) { - fileModeBtn.classList.add('bg-indigo-600', 'text-white'); - fileModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); - pageModeBtn.classList.remove('bg-indigo-600', 'text-white'); - pageModeBtn.classList.add('bg-gray-700', 'text-gray-300'); + if (fileModeBtn && pageModeBtn && filePanel && pagePanel) { + fileModeBtn.classList.add('bg-indigo-600', 'text-white'); + fileModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); + pageModeBtn.classList.remove('bg-indigo-600', 'text-white'); + pageModeBtn.classList.add('bg-gray-700', 'text-gray-300'); - filePanel.classList.remove('hidden'); - pagePanel.classList.add('hidden'); - } + filePanel.classList.remove('hidden'); + pagePanel.classList.add('hidden'); + } - await updateUI(); + await updateUI(); }; - export async function merge() { - showLoader('Merging PDFs...'); - try { - // @ts-ignore - const jobs: MergeJob[] = []; - // @ts-ignore - const filesToMerge: MergeFile[] = []; - const uniqueFileNames = new Set(); + // Check if CPDF is configured + if (!isCpdfAvailable()) { + showWasmRequiredDialog('cpdf'); + return; + } - if (mergeState.activeMode === 'file') { - const fileList = document.getElementById('file-list'); - if (!fileList) throw new Error('File list not found'); + showLoader('Merging PDFs...'); + try { + // @ts-ignore + const jobs: MergeJob[] = []; + // @ts-ignore + const filesToMerge: MergeFile[] = []; + const uniqueFileNames = new Set(); - const sortedFiles = Array.from(fileList.children) - .map((li) => { - return state.files.find((f) => f.name === (li as HTMLElement).dataset.fileName); - }) - .filter(Boolean); + if (mergeState.activeMode === 'file') { + const fileList = document.getElementById('file-list'); + if (!fileList) throw new Error('File list not found'); - for (const file of sortedFiles) { - if (!file) continue; - const safeFileName = file.name.replace(/[^a-zA-Z0-9]/g, '_'); - const rangeInput = document.getElementById(`range-${safeFileName}`) as HTMLInputElement; + const sortedFiles = Array.from(fileList.children) + .map((li) => { + return state.files.find( + (f) => f.name === (li as HTMLElement).dataset.fileName + ); + }) + .filter(Boolean); - uniqueFileNames.add(file.name); + for (const file of sortedFiles) { + if (!file) continue; + const safeFileName = file.name.replace(/[^a-zA-Z0-9]/g, '_'); + const rangeInput = document.getElementById( + `range-${safeFileName}` + ) as HTMLInputElement; - if (rangeInput && rangeInput.value.trim()) { - jobs.push({ - fileName: file.name, - rangeType: 'specific', - rangeString: rangeInput.value.trim() - }); - } else { - jobs.push({ - fileName: file.name, - rangeType: 'all' - }); - } - } + uniqueFileNames.add(file.name); + + if (rangeInput && rangeInput.value.trim()) { + jobs.push({ + fileName: file.name, + rangeType: 'specific', + rangeString: rangeInput.value.trim(), + }); } else { - // Page Mode - const pageContainer = document.getElementById('page-merge-preview'); - if (!pageContainer) throw new Error('Page container not found'); - const pageElements = Array.from(pageContainer.children); + jobs.push({ + fileName: file.name, + rangeType: 'all', + }); + } + } + } else { + // Page Mode + const pageContainer = document.getElementById('page-merge-preview'); + if (!pageContainer) throw new Error('Page container not found'); + const pageElements = Array.from(pageContainer.children); - const rawPages: { fileName: string; pageIndex: number }[] = []; - for (const el of pageElements) { - const element = el as HTMLElement; - const fileName = element.dataset.fileName; - const pageIndex = parseInt(element.dataset.pageIndex || '', 10); // 0-based index from dataset + const rawPages: { fileName: string; pageIndex: number }[] = []; + for (const el of pageElements) { + const element = el as HTMLElement; + const fileName = element.dataset.fileName; + const pageIndex = parseInt(element.dataset.pageIndex || '', 10); // 0-based index from dataset - if (fileName && !isNaN(pageIndex)) { - uniqueFileNames.add(fileName); - rawPages.push({ fileName, pageIndex }); - } - } + if (fileName && !isNaN(pageIndex)) { + uniqueFileNames.add(fileName); + rawPages.push({ fileName, pageIndex }); + } + } - // Group contiguous pages - for (let i = 0; i < rawPages.length; i++) { - const current = rawPages[i]; - let endPage = current.pageIndex; + // Group contiguous pages + for (let i = 0; i < rawPages.length; i++) { + const current = rawPages[i]; + let endPage = current.pageIndex; - while ( - i + 1 < rawPages.length && - rawPages[i + 1].fileName === current.fileName && - rawPages[i + 1].pageIndex === endPage + 1 - ) { - endPage++; - i++; - } - - if (endPage === current.pageIndex) { - // Single page - jobs.push({ - fileName: current.fileName, - rangeType: 'single', - pageIndex: current.pageIndex - }); - } else { - // Range of pages - jobs.push({ - fileName: current.fileName, - rangeType: 'range', - startPage: current.pageIndex + 1, - endPage: endPage + 1 - }); - } - } + while ( + i + 1 < rawPages.length && + rawPages[i + 1].fileName === current.fileName && + rawPages[i + 1].pageIndex === endPage + 1 + ) { + endPage++; + i++; } - if (jobs.length === 0) { - showAlert('Error', 'No files or pages selected to merge.'); - hideLoader(); - return; + if (endPage === current.pageIndex) { + // Single page + jobs.push({ + fileName: current.fileName, + rangeType: 'single', + pageIndex: current.pageIndex, + }); + } else { + // Range of pages + jobs.push({ + fileName: current.fileName, + rangeType: 'range', + startPage: current.pageIndex + 1, + endPage: endPage + 1, + }); } - - for (const name of uniqueFileNames) { - const bytes = mergeState.pdfBytes[name]; - if (bytes) { - filesToMerge.push({ name, data: bytes }); - } - } - - // @ts-ignore - const message: MergeMessage = { - command: 'merge', - files: filesToMerge, - jobs: jobs - }; - - mergeWorker.postMessage(message, filesToMerge.map(f => f.data)); - - // @ts-ignore - mergeWorker.onmessage = (e: MessageEvent) => { - hideLoader(); - if (e.data.status === 'success') { - const blob = new Blob([e.data.pdfBytes], { type: 'application/pdf' }); - downloadFile(blob, 'merged.pdf'); - mergeState.mergeSuccess = true; - showAlert('Success', 'PDFs merged successfully!', 'success', async () => { - await resetState(); - }); - } else { - console.error('Worker merge error:', e.data.message); - showAlert('Error', e.data.message || 'Failed to merge PDFs.'); - } - }; - - mergeWorker.onerror = (e) => { - hideLoader(); - console.error('Worker error:', e); - showAlert('Error', 'An unexpected error occurred in the merge worker.'); - }; - - } catch (e) { - console.error('Merge error:', e); - showAlert( - 'Error', - 'Failed to merge PDFs. Please check that all files are valid and not password-protected.' - ); - hideLoader(); + } } + + if (jobs.length === 0) { + showAlert('Error', 'No files or pages selected to merge.'); + hideLoader(); + return; + } + + for (const name of uniqueFileNames) { + const bytes = mergeState.pdfBytes[name]; + if (bytes) { + filesToMerge.push({ name, data: bytes }); + } + } + + // @ts-ignore + const message: MergeMessage = { + command: 'merge', + files: filesToMerge, + jobs: jobs, + cpdfUrl: WasmProvider.getUrl('cpdf')! + 'coherentpdf.browser.min.js', + }; + + mergeWorker.postMessage( + message, + filesToMerge.map((f) => f.data) + ); + + // @ts-ignore + mergeWorker.onmessage = (e: MessageEvent) => { + hideLoader(); + if (e.data.status === 'success') { + const blob = new Blob([e.data.pdfBytes], { type: 'application/pdf' }); + downloadFile(blob, 'merged.pdf'); + mergeState.mergeSuccess = true; + showAlert( + 'Success', + 'PDFs merged successfully!', + 'success', + async () => { + await resetState(); + } + ); + } else { + console.error('Worker merge error:', e.data.message); + showAlert('Error', e.data.message || 'Failed to merge PDFs.'); + } + }; + + mergeWorker.onerror = (e) => { + hideLoader(); + console.error('Worker error:', e); + showAlert('Error', 'An unexpected error occurred in the merge worker.'); + }; + } catch (e) { + console.error('Merge error:', e); + showAlert( + 'Error', + 'Failed to merge PDFs. Please check that all files are valid and not password-protected.' + ); + hideLoader(); + } } export async function refreshMergeUI() { - document.getElementById('merge-options')?.classList.remove('hidden'); - const processBtn = document.getElementById('process-btn') as HTMLButtonElement; - if (processBtn) processBtn.disabled = false; + document.getElementById('merge-options')?.classList.remove('hidden'); + const processBtn = document.getElementById( + 'process-btn' + ) as HTMLButtonElement; + if (processBtn) processBtn.disabled = false; - const wasInPageMode = mergeState.activeMode === 'page'; + const wasInPageMode = mergeState.activeMode === 'page'; - showLoader('Loading PDF documents...'); - try { - mergeState.pdfDocs = {}; - mergeState.pdfBytes = {}; + showLoader('Loading PDF documents...'); + try { + mergeState.pdfDocs = {}; + mergeState.pdfBytes = {}; - for (const file of state.files) { - const pdfBytes = await readFileAsArrayBuffer(file); - mergeState.pdfBytes[file.name] = pdfBytes as ArrayBuffer; + for (const file of state.files) { + const pdfBytes = await readFileAsArrayBuffer(file); + mergeState.pdfBytes[file.name] = pdfBytes as ArrayBuffer; - const bytesForPdfJs = (pdfBytes as ArrayBuffer).slice(0); - const pdfjsDoc = await getPDFDocument({ data: bytesForPdfJs }).promise; - mergeState.pdfDocs[file.name] = pdfjsDoc; - } - } catch (error) { - console.error('Error loading PDFs:', error); - showAlert('Error', 'Failed to load one or more PDF files'); - return; - } finally { - hideLoader(); + const bytesForPdfJs = (pdfBytes as ArrayBuffer).slice(0); + const pdfjsDoc = await getPDFDocument({ data: bytesForPdfJs }).promise; + mergeState.pdfDocs[file.name] = pdfjsDoc; } + } catch (error) { + console.error('Error loading PDFs:', error); + showAlert('Error', 'Failed to load one or more PDF files'); + return; + } finally { + hideLoader(); + } - const fileModeBtn = document.getElementById('file-mode-btn'); - const pageModeBtn = document.getElementById('page-mode-btn'); - const filePanel = document.getElementById('file-mode-panel'); - const pagePanel = document.getElementById('page-mode-panel'); - const fileList = document.getElementById('file-list'); + const fileModeBtn = document.getElementById('file-mode-btn'); + const pageModeBtn = document.getElementById('page-mode-btn'); + const filePanel = document.getElementById('file-mode-panel'); + const pagePanel = document.getElementById('page-mode-panel'); + const fileList = document.getElementById('file-list'); - if (!fileModeBtn || !pageModeBtn || !filePanel || !pagePanel || !fileList) return; + if (!fileModeBtn || !pageModeBtn || !filePanel || !pagePanel || !fileList) + return; - fileList.textContent = ''; // Clear list safely - (state.files as File[]).forEach((f, index) => { - const doc = mergeState.pdfDocs[f.name]; - const pageCount = doc ? doc.numPages : 'N/A'; - const safeFileName = f.name.replace(/[^a-zA-Z0-9]/g, '_'); + fileList.textContent = ''; // Clear list safely + (state.files as File[]).forEach((f, index) => { + const doc = mergeState.pdfDocs[f.name]; + const pageCount = doc ? doc.numPages : 'N/A'; + const safeFileName = f.name.replace(/[^a-zA-Z0-9]/g, '_'); - const li = document.createElement('li'); - li.className = - 'bg-gray-700 p-3 rounded-lg border border-gray-600 hover:border-indigo-500 transition-colors'; - li.dataset.fileName = f.name; + const li = document.createElement('li'); + li.className = + 'bg-gray-700 p-3 rounded-lg border border-gray-600 hover:border-indigo-500 transition-colors'; + li.dataset.fileName = f.name; - const mainDiv = document.createElement('div'); - mainDiv.className = 'flex items-center justify-between'; + const mainDiv = document.createElement('div'); + mainDiv.className = 'flex items-center justify-between'; - const nameSpan = document.createElement('span'); - nameSpan.className = 'truncate font-medium text-white flex-1 mr-2'; - nameSpan.title = f.name; - nameSpan.textContent = f.name; + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-white flex-1 mr-2'; + nameSpan.title = f.name; + nameSpan.textContent = f.name; - const dragHandle = document.createElement('div'); - dragHandle.className = - 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors'; - dragHandle.innerHTML = ``; // Safe: static content + const dragHandle = document.createElement('div'); + dragHandle.className = + 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors'; + dragHandle.innerHTML = ``; // Safe: static content - mainDiv.append(nameSpan, dragHandle); + mainDiv.append(nameSpan, dragHandle); - const rangeDiv = document.createElement('div'); - rangeDiv.className = 'mt-2 flex items-center gap-2'; + const rangeDiv = document.createElement('div'); + rangeDiv.className = 'mt-2 flex items-center gap-2'; - const inputWrapper = document.createElement('div'); - inputWrapper.className = 'flex-1'; + const inputWrapper = document.createElement('div'); + inputWrapper.className = 'flex-1'; - const label = document.createElement('label'); - label.htmlFor = `range-${safeFileName}`; - label.className = 'text-xs text-gray-400'; - label.textContent = `Pages (e.g., 1-3, 5) - Total: ${pageCount}`; + const label = document.createElement('label'); + label.htmlFor = `range-${safeFileName}`; + label.className = 'text-xs text-gray-400'; + label.textContent = `Pages (e.g., 1-3, 5) - Total: ${pageCount}`; - const input = document.createElement('input'); - input.type = 'text'; - input.id = `range-${safeFileName}`; - input.className = - 'w-full bg-gray-800 border border-gray-600 text-white rounded-md p-2 text-sm mt-1 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-colors'; - input.placeholder = 'Leave blank for all pages'; + const input = document.createElement('input'); + input.type = 'text'; + input.id = `range-${safeFileName}`; + input.className = + 'w-full bg-gray-800 border border-gray-600 text-white rounded-md p-2 text-sm mt-1 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-colors'; + input.placeholder = 'Leave blank for all pages'; - inputWrapper.append(label, input); + inputWrapper.append(label, input); - const deleteBtn = document.createElement('button'); - deleteBtn.className = 'text-red-400 hover:text-red-300 p-2 flex-shrink-0 self-end'; - deleteBtn.innerHTML = ''; - deleteBtn.title = 'Remove file'; - deleteBtn.onclick = (e) => { - e.stopPropagation(); - state.files = state.files.filter((_, i) => i !== index); - updateUI(); - }; + const deleteBtn = document.createElement('button'); + deleteBtn.className = + 'text-red-400 hover:text-red-300 p-2 flex-shrink-0 self-end'; + deleteBtn.innerHTML = ''; + deleteBtn.title = 'Remove file'; + deleteBtn.onclick = (e) => { + e.stopPropagation(); + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; - rangeDiv.append(inputWrapper, deleteBtn); - li.append(mainDiv, rangeDiv); - fileList.appendChild(li); - }); + rangeDiv.append(inputWrapper, deleteBtn); + li.append(mainDiv, rangeDiv); + fileList.appendChild(li); + }); - createIcons({ icons }); - initializeFileListSortable(); + createIcons({ icons }); + initializeFileListSortable(); - const newFileModeBtn = fileModeBtn.cloneNode(true) as HTMLElement; - const newPageModeBtn = pageModeBtn.cloneNode(true) as HTMLElement; - fileModeBtn.replaceWith(newFileModeBtn); - pageModeBtn.replaceWith(newPageModeBtn); + const newFileModeBtn = fileModeBtn.cloneNode(true) as HTMLElement; + const newPageModeBtn = pageModeBtn.cloneNode(true) as HTMLElement; + fileModeBtn.replaceWith(newFileModeBtn); + pageModeBtn.replaceWith(newPageModeBtn); - newFileModeBtn.addEventListener('click', () => { - if (mergeState.activeMode === 'file') return; + newFileModeBtn.addEventListener('click', () => { + if (mergeState.activeMode === 'file') return; - mergeState.activeMode = 'file'; - filePanel.classList.remove('hidden'); - pagePanel.classList.add('hidden'); + mergeState.activeMode = 'file'; + filePanel.classList.remove('hidden'); + pagePanel.classList.add('hidden'); - newFileModeBtn.classList.add('bg-indigo-600', 'text-white'); - newFileModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); - newPageModeBtn.classList.remove('bg-indigo-600', 'text-white'); - newPageModeBtn.classList.add('bg-gray-700', 'text-gray-300'); - }); + newFileModeBtn.classList.add('bg-indigo-600', 'text-white'); + newFileModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); + newPageModeBtn.classList.remove('bg-indigo-600', 'text-white'); + newPageModeBtn.classList.add('bg-gray-700', 'text-gray-300'); + }); - newPageModeBtn.addEventListener('click', async () => { - if (mergeState.activeMode === 'page') return; + newPageModeBtn.addEventListener('click', async () => { + if (mergeState.activeMode === 'page') return; - mergeState.activeMode = 'page'; - filePanel.classList.add('hidden'); - pagePanel.classList.remove('hidden'); + mergeState.activeMode = 'page'; + filePanel.classList.add('hidden'); + pagePanel.classList.remove('hidden'); - newPageModeBtn.classList.add('bg-indigo-600', 'text-white'); - newPageModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); - newFileModeBtn.classList.remove('bg-indigo-600', 'text-white'); - newFileModeBtn.classList.add('bg-gray-700', 'text-gray-300'); + newPageModeBtn.classList.add('bg-indigo-600', 'text-white'); + newPageModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); + newFileModeBtn.classList.remove('bg-indigo-600', 'text-white'); + newFileModeBtn.classList.add('bg-gray-700', 'text-gray-300'); - await renderPageMergeThumbnails(); - }); + await renderPageMergeThumbnails(); + }); - if (wasInPageMode) { - mergeState.activeMode = 'page'; - filePanel.classList.add('hidden'); - pagePanel.classList.remove('hidden'); + if (wasInPageMode) { + mergeState.activeMode = 'page'; + filePanel.classList.add('hidden'); + pagePanel.classList.remove('hidden'); - newPageModeBtn.classList.add('bg-indigo-600', 'text-white'); - newPageModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); - newFileModeBtn.classList.remove('bg-indigo-600', 'text-white'); - newFileModeBtn.classList.add('bg-gray-700', 'text-gray-300'); + newPageModeBtn.classList.add('bg-indigo-600', 'text-white'); + newPageModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); + newFileModeBtn.classList.remove('bg-indigo-600', 'text-white'); + newFileModeBtn.classList.add('bg-gray-700', 'text-gray-300'); - await renderPageMergeThumbnails(); - } else { - newFileModeBtn.classList.add('bg-indigo-600', 'text-white'); - newPageModeBtn.classList.add('bg-gray-700', 'text-gray-300'); - } + await renderPageMergeThumbnails(); + } else { + newFileModeBtn.classList.add('bg-indigo-600', 'text-white'); + newPageModeBtn.classList.add('bg-gray-700', 'text-gray-300'); + } } document.addEventListener('DOMContentLoaded', () => { - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const processBtn = document.getElementById('process-btn'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); - const fileControls = document.getElementById('file-controls'); - const addMoreBtn = document.getElementById('add-more-btn'); - const clearFilesBtn = document.getElementById('clear-files-btn'); - const backBtn = document.getElementById('back-to-tools'); - const mergeOptions = document.getElementById('merge-options'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const mergeOptions = document.getElementById('merge-options'); - if (backBtn) { - backBtn.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; - }); - } + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + if (fileInput && dropZone) { + fileInput.addEventListener('change', async (e) => { + const files = (e.target as HTMLInputElement).files; + if (files && files.length > 0) { + state.files = [...state.files, ...Array.from(files)]; + await updateUI(); + } + }); + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); - if (fileInput && dropZone) { - fileInput.addEventListener('change', async (e) => { - const files = (e.target as HTMLInputElement).files; - if (files && files.length > 0) { - state.files = [...state.files, ...Array.from(files)]; - await updateUI(); - } - }); + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); + dropZone.addEventListener('drop', async (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const pdfFiles = Array.from(files).filter( + (f) => + f.type === 'application/pdf' || + f.name.toLowerCase().endsWith('.pdf') + ); + if (pdfFiles.length > 0) { + state.files = [...state.files, ...pdfFiles]; + await updateUI(); + } + } + }); - dropZone.addEventListener('dragleave', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - }); + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } - dropZone.addEventListener('drop', async (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const files = e.dataTransfer?.files; - if (files && files.length > 0) { - const pdfFiles = Array.from(files).filter(f => f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf')); - if (pdfFiles.length > 0) { - state.files = [...state.files, ...pdfFiles]; - await updateUI(); - } - } - }); - - - fileInput.addEventListener('click', () => { - fileInput.value = ''; - }); - } - - if (addMoreBtn) { - addMoreBtn.addEventListener('click', () => { - fileInput.value = ''; - fileInput.click(); - }); - } - - if (clearFilesBtn) { - clearFilesBtn.addEventListener('click', async () => { - state.files = []; - await updateUI(); - }); - } - - if (processBtn) { - processBtn.addEventListener('click', async () => { - await merge(); - }); - } + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.value = ''; + fileInput.click(); + }); + } + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', async () => { + state.files = []; + await updateUI(); + }); + } + if (processBtn) { + processBtn.addEventListener('click', async () => { + await merge(); + }); + } }); diff --git a/src/js/logic/mobi-to-pdf-page.ts b/src/js/logic/mobi-to-pdf-page.ts new file mode 100644 index 0000000..7ddd5c1 --- /dev/null +++ b/src/js/logic/mobi-to-pdf-page.ts @@ -0,0 +1,213 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; + +const FILETYPE = 'mobi'; +const EXTENSIONS = ['.mobi']; +const TOOL_NAME = 'MOBI'; + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!fileDisplayArea || !processBtn || !fileControls) return; + + if (state.files.length > 0) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + fileControls.classList.remove('hidden'); + processBtn.classList.remove('hidden'); + (processBtn as HTMLButtonElement).disabled = false; + } else { + fileDisplayArea.innerHTML = ''; + fileControls.classList.add('hidden'); + processBtn.classList.add('hidden'); + (processBtn as HTMLButtonElement).disabled = true; + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + const convertToPdf = async () => { + try { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${TOOL_NAME} file.`); + return; + } + + showLoader('Loading engine...'); + const pymupdf = await loadPyMuPDF(); + + if (state.files.length === 1) { + const originalFile = state.files[0]; + showLoader(`Converting ${originalFile.name}...`); + + const pdfBlob = await pymupdf.convertToPdf(originalFile, { + filetype: FILETYPE, + }); + const fileName = originalFile.name.replace(/\.[^.]+$/, '') + '.pdf'; + + downloadFile(pdfBlob, fileName); + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${originalFile.name} to PDF.`, + 'success', + () => resetState() + ); + } else { + showLoader('Converting files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader( + `Converting ${i + 1}/${state.files.length}: ${file.name}...` + ); + + const pdfBlob = await pymupdf.convertToPdf(file, { + filetype: FILETYPE, + }); + const baseName = file.name.replace(/\.[^.]+$/, ''); + const pdfBuffer = await pdfBlob.arrayBuffer(); + zip.file(`${baseName}.pdf`, pdfBuffer); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE}-converted.zip`); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${state.files.length} ${TOOL_NAME} file(s) to PDF.`, + 'success', + () => resetState() + ); + } + } catch (e: any) { + console.error(`[${TOOL_NAME}2PDF] ERROR:`, e); + hideLoader(); + showAlert( + 'Error', + `An error occurred during conversion. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + state.files = [...state.files, ...Array.from(files)]; + updateUI(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const validFiles = Array.from(files).filter((f) => { + const name = f.name.toLowerCase(); + return EXTENSIONS.some((ext) => name.endsWith(ext)); + }); + if (validFiles.length > 0) { + const dataTransfer = new DataTransfer(); + validFiles.forEach((f) => dataTransfer.items.add(f)); + handleFileSelect(dataTransfer.files); + } + } + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + resetState(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } +}); diff --git a/src/js/logic/ocr-pdf-page.ts b/src/js/logic/ocr-pdf-page.ts index 1c62745..04341f7 100644 --- a/src/js/logic/ocr-pdf-page.ts +++ b/src/js/logic/ocr-pdf-page.ts @@ -1,567 +1,416 @@ import { tesseractLanguages } from '../config/tesseract-languages.js'; import { showAlert } from '../ui.js'; -import { downloadFile, formatBytes, getPDFDocument } from '../utils/helpers.js'; -import Tesseract from 'tesseract.js'; -import { PDFDocument as PDFLibDocument, StandardFonts, rgb } from 'pdf-lib'; -import fontkit from '@pdf-lib/fontkit'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; import { icons, createIcons } from 'lucide'; -import * as pdfjsLib from 'pdfjs-dist'; -import { getFontForLanguage } from '../utils/font-loader.js'; - -pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); - -interface Word { - text: string; - bbox: { x0: number; y0: number; x1: number; y1: number }; - confidence: number; -} - -interface OcrState { - file: File | null; - searchablePdfBytes: Uint8Array | null; -} +import { OcrState } from '@/types'; +import { performOcr } from '../utils/ocr.js'; const pageState: OcrState = { - file: null, - searchablePdfBytes: null, + file: null, + searchablePdfBytes: null, }; const whitelistPresets: Record = { - alphanumeric: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,!?-\'"', - 'numbers-currency': '0123456789$€£¥.,- ', - 'letters-only': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ', - 'numbers-only': '0123456789', - invoice: '0123456789$.,/-#: ', - forms: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,()-_/@#:', + alphanumeric: + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,!?-\'"', + 'numbers-currency': '0123456789$€£¥.,- ', + 'letters-only': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ', + 'numbers-only': '0123456789', + invoice: '0123456789$.,/-#: ', + forms: + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,()-_/@#:', }; -function parseHOCR(hocrText: string): Word[] { - const parser = new DOMParser(); - const doc = parser.parseFromString(hocrText, 'text/html'); - const words: Word[] = []; - - const wordElements = doc.querySelectorAll('.ocrx_word'); - - wordElements.forEach(function (wordEl) { - const titleAttr = wordEl.getAttribute('title'); - const text = wordEl.textContent?.trim() || ''; - - if (!titleAttr || !text) return; - - const bboxMatch = titleAttr.match(/bbox (\d+) (\d+) (\d+) (\d+)/); - const confMatch = titleAttr.match(/x_wconf (\d+)/); - - if (bboxMatch) { - words.push({ - text: text, - bbox: { - x0: parseInt(bboxMatch[1]), - y0: parseInt(bboxMatch[2]), - x1: parseInt(bboxMatch[3]), - y1: parseInt(bboxMatch[4]), - }, - confidence: confMatch ? parseInt(confMatch[1]) : 0, - }); - } - }); - - return words; -} - -function binarizeCanvas(ctx: CanvasRenderingContext2D) { - const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); - const data = imageData.data; - for (let i = 0; i < data.length; i += 4) { - const brightness = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; - const color = brightness > 128 ? 255 : 0; - data[i] = data[i + 1] = data[i + 2] = color; - } - ctx.putImageData(imageData, 0, 0); -} - function updateProgress(status: string, progress: number) { - const progressBar = document.getElementById('progress-bar'); - const progressStatus = document.getElementById('progress-status'); - const progressLog = document.getElementById('progress-log'); + const progressBar = document.getElementById('progress-bar'); + const progressStatus = document.getElementById('progress-status'); + const progressLog = document.getElementById('progress-log'); - if (!progressBar || !progressStatus || !progressLog) return; + if (!progressBar || !progressStatus || !progressLog) return; - progressStatus.textContent = status; - progressBar.style.width = `${Math.min(100, progress * 100)}%`; + progressStatus.textContent = status; + progressBar.style.width = `${Math.min(100, progress * 100)}%`; - const logMessage = `Status: ${status}`; - progressLog.textContent += logMessage + '\n'; - progressLog.scrollTop = progressLog.scrollHeight; + const logMessage = `Status: ${status}`; + progressLog.textContent += logMessage + '\n'; + progressLog.scrollTop = progressLog.scrollHeight; } function resetState() { - pageState.file = null; - pageState.searchablePdfBytes = null; + pageState.file = null; + pageState.searchablePdfBytes = null; - const fileDisplayArea = document.getElementById('file-display-area'); - if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + const fileDisplayArea = document.getElementById('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; - const toolOptions = document.getElementById('tool-options'); - if (toolOptions) toolOptions.classList.add('hidden'); + const toolOptions = document.getElementById('tool-options'); + if (toolOptions) toolOptions.classList.add('hidden'); - const ocrProgress = document.getElementById('ocr-progress'); - if (ocrProgress) ocrProgress.classList.add('hidden'); + const ocrProgress = document.getElementById('ocr-progress'); + if (ocrProgress) ocrProgress.classList.add('hidden'); - const ocrResults = document.getElementById('ocr-results'); - if (ocrResults) ocrResults.classList.add('hidden'); + const ocrResults = document.getElementById('ocr-results'); + if (ocrResults) ocrResults.classList.add('hidden'); - const progressLog = document.getElementById('progress-log'); - if (progressLog) progressLog.textContent = ''; + const progressLog = document.getElementById('progress-log'); + if (progressLog) progressLog.textContent = ''; - const progressBar = document.getElementById('progress-bar'); - if (progressBar) progressBar.style.width = '0%'; + const progressBar = document.getElementById('progress-bar'); + if (progressBar) progressBar.style.width = '0%'; - const fileInput = document.getElementById('file-input') as HTMLInputElement; - if (fileInput) fileInput.value = ''; + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; - // Reset selected languages - const langCheckboxes = document.querySelectorAll('.lang-checkbox') as NodeListOf; - langCheckboxes.forEach(function (cb) { cb.checked = false; }); + // Reset selected languages + const langCheckboxes = document.querySelectorAll( + '.lang-checkbox' + ) as NodeListOf; + langCheckboxes.forEach(function (cb) { + cb.checked = false; + }); - const selectedLangsDisplay = document.getElementById('selected-langs-display'); - if (selectedLangsDisplay) selectedLangsDisplay.textContent = 'None'; + const selectedLangsDisplay = document.getElementById( + 'selected-langs-display' + ); + if (selectedLangsDisplay) selectedLangsDisplay.textContent = 'None'; - const processBtn = document.getElementById('process-btn') as HTMLButtonElement; - if (processBtn) processBtn.disabled = true; + const processBtn = document.getElementById( + 'process-btn' + ) as HTMLButtonElement; + if (processBtn) processBtn.disabled = true; } async function runOCR() { - const selectedLangs = Array.from( - document.querySelectorAll('.lang-checkbox:checked') - ).map(function (cb) { return (cb as HTMLInputElement).value; }); + const selectedLangs = Array.from( + document.querySelectorAll('.lang-checkbox:checked') + ).map(function (cb) { + return (cb as HTMLInputElement).value; + }); - const scale = parseFloat( - (document.getElementById('ocr-resolution') as HTMLSelectElement).value + const scale = parseFloat( + (document.getElementById('ocr-resolution') as HTMLSelectElement).value + ); + const binarize = (document.getElementById('ocr-binarize') as HTMLInputElement) + .checked; + const whitelist = ( + document.getElementById('ocr-whitelist') as HTMLInputElement + ).value; + + if (selectedLangs.length === 0) { + showAlert( + 'No Languages Selected', + 'Please select at least one language for OCR.' ); - const binarize = (document.getElementById('ocr-binarize') as HTMLInputElement).checked; - const whitelist = (document.getElementById('ocr-whitelist') as HTMLInputElement).value; + return; + } - if (selectedLangs.length === 0) { - showAlert('No Languages Selected', 'Please select at least one language for OCR.'); - return; - } + if (!pageState.file) { + showAlert('No File', 'Please upload a PDF file first.'); + return; + } - if (!pageState.file) { - showAlert('No File', 'Please upload a PDF file first.'); - return; - } + const langString = selectedLangs.join('+'); - const langString = selectedLangs.join('+'); + const toolOptions = document.getElementById('tool-options'); + const ocrProgress = document.getElementById('ocr-progress'); - const toolOptions = document.getElementById('tool-options'); - const ocrProgress = document.getElementById('ocr-progress'); + if (toolOptions) toolOptions.classList.add('hidden'); + if (ocrProgress) ocrProgress.classList.remove('hidden'); - if (toolOptions) toolOptions.classList.add('hidden'); - if (ocrProgress) ocrProgress.classList.remove('hidden'); + try { + const arrayBuffer = await pageState.file.arrayBuffer(); - try { - const worker = await Tesseract.createWorker(langString, 1, { - logger: function (m: { status: string; progress: number }) { - updateProgress(m.status, m.progress || 0); - }, - }); + const result = await performOcr(new Uint8Array(arrayBuffer), { + language: langString, + resolution: scale, + binarize, + whitelist, + onProgress: updateProgress, + }); - await worker.setParameters({ - tessjs_create_hocr: '1', - tessedit_pageseg_mode: Tesseract.PSM.AUTO, - }); + pageState.searchablePdfBytes = result.pdfBytes; - if (whitelist) { - await worker.setParameters({ - tessedit_char_whitelist: whitelist, - }); - } + const ocrResults = document.getElementById('ocr-results'); + if (ocrProgress) ocrProgress.classList.add('hidden'); + if (ocrResults) ocrResults.classList.remove('hidden'); - const arrayBuffer = await pageState.file.arrayBuffer(); - const pdf = await getPDFDocument({ data: arrayBuffer }).promise; - const newPdfDoc = await PDFLibDocument.create(); + createIcons({ icons }); - newPdfDoc.registerFontkit(fontkit); - - updateProgress('Loading fonts...', 0); - - const cjkLangs = ['jpn', 'chi_sim', 'chi_tra', 'kor']; - const indicLangs = ['hin', 'ben', 'guj', 'kan', 'mal', 'ori', 'pan', 'tam', 'tel', 'sin']; - const priorityLangs = [...cjkLangs, ...indicLangs, 'ara', 'rus', 'ukr']; - - const primaryLang = selectedLangs.find(function (l) { return priorityLangs.includes(l); }) || selectedLangs[0] || 'eng'; - - const hasCJK = selectedLangs.some(function (l) { return cjkLangs.includes(l); }); - const hasIndic = selectedLangs.some(function (l) { return indicLangs.includes(l); }); - const hasLatin = selectedLangs.some(function (l) { return !priorityLangs.includes(l); }) || selectedLangs.includes('eng'); - const isIndicPlusLatin = hasIndic && hasLatin && !hasCJK; - - let primaryFont; - let latinFont; - - try { - if (isIndicPlusLatin) { - const [scriptFontBytes, latinFontBytes] = await Promise.all([ - getFontForLanguage(primaryLang), - getFontForLanguage('eng') - ]); - primaryFont = await newPdfDoc.embedFont(scriptFontBytes, { subset: false }); - latinFont = await newPdfDoc.embedFont(latinFontBytes, { subset: false }); - } else { - const fontBytes = await getFontForLanguage(primaryLang); - primaryFont = await newPdfDoc.embedFont(fontBytes, { subset: false }); - latinFont = primaryFont; - } - } catch (e) { - console.error('Font loading failed, falling back to Helvetica', e); - primaryFont = await newPdfDoc.embedFont(StandardFonts.Helvetica); - latinFont = primaryFont; - showAlert('Font Warning', 'Could not load the specific font for this language. Some characters may not appear correctly.'); - } - - let fullText = ''; - - for (let i = 1; i <= pdf.numPages; i++) { - updateProgress(`Processing page ${i} of ${pdf.numPages}`, (i - 1) / pdf.numPages); - - const page = await pdf.getPage(i); - const viewport = page.getViewport({ scale }); - - const canvas = document.createElement('canvas'); - canvas.width = viewport.width; - canvas.height = viewport.height; - const context = canvas.getContext('2d')!; - - await page.render({ canvasContext: context, viewport, canvas }).promise; - - if (binarize) { - binarizeCanvas(context); - } - - const result = await worker.recognize(canvas, {}, { text: true, hocr: true }); - const data = result.data; - - const newPage = newPdfDoc.addPage([viewport.width, viewport.height]); - - const pngImageBytes = await new Promise(function (resolve) { - canvas.toBlob(function (blob) { - const reader = new FileReader(); - reader.onload = function () { - resolve(new Uint8Array(reader.result as ArrayBuffer)); - }; - reader.readAsArrayBuffer(blob!); - }, 'image/png'); - }); - - const pngImage = await newPdfDoc.embedPng(pngImageBytes); - newPage.drawImage(pngImage, { - x: 0, - y: 0, - width: viewport.width, - height: viewport.height, - }); - - if (data.hocr) { - const words = parseHOCR(data.hocr); - - words.forEach(function (word: Word) { - const { x0, y0, x1, y1 } = word.bbox; - const text = word.text.replace(/[\u0000-\u001F\u007F-\u009F\u200E\u200F\u202A-\u202E\uFEFF]/g, ''); - - if (!text.trim()) return; - - const hasNonLatin = /[^\u0000-\u007F]/.test(text); - const font = hasNonLatin ? primaryFont : latinFont; - - if (!font) { - console.warn(`Font not available for text: "${text}"`); - return; - } - - const bboxWidth = x1 - x0; - const bboxHeight = y1 - y0; - - if (bboxWidth <= 0 || bboxHeight <= 0) { - return; - } - - let fontSize = bboxHeight * 0.9; - try { - let textWidth = font.widthOfTextAtSize(text, fontSize); - while (textWidth > bboxWidth && fontSize > 1) { - fontSize -= 0.5; - textWidth = font.widthOfTextAtSize(text, fontSize); - } - } catch (error) { - console.warn(`Could not calculate text width for "${text}":`, error); - return; - } - - try { - newPage.drawText(text, { - x: x0, - y: viewport.height - y1 + (bboxHeight - fontSize) / 2, - font, - size: fontSize, - color: rgb(0, 0, 0), - opacity: 0, - }); - } catch (error) { - console.warn(`Could not draw text "${text}":`, error); - } - }); - } - - fullText += data.text + '\n\n'; - } - - await worker.terminate(); - - pageState.searchablePdfBytes = await newPdfDoc.save(); - - const ocrResults = document.getElementById('ocr-results'); - if (ocrProgress) ocrProgress.classList.add('hidden'); - if (ocrResults) ocrResults.classList.remove('hidden'); - - createIcons({ icons }); - - const textOutput = document.getElementById('ocr-text-output') as HTMLTextAreaElement; - if (textOutput) textOutput.value = fullText.trim(); - - } catch (e) { - console.error(e); - showAlert('OCR Error', 'An error occurred during the OCR process. The worker may have failed to load. Please try again.'); - if (toolOptions) toolOptions.classList.remove('hidden'); - if (ocrProgress) ocrProgress.classList.add('hidden'); - } + const textOutput = document.getElementById( + 'ocr-text-output' + ) as HTMLTextAreaElement; + if (textOutput) textOutput.value = result.fullText.trim(); + } catch (e) { + console.error(e); + showAlert( + 'OCR Error', + 'An error occurred during the OCR process. The worker may have failed to load. Please try again.' + ); + if (toolOptions) toolOptions.classList.remove('hidden'); + if (ocrProgress) ocrProgress.classList.add('hidden'); + } } async function updateUI() { - const fileDisplayArea = document.getElementById('file-display-area'); - const toolOptions = document.getElementById('tool-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const toolOptions = document.getElementById('tool-options'); - if (!fileDisplayArea) return; + if (!fileDisplayArea) return; - fileDisplayArea.innerHTML = ''; + fileDisplayArea.innerHTML = ''; - if (pageState.file) { - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + if (pageState.file) { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col overflow-hidden'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = pageState.file.name; + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = pageState.file.name; - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = formatBytes(pageState.file.size); + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(pageState.file.size); - infoContainer.append(nameSpan, metaSpan); + infoContainer.append(nameSpan, metaSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = function () { - resetState(); - }; + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = function () { + resetState(); + }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); - if (toolOptions) toolOptions.classList.remove('hidden'); - } else { - if (toolOptions) toolOptions.classList.add('hidden'); - } + if (toolOptions) toolOptions.classList.remove('hidden'); + } else { + if (toolOptions) toolOptions.classList.add('hidden'); + } } function handleFileSelect(files: FileList | null) { - if (files && files.length > 0) { - const file = files[0]; - if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) { - pageState.file = file; - updateUI(); - } + if (files && files.length > 0) { + const file = files[0]; + if ( + file.type === 'application/pdf' || + file.name.toLowerCase().endsWith('.pdf') + ) { + pageState.file = file; + updateUI(); } + } } function populateLanguageList() { - const langList = document.getElementById('lang-list'); - if (!langList) return; + const langList = document.getElementById('lang-list'); + if (!langList) return; - langList.innerHTML = ''; + langList.innerHTML = ''; - Object.entries(tesseractLanguages).forEach(function ([code, name]) { - const label = document.createElement('label'); - label.className = 'flex items-center gap-2 p-2 rounded-md hover:bg-gray-700 cursor-pointer'; + Object.entries(tesseractLanguages).forEach(function ([code, name]) { + const label = document.createElement('label'); + label.className = + 'flex items-center gap-2 p-2 rounded-md hover:bg-gray-700 cursor-pointer'; - const checkbox = document.createElement('input'); - checkbox.type = 'checkbox'; - checkbox.value = code; - checkbox.className = 'lang-checkbox w-4 h-4 rounded text-indigo-600 bg-gray-700 border-gray-600 focus:ring-indigo-500'; + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.value = code; + checkbox.className = + 'lang-checkbox w-4 h-4 rounded text-indigo-600 bg-gray-700 border-gray-600 focus:ring-indigo-500'; - label.append(checkbox); - label.append(document.createTextNode(' ' + name)); - langList.appendChild(label); - }); + label.append(checkbox); + label.append(document.createTextNode(' ' + name)); + langList.appendChild(label); + }); } document.addEventListener('DOMContentLoaded', function () { - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const processBtn = document.getElementById('process-btn') as HTMLButtonElement; - const backBtn = document.getElementById('back-to-tools'); - const langSearch = document.getElementById('lang-search') as HTMLInputElement; - const langList = document.getElementById('lang-list'); - const selectedLangsDisplay = document.getElementById('selected-langs-display'); - const presetSelect = document.getElementById('whitelist-preset') as HTMLSelectElement; - const whitelistInput = document.getElementById('ocr-whitelist') as HTMLInputElement; - const copyBtn = document.getElementById('copy-text-btn'); - const downloadTxtBtn = document.getElementById('download-txt-btn'); - const downloadPdfBtn = document.getElementById('download-searchable-pdf'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById( + 'process-btn' + ) as HTMLButtonElement; + const backBtn = document.getElementById('back-to-tools'); + const langSearch = document.getElementById('lang-search') as HTMLInputElement; + const langList = document.getElementById('lang-list'); + const selectedLangsDisplay = document.getElementById( + 'selected-langs-display' + ); + const presetSelect = document.getElementById( + 'whitelist-preset' + ) as HTMLSelectElement; + const whitelistInput = document.getElementById( + 'ocr-whitelist' + ) as HTMLInputElement; + const copyBtn = document.getElementById('copy-text-btn'); + const downloadTxtBtn = document.getElementById('download-txt-btn'); + const downloadPdfBtn = document.getElementById('download-searchable-pdf'); - populateLanguageList(); + populateLanguageList(); - if (backBtn) { - backBtn.addEventListener('click', function () { - window.location.href = import.meta.env.BASE_URL; - }); - } + if (backBtn) { + backBtn.addEventListener('click', function () { + window.location.href = import.meta.env.BASE_URL; + }); + } - if (fileInput && dropZone) { - fileInput.addEventListener('change', function (e) { - handleFileSelect((e.target as HTMLInputElement).files); - }); - - dropZone.addEventListener('dragover', function (e) { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); - - dropZone.addEventListener('dragleave', function (e) { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', function (e) { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const files = e.dataTransfer?.files; - if (files && files.length > 0) { - const pdfFiles = Array.from(files).filter(function (f) { - return f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf'); - }); - if (pdfFiles.length > 0) { - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(pdfFiles[0]); - handleFileSelect(dataTransfer.files); - } - } - }); - - fileInput.addEventListener('click', function () { - fileInput.value = ''; - }); - } - - // Language search - if (langSearch && langList) { - langSearch.addEventListener('input', function () { - const searchTerm = langSearch.value.toLowerCase(); - langList.querySelectorAll('label').forEach(function (label) { - (label as HTMLElement).style.display = label.textContent?.toLowerCase().includes(searchTerm) ? '' : 'none'; - }); - }); - - langList.addEventListener('change', function () { - const selected = Array.from( - langList.querySelectorAll('.lang-checkbox:checked') - ).map(function (cb) { - return tesseractLanguages[(cb as HTMLInputElement).value as keyof typeof tesseractLanguages]; - }); - - if (selectedLangsDisplay) { - selectedLangsDisplay.textContent = selected.length > 0 ? selected.join(', ') : 'None'; - } - - if (processBtn) { - processBtn.disabled = selected.length === 0; - } - }); - } - - // Whitelist preset - if (presetSelect && whitelistInput) { - presetSelect.addEventListener('change', function () { - const preset = presetSelect.value; - if (preset && preset !== 'custom') { - whitelistInput.value = whitelistPresets[preset] || ''; - whitelistInput.disabled = true; - } else { - whitelistInput.disabled = false; - if (preset === '') { - whitelistInput.value = ''; - } - } - }); - } - - // Details toggle - document.querySelectorAll('details').forEach(function (details) { - details.addEventListener('toggle', function () { - const icon = details.querySelector('.details-icon') as HTMLElement; - if (icon) { - icon.style.transform = (details as HTMLDetailsElement).open ? 'rotate(180deg)' : 'rotate(0deg)'; - } - }); + if (fileInput && dropZone) { + fileInput.addEventListener('change', function (e) { + handleFileSelect((e.target as HTMLInputElement).files); }); - // Process button - if (processBtn) { - processBtn.addEventListener('click', runOCR); - } + dropZone.addEventListener('dragover', function (e) { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); - // Copy button - if (copyBtn) { - copyBtn.addEventListener('click', function () { - const textOutput = document.getElementById('ocr-text-output') as HTMLTextAreaElement; - if (textOutput) { - navigator.clipboard.writeText(textOutput.value).then(function () { - copyBtn.innerHTML = ''; - createIcons({ icons }); + dropZone.addEventListener('dragleave', function (e) { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); - setTimeout(function () { - copyBtn.innerHTML = ''; - createIcons({ icons }); - }, 2000); - }); - } + dropZone.addEventListener('drop', function (e) { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const pdfFiles = Array.from(files).filter(function (f) { + return ( + f.type === 'application/pdf' || + f.name.toLowerCase().endsWith('.pdf') + ); }); - } + if (pdfFiles.length > 0) { + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(pdfFiles[0]); + handleFileSelect(dataTransfer.files); + } + } + }); - // Download txt - if (downloadTxtBtn) { - downloadTxtBtn.addEventListener('click', function () { - const textOutput = document.getElementById('ocr-text-output') as HTMLTextAreaElement; - if (textOutput) { - const blob = new Blob([textOutput.value], { type: 'text/plain' }); - downloadFile(blob, 'ocr-text.txt'); - } - }); - } + fileInput.addEventListener('click', function () { + fileInput.value = ''; + }); + } - // Download PDF - if (downloadPdfBtn) { - downloadPdfBtn.addEventListener('click', function () { - if (pageState.searchablePdfBytes) { - downloadFile( - new Blob([new Uint8Array(pageState.searchablePdfBytes)], { type: 'application/pdf' }), - 'searchable.pdf' - ); - } + // Language search + if (langSearch && langList) { + langSearch.addEventListener('input', function () { + const searchTerm = langSearch.value.toLowerCase(); + langList.querySelectorAll('label').forEach(function (label) { + (label as HTMLElement).style.display = label.textContent + ?.toLowerCase() + .includes(searchTerm) + ? '' + : 'none'; + }); + }); + + langList.addEventListener('change', function () { + const selected = Array.from( + langList.querySelectorAll('.lang-checkbox:checked') + ).map(function (cb) { + return tesseractLanguages[ + (cb as HTMLInputElement).value as keyof typeof tesseractLanguages + ]; + }); + + if (selectedLangsDisplay) { + selectedLangsDisplay.textContent = + selected.length > 0 ? selected.join(', ') : 'None'; + } + + if (processBtn) { + processBtn.disabled = selected.length === 0; + } + }); + } + + // Whitelist preset + if (presetSelect && whitelistInput) { + presetSelect.addEventListener('change', function () { + const preset = presetSelect.value; + if (preset && preset !== 'custom') { + whitelistInput.value = whitelistPresets[preset] || ''; + whitelistInput.disabled = true; + } else { + whitelistInput.disabled = false; + if (preset === '') { + whitelistInput.value = ''; + } + } + }); + } + + // Details toggle + document.querySelectorAll('details').forEach(function (details) { + details.addEventListener('toggle', function () { + const icon = details.querySelector('.details-icon') as HTMLElement; + if (icon) { + icon.style.transform = (details as HTMLDetailsElement).open + ? 'rotate(180deg)' + : 'rotate(0deg)'; + } + }); + }); + + // Process button + if (processBtn) { + processBtn.addEventListener('click', runOCR); + } + + // Copy button + if (copyBtn) { + copyBtn.addEventListener('click', function () { + const textOutput = document.getElementById( + 'ocr-text-output' + ) as HTMLTextAreaElement; + if (textOutput) { + navigator.clipboard.writeText(textOutput.value).then(function () { + copyBtn.innerHTML = + ''; + createIcons({ icons }); + + setTimeout(function () { + copyBtn.innerHTML = + ''; + createIcons({ icons }); + }, 2000); }); - } + } + }); + } + + // Download txt + if (downloadTxtBtn) { + downloadTxtBtn.addEventListener('click', function () { + const textOutput = document.getElementById( + 'ocr-text-output' + ) as HTMLTextAreaElement; + if (textOutput) { + const blob = new Blob([textOutput.value], { type: 'text/plain' }); + downloadFile(blob, 'ocr-text.txt'); + } + }); + } + + // Download PDF + if (downloadPdfBtn) { + downloadPdfBtn.addEventListener('click', function () { + if (pageState.searchablePdfBytes) { + downloadFile( + new Blob([new Uint8Array(pageState.searchablePdfBytes)], { + type: 'application/pdf', + }), + 'searchable.pdf' + ); + } + }); + } }); diff --git a/src/js/logic/odg-to-pdf-page.ts b/src/js/logic/odg-to-pdf-page.ts new file mode 100644 index 0000000..1d874d4 --- /dev/null +++ b/src/js/logic/odg-to-pdf-page.ts @@ -0,0 +1,189 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +const ACCEPTED_EXTENSIONS = ['.odg']; +const FILETYPE_NAME = 'ODG'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + updateUI(); + }; + + const convert = async () => { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${FILETYPE_NAME} file.`); + return; + } + + try { + const converter = getLibreOfficeConverter(); + + showLoader('Loading engine...'); + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + downloadFile(pdfBlob, `${baseName}.pdf`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${file.name} to PDF.`, 'success', () => resetState()); + } else { + showLoader('Converting multiple files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + zip.file(`${baseName}.pdf`, pdfBlob); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE_NAME.toLowerCase()}-to-pdf.zip`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${state.files.length} files to PDF.`, 'success', () => resetState()); + } + } catch (err) { + hideLoader(); + const message = err instanceof Error ? err.message : 'Unknown error'; + console.error(`[${FILETYPE_NAME}ToPDF] Error:`, err); + showAlert('Error', `An error occurred during conversion. Error: ${message}`); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter(file => { + const ext = '.' + file.name.split('.').pop()?.toLowerCase(); + return ACCEPTED_EXTENSIONS.includes(ext); + }); + if (validFiles.length > 0) { + state.files = [...state.files, ...validFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => fileInput.click()); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', resetState); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } + + updateUI(); +}); diff --git a/src/js/logic/odp-to-pdf-page.ts b/src/js/logic/odp-to-pdf-page.ts new file mode 100644 index 0000000..b9c7cd2 --- /dev/null +++ b/src/js/logic/odp-to-pdf-page.ts @@ -0,0 +1,189 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +const ACCEPTED_EXTENSIONS = ['.odp']; +const FILETYPE_NAME = 'ODP'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + updateUI(); + }; + + const convert = async () => { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${FILETYPE_NAME} file.`); + return; + } + + try { + const converter = getLibreOfficeConverter(); + + showLoader('Loading engine...'); + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + downloadFile(pdfBlob, `${baseName}.pdf`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${file.name} to PDF.`, 'success', () => resetState()); + } else { + showLoader('Converting multiple files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + zip.file(`${baseName}.pdf`, pdfBlob); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE_NAME.toLowerCase()}-to-pdf.zip`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${state.files.length} files to PDF.`, 'success', () => resetState()); + } + } catch (err) { + hideLoader(); + const message = err instanceof Error ? err.message : 'Unknown error'; + console.error(`[${FILETYPE_NAME}ToPDF] Error:`, err); + showAlert('Error', `An error occurred during conversion. Error: ${message}`); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter(file => { + const ext = '.' + file.name.split('.').pop()?.toLowerCase(); + return ACCEPTED_EXTENSIONS.includes(ext); + }); + if (validFiles.length > 0) { + state.files = [...state.files, ...validFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => fileInput.click()); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', resetState); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } + + updateUI(); +}); diff --git a/src/js/logic/ods-to-pdf-page.ts b/src/js/logic/ods-to-pdf-page.ts new file mode 100644 index 0000000..24343ec --- /dev/null +++ b/src/js/logic/ods-to-pdf-page.ts @@ -0,0 +1,189 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +const ACCEPTED_EXTENSIONS = ['.ods']; +const FILETYPE_NAME = 'ODS'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + updateUI(); + }; + + const convert = async () => { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${FILETYPE_NAME} file.`); + return; + } + + try { + const converter = getLibreOfficeConverter(); + + showLoader('Loading engine...'); + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + downloadFile(pdfBlob, `${baseName}.pdf`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${file.name} to PDF.`, 'success', () => resetState()); + } else { + showLoader('Converting multiple files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + zip.file(`${baseName}.pdf`, pdfBlob); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE_NAME.toLowerCase()}-to-pdf.zip`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${state.files.length} files to PDF.`, 'success', () => resetState()); + } + } catch (err) { + hideLoader(); + const message = err instanceof Error ? err.message : 'Unknown error'; + console.error(`[${FILETYPE_NAME}ToPDF] Error:`, err); + showAlert('Error', `An error occurred during conversion. Error: ${message}`); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter(file => { + const ext = '.' + file.name.split('.').pop()?.toLowerCase(); + return ACCEPTED_EXTENSIONS.includes(ext); + }); + if (validFiles.length > 0) { + state.files = [...state.files, ...validFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => fileInput.click()); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', resetState); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } + + updateUI(); +}); diff --git a/src/js/logic/odt-to-pdf-page.ts b/src/js/logic/odt-to-pdf-page.ts new file mode 100644 index 0000000..43ff895 --- /dev/null +++ b/src/js/logic/odt-to-pdf-page.ts @@ -0,0 +1,215 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + const convertToPdf = async () => { + try { + if (state.files.length === 0) { + showAlert('No Files', 'Please select at least one ODT file.'); + hideLoader(); + return; + } + + const converter = getLibreOfficeConverter(); + + // Initialize LibreOffice if not already done + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + + if (state.files.length === 1) { + const originalFile = state.files[0]; + + showLoader('Processing...'); + + const pdfBlob = await converter.convertToPdf(originalFile); + + const fileName = originalFile.name.replace(/\.odt$/i, '') + '.pdf'; + + downloadFile(pdfBlob, fileName); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${originalFile.name} to PDF.`, + 'success', + () => resetState() + ); + } else { + showLoader('Processing...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.odt$/i, ''); + const pdfBuffer = await pdfBlob.arrayBuffer(); + zip.file(`${baseName}.pdf`, pdfBuffer); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + + downloadFile(zipBlob, 'odt-converted.zip'); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${state.files.length} ODT file(s) to PDF.`, + 'success', + () => resetState() + ); + } + } catch (e: any) { + hideLoader(); + showAlert( + 'Error', + `An error occurred during conversion. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + state.files = [...state.files, ...Array.from(files)]; + updateUI(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const odtFiles = Array.from(files).filter(f => f.name.toLowerCase().endsWith('.odt') || f.type === 'application/vnd.oasis.opendocument.text'); + if (odtFiles.length > 0) { + const dataTransfer = new DataTransfer(); + odtFiles.forEach(f => dataTransfer.items.add(f)); + handleFileSelect(dataTransfer.files); + } + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + resetState(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } + + updateUI(); +}); diff --git a/src/js/logic/organize-pdf-page.ts b/src/js/logic/organize-pdf-page.ts index 251e0d2..7ca97ff 100644 --- a/src/js/logic/organize-pdf-page.ts +++ b/src/js/logic/organize-pdf-page.ts @@ -1,294 +1,425 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; -import { readFileAsArrayBuffer, formatBytes, downloadFile, getPDFDocument } from '../utils/helpers.js'; +import { + readFileAsArrayBuffer, + formatBytes, + downloadFile, + getPDFDocument, +} from '../utils/helpers.js'; +import { initPagePreview } from '../utils/page-preview.js'; import { PDFDocument } from 'pdf-lib'; import * as pdfjsLib from 'pdfjs-dist'; import Sortable from 'sortablejs'; -pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); interface OrganizeState { - file: File | null; - pdfDoc: any; - pdfJsDoc: any; - totalPages: number; - sortableInstance: any; + file: File | null; + pdfDoc: any; + pdfJsDoc: any; + totalPages: number; + sortableInstance: any; } const organizeState: OrganizeState = { - file: null, - pdfDoc: null, - pdfJsDoc: null, - totalPages: 0, - sortableInstance: null, + file: null, + pdfDoc: null, + pdfJsDoc: null, + totalPages: 0, + sortableInstance: null, }; if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } function initializePage() { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const processBtn = document.getElementById('process-btn'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); - if (fileInput) fileInput.addEventListener('change', handleFileUpload); + if (fileInput) fileInput.addEventListener('change', handleFileUpload); - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('bg-gray-700'); }); - dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('bg-gray-700'); }); - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) handleFile(droppedFiles[0]); - }); - // Clear value on click to allow re-selecting the same file - fileInput?.addEventListener('click', () => { - if (fileInput) fileInput.value = ''; - }); - } - - if (processBtn) processBtn.addEventListener('click', saveChanges); - - document.getElementById('back-to-tools')?.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); }); + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) handleFile(droppedFiles[0]); + }); + // Clear value on click to allow re-selecting the same file + fileInput?.addEventListener('click', () => { + if (fileInput) fileInput.value = ''; + }); + } + + if (processBtn) processBtn.addEventListener('click', saveChanges); + + document.getElementById('back-to-tools')?.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + + const applyOrderBtn = document.getElementById('apply-order-btn'); + if (applyOrderBtn) applyOrderBtn.addEventListener('click', applyCustomOrder); +} + +function applyCustomOrder() { + const orderInput = document.getElementById( + 'page-order-input' + ) as HTMLInputElement; + const grid = document.getElementById('page-grid'); + + if (!orderInput || !grid) return; + + const orderString = orderInput.value; + if (!orderString) { + showAlert('Invalid Order', 'Please enter a page order.'); + return; + } + + const newOrder = orderString.split(',').map((s) => parseInt(s.trim(), 10)); + + // Validation + const currentGridCount = grid.children.length; + const validNumbers = newOrder.every((n) => !isNaN(n) && n > 0); // Basic check, will validate against available thumbnails + if (!validNumbers) { + showAlert('Invalid Page Numbers', `Please enter positive numbers.`); + return; + } + + if (newOrder.length !== currentGridCount) { + showAlert( + 'Incorrect Page Count', + `The number of pages specified (${newOrder.length}) does not match the current number of pages in the document (${currentGridCount}). Please provide a complete ordering for all pages.` + ); + return; + } + + const uniqueNumbers = new Set(newOrder); + if (uniqueNumbers.size !== newOrder.length) { + showAlert( + 'Duplicate Page Numbers', + 'Please ensure all page numbers in the order are unique.' + ); + return; + } + + const currentThumbnails = Array.from(grid.children) as HTMLElement[]; + const reorderedThumbnails: HTMLElement[] = []; + const foundIndices = new Set(); + + for (const pageNum of newOrder) { + const originalIndexToFind = pageNum - 1; // pageNum is 1-based, originalPageIndex is 0-based + const foundThumbnail = currentThumbnails.find( + (thumb) => + thumb.dataset.originalPageIndex === originalIndexToFind.toString() + ); + + if (foundThumbnail) { + reorderedThumbnails.push(foundThumbnail); + foundIndices.add(originalIndexToFind.toString()); + } + } + + const allOriginalIndicesPresent = currentThumbnails.every((thumb) => + foundIndices.has(thumb.dataset.originalPageIndex) + ); + + if ( + reorderedThumbnails.length !== currentGridCount || + !allOriginalIndicesPresent + ) { + showAlert( + 'Invalid Page Order', + 'The specified page order is incomplete or contains invalid page numbers. Please ensure you provide a new position for every original page.' + ); + return; + } + + // Clear the grid and append the reordered thumbnails + grid.innerHTML = ''; + reorderedThumbnails.forEach((thumb) => grid.appendChild(thumb)); + + initializeSortable(); // Re-initialize sortable on the new order + + showAlert('Success', 'Pages have been reordered.', 'success'); } function handleFileUpload(e: Event) { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) handleFile(input.files[0]); + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) handleFile(input.files[0]); } async function handleFile(file: File) { - if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { - showAlert('Invalid File', 'Please select a PDF file.'); - return; - } + if ( + file.type !== 'application/pdf' && + !file.name.toLowerCase().endsWith('.pdf') + ) { + showAlert('Invalid File', 'Please select a PDF file.'); + return; + } - showLoader('Loading PDF...'); - organizeState.file = file; + showLoader('Loading PDF...'); + organizeState.file = file; - try { - const arrayBuffer = await readFileAsArrayBuffer(file); - organizeState.pdfDoc = await PDFDocument.load(arrayBuffer as ArrayBuffer, { ignoreEncryption: true, throwOnInvalidObject: false }); - organizeState.pdfJsDoc = await getPDFDocument({ data: (arrayBuffer as ArrayBuffer).slice(0) }).promise; - organizeState.totalPages = organizeState.pdfDoc.getPageCount(); + try { + const arrayBuffer = await readFileAsArrayBuffer(file); + organizeState.pdfDoc = await PDFDocument.load(arrayBuffer as ArrayBuffer, { + ignoreEncryption: true, + throwOnInvalidObject: false, + }); + organizeState.pdfJsDoc = await getPDFDocument({ + data: (arrayBuffer as ArrayBuffer).slice(0), + }).promise; + organizeState.totalPages = organizeState.pdfDoc.getPageCount(); - updateFileDisplay(); - await renderThumbnails(); - hideLoader(); - } catch (error) { - console.error('Error loading PDF:', error); - hideLoader(); - showAlert('Error', 'Failed to load PDF file.'); - } + updateFileDisplay(); + await renderThumbnails(); + hideLoader(); + } catch (error) { + console.error('Error loading PDF:', error); + hideLoader(); + showAlert('Error', 'Failed to load PDF file.'); + } } function updateFileDisplay() { - const fileDisplayArea = document.getElementById('file-display-area'); - if (!fileDisplayArea || !organizeState.file) return; + const fileDisplayArea = document.getElementById('file-display-area'); + if (!fileDisplayArea || !organizeState.file) return; - fileDisplayArea.innerHTML = ''; - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + fileDisplayArea.innerHTML = ''; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col flex-1 min-w-0'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = organizeState.file.name; + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = organizeState.file.name; - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(organizeState.file.size)} • ${organizeState.totalPages} pages`; + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(organizeState.file.size)} • ${organizeState.totalPages} pages`; - infoContainer.append(nameSpan, metaSpan); + infoContainer.append(nameSpan, metaSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => resetState(); + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => resetState(); - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); } function renumberPages() { - const grid = document.getElementById('page-grid'); - if (!grid) return; - const labels = grid.querySelectorAll('.page-number'); - labels.forEach((label, index) => { - label.textContent = (index + 1).toString(); - }); + const grid = document.getElementById('page-grid'); + if (!grid) return; + const labels = grid.querySelectorAll('.page-number'); + labels.forEach((label, index) => { + label.textContent = (index + 1).toString(); + }); } function attachEventListeners(element: HTMLElement) { - const duplicateBtn = element.querySelector('.duplicate-btn'); - const deleteBtn = element.querySelector('.delete-btn'); + const duplicateBtn = element.querySelector('.duplicate-btn'); + const deleteBtn = element.querySelector('.delete-btn'); - duplicateBtn?.addEventListener('click', (e) => { - e.stopPropagation(); - const clone = element.cloneNode(true) as HTMLElement; - element.after(clone); - attachEventListeners(clone); - renumberPages(); - createIcons({ icons }); - initializeSortable(); - }); + duplicateBtn?.addEventListener('click', (e) => { + e.stopPropagation(); + const clone = element.cloneNode(true) as HTMLElement; + element.after(clone); + attachEventListeners(clone); + renumberPages(); + createIcons({ icons }); + initializeSortable(); + }); - deleteBtn?.addEventListener('click', (e) => { - e.stopPropagation(); - const grid = document.getElementById('page-grid'); - if (grid && grid.children.length > 1) { - element.remove(); - renumberPages(); - initializeSortable(); - } else { - showAlert('Cannot Delete', 'You cannot delete the last page of the document.'); - } - }); + deleteBtn?.addEventListener('click', (e) => { + e.stopPropagation(); + const grid = document.getElementById('page-grid'); + if (grid && grid.children.length > 1) { + element.remove(); + renumberPages(); + initializeSortable(); + } else { + showAlert( + 'Cannot Delete', + 'You cannot delete the last page of the document.' + ); + } + }); } async function renderThumbnails() { - const grid = document.getElementById('page-grid'); - const processBtn = document.getElementById('process-btn'); - if (!grid) return; + const grid = document.getElementById('page-grid'); + const processBtn = document.getElementById('process-btn'); + const advancedSettings = document.getElementById('advanced-settings'); + if (!grid || !processBtn || !advancedSettings) return; - grid.innerHTML = ''; - grid.classList.remove('hidden'); - processBtn?.classList.remove('hidden'); + grid.innerHTML = ''; + grid.classList.remove('hidden'); + processBtn.classList.remove('hidden'); + advancedSettings.classList.remove('hidden'); - for (let i = 1; i <= organizeState.totalPages; i++) { - const page = await organizeState.pdfJsDoc.getPage(i); - const viewport = page.getViewport({ scale: 0.5 }); + for (let i = 1; i <= organizeState.totalPages; i++) { + const page = await organizeState.pdfJsDoc.getPage(i); + const viewport = page.getViewport({ scale: 1 }); - const canvas = document.createElement('canvas'); - canvas.width = viewport.width; - canvas.height = viewport.height; - const ctx = canvas.getContext('2d'); - await page.render({ canvasContext: ctx, viewport }).promise; + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + const ctx = canvas.getContext('2d'); + await page.render({ canvasContext: ctx, viewport }).promise; - const wrapper = document.createElement('div'); - wrapper.className = 'page-thumbnail relative cursor-move flex flex-col items-center gap-2'; - wrapper.dataset.originalPageIndex = (i - 1).toString(); + const wrapper = document.createElement('div'); + wrapper.className = + 'page-thumbnail relative cursor-move flex flex-col items-center gap-1 p-2 border-2 border-gray-600 hover:border-indigo-500 rounded-lg bg-gray-700 transition-colors group'; + wrapper.dataset.originalPageIndex = (i - 1).toString(); + wrapper.dataset.pageNumber = i.toString(); - const imgContainer = document.createElement('div'); - imgContainer.className = 'w-full h-36 bg-gray-900 rounded-lg flex items-center justify-center overflow-hidden border-2 border-gray-600'; + const imgContainer = document.createElement('div'); + imgContainer.className = 'relative'; - const img = document.createElement('img'); - img.src = canvas.toDataURL(); - img.className = 'max-w-full max-h-full object-contain'; - imgContainer.appendChild(img); + const img = document.createElement('img'); + img.src = canvas.toDataURL(); + img.className = 'rounded-md shadow-md max-w-full h-auto'; + imgContainer.appendChild(img); - const pageLabel = document.createElement('span'); - pageLabel.className = 'page-number absolute top-1 left-1 bg-gray-900 bg-opacity-75 text-white text-xs rounded-full px-2 py-1'; - pageLabel.textContent = i.toString(); + const pageLabel = document.createElement('div'); + pageLabel.className = + 'page-number absolute top-1 left-1 bg-indigo-600 text-white text-xs px-2 py-1 rounded-md font-semibold shadow-lg z-10 pointer-events-none'; + pageLabel.textContent = i.toString(); + imgContainer.appendChild(pageLabel); - const controlsDiv = document.createElement('div'); - controlsDiv.className = 'flex items-center justify-center gap-4'; + const controlsDiv = document.createElement('div'); + controlsDiv.className = 'flex items-center justify-center gap-4'; - const duplicateBtn = document.createElement('button'); - duplicateBtn.className = 'duplicate-btn bg-green-600 hover:bg-green-700 text-white rounded-full w-8 h-8 flex items-center justify-center'; - duplicateBtn.title = 'Duplicate Page'; - duplicateBtn.innerHTML = ''; + const duplicateBtn = document.createElement('button'); + duplicateBtn.className = + 'duplicate-btn bg-green-600 hover:bg-green-700 text-white rounded-full w-8 h-8 flex items-center justify-center'; + duplicateBtn.title = 'Duplicate Page'; + duplicateBtn.innerHTML = ''; - const deleteBtn = document.createElement('button'); - deleteBtn.className = 'delete-btn bg-red-600 hover:bg-red-700 text-white rounded-full w-8 h-8 flex items-center justify-center'; - deleteBtn.title = 'Delete Page'; - deleteBtn.innerHTML = ''; + const deleteBtn = document.createElement('button'); + deleteBtn.className = + 'delete-btn bg-red-600 hover:bg-red-700 text-white rounded-full w-8 h-8 flex items-center justify-center'; + deleteBtn.title = 'Delete Page'; + deleteBtn.innerHTML = ''; - controlsDiv.append(duplicateBtn, deleteBtn); - wrapper.append(imgContainer, pageLabel, controlsDiv); - grid.appendChild(wrapper); + controlsDiv.append(duplicateBtn, deleteBtn); + wrapper.append(imgContainer, controlsDiv); + grid.appendChild(wrapper); - attachEventListeners(wrapper); - } + attachEventListeners(wrapper); + } - createIcons({ icons }); - initializeSortable(); + createIcons({ icons }); + initializeSortable(); + initPagePreview(grid, organizeState.pdfJsDoc); } function initializeSortable() { - const grid = document.getElementById('page-grid'); - if (!grid) return; + const grid = document.getElementById('page-grid'); + if (!grid) return; - if (organizeState.sortableInstance) organizeState.sortableInstance.destroy(); + if (organizeState.sortableInstance) organizeState.sortableInstance.destroy(); - organizeState.sortableInstance = Sortable.create(grid, { - animation: 150, - ghostClass: 'sortable-ghost', - chosenClass: 'sortable-chosen', - dragClass: 'sortable-drag', - filter: '.duplicate-btn, .delete-btn', - preventOnFilter: true, - onStart: (evt) => { - if (evt.item) evt.item.style.opacity = '0.5'; - }, - onEnd: (evt) => { - if (evt.item) evt.item.style.opacity = '1'; - }, - }); + organizeState.sortableInstance = Sortable.create(grid, { + animation: 150, + ghostClass: 'sortable-ghost', + chosenClass: 'sortable-chosen', + dragClass: 'sortable-drag', + filter: '.duplicate-btn, .delete-btn', + preventOnFilter: true, + onStart: (evt) => { + if (evt.item) evt.item.style.opacity = '0.5'; + }, + onEnd: (evt) => { + if (evt.item) evt.item.style.opacity = '1'; + }, + }); } async function saveChanges() { - showLoader('Building new PDF...'); + showLoader('Building new PDF...'); - try { - const grid = document.getElementById('page-grid'); - if (!grid) return; + try { + const grid = document.getElementById('page-grid'); + if (!grid) return; - const finalPageElements = grid.querySelectorAll('.page-thumbnail'); - const finalIndices = Array.from(finalPageElements) - .map(el => parseInt((el as HTMLElement).dataset.originalPageIndex || '', 10)) - .filter(index => !isNaN(index) && index >= 0); + const finalPageElements = grid.querySelectorAll('.page-thumbnail'); + const finalIndices = Array.from(finalPageElements) + .map((el) => + parseInt((el as HTMLElement).dataset.originalPageIndex || '', 10) + ) + .filter((index) => !isNaN(index) && index >= 0); - if (finalIndices.length === 0) { - showAlert('Error', 'No valid pages to save.'); - return; - } - - const newPdf = await PDFDocument.create(); - const copiedPages = await newPdf.copyPages(organizeState.pdfDoc, finalIndices); - copiedPages.forEach(page => newPdf.addPage(page)); - - const pdfBytes = await newPdf.save(); - const baseName = organizeState.file?.name.replace('.pdf', '') || 'document'; - downloadFile(new Blob([pdfBytes as BlobPart], { type: 'application/pdf' }), `${baseName}_organized.pdf`); - - hideLoader(); - showAlert('Success', 'PDF organized successfully!', 'success', () => resetState()); - } catch (error) { - console.error('Error saving changes:', error); - hideLoader(); - showAlert('Error', 'Failed to save changes.'); + if (finalIndices.length === 0) { + showAlert('Error', 'No valid pages to save.'); + return; } + + const newPdf = await PDFDocument.create(); + const copiedPages = await newPdf.copyPages( + organizeState.pdfDoc, + finalIndices + ); + copiedPages.forEach((page) => newPdf.addPage(page)); + + const pdfBytes = await newPdf.save(); + const baseName = organizeState.file?.name.replace('.pdf', '') || 'document'; + downloadFile( + new Blob([pdfBytes as BlobPart], { type: 'application/pdf' }), + `${baseName}_organized.pdf` + ); + + hideLoader(); + showAlert('Success', 'PDF organized successfully!', 'success', () => + resetState() + ); + } catch (error) { + console.error('Error saving changes:', error); + hideLoader(); + showAlert('Error', 'Failed to save changes.'); + } } function resetState() { - if (organizeState.sortableInstance) { - organizeState.sortableInstance.destroy(); - organizeState.sortableInstance = null; - } + if (organizeState.sortableInstance) { + organizeState.sortableInstance.destroy(); + organizeState.sortableInstance = null; + } - organizeState.file = null; - organizeState.pdfDoc = null; - organizeState.pdfJsDoc = null; - organizeState.totalPages = 0; + organizeState.file = null; + organizeState.pdfDoc = null; + organizeState.pdfJsDoc = null; + organizeState.totalPages = 0; - const grid = document.getElementById('page-grid'); - if (grid) { - grid.innerHTML = ''; - grid.classList.add('hidden'); - } - document.getElementById('process-btn')?.classList.add('hidden'); - const fileDisplayArea = document.getElementById('file-display-area'); - if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + const grid = document.getElementById('page-grid'); + if (grid) { + grid.innerHTML = ''; + grid.classList.add('hidden'); + } + document.getElementById('process-btn')?.classList.add('hidden'); + document.getElementById('advanced-settings')?.classList.add('hidden'); + const fileDisplayArea = document.getElementById('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; } diff --git a/src/js/logic/page-dimensions-page.ts b/src/js/logic/page-dimensions-page.ts index 9f1c9d0..71458f4 100644 --- a/src/js/logic/page-dimensions-page.ts +++ b/src/js/logic/page-dimensions-page.ts @@ -2,13 +2,9 @@ import { showAlert } from '../ui.js'; import { formatBytes, getStandardPageName, convertPoints } from '../utils/helpers.js'; import { PDFDocument } from 'pdf-lib'; import { icons, createIcons } from 'lucide'; +import { PageDimensionsState } from '@/types'; -interface PageState { - file: File | null; - pdfDoc: PDFDocument | null; -} - -const pageState: PageState = { +const pageState: PageDimensionsState = { file: null, pdfDoc: null, }; diff --git a/src/js/logic/page-numbers-page.ts b/src/js/logic/page-numbers-page.ts index ae387fe..3971814 100644 --- a/src/js/logic/page-numbers-page.ts +++ b/src/js/logic/page-numbers-page.ts @@ -1,230 +1,190 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; import { downloadFile, hexToRgb, formatBytes } from '../utils/helpers.js'; -import { PDFDocument as PDFLibDocument, rgb, StandardFonts } from 'pdf-lib'; +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; +import { + addPageNumbers as addPageNumbersToPdf, + type PageNumberPosition, + type PageNumberFormat, +} from '../utils/pdf-operations.js'; interface PageState { - file: File | null; - pdfDoc: PDFLibDocument | null; + file: File | null; + pdfDoc: PDFLibDocument | null; } const pageState: PageState = { - file: null, - pdfDoc: null, + file: null, + pdfDoc: null, }; if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } function initializePage() { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const backBtn = document.getElementById('back-to-tools'); - const processBtn = document.getElementById('process-btn'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); - if (fileInput) { - fileInput.addEventListener('change', handleFileUpload); - fileInput.addEventListener('click', () => { fileInput.value = ''; }); - } + if (fileInput) { + fileInput.addEventListener('change', handleFileUpload); + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('border-indigo-500'); - }); + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('border-indigo-500'); + }); - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('border-indigo-500'); - }); + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('border-indigo-500'); + }); - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('border-indigo-500'); - if (e.dataTransfer?.files.length) { - handleFiles(e.dataTransfer.files); - } - }); - } + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('border-indigo-500'); + if (e.dataTransfer?.files.length) { + handleFiles(e.dataTransfer.files); + } + }); + } - if (backBtn) { - backBtn.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; - }); - } + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } - if (processBtn) { - processBtn.addEventListener('click', addPageNumbers); - } + if (processBtn) { + processBtn.addEventListener('click', addPageNumbers); + } } function handleFileUpload(e: Event) { - const input = e.target as HTMLInputElement; - if (input.files?.length) { - handleFiles(input.files); - } + const input = e.target as HTMLInputElement; + if (input.files?.length) { + handleFiles(input.files); + } } async function handleFiles(files: FileList) { - const file = files[0]; - if (!file || file.type !== 'application/pdf') { - showAlert('Invalid File', 'Please upload a valid PDF file.'); - return; - } + const file = files[0]; + if (!file || file.type !== 'application/pdf') { + showAlert('Invalid File', 'Please upload a valid PDF file.'); + return; + } - showLoader('Loading PDF...'); - try { - const arrayBuffer = await file.arrayBuffer(); - pageState.pdfDoc = await PDFLibDocument.load(arrayBuffer); - pageState.file = file; + showLoader('Loading PDF...'); + try { + const arrayBuffer = await file.arrayBuffer(); + pageState.pdfDoc = await PDFLibDocument.load(arrayBuffer); + pageState.file = file; - updateFileDisplay(); - document.getElementById('options-panel')?.classList.remove('hidden'); - } catch (error) { - console.error(error); - showAlert('Error', 'Failed to load PDF file.'); - } finally { - hideLoader(); - } + updateFileDisplay(); + document.getElementById('options-panel')?.classList.remove('hidden'); + } catch (error) { + console.error(error); + showAlert('Error', 'Failed to load PDF file.'); + } finally { + hideLoader(); + } } function updateFileDisplay() { - const fileDisplayArea = document.getElementById('file-display-area'); - if (!fileDisplayArea || !pageState.file || !pageState.pdfDoc) return; + const fileDisplayArea = document.getElementById('file-display-area'); + if (!fileDisplayArea || !pageState.file || !pageState.pdfDoc) return; - fileDisplayArea.innerHTML = ''; - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + fileDisplayArea.innerHTML = ''; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col flex-1 min-w-0'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = pageState.file.name; + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = pageState.file.name; - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageState.pdfDoc.getPageCount()} pages`; + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageState.pdfDoc.getPageCount()} pages`; - infoContainer.append(nameSpan, metaSpan); + infoContainer.append(nameSpan, metaSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = resetState; + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = resetState; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); } function resetState() { - pageState.file = null; - pageState.pdfDoc = null; - const fileDisplayArea = document.getElementById('file-display-area'); - if (fileDisplayArea) fileDisplayArea.innerHTML = ''; - document.getElementById('options-panel')?.classList.add('hidden'); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - if (fileInput) fileInput.value = ''; + pageState.file = null; + pageState.pdfDoc = null; + const fileDisplayArea = document.getElementById('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + document.getElementById('options-panel')?.classList.add('hidden'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; } async function addPageNumbers() { - if (!pageState.pdfDoc) { - showAlert('Error', 'Please upload a PDF file first.'); - return; - } + if (!pageState.pdfDoc) { + showAlert('Error', 'Please upload a PDF file first.'); + return; + } - showLoader('Adding page numbers...'); - try { - const position = (document.getElementById('position') as HTMLSelectElement).value; - const fontSize = parseInt((document.getElementById('font-size') as HTMLInputElement).value) || 12; - const format = (document.getElementById('number-format') as HTMLSelectElement).value; - const colorHex = (document.getElementById('text-color') as HTMLInputElement).value; - const textColor = hexToRgb(colorHex); + showLoader('Adding page numbers...'); + try { + const position = (document.getElementById('position') as HTMLSelectElement) + .value as PageNumberPosition; + const fontSize = + parseInt( + (document.getElementById('font-size') as HTMLInputElement).value + ) || 12; + const format = + (document.getElementById('number-format') as HTMLSelectElement).value === + 'page_x_of_y' + ? ('page_x_of_y' as PageNumberFormat) + : ('simple' as PageNumberFormat); + const colorHex = (document.getElementById('text-color') as HTMLInputElement) + .value; + const textColor = hexToRgb(colorHex); - const pages = pageState.pdfDoc.getPages(); - const totalPages = pages.length; - const helveticaFont = await pageState.pdfDoc.embedFont(StandardFonts.Helvetica); + const pdfBytes = new Uint8Array(await pageState.pdfDoc.save()); + const resultBytes = await addPageNumbersToPdf(pdfBytes, { + position, + fontSize, + format, + color: textColor, + }); - for (let i = 0; i < totalPages; i++) { - const page = pages[i]; - - const mediaBox = page.getMediaBox(); - const cropBox = page.getCropBox(); - const bounds = cropBox || mediaBox; - const width = bounds.width; - const height = bounds.height; - const xOffset = bounds.x || 0; - const yOffset = bounds.y || 0; - - let pageNumText = format === 'page_x_of_y' ? `${i + 1} / ${totalPages}` : `${i + 1}`; - - const textWidth = helveticaFont.widthOfTextAtSize(pageNumText, fontSize); - const textHeight = fontSize; - - const minMargin = 8; - const maxMargin = 40; - const marginPercentage = 0.04; - - const horizontalMargin = Math.max(minMargin, Math.min(maxMargin, width * marginPercentage)); - const verticalMargin = Math.max(minMargin, Math.min(maxMargin, height * marginPercentage)); - - const safeHorizontalMargin = Math.max(horizontalMargin, textWidth / 2 + 3); - const safeVerticalMargin = Math.max(verticalMargin, textHeight + 3); - - let x = 0, y = 0; - - switch (position) { - case 'bottom-center': - x = Math.max(safeHorizontalMargin, Math.min(width - safeHorizontalMargin - textWidth, (width - textWidth) / 2)) + xOffset; - y = safeVerticalMargin + yOffset; - break; - case 'bottom-left': - x = safeHorizontalMargin + xOffset; - y = safeVerticalMargin + yOffset; - break; - case 'bottom-right': - x = Math.max(safeHorizontalMargin, width - safeHorizontalMargin - textWidth) + xOffset; - y = safeVerticalMargin + yOffset; - break; - case 'top-center': - x = Math.max(safeHorizontalMargin, Math.min(width - safeHorizontalMargin - textWidth, (width - textWidth) / 2)) + xOffset; - y = height - safeVerticalMargin - textHeight + yOffset; - break; - case 'top-left': - x = safeHorizontalMargin + xOffset; - y = height - safeVerticalMargin - textHeight + yOffset; - break; - case 'top-right': - x = Math.max(safeHorizontalMargin, width - safeHorizontalMargin - textWidth) + xOffset; - y = height - safeVerticalMargin - textHeight + yOffset; - break; - } - - x = Math.max(xOffset + 3, Math.min(xOffset + width - textWidth - 3, x)); - y = Math.max(yOffset + 3, Math.min(yOffset + height - textHeight - 3, y)); - - page.drawText(pageNumText, { - x, - y, - font: helveticaFont, - size: fontSize, - color: rgb(textColor.r, textColor.g, textColor.b), - }); - } - - const newPdfBytes = await pageState.pdfDoc.save(); - downloadFile(new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }), 'paginated.pdf'); - showAlert('Success', 'Page numbers added successfully!', 'success', () => { resetState(); }); - } catch (e) { - console.error(e); - showAlert('Error', 'Could not add page numbers.'); - } finally { - hideLoader(); - } + downloadFile( + new Blob([resultBytes as unknown as BlobPart], { + type: 'application/pdf', + }), + 'paginated.pdf' + ); + showAlert('Success', 'Page numbers added successfully!', 'success', () => { + resetState(); + }); + } catch (e) { + console.error(e); + showAlert('Error', 'Could not add page numbers.'); + } finally { + hideLoader(); + } } diff --git a/src/js/logic/pages-to-pdf-page.ts b/src/js/logic/pages-to-pdf-page.ts new file mode 100644 index 0000000..506eced --- /dev/null +++ b/src/js/logic/pages-to-pdf-page.ts @@ -0,0 +1,188 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +const ACCEPTED_EXTENSIONS = ['.pages']; +const FILETYPE_NAME = 'Pages'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + updateUI(); + }; + + const convert = async () => { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${FILETYPE_NAME} file.`); + return; + } + + try { + const converter = getLibreOfficeConverter(); + + showLoader('Loading engine...'); + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + downloadFile(pdfBlob, `${baseName}.pdf`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${file.name} to PDF.`, 'success', () => resetState()); + } else { + showLoader('Converting multiple files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + zip.file(`${baseName}.pdf`, pdfBlob); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE_NAME.toLowerCase()}-to-pdf.zip`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${state.files.length} files to PDF.`, 'success', () => resetState()); + } + } catch (e: any) { + hideLoader(); + console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e); + showAlert('Error', `An error occurred during conversion. Error: ${e.message}`); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter(file => { + const ext = '.' + file.name.split('.').pop()?.toLowerCase(); + return ACCEPTED_EXTENSIONS.includes(ext); + }); + if (validFiles.length > 0) { + state.files = [...state.files, ...validFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => fileInput.click()); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', resetState); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } + + updateUI(); +}); diff --git a/src/js/logic/pdf-booklet-page.ts b/src/js/logic/pdf-booklet-page.ts new file mode 100644 index 0000000..0fbca6b --- /dev/null +++ b/src/js/logic/pdf-booklet-page.ts @@ -0,0 +1,622 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { createIcons, icons } from 'lucide'; +import { PDFDocument as PDFLibDocument, degrees, PageSizes } from 'pdf-lib'; +import * as pdfjsLib from 'pdfjs-dist'; + +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.mjs', + import.meta.url +).toString(); + +interface BookletState { + file: File | null; + pdfDoc: PDFLibDocument | null; + pdfBytes: Uint8Array | null; + pdfjsDoc: pdfjsLib.PDFDocumentProxy | null; +} + +const pageState: BookletState = { + file: null, + pdfDoc: null, + pdfBytes: null, + pdfjsDoc: null, +}; + +function resetState() { + pageState.file = null; + pageState.pdfDoc = null; + pageState.pdfBytes = null; + pageState.pdfjsDoc = null; + + const fileDisplayArea = document.getElementById('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + + const toolOptions = document.getElementById('tool-options'); + if (toolOptions) toolOptions.classList.add('hidden'); + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; + + const previewArea = document.getElementById('booklet-preview'); + if (previewArea) + previewArea.innerHTML = + '

Upload a PDF and click "Generate Preview" to see the booklet layout

'; + + const downloadBtn = document.getElementById( + 'download-btn' + ) as HTMLButtonElement; + if (downloadBtn) downloadBtn.disabled = true; +} + +async function updateUI() { + const fileDisplayArea = document.getElementById('file-display-area'); + const toolOptions = document.getElementById('tool-options'); + + if (!fileDisplayArea) return; + + fileDisplayArea.innerHTML = ''; + + if (pageState.file) { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = pageState.file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(pageState.file.size)} • Loading...`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = function () { + resetState(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); + + try { + showLoader('Loading PDF...'); + const arrayBuffer = await pageState.file.arrayBuffer(); + pageState.pdfBytes = new Uint8Array(arrayBuffer); + + pageState.pdfDoc = await PDFLibDocument.load(pageState.pdfBytes, { + ignoreEncryption: true, + throwOnInvalidObject: false, + }); + + pageState.pdfjsDoc = await pdfjsLib.getDocument({ + data: pageState.pdfBytes.slice(), + }).promise; + + hideLoader(); + + const pageCount = pageState.pdfDoc.getPageCount(); + metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageCount} pages`; + + if (toolOptions) toolOptions.classList.remove('hidden'); + + const previewBtn = document.getElementById( + 'preview-btn' + ) as HTMLButtonElement; + if (previewBtn) previewBtn.disabled = false; + } catch (error) { + console.error('Error loading PDF:', error); + hideLoader(); + showAlert('Error', 'Failed to load PDF file.'); + resetState(); + } + } else { + if (toolOptions) toolOptions.classList.add('hidden'); + } +} + +function getGridDimensions(): { rows: number; cols: number } { + const gridMode = + ( + document.querySelector( + 'input[name="grid-mode"]:checked' + ) as HTMLInputElement + )?.value || '1x2'; + switch (gridMode) { + case '1x2': + return { rows: 1, cols: 2 }; + case '2x2': + return { rows: 2, cols: 2 }; + case '2x4': + return { rows: 2, cols: 4 }; + case '4x4': + return { rows: 4, cols: 4 }; + default: + return { rows: 1, cols: 2 }; + } +} + +function getOrientation(isBookletMode: boolean): 'portrait' | 'landscape' { + const orientationValue = + ( + document.querySelector( + 'input[name="orientation"]:checked' + ) as HTMLInputElement + )?.value || 'auto'; + if (orientationValue === 'portrait') return 'portrait'; + if (orientationValue === 'landscape') return 'landscape'; + return isBookletMode ? 'landscape' : 'portrait'; +} + +function getSheetDimensions(isBookletMode: boolean): { + width: number; + height: number; +} { + const paperSizeKey = ( + document.getElementById('paper-size') as HTMLSelectElement + ).value as keyof typeof PageSizes; + const pageDims = PageSizes[paperSizeKey] || PageSizes.Letter; + const orientation = getOrientation(isBookletMode); + if (orientation === 'landscape') { + return { width: pageDims[1], height: pageDims[0] }; + } + return { width: pageDims[0], height: pageDims[1] }; +} + +async function generatePreview() { + if (!pageState.pdfDoc || !pageState.pdfjsDoc) { + showAlert('Error', 'Please load a PDF first.'); + return; + } + + const previewArea = document.getElementById('booklet-preview')!; + const totalPages = pageState.pdfDoc.getPageCount(); + const { rows, cols } = getGridDimensions(); + const pagesPerSheet = rows * cols; + const isBookletMode = rows === 1 && cols === 2; + + let numSheets: number; + if (isBookletMode) { + const sheetsNeeded = Math.ceil(totalPages / 4); + numSheets = sheetsNeeded * 2; + } else { + numSheets = Math.ceil(totalPages / pagesPerSheet); + } + + const { width: sheetWidth, height: sheetHeight } = + getSheetDimensions(isBookletMode); + + // Get container width to make canvas fill it + const previewContainer = document.getElementById('booklet-preview')!; + const containerWidth = previewContainer.clientWidth - 32; // account for padding + const aspectRatio = sheetWidth / sheetHeight; + const canvasWidth = containerWidth; + const canvasHeight = containerWidth / aspectRatio; + + previewArea.innerHTML = + '

Generating preview...

'; + + const totalRounded = isBookletMode + ? Math.ceil(totalPages / 4) * 4 + : totalPages; + const rotationMode = + ( + document.querySelector( + 'input[name="rotation"]:checked' + ) as HTMLInputElement + )?.value || 'none'; + + const pageThumbnails: Map = new Map(); + const thumbnailScale = 1; + + for (let i = 1; i <= totalPages; i++) { + try { + const page = await pageState.pdfjsDoc.getPage(i); + const viewport = page.getViewport({ scale: thumbnailScale }); + + const offscreen = new OffscreenCanvas(viewport.width, viewport.height); + const ctx = offscreen.getContext('2d')!; + + await page.render({ + canvasContext: ctx as any, + viewport: viewport, + canvas: offscreen as any, + }).promise; + + const bitmap = await createImageBitmap(offscreen); + pageThumbnails.set(i, bitmap); + } catch (e) { + console.error(`Failed to render page ${i}:`, e); + } + } + + previewArea.innerHTML = `

${totalPages} pages → ${numSheets} output sheets

`; + + for (let sheetIndex = 0; sheetIndex < numSheets; sheetIndex++) { + const canvas = document.createElement('canvas'); + canvas.width = canvasWidth; + canvas.height = canvasHeight; + canvas.className = 'border border-gray-600 rounded-lg mb-4'; + + const ctx = canvas.getContext('2d')!; + + const isFront = sheetIndex % 2 === 0; + ctx.fillStyle = isFront ? '#1f2937' : '#1a2e1a'; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + + ctx.strokeStyle = '#4b5563'; + ctx.lineWidth = 1; + ctx.strokeRect(0, 0, canvasWidth, canvasHeight); + + const cellWidth = canvasWidth / cols; + const cellHeight = canvasHeight / rows; + const padding = 4; + + ctx.strokeStyle = '#374151'; + ctx.lineWidth = 1; + ctx.setLineDash([2, 2]); + for (let c = 1; c < cols; c++) { + ctx.beginPath(); + ctx.moveTo(c * cellWidth, 0); + ctx.lineTo(c * cellWidth, canvasHeight); + ctx.stroke(); + } + for (let r = 1; r < rows; r++) { + ctx.beginPath(); + ctx.moveTo(0, r * cellHeight); + ctx.lineTo(canvasWidth, r * cellHeight); + ctx.stroke(); + } + ctx.setLineDash([]); + + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + const slotIndex = r * cols + c; + let pageNumber: number; + + if (isBookletMode) { + const physicalSheet = Math.floor(sheetIndex / 2); + const isFrontSide = sheetIndex % 2 === 0; + if (isFrontSide) { + pageNumber = + c === 0 + ? totalRounded - 2 * physicalSheet + : 2 * physicalSheet + 1; + } else { + pageNumber = + c === 0 + ? 2 * physicalSheet + 2 + : totalRounded - 2 * physicalSheet - 1; + } + } else { + pageNumber = sheetIndex * pagesPerSheet + slotIndex + 1; + } + + const x = c * cellWidth + padding; + const y = r * cellHeight + padding; + const slotWidth = cellWidth - padding * 2; + const slotHeight = cellHeight - padding * 2; + + const exists = pageNumber >= 1 && pageNumber <= totalPages; + + if (exists) { + const thumbnail = pageThumbnails.get(pageNumber); + if (thumbnail) { + let rotation = 0; + if (rotationMode === '90cw') rotation = 90; + else if (rotationMode === '90ccw') rotation = -90; + else if (rotationMode === 'alternate') + rotation = pageNumber % 2 === 1 ? 90 : -90; + + const isRotated = rotation !== 0; + const srcWidth = isRotated ? thumbnail.height : thumbnail.width; + const srcHeight = isRotated ? thumbnail.width : thumbnail.height; + const scale = Math.min( + slotWidth / srcWidth, + slotHeight / srcHeight + ); + const drawWidth = srcWidth * scale; + const drawHeight = srcHeight * scale; + const drawX = x + (slotWidth - drawWidth) / 2; + const drawY = y + (slotHeight - drawHeight) / 2; + + ctx.save(); + if (rotation !== 0) { + const centerX = drawX + drawWidth / 2; + const centerY = drawY + drawHeight / 2; + ctx.translate(centerX, centerY); + ctx.rotate((rotation * Math.PI) / 180); + ctx.drawImage( + thumbnail, + -drawHeight / 2, + -drawWidth / 2, + drawHeight, + drawWidth + ); + } else { + ctx.drawImage(thumbnail, drawX, drawY, drawWidth, drawHeight); + } + ctx.restore(); + + ctx.strokeStyle = '#6b7280'; + ctx.lineWidth = 1; + ctx.strokeRect(drawX, drawY, drawWidth, drawHeight); + + ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; + ctx.font = 'bold 10px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText( + `${pageNumber}`, + x + slotWidth / 2, + y + slotHeight - 4 + ); + } + } else { + ctx.fillStyle = '#374151'; + ctx.fillRect(x, y, slotWidth, slotHeight); + ctx.strokeStyle = '#4b5563'; + ctx.lineWidth = 1; + ctx.strokeRect(x, y, slotWidth, slotHeight); + + ctx.fillStyle = '#6b7280'; + ctx.font = '10px sans-serif'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('(blank)', x + slotWidth / 2, y + slotHeight / 2); + } + } + } + + ctx.fillStyle = '#9ca3af'; + ctx.font = 'bold 10px sans-serif'; + ctx.textAlign = 'right'; + ctx.textBaseline = 'top'; + const sideLabel = isBookletMode ? (isFront ? 'Front' : 'Back') : ''; + ctx.fillText( + `Sheet ${Math.floor(sheetIndex / (isBookletMode ? 2 : 1)) + 1} ${sideLabel}`, + canvasWidth - 6, + 4 + ); + + previewArea.appendChild(canvas); + } + + pageThumbnails.forEach((bitmap) => bitmap.close()); + + const downloadBtn = document.getElementById( + 'download-btn' + ) as HTMLButtonElement; + downloadBtn.disabled = false; +} + +function applyRotation(doc: PDFLibDocument, mode: string) { + const pages = doc.getPages(); + pages.forEach((page, index) => { + let rotation: number; + switch (mode) { + case '90cw': + rotation = 90; + break; + case '90ccw': + rotation = -90; + break; + case 'alternate': + rotation = index % 2 === 0 ? 90 : -90; + break; + default: + rotation = 0; + } + if (rotation !== 0) { + page.setRotation(degrees(page.getRotation().angle + rotation)); + } + }); +} + +async function createBooklet() { + if (!pageState.pdfBytes) { + showAlert('Error', 'Please load a PDF first.'); + return; + } + + showLoader('Creating Booklet...'); + + try { + const sourceDoc = await PDFLibDocument.load(pageState.pdfBytes.slice()); + const rotationMode = + ( + document.querySelector( + 'input[name="rotation"]:checked' + ) as HTMLInputElement + )?.value || 'none'; + applyRotation(sourceDoc, rotationMode); + + const totalPages = sourceDoc.getPageCount(); + const { rows, cols } = getGridDimensions(); + const pagesPerSheet = rows * cols; + const isBookletMode = rows === 1 && cols === 2; + + const { width: sheetWidth, height: sheetHeight } = + getSheetDimensions(isBookletMode); + + const outputDoc = await PDFLibDocument.create(); + + let numSheets: number; + let totalRounded: number; + if (isBookletMode) { + totalRounded = Math.ceil(totalPages / 4) * 4; + numSheets = Math.ceil(totalPages / 4) * 2; + } else { + totalRounded = totalPages; + numSheets = Math.ceil(totalPages / pagesPerSheet); + } + + const cellWidth = sheetWidth / cols; + const cellHeight = sheetHeight / rows; + const padding = 10; + + for (let sheetIndex = 0; sheetIndex < numSheets; sheetIndex++) { + const outputPage = outputDoc.addPage([sheetWidth, sheetHeight]); + + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + const slotIndex = r * cols + c; + let pageNumber: number; + + if (isBookletMode) { + const physicalSheet = Math.floor(sheetIndex / 2); + const isFrontSide = sheetIndex % 2 === 0; + if (isFrontSide) { + pageNumber = + c === 0 + ? totalRounded - 2 * physicalSheet + : 2 * physicalSheet + 1; + } else { + pageNumber = + c === 0 + ? 2 * physicalSheet + 2 + : totalRounded - 2 * physicalSheet - 1; + } + } else { + pageNumber = sheetIndex * pagesPerSheet + slotIndex + 1; + } + + if (pageNumber >= 1 && pageNumber <= totalPages) { + const [embeddedPage] = await outputDoc.embedPdf(sourceDoc, [ + pageNumber - 1, + ]); + const { width: srcW, height: srcH } = embeddedPage; + + const availableWidth = cellWidth - padding * 2; + const availableHeight = cellHeight - padding * 2; + const scale = Math.min( + availableWidth / srcW, + availableHeight / srcH + ); + + const scaledWidth = srcW * scale; + const scaledHeight = srcH * scale; + + const x = + c * cellWidth + padding + (availableWidth - scaledWidth) / 2; + const y = + sheetHeight - + (r + 1) * cellHeight + + padding + + (availableHeight - scaledHeight) / 2; + + outputPage.drawPage(embeddedPage, { + x, + y, + width: scaledWidth, + height: scaledHeight, + }); + } + } + } + } + + const pdfBytes = await outputDoc.save(); + const originalName = + pageState.file?.name.replace(/\.pdf$/i, '') || 'document'; + + downloadFile( + new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), + `${originalName}_booklet.pdf` + ); + + showAlert( + 'Success', + `Booklet created with ${numSheets} sheets!`, + 'success', + function () { + resetState(); + } + ); + } catch (e) { + console.error(e); + showAlert('Error', 'An error occurred while creating the booklet.'); + } finally { + hideLoader(); + } +} + +function handleFileSelect(files: FileList | null) { + if (files && files.length > 0) { + const file = files[0]; + if ( + file.type === 'application/pdf' || + file.name.toLowerCase().endsWith('.pdf') + ) { + pageState.file = file; + updateUI(); + } + } +} + +document.addEventListener('DOMContentLoaded', function () { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const previewBtn = document.getElementById('preview-btn'); + const downloadBtn = document.getElementById('download-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', function () { + window.location.href = import.meta.env.BASE_URL; + }); + } + + if (fileInput && dropZone) { + fileInput.addEventListener('change', function (e) { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', function (e) { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', function (e) { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', function (e) { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const pdfFiles = Array.from(files).filter(function (f) { + return ( + f.type === 'application/pdf' || + f.name.toLowerCase().endsWith('.pdf') + ); + }); + if (pdfFiles.length > 0) { + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(pdfFiles[0]); + handleFileSelect(dataTransfer.files); + } + } + }); + + fileInput.addEventListener('click', function () { + fileInput.value = ''; + }); + } + + if (previewBtn) { + previewBtn.addEventListener('click', generatePreview); + } + + if (downloadBtn) { + downloadBtn.addEventListener('click', createBooklet); + } +}); diff --git a/src/js/logic/pdf-layers-page.ts b/src/js/logic/pdf-layers-page.ts new file mode 100644 index 0000000..22ea415 --- /dev/null +++ b/src/js/logic/pdf-layers-page.ts @@ -0,0 +1,462 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, + getPDFDocument, +} from '../utils/helpers.js'; +import { createIcons, icons } from 'lucide'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; + +interface LayerData { + number: number; + xref: number; + text: string; + on: boolean; + locked: boolean; + depth: number; + parentXref: number; + displayOrder: number; +} + +let currentFile: File | null = null; +let currentDoc: any = null; +const layersMap = new Map(); +let nextDisplayOrder = 0; + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const processBtnContainer = document.getElementById('process-btn-container'); + const fileDisplayArea = document.getElementById('file-display-area'); + const layersContainer = document.getElementById('layers-container'); + const layersList = document.getElementById('layers-list'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!fileDisplayArea || !processBtnContainer || !processBtn) return; + + if (currentFile) { + fileDisplayArea.innerHTML = ''; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = currentFile.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(currentFile.size)} • Loading pages...`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + resetState(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + + try { + const arrayBuffer = await readFileAsArrayBuffer(currentFile); + const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; + metaSpan.textContent = `${formatBytes(currentFile.size)} • ${pdfDoc.numPages} pages`; + } catch (error) { + console.error('Error loading PDF:', error); + metaSpan.textContent = `${formatBytes(currentFile.size)} • Could not load page count`; + } + + createIcons({ icons }); + processBtnContainer.classList.remove('hidden'); + (processBtn as HTMLButtonElement).disabled = false; + } else { + fileDisplayArea.innerHTML = ''; + processBtnContainer.classList.add('hidden'); + (processBtn as HTMLButtonElement).disabled = true; + } + }; + + const resetState = () => { + currentFile = null; + currentDoc = null; + layersMap.clear(); + nextDisplayOrder = 0; + + if (dropZone) dropZone.style.display = 'flex'; + if (layersContainer) layersContainer.classList.add('hidden'); + updateUI(); + }; + + const promptForInput = ( + title: string, + message: string, + defaultValue: string = '' + ): Promise => { + return new Promise((resolve) => { + const modal = document.getElementById('input-modal'); + const titleEl = document.getElementById('input-title'); + const messageEl = document.getElementById('input-message'); + const inputEl = document.getElementById( + 'input-value' + ) as HTMLInputElement; + const confirmBtn = document.getElementById('input-confirm'); + const cancelBtn = document.getElementById('input-cancel'); + + if ( + !modal || + !titleEl || + !messageEl || + !inputEl || + !confirmBtn || + !cancelBtn + ) { + console.error('Input modal elements not found'); + resolve(null); + return; + } + + titleEl.textContent = title; + messageEl.textContent = message; + inputEl.value = defaultValue; + + const closeModal = () => { + modal.classList.add('hidden'); + confirmBtn.onclick = null; + cancelBtn.onclick = null; + inputEl.onkeydown = null; + }; + + const confirm = () => { + const val = inputEl.value.trim(); + closeModal(); + resolve(val); + }; + + const cancel = () => { + closeModal(); + resolve(null); + }; + + confirmBtn.onclick = confirm; + cancelBtn.onclick = cancel; + + inputEl.onkeydown = (e) => { + if (e.key === 'Enter') confirm(); + if (e.key === 'Escape') cancel(); + }; + + modal.classList.remove('hidden'); + inputEl.focus(); + }); + }; + + const renderLayers = () => { + if (!layersList) return; + + const layersArray = Array.from(layersMap.values()); + + if (layersArray.length === 0) { + layersList.innerHTML = ` +
+

This PDF has no layers (OCG).

+

Add a new layer to get started!

+
+ `; + return; + } + + // Sort layers by displayOrder + const sortedLayers = layersArray.sort( + (a, b) => a.displayOrder - b.displayOrder + ); + + layersList.innerHTML = sortedLayers + .map( + (layer: LayerData) => ` +
+ +
+ ${!layer.locked ? `` : ''} + ${!layer.locked ? `` : ''} +
+
+ ` + ) + .join(''); + + // Attach toggle handlers + layersList + .querySelectorAll('input[type="checkbox"]') + .forEach((checkbox) => { + checkbox.addEventListener('change', (e) => { + const target = e.target as HTMLInputElement; + const xref = parseInt(target.dataset.xref || '0'); + const isOn = target.checked; + + try { + currentDoc.setLayerVisibility(xref, isOn); + const layer = Array.from(layersMap.values()).find( + (l) => l.xref === xref + ); + if (layer) { + layer.on = isOn; + } + } catch (err) { + console.error('Failed to set layer visibility:', err); + target.checked = !isOn; + showAlert('Error', 'Failed to toggle layer visibility'); + } + }); + }); + + // Attach delete handlers + layersList.querySelectorAll('.layer-delete').forEach((btn) => { + btn.addEventListener('click', (e) => { + const target = e.target as HTMLButtonElement; + const xref = parseInt(target.dataset.xref || '0'); + const layer = Array.from(layersMap.values()).find( + (l) => l.xref === xref + ); + + if (!layer) { + showAlert('Error', 'Layer not found'); + return; + } + + try { + currentDoc.deleteOCG(layer.number); + layersMap.delete(layer.number); + renderLayers(); + } catch (err) { + console.error('Failed to delete layer:', err); + showAlert('Error', 'Failed to delete layer'); + } + }); + }); + + layersList.querySelectorAll('.layer-add-child').forEach((btn) => { + btn.addEventListener('click', async (e) => { + const target = e.target as HTMLButtonElement; + const parentXref = parseInt(target.dataset.xref || '0'); + const parentLayer = Array.from(layersMap.values()).find( + (l) => l.xref === parentXref + ); + + const childName = await promptForInput( + 'Add Child Layer', + `Enter name for child layer under "${parentLayer?.text || 'Layer'}":` + ); + + if (!childName || !childName.trim()) return; + + try { + const childXref = currentDoc.addOCGWithParent( + childName.trim(), + parentXref + ); + const parentDisplayOrder = parentLayer?.displayOrder || 0; + layersMap.forEach((l) => { + if (l.displayOrder > parentDisplayOrder) { + l.displayOrder += 1; + } + }); + + layersMap.set(childXref, { + number: childXref, + xref: childXref, + text: childName.trim(), + on: true, + locked: false, + depth: (parentLayer?.depth || 0) + 1, + parentXref: parentXref, + displayOrder: parentDisplayOrder + 1, + }); + + renderLayers(); + } catch (err) { + console.error('Failed to add child layer:', err); + showAlert('Error', 'Failed to add child layer'); + } + }); + }); + }; + + const loadLayers = async () => { + if (!currentFile) { + showAlert('No File', 'Please select a PDF file.'); + return; + } + + try { + showLoader('Loading engine...'); + const pymupdf = await loadPyMuPDF(); + + showLoader(`Loading layers from ${currentFile.name}...`); + currentDoc = await pymupdf.open(currentFile); + + showLoader('Reading layer configuration...'); + const existingLayers = currentDoc.getLayerConfig(); + + // Reset and populate layers map + layersMap.clear(); + nextDisplayOrder = 0; + + existingLayers.forEach((layer: any) => { + layersMap.set(layer.number, { + number: layer.number, + xref: layer.xref ?? layer.number, + text: layer.text, + on: layer.on, + locked: layer.locked, + depth: layer.depth ?? 0, + parentXref: layer.parentXref ?? 0, + displayOrder: layer.displayOrder ?? nextDisplayOrder++, + }); + if ((layer.displayOrder ?? -1) >= nextDisplayOrder) { + nextDisplayOrder = layer.displayOrder + 1; + } + }); + + hideLoader(); + + // Hide upload zone, show layers container + if (dropZone) dropZone.style.display = 'none'; + if (processBtnContainer) processBtnContainer.classList.add('hidden'); + if (layersContainer) layersContainer.classList.remove('hidden'); + + renderLayers(); + setupLayerHandlers(); + } catch (error: any) { + hideLoader(); + showAlert('Error', error.message || 'Failed to load PDF layers'); + console.error('Layers error:', error); + } + }; + + const setupLayerHandlers = () => { + const addLayerBtn = document.getElementById('add-layer-btn'); + const newLayerInput = document.getElementById( + 'new-layer-name' + ) as HTMLInputElement; + const saveLayersBtn = document.getElementById('save-layers-btn'); + + if (addLayerBtn && newLayerInput) { + addLayerBtn.onclick = () => { + const name = newLayerInput.value.trim(); + if (!name) { + showAlert('Invalid Name', 'Please enter a layer name'); + return; + } + + try { + const xref = currentDoc.addOCG(name); + newLayerInput.value = ''; + + const newDisplayOrder = nextDisplayOrder++; + layersMap.set(xref, { + number: xref, + xref: xref, + text: name, + on: true, + locked: false, + depth: 0, + parentXref: 0, + displayOrder: newDisplayOrder, + }); + + renderLayers(); + } catch (err: any) { + showAlert('Error', 'Failed to add layer: ' + err.message); + } + }; + } + + if (saveLayersBtn) { + saveLayersBtn.onclick = () => { + try { + showLoader('Saving PDF with layer changes...'); + const pdfBytes = currentDoc.save(); + const blob = new Blob([new Uint8Array(pdfBytes)], { + type: 'application/pdf', + }); + const outName = + currentFile!.name.replace(/\.pdf$/i, '') + '_layers.pdf'; + downloadFile(blob, outName); + hideLoader(); + resetState(); + showAlert('Success', 'PDF with layer changes saved!', 'success'); + } catch (err: any) { + hideLoader(); + showAlert('Error', 'Failed to save PDF: ' + err.message); + } + }; + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const file = files[0]; + if ( + file.type === 'application/pdf' || + file.name.toLowerCase().endsWith('.pdf') + ) { + currentFile = file; + updateUI(); + } else { + showAlert('Invalid File', 'Please select a PDF file.'); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (processBtn) { + processBtn.addEventListener('click', loadLayers); + } +}); diff --git a/src/js/logic/pdf-multi-tool.ts b/src/js/logic/pdf-multi-tool.ts index 92c4e7d..4515f06 100644 --- a/src/js/logic/pdf-multi-tool.ts +++ b/src/js/logic/pdf-multi-tool.ts @@ -1,10 +1,15 @@ import { createIcons, icons } from 'lucide'; -import { degrees, PDFDocument as PDFLibDocument } from 'pdf-lib'; +import { degrees, PDFDocument as PDFLibDocument, PDFPage } from 'pdf-lib'; import * as pdfjsLib from 'pdfjs-dist'; import JSZip from 'jszip'; import Sortable from 'sortablejs'; import { downloadFile, getPDFDocument } from '../utils/helpers'; -import { renderPagesProgressively, cleanupLazyRendering, renderPageToCanvas, createPlaceholder } from '../utils/render-utils'; +import { + renderPagesProgressively, + cleanupLazyRendering, + renderPageToCanvas, + createPlaceholder, +} from '../utils/render-utils'; import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'; import { repairPdfFile } from './repair-pdf.js'; @@ -41,13 +46,17 @@ let sortableInstance: Sortable | null = null; const pageCanvasCache = new Map(); -type Snapshot = { allPages: PageData[]; selectedPages: number[]; splitMarkers: number[] }; +type Snapshot = { + allPages: PageData[]; + selectedPages: number[]; + splitMarkers: number[]; +}; const undoStack: Snapshot[] = []; const redoStack: Snapshot[] = []; function snapshot() { const snap: Snapshot = { - allPages: allPages.map(p => ({ ...p, canvas: p.canvas })), + allPages: allPages.map((p) => ({ ...p, canvas: p.canvas })), selectedPages: Array.from(selectedPages), splitMarkers: Array.from(splitMarkers), }; @@ -56,16 +65,20 @@ function snapshot() { } function restore(snap: Snapshot) { - allPages = snap.allPages.map(p => ({ + allPages = snap.allPages.map((p) => ({ ...p, - canvas: p.canvas + canvas: p.canvas, })); selectedPages = new Set(snap.selectedPages); splitMarkers = new Set(snap.splitMarkers); updatePageDisplay(); } -function showModal(title: string, message: string, type: 'info' | 'error' | 'success' = 'info') { +function showModal( + title: string, + message: string, + type: 'info' | 'error' | 'success' = 'info' +) { const modal = document.getElementById('modal'); const modalTitle = document.getElementById('modal-title'); const modalMessage = document.getElementById('modal-message'); @@ -79,12 +92,12 @@ function showModal(title: string, message: string, type: 'info' | 'error' | 'suc const iconMap = { info: 'info', error: 'alert-circle', - success: 'check-circle' + success: 'check-circle', }; const colorMap = { info: 'text-blue-400', error: 'text-red-400', - success: 'text-green-400' + success: 'text-green-400', }; modalIcon.innerHTML = ``; @@ -112,7 +125,10 @@ function showLoading(current: number, total: number) { text.textContent = t('multiTool.renderingPages'); } -async function withButtonLoading(buttonId: string, action: () => Promise) { +async function withButtonLoading( + buttonId: string, + action: () => Promise +) { const button = document.getElementById(buttonId) as HTMLButtonElement; if (!button) return; @@ -122,7 +138,8 @@ async function withButtonLoading(buttonId: string, action: () => Promise) try { button.disabled = true; button.style.pointerEvents = 'none'; - button.innerHTML = ''; + button.innerHTML = + ''; await action(); } finally { @@ -143,7 +160,9 @@ if (document.readyState === 'loading') { initializeTool(); }); } else { - console.log('PDF Multi Tool: DOMContentLoaded already fired, initializing immediately'); + console.log( + 'PDF Multi Tool: DOMContentLoaded already fired, initializing immediately' + ); initializeTool(); } @@ -160,20 +179,30 @@ function initializeTool() { document.getElementById('upload-pdfs-btn')?.addEventListener('click', () => { console.log('Upload button clicked, isRendering:', isRendering); if (isRendering) { - showModal(t('multiTool.pleaseWait'), t('multiTool.pagesRendering'), 'info'); + showModal( + t('multiTool.pleaseWait'), + t('multiTool.pagesRendering'), + 'info' + ); return; } document.getElementById('pdf-file-input')?.click(); }); - document.getElementById('pdf-file-input')?.addEventListener('change', handlePdfUpload); - document.getElementById('insert-pdf-input')?.addEventListener('change', handleInsertPdf); + document + .getElementById('pdf-file-input') + ?.addEventListener('change', handlePdfUpload); + document + .getElementById('insert-pdf-input') + ?.addEventListener('change', handleInsertPdf); - document.getElementById('bulk-rotate-left-btn')?.addEventListener('click', () => { - if (isRendering) return; - snapshot(); - bulkRotate(-90); - }); + document + .getElementById('bulk-rotate-left-btn') + ?.addEventListener('click', () => { + if (isRendering) return; + snapshot(); + bulkRotate(-90); + }); document.getElementById('bulk-rotate-btn')?.addEventListener('click', () => { if (isRendering) return; snapshot(); @@ -184,27 +213,38 @@ function initializeTool() { snapshot(); bulkDelete(); }); - document.getElementById('bulk-duplicate-btn')?.addEventListener('click', () => { - if (isRendering) return; - snapshot(); - bulkDuplicate(); - }); + document + .getElementById('bulk-duplicate-btn') + ?.addEventListener('click', () => { + if (isRendering) return; + snapshot(); + bulkDuplicate(); + }); document.getElementById('bulk-split-btn')?.addEventListener('click', () => { if (isRendering) return; snapshot(); bulkSplit(); }); - document.getElementById('bulk-download-btn')?.addEventListener('click', () => { - if (isRendering) return; - if (isRendering) return; - if (selectedPages.size === 0) { - showModal(t('multiTool.noPagesSelected'), t('multiTool.selectOnePage'), 'info'); - return; - } - withButtonLoading('bulk-download-btn', async () => { - await downloadPagesAsPdf(Array.from(selectedPages).sort((a, b) => a - b), 'selected-pages.pdf'); + document + .getElementById('bulk-download-btn') + ?.addEventListener('click', () => { + if (isRendering) return; + if (isRendering) return; + if (selectedPages.size === 0) { + showModal( + t('multiTool.noPagesSelected'), + t('multiTool.selectOnePage'), + 'info' + ); + return; + } + withButtonLoading('bulk-download-btn', async () => { + await downloadPagesAsPdf( + Array.from(selectedPages).sort((a, b) => a - b), + 'selected-pages.pdf' + ); + }); }); - }); document.getElementById('select-all-btn')?.addEventListener('click', () => { if (isRendering) return; @@ -229,17 +269,19 @@ function initializeTool() { await downloadAll(); }); }); - document.getElementById('add-blank-page-btn')?.addEventListener('click', () => { - if (isRendering) return; - snapshot(); - addBlankPage(); - }); + document + .getElementById('add-blank-page-btn') + ?.addEventListener('click', () => { + if (isRendering) return; + snapshot(); + addBlankPage(); + }); document.getElementById('undo-btn')?.addEventListener('click', () => { if (isRendering) return; const last = undoStack.pop(); if (last) { const current: Snapshot = { - allPages: allPages.map(p => ({ ...p })), + allPages: allPages.map((p) => ({ ...p })), selectedPages: Array.from(selectedPages), splitMarkers: Array.from(splitMarkers), }; @@ -252,7 +294,7 @@ function initializeTool() { const next = redoStack.pop(); if (next) { const current: Snapshot = { - allPages: allPages.map(p => ({ ...p })), + allPages: allPages.map((p) => ({ ...p })), selectedPages: Array.from(selectedPages), splitMarkers: Array.from(splitMarkers), }; @@ -269,7 +311,9 @@ function initializeTool() { } }); - document.getElementById('modal-close-btn')?.addEventListener('click', hideModal); + document + .getElementById('modal-close-btn') + ?.addEventListener('click', hideModal); document.getElementById('modal')?.addEventListener('click', (e) => { if (e.target === document.getElementById('modal')) { hideModal(); @@ -288,7 +332,9 @@ function initializeTool() { uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('border-indigo-500'); - const files = Array.from(e.dataTransfer?.files || []).filter(f => f.type === 'application/pdf'); + const files = Array.from(e.dataTransfer?.files || []).filter( + (f) => f.type === 'application/pdf' + ); if (files.length > 0) { loadPdfs(files); } @@ -361,27 +407,38 @@ async function loadPdfs(files: File[]) { try { console.log(`Repairing ${file.name}...`); const loadingText = document.getElementById('loading-text'); - if (loadingText) loadingText.textContent = `Repairing ${file.name}...`; + if (loadingText) + loadingText.textContent = `Repairing ${file.name}...`; const repairedData = await repairPdfFile(file); if (repairedData) { arrayBuffer = repairedData.buffer as ArrayBuffer; console.log(`Successfully repaired ${file.name} before loading.`); } else { - console.warn(`Repair returned null for ${file.name}, using original file.`); + console.warn( + `Repair returned null for ${file.name}, using original file.` + ); arrayBuffer = await file.arrayBuffer(); } } catch (repairError) { - console.warn(`Failed to repair ${file.name}, attempting to load original:`, repairError); + console.warn( + `Failed to repair ${file.name}, attempting to load original:`, + repairError + ); arrayBuffer = await file.arrayBuffer(); } - const pdfDoc = await PDFLibDocument.load(arrayBuffer, { ignoreEncryption: true, throwOnInvalidObject: false }); + const pdfDoc = await PDFLibDocument.load(arrayBuffer, { + ignoreEncryption: true, + throwOnInvalidObject: false, + }); currentPdfDocs.push(pdfDoc); const pdfIndex = currentPdfDocs.length - 1; const pdfBytes = await pdfDoc.save(); - const pdfjsDoc = await getPDFDocument({ data: new Uint8Array(pdfBytes) }).promise; + const pdfjsDoc = await getPDFDocument({ + data: new Uint8Array(pdfBytes), + }).promise; const numPages = pdfjsDoc.numPages; // Pre-fill allPages with placeholders to maintain order/state @@ -425,10 +482,13 @@ async function loadPdfs(files: File[]) { shouldCancel: () => renderCancelled, // Pass cancellation check } ); - } catch (e) { console.error(`Failed to load PDF ${file.name}:`, e); - showModal(t('multiTool.error'), `${t('multiTool.failedToLoad')} ${file.name}.`, 'error'); + showModal( + t('multiTool.error'), + `${t('multiTool.failedToLoad')} ${file.name}.`, + 'error' + ); } } @@ -439,7 +499,9 @@ async function loadPdfs(files: File[]) { } finally { hideLoading(); isRendering = false; - console.log('PDF Multi Tool: Render finished/cancelled, isRendering set to false'); + console.log( + 'PDF Multi Tool: Render finished/cancelled, isRendering set to false' + ); if (renderCancelled) { renderCancelled = false; } @@ -464,7 +526,10 @@ function createPageCard(pageData: PageData, index: number) { } // Modified to return the element instead of appending it -function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTMLElement { +function createPageElement( + canvas: HTMLCanvasElement | null, + index: number +): HTMLElement { const pageData = allPages[index]; if (!pageData) { console.error(`Page data not found for index ${index}`); @@ -472,7 +537,8 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM } const card = document.createElement('div'); - card.className = 'bg-gray-800 rounded-lg border-2 border-gray-700 p-2 relative group cursor-move'; + card.className = + 'bg-gray-800 rounded-lg border-2 border-gray-700 p-2 relative group cursor-move'; card.dataset.pageIndex = index.toString(); card.dataset.pageId = pageData.id; // Set ID for reconciliation @@ -490,7 +556,8 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM } const preview = document.createElement('div'); - preview.className = 'bg-white rounded mb-2 overflow-hidden w-full flex items-center justify-center relative h-36 sm:h-64'; + preview.className = + 'bg-white rounded mb-2 overflow-hidden w-full flex items-center justify-center relative h-36 sm:h-64'; if (canvas) { const previewCanvas = canvas; @@ -502,7 +569,8 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM } else { // Show loading placeholder if canvas is null const loading = document.createElement('div'); - loading.className = 'flex flex-col items-center justify-center text-gray-400'; + loading.className = + 'flex flex-col items-center justify-center text-gray-400'; loading.innerHTML = ` ${t('common.loading')} @@ -518,15 +586,18 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM // Actions toolbar const actions = document.createElement('div'); - actions.className = 'flex items-center justify-center gap-1 sm:opacity-0 group-hover:opacity-100 transition-opacity absolute bottom-2 left-0 right-0'; + actions.className = + 'flex items-center justify-center gap-1 sm:opacity-0 group-hover:opacity-100 transition-opacity absolute bottom-2 left-0 right-0'; const actionsInner = document.createElement('div'); - actionsInner.className = 'flex items-center gap-1 bg-gray-900/90 rounded px-2 py-1'; + actionsInner.className = + 'flex items-center gap-1 bg-gray-900/90 rounded px-2 py-1'; actions.appendChild(actionsInner); // Select checkbox const selectBtn = document.createElement('button'); - selectBtn.className = 'absolute top-2 right-2 p-1 rounded bg-gray-900/70 hover:bg-gray-800 z-10'; + selectBtn.className = + 'absolute top-2 right-2 p-1 rounded bg-gray-900/70 hover:bg-gray-800 z-10'; selectBtn.innerHTML = selectedPages.has(index) ? '' : ''; @@ -538,14 +609,16 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM // Rotate button const rotateBtn = document.createElement('button'); rotateBtn.className = 'p-1 rounded hover:bg-gray-700'; - rotateBtn.innerHTML = ''; + rotateBtn.innerHTML = + ''; rotateBtn.onclick = (e) => { e.stopPropagation(); rotatePage(index, 90); }; const rotateLeftBtn = document.createElement('button'); rotateLeftBtn.className = 'p-1 rounded hover:bg-gray-700'; - rotateLeftBtn.innerHTML = ''; + rotateLeftBtn.innerHTML = + ''; rotateLeftBtn.onclick = (e) => { e.stopPropagation(); rotatePage(index, -90); @@ -554,7 +627,8 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM // Duplicate button const duplicateBtn = document.createElement('button'); duplicateBtn.className = 'p-1 rounded hover:bg-gray-700'; - duplicateBtn.innerHTML = ''; + duplicateBtn.innerHTML = + ''; duplicateBtn.title = t('multiTool.actions.duplicatePage'); duplicateBtn.onclick = (e) => { e.stopPropagation(); @@ -565,7 +639,8 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM // Delete button const deleteBtn = document.createElement('button'); deleteBtn.className = 'p-1 rounded hover:bg-gray-700'; - deleteBtn.innerHTML = ''; + deleteBtn.innerHTML = + ''; deleteBtn.title = t('multiTool.actions.deletePage'); deleteBtn.onclick = (e) => { e.stopPropagation(); @@ -576,7 +651,8 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM // Insert PDF button const insertBtn = document.createElement('button'); insertBtn.className = 'p-1 rounded hover:bg-gray-700'; - insertBtn.innerHTML = ''; + insertBtn.innerHTML = + ''; insertBtn.title = t('multiTool.actions.insertPdf'); insertBtn.onclick = (e) => { e.stopPropagation(); @@ -587,7 +663,8 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM // Split button const splitBtn = document.createElement('button'); splitBtn.className = 'p-1 rounded hover:bg-gray-700'; - splitBtn.innerHTML = ''; + splitBtn.innerHTML = + ''; splitBtn.title = t('multiTool.actions.toggleSplit'); splitBtn.onclick = (e) => { e.stopPropagation(); @@ -596,14 +673,23 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM renderSplitMarkers(); }; - actionsInner.append(rotateLeftBtn, rotateBtn, duplicateBtn, insertBtn, splitBtn, deleteBtn); + actionsInner.append( + rotateLeftBtn, + rotateBtn, + duplicateBtn, + insertBtn, + splitBtn, + deleteBtn + ); card.append(preview, info, actions, selectBtn); // Check for split marker if (splitMarkers.has(index)) { const marker = document.createElement('div'); - marker.className = 'split-marker absolute -right-3 top-0 bottom-0 w-6 flex items-center justify-center z-20 pointer-events-none'; - marker.innerHTML = '
'; + marker.className = + 'split-marker absolute -right-3 top-0 bottom-0 w-6 flex items-center justify-center z-20 pointer-events-none'; + marker.innerHTML = + '
'; card.appendChild(marker); } @@ -655,15 +741,19 @@ function toggleSelectOptimized(index: number) { const card = pagesContainer.children[index] as HTMLElement; if (!card) return; - const selectBtn = card.querySelector('button[class*="absolute top-2 right-2"]'); + const selectBtn = card.querySelector( + 'button[class*="absolute top-2 right-2"]' + ); if (!selectBtn) return; if (selectedPages.has(index)) { card.classList.add('border-indigo-500', 'ring-2', 'ring-indigo-500'); - selectBtn.innerHTML = ''; + selectBtn.innerHTML = + ''; } else { card.classList.remove('border-indigo-500', 'ring-2', 'ring-indigo-500'); - selectBtn.innerHTML = ''; + selectBtn.innerHTML = + ''; } createIcons({ icons }); @@ -730,7 +820,7 @@ function deletePage(index: number) { allPages.splice(index, 1); selectedPages.delete(index); const newSelected = new Set(); - selectedPages.forEach(i => { + selectedPages.forEach((i) => { if (i > index) newSelected.add(i - 1); else if (i < index) newSelected.add(i); }); @@ -759,13 +849,17 @@ async function handleInsertPdf(e: Event) { try { const arrayBuffer = await file.arrayBuffer(); - const pdfDoc = await PDFLibDocument.load(arrayBuffer, { ignoreEncryption: true, throwOnInvalidObject: false }); + const pdfDoc = await PDFLibDocument.load(arrayBuffer, { + ignoreEncryption: true, + throwOnInvalidObject: false, + }); currentPdfDocs.push(pdfDoc); const pdfIndex = currentPdfDocs.length - 1; // Load PDF.js document for rendering const pdfBytes = await pdfDoc.save(); - const pdfjsDoc = await getPDFDocument({ data: new Uint8Array(pdfBytes) }).promise; + const pdfjsDoc = await getPDFDocument({ data: new Uint8Array(pdfBytes) }) + .promise; const numPages = pdfjsDoc.numPages; const newPages: PageData[] = []; @@ -802,13 +896,18 @@ async function handleInsertPdf(e: Event) { // Update UI if card exists const pagesContainer = document.getElementById('pages-container'); - const card = pagesContainer?.querySelector(`div[data-page-index="${globalIndex}"]`); + const card = pagesContainer?.querySelector( + `div[data-page-index="${globalIndex}"]` + ); if (card) { - const preview = card.querySelector('.bg-gray-700') || card.querySelector('.bg-white'); + const preview = + card.querySelector('.bg-gray-700') || + card.querySelector('.bg-white'); if (preview) { // Re-create the preview content preview.innerHTML = ''; - preview.className = 'bg-white rounded mb-2 overflow-hidden w-full flex items-center justify-center relative h-36 sm:h-64'; + preview.className = + 'bg-white rounded mb-2 overflow-hidden w-full flex items-center justify-center relative h-36 sm:h-64'; const previewCanvas = canvas; previewCanvas.className = 'max-w-full max-h-full object-contain'; @@ -819,10 +918,13 @@ async function handleInsertPdf(e: Event) { } } } - } catch (e) { console.error('Failed to insert PDF:', e); - showModal('Error', 'Failed to insert PDF. The file may be corrupted.', 'error'); + showModal( + 'Error', + 'Failed to insert PDF. The file may be corrupted.', + 'error' + ); } input.value = ''; @@ -837,13 +939,15 @@ function renderSplitMarkers() { const pagesContainer = document.getElementById('pages-container'); if (!pagesContainer) return; - pagesContainer.querySelectorAll('.split-marker').forEach(m => m.remove()); + pagesContainer.querySelectorAll('.split-marker').forEach((m) => m.remove()); Array.from(pagesContainer.children).forEach((cardEl, i) => { if (splitMarkers.has(i)) { const marker = document.createElement('div'); - marker.className = 'split-marker absolute -right-3 top-0 bottom-0 w-6 flex items-center justify-center z-20 pointer-events-none'; - marker.innerHTML = '
'; + marker.className = + 'split-marker absolute -right-3 top-0 bottom-0 w-6 flex items-center justify-center z-20 pointer-events-none'; + marker.innerHTML = + '
'; (cardEl as HTMLElement).appendChild(marker); } }); @@ -881,7 +985,7 @@ function bulkRotate(delta: number) { return; } - selectedPages.forEach(index => { + selectedPages.forEach((index) => { const pageData = allPages[index]; if (pageData) { // Update state @@ -890,13 +994,15 @@ function bulkRotate(delta: number) { // Update DOM immediately if it exists const pagesContainer = document.getElementById('pages-container'); - const card = pagesContainer?.querySelector(`div[data-page-index="${index}"]`); + const card = pagesContainer?.querySelector( + `div[data-page-index="${index}"]` + ); if (card) { const canvas = card.querySelector('canvas'); if (canvas) { canvas.style.transform = `rotate(${pageData.visualRotation}deg)`; } - // If no canvas (placeholder), the state update is enough. + // If no canvas (placeholder), the state update is enough. // When it eventually renders, createPageElement will use the new rotation. } } @@ -911,7 +1017,7 @@ function bulkDelete() { return; } const indices = Array.from(selectedPages).sort((a, b) => b - a); - indices.forEach(index => allPages.splice(index, 1)); + indices.forEach((index) => allPages.splice(index, 1)); selectedPages.clear(); if (allPages.length === 0) { @@ -928,7 +1034,7 @@ function bulkDuplicate() { return; } const indices = Array.from(selectedPages).sort((a, b) => b - a); - indices.forEach(index => { + indices.forEach((index) => { duplicatePage(index); }); selectedPages.clear(); @@ -937,11 +1043,15 @@ function bulkDuplicate() { function bulkSplit() { if (selectedPages.size === 0) { - showModal('No Selection', 'Please select pages to mark for splitting.', 'info'); + showModal( + 'No Selection', + 'Please select pages to mark for splitting.', + 'info' + ); return; } const indices = Array.from(selectedPages); - indices.forEach(index => { + indices.forEach((index) => { if (!splitMarkers.has(index)) { splitMarkers.add(index); } @@ -951,7 +1061,6 @@ function bulkSplit() { updatePageDisplay(); } - async function downloadAll() { if (allPages.length === 0) { showModal('No Pages', 'Please upload PDFs first.', 'info'); @@ -993,24 +1102,63 @@ async function downloadSplitPdfs() { segments.push(currentSegment); } - // Create PDFs for each segment for (let segIndex = 0; segIndex < segments.length; segIndex++) { const segment = segments[segIndex]; const newPdf = await PDFLibDocument.create(); + const segSpecs: ( + | { + type: 'pdf'; + pdfDoc: PDFLibDocument; + originalPageIndex: number; + rotation: number; + } + | { type: 'blank' } + )[] = []; for (const index of segment) { const pageData = allPages[index]; - if (!pageData) { - console.warn(`Page data missing for index ${index}`); - continue; - } + if (!pageData) continue; if (pageData.pdfDoc && pageData.originalPageIndex >= 0) { - const [copiedPage] = await newPdf.copyPages(pageData.pdfDoc, [pageData.originalPageIndex]); - const page = newPdf.addPage(copiedPage); + segSpecs.push({ + type: 'pdf', + pdfDoc: pageData.pdfDoc, + originalPageIndex: pageData.originalPageIndex, + rotation: pageData.rotation, + }); + } else { + segSpecs.push({ type: 'blank' }); + } + } - if (pageData.rotation !== 0) { + const docPageIndices = new Map(); + for (const spec of segSpecs) { + if (spec.type === 'pdf') { + if (!docPageIndices.has(spec.pdfDoc)) { + docPageIndices.set(spec.pdfDoc, []); + } + docPageIndices.get(spec.pdfDoc)!.push(spec.originalPageIndex); + } + } + + const copiedPagesMap = new Map(); + for (const [doc, pageIdxs] of Array.from(docPageIndices)) { + const copied = await newPdf.copyPages(doc, pageIdxs); + copiedPagesMap.set(doc, copied); + } + + const docConsumeIndex = new Map(); + docPageIndices.forEach((_, doc) => docConsumeIndex.set(doc, 0)); + + for (const spec of segSpecs) { + if (spec.type === 'pdf') { + const idx = docConsumeIndex.get(spec.pdfDoc)!; + const copiedPage = copiedPagesMap.get(spec.pdfDoc)![idx]; + docConsumeIndex.set(spec.pdfDoc, idx + 1); + + const page = newPdf.addPage(copiedPage); + if (spec.rotation !== 0) { const currentRotation = page.getRotation().angle; - page.setRotation(degrees(currentRotation + pageData.rotation)); + page.setRotation(degrees(currentRotation + spec.rotation)); } } else { newPdf.addPage([595, 842]); @@ -1025,7 +1173,11 @@ async function downloadSplitPdfs() { const zipBlob = await zip.generateAsync({ type: 'blob' }); downloadFile(zipBlob, 'split-documents.zip'); - showModal('Success', `Downloaded ${segments.length} PDF files in a ZIP archive.`, 'success'); + showModal( + 'Success', + `Downloaded ${segments.length} PDF files in a ZIP archive.`, + 'success' + ); } catch (e) { console.error('Failed to create split PDFs:', e); showModal('Error', 'Failed to create split PDFs.', 'error'); @@ -1038,20 +1190,59 @@ async function downloadPagesAsPdf(indices: number[], filename: string) { try { const newPdf = await PDFLibDocument.create(); + const pageSpecs: ( + | { + type: 'pdf'; + pdfDoc: PDFLibDocument; + originalPageIndex: number; + rotation: number; + } + | { type: 'blank' } + )[] = []; for (const index of indices) { const pageData = allPages[index]; - if (!pageData) { - console.warn(`Page data missing for index ${index}`); - continue; - } + if (!pageData) continue; if (pageData.pdfDoc && pageData.originalPageIndex >= 0) { - // Copy page from original PDF - const [copiedPage] = await newPdf.copyPages(pageData.pdfDoc, [pageData.originalPageIndex]); - const page = newPdf.addPage(copiedPage); + pageSpecs.push({ + type: 'pdf', + pdfDoc: pageData.pdfDoc, + originalPageIndex: pageData.originalPageIndex, + rotation: pageData.rotation, + }); + } else { + pageSpecs.push({ type: 'blank' }); + } + } - if (pageData.rotation !== 0) { + const docPageIndices = new Map(); + for (const spec of pageSpecs) { + if (spec.type === 'pdf') { + if (!docPageIndices.has(spec.pdfDoc)) { + docPageIndices.set(spec.pdfDoc, []); + } + docPageIndices.get(spec.pdfDoc)!.push(spec.originalPageIndex); + } + } + + const copiedPagesMap = new Map(); + for (const [doc, pageIdxs] of Array.from(docPageIndices)) { + const copied = await newPdf.copyPages(doc, pageIdxs); + copiedPagesMap.set(doc, copied); + } + + const docConsumeIndex = new Map(); + docPageIndices.forEach((_, doc) => docConsumeIndex.set(doc, 0)); + + for (const spec of pageSpecs) { + if (spec.type === 'pdf') { + const idx = docConsumeIndex.get(spec.pdfDoc)!; + const copiedPage = copiedPagesMap.get(spec.pdfDoc)![idx]; + docConsumeIndex.set(spec.pdfDoc, idx + 1); + + const page = newPdf.addPage(copiedPage); + if (spec.rotation !== 0) { const currentRotation = page.getRotation().angle; - page.setRotation(degrees(currentRotation + pageData.rotation)); + page.setRotation(degrees(currentRotation + spec.rotation)); } } else { newPdf.addPage([595, 842]); @@ -1059,7 +1250,9 @@ async function downloadPagesAsPdf(indices: number[], filename: string) { } const pdfBytes = await newPdf.save(); - const blob = new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }); + const blob = new Blob([new Uint8Array(pdfBytes)], { + type: 'application/pdf', + }); downloadFile(blob, filename); showModal('Success', 'PDF downloaded successfully.', 'success'); @@ -1101,18 +1294,28 @@ function updatePageDisplay() { // Update index-dependent attributes card.dataset.pageIndex = index.toString(); - const info = card.querySelector('.text-xs.text-gray-400.text-center.mb-2'); + const info = card.querySelector( + '.text-xs.text-gray-400.text-center.mb-2' + ); if (info) info.textContent = `Page ${index + 1} `; // Update selection state - const selectBtn = card.querySelector('button[class*="absolute top-2 right-2"]'); + const selectBtn = card.querySelector( + 'button[class*="absolute top-2 right-2"]' + ); if (selectBtn) { if (selectedPages.has(index)) { card.classList.add('border-indigo-500', 'ring-2', 'ring-indigo-500'); - selectBtn.innerHTML = ''; + selectBtn.innerHTML = + ''; } else { - card.classList.remove('border-indigo-500', 'ring-2', 'ring-indigo-500'); - selectBtn.innerHTML = ''; + card.classList.remove( + 'border-indigo-500', + 'ring-2', + 'ring-indigo-500' + ); + selectBtn.innerHTML = + ''; } // Update click handler to use new index (selectBtn as HTMLElement).onclick = (e) => { @@ -1128,17 +1331,47 @@ function updatePageDisplay() { } // Update action buttons - const actionsInner = card.querySelector('.flex.items-center.gap-1.bg-gray-900\\/90'); + const actionsInner = card.querySelector( + '.flex.items-center.gap-1.bg-gray-900\\/90' + ); if (actionsInner) { const buttons = actionsInner.querySelectorAll('button'); - if (buttons[0]) (buttons[0] as HTMLElement).onclick = (e) => { e.stopPropagation(); rotatePage(index, -90); }; - if (buttons[1]) (buttons[1] as HTMLElement).onclick = (e) => { e.stopPropagation(); rotatePage(index, 90); }; - if (buttons[2]) (buttons[2] as HTMLElement).onclick = (e) => { e.stopPropagation(); snapshot(); duplicatePage(index); }; - if (buttons[3]) (buttons[3] as HTMLElement).onclick = (e) => { e.stopPropagation(); snapshot(); insertPdfAfter(index); }; - if (buttons[4]) (buttons[4] as HTMLElement).onclick = (e) => { e.stopPropagation(); snapshot(); toggleSplitMarker(index); renderSplitMarkers(); }; - if (buttons[5]) (buttons[5] as HTMLElement).onclick = (e) => { e.stopPropagation(); snapshot(); deletePage(index); }; + if (buttons[0]) + (buttons[0] as HTMLElement).onclick = (e) => { + e.stopPropagation(); + rotatePage(index, -90); + }; + if (buttons[1]) + (buttons[1] as HTMLElement).onclick = (e) => { + e.stopPropagation(); + rotatePage(index, 90); + }; + if (buttons[2]) + (buttons[2] as HTMLElement).onclick = (e) => { + e.stopPropagation(); + snapshot(); + duplicatePage(index); + }; + if (buttons[3]) + (buttons[3] as HTMLElement).onclick = (e) => { + e.stopPropagation(); + snapshot(); + insertPdfAfter(index); + }; + if (buttons[4]) + (buttons[4] as HTMLElement).onclick = (e) => { + e.stopPropagation(); + snapshot(); + toggleSplitMarker(index); + renderSplitMarkers(); + }; + if (buttons[5]) + (buttons[5] as HTMLElement).onclick = (e) => { + e.stopPropagation(); + snapshot(); + deletePage(index); + }; } - } else { // Element doesn't exist, create it card = createPageElement(pageData.canvas, index); @@ -1179,7 +1412,9 @@ function updatePageNumbers() { // We need to find the buttons and update their onclick handlers // This is necessary because the original handlers captured the old index - const selectBtn = card.querySelector('button[class*="absolute top-2 right-2"]') as HTMLButtonElement; + const selectBtn = card.querySelector( + 'button[class*="absolute top-2 right-2"]' + ) as HTMLButtonElement; if (selectBtn) { selectBtn.onclick = (e) => { e.stopPropagation(); @@ -1187,16 +1422,47 @@ function updatePageNumbers() { }; } - const actionsInner = card.querySelector('.flex.items-center.gap-1.bg-gray-900\\/90'); + const actionsInner = card.querySelector( + '.flex.items-center.gap-1.bg-gray-900\\/90' + ); if (actionsInner) { const buttons = actionsInner.querySelectorAll('button'); // Order: Rotate Left, Rotate Right, Duplicate, Insert, Split, Delete - if (buttons[0]) buttons[0].onclick = (e) => { e.stopPropagation(); rotatePage(index, -90); }; - if (buttons[1]) buttons[1].onclick = (e) => { e.stopPropagation(); rotatePage(index, 90); }; - if (buttons[2]) buttons[2].onclick = (e) => { e.stopPropagation(); snapshot(); duplicatePage(index); }; - if (buttons[3]) buttons[3].onclick = (e) => { e.stopPropagation(); snapshot(); insertPdfAfter(index); }; - if (buttons[4]) buttons[4].onclick = (e) => { e.stopPropagation(); snapshot(); toggleSplitMarker(index); renderSplitMarkers(); }; - if (buttons[5]) buttons[5].onclick = (e) => { e.stopPropagation(); snapshot(); deletePage(index); }; + if (buttons[0]) + buttons[0].onclick = (e) => { + e.stopPropagation(); + rotatePage(index, -90); + }; + if (buttons[1]) + buttons[1].onclick = (e) => { + e.stopPropagation(); + rotatePage(index, 90); + }; + if (buttons[2]) + buttons[2].onclick = (e) => { + e.stopPropagation(); + snapshot(); + duplicatePage(index); + }; + if (buttons[3]) + buttons[3].onclick = (e) => { + e.stopPropagation(); + snapshot(); + insertPdfAfter(index); + }; + if (buttons[4]) + buttons[4].onclick = (e) => { + e.stopPropagation(); + snapshot(); + toggleSplitMarker(index); + renderSplitMarkers(); + }; + if (buttons[5]) + buttons[5].onclick = (e) => { + e.stopPropagation(); + snapshot(); + deletePage(index); + }; } }); -} \ No newline at end of file +} diff --git a/src/js/logic/pdf-to-bmp-page.ts b/src/js/logic/pdf-to-bmp-page.ts index 00830ef..d165851 100644 --- a/src/js/logic/pdf-to-bmp-page.ts +++ b/src/js/logic/pdf-to-bmp-page.ts @@ -1,9 +1,8 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument, getCleanPdfFilename } from '../utils/helpers.js'; +import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js'; import { createIcons, icons } from 'lucide'; import JSZip from 'jszip'; import * as pdfjsLib from 'pdfjs-dist'; -import { PDFPageProxy } from 'pdfjs-dist'; pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); @@ -85,28 +84,28 @@ async function convert() { const pdf = await getPDFDocument( await readFileAsArrayBuffer(files[0]) ).promise; + const zip = new JSZip(); - if (pdf.numPages === 1) { - const page = await pdf.getPage(1); - const blob = await renderPage(page); - downloadFile(blob, getCleanPdfFilename(files[0].name) + '.bmp'); - } else { - const zip = new JSZip(); - for (let i = 1; i <= pdf.numPages; i++) { - const page = await pdf.getPage(i); - const blob = await renderPage(page); - if (blob) { - zip.file(`page_${i}.bmp`, blob); - } - } + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const viewport = page.getViewport({ scale: 2.0 }); + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; - const zipBlob = await zip.generateAsync({ type: 'blob' }); - downloadFile( - zipBlob, - getCleanPdfFilename(files[0].name) + '_bmps.zip' + await page.render({ canvasContext: context!, viewport: viewport, canvas }).promise; + + const blob = await new Promise((resolve) => + canvas.toBlob(resolve, 'image/bmp') ); + if (blob) { + zip.file(`page_${i}.bmp`, blob); + } } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'converted_images.zip'); showAlert('Success', 'PDF converted to BMPs successfully!', 'success', () => { resetState(); }); @@ -121,25 +120,6 @@ async function convert() { } } -async function renderPage(page: PDFPageProxy): Promise { - const viewport = page.getViewport({ scale: 2.0 }); - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - canvas.height = viewport.height; - canvas.width = viewport.width; - - await page.render({ - canvasContext: context!, - viewport: viewport, - canvas, - }).promise; - - const blob = await new Promise((resolve) => - canvas.toBlob(resolve, 'image/bmp') - ); - return blob; -} - document.addEventListener('DOMContentLoaded', () => { const fileInput = document.getElementById('file-input') as HTMLInputElement; const dropZone = document.getElementById('drop-zone'); diff --git a/src/js/logic/pdf-to-csv-page.ts b/src/js/logic/pdf-to-csv-page.ts new file mode 100644 index 0000000..ef94b8c --- /dev/null +++ b/src/js/logic/pdf-to-csv-page.ts @@ -0,0 +1,187 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { createIcons, icons } from 'lucide'; +import JSZip from 'jszip'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; +let file: File | null = null; + +const updateUI = () => { + const fileDisplayArea = document.getElementById('file-display-area'); + const optionsPanel = document.getElementById('options-panel'); + + if (!fileDisplayArea || !optionsPanel) return; + + fileDisplayArea.innerHTML = ''; + + if (file) { + optionsPanel.classList.remove('hidden'); + + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = resetState; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + + createIcons({ icons }); + } else { + optionsPanel.classList.add('hidden'); + } +}; + +const resetState = () => { + file = null; + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; + updateUI(); +}; + +function tableToCsv(rows: (string | null)[][]): string { + return rows + .map((row) => + row + .map((cell) => { + const cellStr = cell ?? ''; + if ( + cellStr.includes(',') || + cellStr.includes('"') || + cellStr.includes('\n') + ) { + return `"${cellStr.replace(/"/g, '""')}"`; + } + return cellStr; + }) + .join(',') + ) + .join('\n'); +} + +async function convert() { + if (!file) { + showAlert('No File', 'Please upload a PDF file first.'); + return; + } + + showLoader('Loading Engine...'); + + try { + const pymupdf = await loadPyMuPDF(); + showLoader('Extracting tables...'); + + const doc = await pymupdf.open(file); + const pageCount = doc.pageCount; + const baseName = file.name.replace(/\.[^/.]+$/, ''); + + const allRows: (string | null)[][] = []; + + for (let i = 0; i < pageCount; i++) { + showLoader(`Scanning page ${i + 1} of ${pageCount}...`); + const page = doc.getPage(i); + const tables = page.findTables(); + + tables.forEach((table) => { + allRows.push(...table.rows); + allRows.push([]); + }); + } + + if (allRows.length === 0) { + showAlert('No Tables Found', 'No tables were detected in this PDF.'); + return; + } + + const csvContent = tableToCsv(allRows.filter((row) => row.length > 0)); + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + downloadFile(blob, `${baseName}.csv`); + showAlert( + 'Success', + 'PDF converted to CSV successfully!', + 'success', + resetState + ); + } catch (e) { + console.error(e); + const message = e instanceof Error ? e.message : 'Unknown error'; + showAlert('Error', `Failed to convert PDF to CSV. ${message}`); + } finally { + hideLoader(); + } +} + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const handleFileSelect = (newFiles: FileList | null) => { + if (!newFiles || newFiles.length === 0) return; + const validFile = Array.from(newFiles).find( + (f) => f.type === 'application/pdf' + ); + + if (!validFile) { + showAlert('Invalid File', 'Please upload a PDF file.'); + return; + } + + file = validFile; + updateUI(); + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } +}); diff --git a/src/js/logic/pdf-to-docx-page.ts b/src/js/logic/pdf-to-docx-page.ts new file mode 100644 index 0000000..3850fbc --- /dev/null +++ b/src/js/logic/pdf-to-docx-page.ts @@ -0,0 +1,216 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, + getPDFDocument, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const fileDisplayArea = document.getElementById('file-display-area'); + const convertOptions = document.getElementById('convert-options'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!fileDisplayArea || !convertOptions || !processBtn || !fileControls) + return; + + if (state.files.length > 0) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_: File, i: number) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + + try { + const arrayBuffer = await readFileAsArrayBuffer(file); + const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; + metaSpan.textContent = `${formatBytes(file.size)} • ${pdfDoc.numPages} pages`; + } catch (error) { + metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`; + } + } + + createIcons({ icons }); + fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + (processBtn as HTMLButtonElement).disabled = false; + } else { + fileDisplayArea.innerHTML = ''; + fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + (processBtn as HTMLButtonElement).disabled = true; + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + const convert = async () => { + try { + if (state.files.length === 0) { + showAlert('No Files', 'Please select at least one PDF file.'); + return; + } + + showLoader('Loading PDF converter...'); + const pymupdf = await loadPyMuPDF(); + + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + + const docxBlob = await pymupdf.pdfToDocx(file); + const outName = file.name.replace(/\.pdf$/i, '') + '.docx'; + + downloadFile(docxBlob, outName); + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${file.name} to DOCX.`, + 'success', + () => resetState() + ); + } else { + showLoader('Converting multiple PDFs...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader( + `Converting ${i + 1}/${state.files.length}: ${file.name}...` + ); + + const docxBlob = await pymupdf.pdfToDocx(file); + const baseName = file.name.replace(/\.pdf$/i, ''); + const arrayBuffer = await docxBlob.arrayBuffer(); + zip.file(`${baseName}.docx`, arrayBuffer); + } + + showLoader('Creating ZIP archive...'); + const zipBlob = await zip.generateAsync({ type: 'blob' }); + + downloadFile(zipBlob, 'converted-documents.zip'); + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${state.files.length} PDF(s) to DOCX.`, + 'success', + () => resetState() + ); + } + } catch (e: any) { + hideLoader(); + showAlert( + 'Error', + `An error occurred during conversion. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const pdfFiles = Array.from(files).filter( + (f) => + f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf') + ); + state.files = [...state.files, ...pdfFiles]; + updateUI(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + handleFileSelect(files); + } + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + resetState(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } +}); diff --git a/src/js/logic/pdf-to-excel-page.ts b/src/js/logic/pdf-to-excel-page.ts new file mode 100644 index 0000000..33fa38b --- /dev/null +++ b/src/js/logic/pdf-to-excel-page.ts @@ -0,0 +1,194 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { createIcons, icons } from 'lucide'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; +import * as XLSX from 'xlsx'; +let file: File | null = null; + +const updateUI = () => { + const fileDisplayArea = document.getElementById('file-display-area'); + const optionsPanel = document.getElementById('options-panel'); + + if (!fileDisplayArea || !optionsPanel) return; + + fileDisplayArea.innerHTML = ''; + + if (file) { + optionsPanel.classList.remove('hidden'); + + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = resetState; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + + createIcons({ icons }); + } else { + optionsPanel.classList.add('hidden'); + } +}; + +const resetState = () => { + file = null; + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; + updateUI(); +}; + +async function convert() { + if (!file) { + showAlert('No File', 'Please upload a PDF file first.'); + return; + } + + showLoader('Loading Engine...'); + + try { + const pymupdf = await loadPyMuPDF(); + showLoader('Extracting tables...'); + + const doc = await pymupdf.open(file); + const pageCount = doc.pageCount; + const baseName = file.name.replace(/\.[^/.]+$/, ''); + + interface TableData { + page: number; + rows: (string | null)[][]; + } + + const allTables: TableData[] = []; + + for (let i = 0; i < pageCount; i++) { + showLoader(`Scanning page ${i + 1} of ${pageCount}...`); + const page = doc.getPage(i); + const tables = page.findTables(); + + tables.forEach((table) => { + allTables.push({ + page: i + 1, + rows: table.rows, + }); + }); + } + + if (allTables.length === 0) { + showAlert('No Tables Found', 'No tables were detected in this PDF.'); + return; + } + + showLoader('Creating Excel file...'); + + const workbook = XLSX.utils.book_new(); + + if (allTables.length === 1) { + const worksheet = XLSX.utils.aoa_to_sheet(allTables[0].rows); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Table'); + } else { + allTables.forEach((table, idx) => { + const sheetName = `Table ${idx + 1} (Page ${table.page})`.substring( + 0, + 31 + ); + const worksheet = XLSX.utils.aoa_to_sheet(table.rows); + XLSX.utils.book_append_sheet(workbook, worksheet, sheetName); + }); + } + + const xlsxData = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }); + const blob = new Blob([xlsxData], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }); + downloadFile(blob, `${baseName}.xlsx`); + showAlert( + 'Success', + `Extracted ${allTables.length} table(s) to Excel!`, + 'success', + resetState + ); + } catch (e) { + console.error(e); + const message = e instanceof Error ? e.message : 'Unknown error'; + showAlert('Error', `Failed to convert PDF to Excel. ${message}`); + } finally { + hideLoader(); + } +} + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const handleFileSelect = (newFiles: FileList | null) => { + if (!newFiles || newFiles.length === 0) return; + const validFile = Array.from(newFiles).find( + (f) => f.type === 'application/pdf' + ); + + if (!validFile) { + showAlert('Invalid File', 'Please upload a PDF file.'); + return; + } + + file = validFile; + updateUI(); + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } +}); diff --git a/src/js/logic/pdf-to-greyscale-page.ts b/src/js/logic/pdf-to-greyscale-page.ts index 9198bfe..64c8df5 100644 --- a/src/js/logic/pdf-to-greyscale-page.ts +++ b/src/js/logic/pdf-to-greyscale-page.ts @@ -1,206 +1,222 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js'; +import { + downloadFile, + formatBytes, + readFileAsArrayBuffer, + getPDFDocument, +} from '../utils/helpers.js'; import { createIcons, icons } from 'lucide'; import { PDFDocument } from 'pdf-lib'; +import { applyGreyscale } from '../utils/image-effects.js'; import * as pdfjsLib from 'pdfjs-dist'; -pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); let files: File[] = []; const updateUI = () => { - const fileDisplayArea = document.getElementById('file-display-area'); - const optionsPanel = document.getElementById('options-panel'); - const dropZone = document.getElementById('drop-zone'); + const fileDisplayArea = document.getElementById('file-display-area'); + const optionsPanel = document.getElementById('options-panel'); + const dropZone = document.getElementById('drop-zone'); - if (!fileDisplayArea || !optionsPanel || !dropZone) return; + if (!fileDisplayArea || !optionsPanel || !dropZone) return; - fileDisplayArea.innerHTML = ''; + fileDisplayArea.innerHTML = ''; - if (files.length > 0) { - optionsPanel.classList.remove('hidden'); + if (files.length > 0) { + optionsPanel.classList.remove('hidden'); - // Render files synchronously first - files.forEach((file) => { - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + // Render files synchronously first + files.forEach((file) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col overflow-hidden'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = file.name; + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Initial state + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Initial state - infoContainer.append(nameSpan, metaSpan); + infoContainer.append(nameSpan, metaSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - files = []; - updateUI(); - }; + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + files = []; + updateUI(); + }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); - // Fetch page count asynchronously - readFileAsArrayBuffer(file).then(buffer => { - return getPDFDocument(buffer).promise; - }).then(pdf => { - metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`; - }).catch(e => { - console.warn('Error loading PDF page count:', e); - metaSpan.textContent = formatBytes(file.size); - }); + // Fetch page count asynchronously + readFileAsArrayBuffer(file) + .then((buffer) => { + return getPDFDocument(buffer).promise; + }) + .then((pdf) => { + metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`; + }) + .catch((e) => { + console.warn('Error loading PDF page count:', e); + metaSpan.textContent = formatBytes(file.size); }); + }); - // Initialize icons immediately after synchronous render - createIcons({ icons }); - } else { - optionsPanel.classList.add('hidden'); - } + // Initialize icons immediately after synchronous render + createIcons({ icons }); + } else { + optionsPanel.classList.add('hidden'); + } }; const resetState = () => { - files = []; - const fileInput = document.getElementById('file-input') as HTMLInputElement; - if (fileInput) fileInput.value = ''; - updateUI(); + files = []; + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; + updateUI(); }; async function convert() { - if (files.length === 0) { - showAlert('No File', 'Please upload a PDF file first.'); - return; - } - showLoader('Converting to greyscale...'); - try { - const pdfBytes = await readFileAsArrayBuffer(files[0]) as ArrayBuffer; - const pdfDoc = await PDFDocument.load(pdfBytes); - const pages = pdfDoc.getPages(); + if (files.length === 0) { + showAlert('No File', 'Please upload a PDF file first.'); + return; + } + showLoader('Converting to greyscale...'); + try { + const pdfBytes = (await readFileAsArrayBuffer(files[0])) as ArrayBuffer; + const pdfDoc = await PDFDocument.load(pdfBytes); + const pages = pdfDoc.getPages(); - const pdfjsDoc = await getPDFDocument({ data: pdfBytes }).promise; - const newPdfDoc = await PDFDocument.create(); + const pdfjsDoc = await getPDFDocument({ data: pdfBytes }).promise; + const newPdfDoc = await PDFDocument.create(); - for (let i = 1; i <= pdfjsDoc.numPages; i++) { - const page = await pdfjsDoc.getPage(i); - const viewport = page.getViewport({ scale: 2.0 }); - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - canvas.height = viewport.height; - canvas.width = viewport.width; + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + const page = await pdfjsDoc.getPage(i); + const viewport = page.getViewport({ scale: 2.0 }); + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; - await page.render({ canvasContext: context!, viewport: viewport, canvas }).promise; + await page.render({ canvasContext: context!, viewport: viewport, canvas }) + .promise; - const imageData = context!.getImageData(0, 0, canvas.width, canvas.height); - const data = imageData.data; + const imageData = context!.getImageData( + 0, + 0, + canvas.width, + canvas.height + ); + applyGreyscale(imageData); + context!.putImageData(imageData, 0, 0); - // Convert to greyscale - for (let j = 0; j < data.length; j += 4) { - const grey = Math.round(0.299 * data[j] + 0.587 * data[j + 1] + 0.114 * data[j + 2]); - data[j] = grey; - data[j + 1] = grey; - data[j + 2] = grey; - } + const jpegBlob = await new Promise((resolve) => + canvas.toBlob(resolve, 'image/jpeg', 0.9) + ); - context!.putImageData(imageData, 0, 0); - - const jpegBlob = await new Promise((resolve) => - canvas.toBlob(resolve, 'image/jpeg', 0.9) - ); - - if (jpegBlob) { - const jpegBytes = await jpegBlob.arrayBuffer(); - const jpegImage = await newPdfDoc.embedJpg(jpegBytes); - const newPage = newPdfDoc.addPage([viewport.width, viewport.height]); - newPage.drawImage(jpegImage, { - x: 0, - y: 0, - width: viewport.width, - height: viewport.height, - }); - } - } - - const resultBytes = await newPdfDoc.save(); - downloadFile( - new Blob([new Uint8Array(resultBytes)], { type: 'application/pdf' }), - 'greyscale.pdf' - ); - showAlert('Success', 'PDF converted to greyscale successfully!', 'success', () => { - resetState(); + if (jpegBlob) { + const jpegBytes = await jpegBlob.arrayBuffer(); + const jpegImage = await newPdfDoc.embedJpg(jpegBytes); + const newPage = newPdfDoc.addPage([viewport.width, viewport.height]); + newPage.drawImage(jpegImage, { + x: 0, + y: 0, + width: viewport.width, + height: viewport.height, }); - } catch (e) { - console.error(e); - showAlert( - 'Error', - 'Failed to convert PDF to greyscale. The file might be corrupted.' - ); - } finally { - hideLoader(); + } } + + const resultBytes = await newPdfDoc.save(); + downloadFile( + new Blob([new Uint8Array(resultBytes)], { type: 'application/pdf' }), + 'greyscale.pdf' + ); + showAlert( + 'Success', + 'PDF converted to greyscale successfully!', + 'success', + () => { + resetState(); + } + ); + } catch (e) { + console.error(e); + showAlert( + 'Error', + 'Failed to convert PDF to greyscale. The file might be corrupted.' + ); + } finally { + hideLoader(); + } } document.addEventListener('DOMContentLoaded', () => { - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const processBtn = document.getElementById('process-btn'); - const backBtn = document.getElementById('back-to-tools'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const backBtn = document.getElementById('back-to-tools'); - if (backBtn) { - backBtn.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; - }); + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const handleFileSelect = (newFiles: FileList | null) => { + if (!newFiles || newFiles.length === 0) return; + const validFiles = Array.from(newFiles).filter( + (file) => file.type === 'application/pdf' + ); + + if (validFiles.length === 0) { + showAlert('Invalid File', 'Please upload a PDF file.'); + return; } - const handleFileSelect = (newFiles: FileList | null) => { - if (!newFiles || newFiles.length === 0) return; - const validFiles = Array.from(newFiles).filter( - (file) => file.type === 'application/pdf' - ); + files = [validFiles[0]]; + updateUI(); + }; - if (validFiles.length === 0) { - showAlert('Invalid File', 'Please upload a PDF file.'); - return; - } + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); - files = [validFiles[0]]; - updateUI(); - }; + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); - if (fileInput && dropZone) { - fileInput.addEventListener('change', (e) => { - handleFileSelect((e.target as HTMLInputElement).files); - }); + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); - dropZone.addEventListener('dragleave', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - }); + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - handleFileSelect(e.dataTransfer?.files ?? null); - }); - - fileInput.addEventListener('click', () => { - fileInput.value = ''; - }); - } - - if (processBtn) { - processBtn.addEventListener('click', convert); - } + if (processBtn) { + processBtn.addEventListener('click', convert); + } }); diff --git a/src/js/logic/pdf-to-jpg-page.ts b/src/js/logic/pdf-to-jpg-page.ts index a8fea45..b01bdc3 100644 --- a/src/js/logic/pdf-to-jpg-page.ts +++ b/src/js/logic/pdf-to-jpg-page.ts @@ -1,9 +1,8 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument, getCleanPdfFilename } from '../utils/helpers.js'; +import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js'; import { createIcons, icons } from 'lucide'; import JSZip from 'jszip'; import * as pdfjsLib from 'pdfjs-dist'; -import { PDFPageProxy } from 'pdfjs-dist'; pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); @@ -88,31 +87,31 @@ async function convert() { const pdf = await getPDFDocument( await readFileAsArrayBuffer(files[0]) ).promise; + const zip = new JSZip(); const qualityInput = document.getElementById('jpg-quality') as HTMLInputElement; const quality = qualityInput ? parseFloat(qualityInput.value) : 0.9; - if (pdf.numPages === 1) { - const page = await pdf.getPage(1); - const blob = await renderPage(page, quality); - downloadFile(blob, getCleanPdfFilename(files[0].name) + '.jpg'); - } else { - const zip = new JSZip(); - for (let i = 1; i <= pdf.numPages; i++) { - const page = await pdf.getPage(i); - const blob = await renderPage(page, quality); - if (blob) { - zip.file(`page_${i}.jpg`, blob); - } - } + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const viewport = page.getViewport({ scale: 2.0 }); + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; - const zipBlob = await zip.generateAsync({ type: 'blob' }); - downloadFile( - zipBlob, - getCleanPdfFilename(files[0].name) + '_jpgs.zip' + await page.render({ canvasContext: context!, viewport: viewport, canvas }).promise; + + const blob = await new Promise((resolve) => + canvas.toBlob(resolve, 'image/jpeg', quality) ); + if (blob) { + zip.file(`page_${i}.jpg`, blob); + } } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'converted_images.zip'); showAlert('Success', 'PDF converted to JPGs successfully!', 'success', () => { resetState(); }); @@ -127,28 +126,6 @@ async function convert() { } } -async function renderPage( - page: PDFPageProxy, - quality: number -): Promise { - const viewport = page.getViewport({ scale: 2.0 }); - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - canvas.height = viewport.height; - canvas.width = viewport.width; - - await page.render({ - canvasContext: context!, - viewport: viewport, - canvas, - }).promise; - - const blob = await new Promise((resolve) => - canvas.toBlob(resolve, 'image/jpeg', quality) - ); - return blob; -} - document.addEventListener('DOMContentLoaded', () => { const fileInput = document.getElementById('file-input') as HTMLInputElement; const dropZone = document.getElementById('drop-zone'); diff --git a/src/js/logic/pdf-to-json.ts b/src/js/logic/pdf-to-json.ts index babeaeb..3ada998 100644 --- a/src/js/logic/pdf-to-json.ts +++ b/src/js/logic/pdf-to-json.ts @@ -1,101 +1,133 @@ -import JSZip from 'jszip' -import { downloadFile, formatBytes, readFileAsArrayBuffer } from '../utils/helpers'; +import JSZip from 'jszip'; +import { + downloadFile, + formatBytes, + readFileAsArrayBuffer, +} from '../utils/helpers'; import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'; +import { isCpdfAvailable } from '../utils/cpdf-helper.js'; +import { + showWasmRequiredDialog, + WasmProvider, +} from '../utils/wasm-provider.js'; -const worker = new Worker(import.meta.env.BASE_URL + 'workers/pdf-to-json.worker.js'); +const worker = new Worker( + import.meta.env.BASE_URL + 'workers/pdf-to-json.worker.js' +); -let selectedFiles: File[] = [] +let selectedFiles: File[] = []; -const pdfFilesInput = document.getElementById('pdfFiles') as HTMLInputElement -const convertBtn = document.getElementById('convertBtn') as HTMLButtonElement -const statusMessage = document.getElementById('status-message') as HTMLDivElement -const fileListDiv = document.getElementById('fileList') as HTMLDivElement -const backToToolsBtn = document.getElementById('back-to-tools') as HTMLButtonElement +const pdfFilesInput = document.getElementById('pdfFiles') as HTMLInputElement; +const convertBtn = document.getElementById('convertBtn') as HTMLButtonElement; +const statusMessage = document.getElementById( + 'status-message' +) as HTMLDivElement; +const fileListDiv = document.getElementById('fileList') as HTMLDivElement; +const backToToolsBtn = document.getElementById( + 'back-to-tools' +) as HTMLButtonElement; function showStatus( message: string, type: 'success' | 'error' | 'info' = 'info' ) { - statusMessage.textContent = message - statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${type === 'success' - ? 'bg-green-900 text-green-200' - : type === 'error' - ? 'bg-red-900 text-red-200' - : 'bg-blue-900 text-blue-200' - }` - statusMessage.classList.remove('hidden') + statusMessage.textContent = message; + statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${ + type === 'success' + ? 'bg-green-900 text-green-200' + : type === 'error' + ? 'bg-red-900 text-red-200' + : 'bg-blue-900 text-blue-200' + }`; + statusMessage.classList.remove('hidden'); } function hideStatus() { - statusMessage.classList.add('hidden') + statusMessage.classList.add('hidden'); } function updateFileList() { - fileListDiv.innerHTML = '' + fileListDiv.innerHTML = ''; if (selectedFiles.length === 0) { - fileListDiv.classList.add('hidden') - return + fileListDiv.classList.add('hidden'); + return; } - fileListDiv.classList.remove('hidden') + fileListDiv.classList.remove('hidden'); selectedFiles.forEach((file) => { - const fileDiv = document.createElement('div') - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm mb-2' + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm mb-2'; - const nameSpan = document.createElement('span') - nameSpan.className = 'truncate font-medium text-gray-200' - nameSpan.textContent = file.name + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; - const sizeSpan = document.createElement('span') - sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400' - sizeSpan.textContent = formatBytes(file.size) + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400'; + sizeSpan.textContent = formatBytes(file.size); - fileDiv.append(nameSpan, sizeSpan) - fileListDiv.appendChild(fileDiv) - }) + fileDiv.append(nameSpan, sizeSpan); + fileListDiv.appendChild(fileDiv); + }); } pdfFilesInput.addEventListener('change', (e) => { - const target = e.target as HTMLInputElement + const target = e.target as HTMLInputElement; if (target.files && target.files.length > 0) { - selectedFiles = Array.from(target.files) - convertBtn.disabled = selectedFiles.length === 0 - updateFileList() + selectedFiles = Array.from(target.files); + convertBtn.disabled = selectedFiles.length === 0; + updateFileList(); if (selectedFiles.length === 0) { - showStatus('Please select at least 1 PDF file', 'info') + showStatus('Please select at least 1 PDF file', 'info'); } else { - showStatus(`${selectedFiles.length} file(s) selected. Ready to convert!`, 'info') + showStatus( + `${selectedFiles.length} file(s) selected. Ready to convert!`, + 'info' + ); } } -}) +}); async function convertPDFsToJSON() { if (selectedFiles.length === 0) { - showStatus('Please select at least 1 PDF file', 'error') - return + showStatus('Please select at least 1 PDF file', 'error'); + return; + } + + // Check if CPDF is configured + if (!isCpdfAvailable()) { + showWasmRequiredDialog('cpdf'); + return; } try { - convertBtn.disabled = true - showStatus('Reading files (Main Thread)...', 'info') + convertBtn.disabled = true; + showStatus('Reading files (Main Thread)...', 'info'); const fileBuffers = await Promise.all( - selectedFiles.map(file => readFileAsArrayBuffer(file)) - ) + selectedFiles.map((file) => readFileAsArrayBuffer(file)) + ); - showStatus('Converting PDFs to JSON..', 'info') - - worker.postMessage({ - command: 'convert', - fileBuffers: fileBuffers, - fileNames: selectedFiles.map(f => f.name) - }, fileBuffers); + showStatus('Converting PDFs to JSON..', 'info'); + worker.postMessage( + { + command: 'convert', + fileBuffers: fileBuffers, + fileNames: selectedFiles.map((f) => f.name), + cpdfUrl: WasmProvider.getUrl('cpdf')! + 'coherentpdf.browser.min.js', + }, + fileBuffers + ); } catch (error) { - console.error('Error reading files:', error) - showStatus(`❌ Error reading files: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error') - convertBtn.disabled = false + console.error('Error reading files:', error); + showStatus( + `❌ Error reading files: ${error instanceof Error ? error.message : 'Unknown error'}`, + 'error' + ); + convertBtn.disabled = false; } } @@ -103,38 +135,45 @@ worker.onmessage = async (e: MessageEvent) => { convertBtn.disabled = false; if (e.data.status === 'success') { - const jsonFiles = e.data.jsonFiles as Array<{ name: string, data: ArrayBuffer }>; + const jsonFiles = e.data.jsonFiles as Array<{ + name: string; + data: ArrayBuffer; + }>; try { - showStatus('Creating ZIP file...', 'info') + showStatus('Creating ZIP file...', 'info'); - const zip = new JSZip() + const zip = new JSZip(); jsonFiles.forEach(({ name, data }) => { - const jsonName = name.replace(/\.pdf$/i, '.json') - const uint8Array = new Uint8Array(data) - zip.file(jsonName, uint8Array) - }) + const jsonName = name.replace(/\.pdf$/i, '.json'); + const uint8Array = new Uint8Array(data); + zip.file(jsonName, uint8Array); + }); - const zipBlob = await zip.generateAsync({ type: 'blob' }) - downloadFile(zipBlob, 'pdfs-to-json.zip') + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'pdfs-to-json.zip'); - showStatus('✅ PDFs converted to JSON successfully! ZIP download started.', 'success') + showStatus( + '✅ PDFs converted to JSON successfully! ZIP download started.', + 'success' + ); - selectedFiles = [] - pdfFilesInput.value = '' - fileListDiv.innerHTML = '' - fileListDiv.classList.add('hidden') - convertBtn.disabled = true + selectedFiles = []; + pdfFilesInput.value = ''; + fileListDiv.innerHTML = ''; + fileListDiv.classList.add('hidden'); + convertBtn.disabled = true; setTimeout(() => { - hideStatus() - }, 3000) - + hideStatus(); + }, 3000); } catch (error) { - console.error('Error creating ZIP:', error) - showStatus(`❌ Error creating ZIP: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error') + console.error('Error creating ZIP:', error); + showStatus( + `❌ Error creating ZIP: ${error instanceof Error ? error.message : 'Unknown error'}`, + 'error' + ); } - } else if (e.data.status === 'error') { const errorMessage = e.data.message || 'Unknown error occurred in worker.'; console.error('Worker Error:', errorMessage); @@ -144,11 +183,11 @@ worker.onmessage = async (e: MessageEvent) => { if (backToToolsBtn) { backToToolsBtn.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL - }) + window.location.href = import.meta.env.BASE_URL; + }); } -convertBtn.addEventListener('click', convertPDFsToJSON) +convertBtn.addEventListener('click', convertPDFsToJSON); -showStatus('Select PDF files to get started', 'info') -initializeGlobalShortcuts() +showStatus('Select PDF files to get started', 'info'); +initializeGlobalShortcuts(); diff --git a/src/js/logic/pdf-to-markdown-page.ts b/src/js/logic/pdf-to-markdown-page.ts new file mode 100644 index 0000000..f99294b --- /dev/null +++ b/src/js/logic/pdf-to-markdown-page.ts @@ -0,0 +1,221 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, + getPDFDocument, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const fileDisplayArea = document.getElementById('file-display-area'); + const convertOptions = document.getElementById('convert-options'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const includeImagesCheckbox = document.getElementById( + 'include-images' + ) as HTMLInputElement; + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!fileDisplayArea || !convertOptions || !processBtn || !fileControls) + return; + + if (state.files.length > 0) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_: File, i: number) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + + try { + const arrayBuffer = await readFileAsArrayBuffer(file); + const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; + metaSpan.textContent = `${formatBytes(file.size)} • ${pdfDoc.numPages} pages`; + } catch (error) { + metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`; + } + } + + createIcons({ icons }); + fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + (processBtn as HTMLButtonElement).disabled = false; + } else { + fileDisplayArea.innerHTML = ''; + fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + (processBtn as HTMLButtonElement).disabled = true; + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + const convert = async () => { + try { + if (state.files.length === 0) { + showAlert('No Files', 'Please select at least one PDF file.'); + return; + } + + showLoader('Loading PDF converter...'); + const pymupdf = await loadPyMuPDF(); + + const includeImages = includeImagesCheckbox?.checked ?? false; + + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + + const markdown = await pymupdf.pdfToMarkdown(file, { includeImages }); + const outName = file.name.replace(/\.pdf$/i, '') + '.md'; + const blob = new Blob([markdown], { type: 'text/markdown' }); + + downloadFile(blob, outName); + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${file.name} to Markdown.`, + 'success', + () => resetState() + ); + } else { + showLoader('Converting multiple PDFs...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader( + `Converting ${i + 1}/${state.files.length}: ${file.name}...` + ); + + const markdown = await pymupdf.pdfToMarkdown(file, { includeImages }); + const baseName = file.name.replace(/\.pdf$/i, ''); + zip.file(`${baseName}.md`, markdown); + } + + showLoader('Creating ZIP archive...'); + const zipBlob = await zip.generateAsync({ type: 'blob' }); + + downloadFile(zipBlob, 'markdown-files.zip'); + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${state.files.length} PDF(s) to Markdown.`, + 'success', + () => resetState() + ); + } + } catch (e: any) { + hideLoader(); + showAlert( + 'Error', + `An error occurred during conversion. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const pdfFiles = Array.from(files).filter( + (f) => + f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf') + ); + state.files = [...state.files, ...pdfFiles]; + updateUI(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + handleFileSelect(files); + } + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + resetState(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } +}); diff --git a/src/js/logic/pdf-to-pdfa-page.ts b/src/js/logic/pdf-to-pdfa-page.ts new file mode 100644 index 0000000..0de4073 --- /dev/null +++ b/src/js/logic/pdf-to-pdfa-page.ts @@ -0,0 +1,270 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, + getPDFDocument, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { convertFileToPdfA, type PdfALevel } from '../utils/ghostscript-loader'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const fileDisplayArea = document.getElementById('file-display-area'); + const optionsContainer = document.getElementById('options-container'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const pdfaLevelSelect = document.getElementById( + 'pdfa-level' + ) as HTMLSelectElement; + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!fileDisplayArea || !optionsContainer || !processBtn || !fileControls) + return; + + if (state.files.length > 0) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + + try { + const arrayBuffer = await readFileAsArrayBuffer(file); + const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; + metaSpan.textContent = `${formatBytes(file.size)} • ${pdfDoc.numPages} pages`; + } catch (error) { + console.error('Error loading PDF:', error); + metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`; + } + } + + createIcons({ icons }); + fileControls.classList.remove('hidden'); + optionsContainer.classList.remove('hidden'); + (processBtn as HTMLButtonElement).disabled = false; + } else { + fileDisplayArea.innerHTML = ''; + fileControls.classList.add('hidden'); + optionsContainer.classList.add('hidden'); + (processBtn as HTMLButtonElement).disabled = true; + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + + if (pdfaLevelSelect) pdfaLevelSelect.value = 'PDF/A-2b'; + + updateUI(); + }; + + const convertToPdfA = async () => { + const level = pdfaLevelSelect.value as PdfALevel; + + try { + if (state.files.length === 0) { + showAlert('No Files', 'Please select at least one PDF file.'); + hideLoader(); + return; + } + + if (state.files.length === 1) { + const originalFile = state.files[0]; + const preFlattenCheckbox = document.getElementById( + 'pre-flatten' + ) as HTMLInputElement; + const shouldPreFlatten = preFlattenCheckbox?.checked || false; + + let fileToConvert = originalFile; + + // Pre-flatten using PyMuPDF rasterization if checkbox is checked + if (shouldPreFlatten) { + if (!isPyMuPDFAvailable()) { + showWasmRequiredDialog('pymupdf'); + hideLoader(); + return; + } + + showLoader('Pre-flattening PDF...'); + const pymupdf = await loadPyMuPDF(); + + // Rasterize PDF to images and back to PDF (300 DPI for quality) + const flattenedBlob = await (pymupdf as any).rasterizePdf( + originalFile, + { + dpi: 300, + format: 'png', + } + ); + + fileToConvert = new File([flattenedBlob], originalFile.name, { + type: 'application/pdf', + }); + } + + showLoader('Initializing Ghostscript...'); + + const convertedBlob = await convertFileToPdfA( + fileToConvert, + level, + (msg) => showLoader(msg) + ); + + const fileName = originalFile.name.replace(/\.pdf$/i, '') + '_pdfa.pdf'; + + downloadFile(convertedBlob, fileName); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${originalFile.name} to ${level}.`, + 'success', + () => resetState() + ); + } else { + showLoader('Converting multiple PDFs to PDF/A...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader( + `Converting ${i + 1}/${state.files.length}: ${file.name}...` + ); + + const convertedBlob = await convertFileToPdfA(file, level, (msg) => + showLoader(msg) + ); + + const baseName = file.name.replace(/\.pdf$/i, ''); + const blobBuffer = await convertedBlob.arrayBuffer(); + zip.file(`${baseName}_pdfa.pdf`, blobBuffer); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + + downloadFile(zipBlob, 'pdfa-converted.zip'); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${state.files.length} PDF(s) to ${level}.`, + 'success', + () => resetState() + ); + } + } catch (e: any) { + hideLoader(); + showAlert( + 'Error', + `An error occurred during conversion. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + state.files = [...state.files, ...Array.from(files)]; + updateUI(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const pdfFiles = Array.from(files).filter( + (f) => + f.type === 'application/pdf' || + f.name.toLowerCase().endsWith('.pdf') + ); + if (pdfFiles.length > 0) { + const dataTransfer = new DataTransfer(); + pdfFiles.forEach((f) => dataTransfer.items.add(f)); + handleFileSelect(dataTransfer.files); + } + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + resetState(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdfA); + } +}); diff --git a/src/js/logic/pdf-to-png-page.ts b/src/js/logic/pdf-to-png-page.ts index 3e05165..f453b65 100644 --- a/src/js/logic/pdf-to-png-page.ts +++ b/src/js/logic/pdf-to-png-page.ts @@ -1,9 +1,8 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument, getCleanPdfFilename } from '../utils/helpers.js'; +import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js'; import { createIcons, icons } from 'lucide'; import JSZip from 'jszip'; import * as pdfjsLib from 'pdfjs-dist'; -import { PDFPageProxy } from 'pdfjs-dist'; pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); @@ -88,31 +87,31 @@ async function convert() { const pdf = await getPDFDocument( await readFileAsArrayBuffer(files[0]) ).promise; + const zip = new JSZip(); const scaleInput = document.getElementById('png-scale') as HTMLInputElement; const scale = scaleInput ? parseFloat(scaleInput.value) : 2.0; - if (pdf.numPages === 1) { - const page = await pdf.getPage(1); - const blob = await renderPage(page, scale); - downloadFile(blob, getCleanPdfFilename(files[0].name) + '.png'); - } else { - const zip = new JSZip(); - for (let i = 1; i <= pdf.numPages; i++) { - const page = await pdf.getPage(i); - const blob = await renderPage(page, scale); - if (blob) { - zip.file(`page_${i}.png`, blob); - } - } + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const viewport = page.getViewport({ scale }); + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; - const zipBlob = await zip.generateAsync({ type: 'blob' }); - downloadFile( - zipBlob, - getCleanPdfFilename(files[0].name) + '_pngs.zip' + await page.render({ canvasContext: context!, viewport: viewport, canvas }).promise; + + const blob = await new Promise((resolve) => + canvas.toBlob(resolve, 'image/png') ); + if (blob) { + zip.file(`page_${i}.png`, blob); + } } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'converted_images.zip'); showAlert('Success', 'PDF converted to PNGs successfully!', 'success', () => { resetState(); }); @@ -127,28 +126,6 @@ async function convert() { } } -async function renderPage( - page: PDFPageProxy, - scale: number -): Promise { - const viewport = page.getViewport({ scale }); - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - canvas.height = viewport.height; - canvas.width = viewport.width; - - await page.render({ - canvasContext: context!, - viewport: viewport, - canvas, - }).promise; - - const blob = await new Promise((resolve) => - canvas.toBlob(resolve, 'image/png') - ); - return blob; -} - document.addEventListener('DOMContentLoaded', () => { const fileInput = document.getElementById('file-input') as HTMLInputElement; const dropZone = document.getElementById('drop-zone'); diff --git a/src/js/logic/pdf-to-svg-page.ts b/src/js/logic/pdf-to-svg-page.ts new file mode 100644 index 0000000..6bae490 --- /dev/null +++ b/src/js/logic/pdf-to-svg-page.ts @@ -0,0 +1,238 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { createIcons, icons } from 'lucide'; +import JSZip from 'jszip'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; + +let pymupdf: any = null; +let files: File[] = []; + +const updateUI = () => { + const fileDisplayArea = document.getElementById('file-display-area'); + const optionsPanel = document.getElementById('options-panel'); + const fileControls = document.getElementById('file-controls'); + + if (!fileDisplayArea || !optionsPanel) return; + + fileDisplayArea.innerHTML = ''; + + if (files.length > 0) { + optionsPanel.classList.remove('hidden'); + if (fileControls) fileControls.classList.remove('hidden'); + + files.forEach((file, index) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + files = files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + }); + + createIcons({ icons }); + } else { + optionsPanel.classList.add('hidden'); + if (fileControls) fileControls.classList.add('hidden'); + } +}; + +const resetState = () => { + files = []; + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; + updateUI(); +}; + +async function convert() { + if (files.length === 0) { + showAlert('No Files', 'Please upload at least one PDF file.'); + return; + } + + // Check if PyMuPDF is configured + if (!isPyMuPDFAvailable()) { + showWasmRequiredDialog('pymupdf'); + return; + } + + showLoader('Loading Engine...'); + + try { + // Load PyMuPDF dynamically if not already loaded + if (!pymupdf) { + pymupdf = await loadPyMuPDF(); + } + + const isSingleFile = files.length === 1; + + if (isSingleFile) { + const doc = await pymupdf.open(files[0]); + const pageCount = doc.pageCount; + const baseName = files[0].name.replace(/\.[^/.]+$/, ''); + + if (pageCount === 1) { + showLoader('Converting to SVG...'); + const page = doc.getPage(0); + const svgContent = page.toSvg(); + const svgBlob = new Blob([svgContent], { type: 'image/svg+xml' }); + downloadFile(svgBlob, `${baseName}.svg`); + showAlert( + 'Success', + 'PDF converted to SVG successfully!', + 'success', + () => resetState() + ); + } else { + const zip = new JSZip(); + for (let i = 0; i < pageCount; i++) { + showLoader(`Converting page ${i + 1} of ${pageCount}...`); + const page = doc.getPage(i); + const svgContent = page.toSvg(); + zip.file(`page_${i + 1}.svg`, svgContent); + } + showLoader('Creating ZIP file...'); + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${baseName}_svg.zip`); + showAlert( + 'Success', + `Converted ${pageCount} pages to SVG!`, + 'success', + () => resetState() + ); + } + } else { + const zip = new JSZip(); + let totalPages = 0; + + for (let f = 0; f < files.length; f++) { + const file = files[f]; + showLoader(`Processing file ${f + 1} of ${files.length}...`); + const doc = await pymupdf.open(file); + const pageCount = doc.pageCount; + const baseName = file.name.replace(/\.[^/.]+$/, ''); + + for (let i = 0; i < pageCount; i++) { + showLoader( + `File ${f + 1}/${files.length}: Page ${i + 1}/${pageCount}` + ); + const page = doc.getPage(i); + const svgContent = page.toSvg(); + const fileName = + pageCount === 1 + ? `${baseName}.svg` + : `${baseName}_page_${i + 1}.svg`; + zip.file(fileName, svgContent); + totalPages++; + } + } + + showLoader('Creating ZIP file...'); + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'pdf_to_svg.zip'); + showAlert( + 'Success', + `Converted ${files.length} files (${totalPages} pages) to SVG!`, + 'success', + () => resetState() + ); + } + } catch (e) { + console.error(e); + const message = e instanceof Error ? e.message : 'Unknown error'; + showAlert('Error', `Failed to convert PDF to SVG. ${message}`); + } finally { + hideLoader(); + } +} + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const backBtn = document.getElementById('back-to-tools'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const handleFileSelect = (newFiles: FileList | null, replace = false) => { + if (!newFiles || newFiles.length === 0) return; + const validFiles = Array.from(newFiles).filter( + (file) => file.type === 'application/pdf' + ); + + if (validFiles.length === 0) { + showAlert('Invalid Files', 'Please upload PDF files.'); + return; + } + + if (replace) { + files = validFiles; + } else { + files = [...files, ...validFiles]; + } + updateUI(); + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect( + (e.target as HTMLInputElement).files, + files.length === 0 + ); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null, files.length === 0); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) + addMoreBtn.addEventListener('click', () => fileInput?.click()); + if (clearFilesBtn) clearFilesBtn.addEventListener('click', resetState); + if (processBtn) processBtn.addEventListener('click', convert); +}); diff --git a/src/js/logic/pdf-to-text-page.ts b/src/js/logic/pdf-to-text-page.ts new file mode 100644 index 0000000..efa00ce --- /dev/null +++ b/src/js/logic/pdf-to-text-page.ts @@ -0,0 +1,233 @@ +import { createIcons, icons } from 'lucide'; +import { showAlert, showLoader, hideLoader } from '../ui.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; + +let files: File[] = []; +let pymupdf: any = null; + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializePage); +} else { + initializePage(); +} + +function initializePage() { + createIcons({ icons }); + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const processBtn = document.getElementById( + 'process-btn' + ) as HTMLButtonElement; + + if (fileInput) { + fileInput.addEventListener('change', handleFileUpload); + } + + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-600'); + }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-600'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-600'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleFiles(droppedFiles); + } + }); + + fileInput?.addEventListener('click', () => { + if (fileInput) fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput?.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + files = []; + updateUI(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', extractText); + } + + document.getElementById('back-to-tools')?.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); +} + +function handleFileUpload(e: Event) { + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleFiles(input.files); + } +} + +function handleFiles(newFiles: FileList) { + const validFiles = Array.from(newFiles).filter( + (file) => + file.type === 'application/pdf' || + file.name.toLowerCase().endsWith('.pdf') + ); + + if (validFiles.length < newFiles.length) { + showAlert( + 'Invalid Files', + 'Some files were skipped. Only PDF files are allowed.' + ); + } + + if (validFiles.length > 0) { + files = [...files, ...validFiles]; + updateUI(); + } +} + +const resetState = () => { + files = []; + updateUI(); +}; + +function updateUI() { + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const extractOptions = document.getElementById('extract-options'); + + if (!fileDisplayArea || !fileControls || !extractOptions) return; + + fileDisplayArea.innerHTML = ''; + + if (files.length > 0) { + fileControls.classList.remove('hidden'); + extractOptions.classList.remove('hidden'); + + files.forEach((file, index) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex items-center gap-2 overflow-hidden'; + + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; + + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; + sizeSpan.textContent = `(${formatBytes(file.size)})`; + + infoContainer.append(nameSpan, sizeSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + files = files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + }); + createIcons({ icons }); + } else { + fileControls.classList.add('hidden'); + extractOptions.classList.add('hidden'); + } +} + +async function ensurePyMuPDF(): Promise { + if (!pymupdf) { + pymupdf = await loadPyMuPDF(); + } + return pymupdf; +} + +async function extractText() { + if (files.length === 0) { + showAlert('No Files', 'Please select at least one PDF file.'); + return; + } + + showLoader('Loading engine...'); + + try { + const mupdf = await ensurePyMuPDF(); + + if (files.length === 1) { + const file = files[0]; + showLoader(`Extracting text from ${file.name}...`); + + const fullText = await mupdf.pdfToText(file); + + const baseName = file.name.replace(/\.pdf$/i, ''); + const textBlob = new Blob([fullText], { + type: 'text/plain;charset=utf-8', + }); + downloadFile(textBlob, `${baseName}.txt`); + + hideLoader(); + showAlert('Success', 'Text extracted successfully!', 'success', () => { + resetState(); + }); + } else { + showLoader('Extracting text from multiple files...'); + + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + showLoader( + `Extracting text from file ${i + 1}/${files.length}: ${file.name}...` + ); + + const fullText = await mupdf.pdfToText(file); + + const baseName = file.name.replace(/\.pdf$/i, ''); + zip.file(`${baseName}.txt`, fullText); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'pdf-to-text.zip'); + + hideLoader(); + showAlert( + 'Success', + `Extracted text from ${files.length} PDF files!`, + 'success', + () => { + resetState(); + } + ); + } + } catch (e: any) { + console.error('[PDFToText]', e); + hideLoader(); + showAlert( + 'Extraction Error', + e.message || 'Failed to extract text from PDF.' + ); + } +} diff --git a/src/js/logic/pdf-to-tiff-page.ts b/src/js/logic/pdf-to-tiff-page.ts index 38d4ca1..d92734d 100644 --- a/src/js/logic/pdf-to-tiff-page.ts +++ b/src/js/logic/pdf-to-tiff-page.ts @@ -1,10 +1,9 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument, getCleanPdfFilename } from '../utils/helpers.js'; +import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js'; import { createIcons, icons } from 'lucide'; import JSZip from 'jszip'; import * as pdfjsLib from 'pdfjs-dist'; import UTIF from 'utif'; -import { PDFPageProxy } from 'pdfjs-dist'; pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); @@ -85,27 +84,39 @@ async function convert() { const pdf = await getPDFDocument( await readFileAsArrayBuffer(files[0]) ).promise; + const zip = new JSZip(); - if (pdf.numPages === 1) { - const page = await pdf.getPage(1); - const blob = await renderPage(page, 1); - downloadFile(blob.blobData, getCleanPdfFilename(files[0].name) + '.' + blob.ending); - } else { - const zip = new JSZip(); - for (let i = 1; i <= pdf.numPages; i++) { - const page = await pdf.getPage(i); - const blob = await renderPage(page, i); - if (blob.blobData) { - zip.file(`page_${i}.` + blob.ending, blob.blobData); + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const viewport = page.getViewport({ scale: 2.0 }); + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; + + await page.render({ canvasContext: context!, viewport: viewport, canvas }).promise; + + const imageData = context!.getImageData(0, 0, canvas.width, canvas.height); + const rgba = imageData.data; + + try { + const tiffData = UTIF.encodeImage(new Uint8Array(rgba), canvas.width, canvas.height); + const tiffBlob = new Blob([tiffData], { type: 'image/tiff' }); + zip.file(`page_${i}.tiff`, tiffBlob); + } catch (encodeError: any) { + console.warn(`TIFF encoding failed for page ${i}, using PNG fallback:`, encodeError); + // Fallback to PNG if TIFF encoding fails (e.g., PackBits compression issues) + const pngBlob = await new Promise((resolve) => + canvas.toBlob(resolve, 'image/png') + ); + if (pngBlob) { + zip.file(`page_${i}.png`, pngBlob); } } - - const zipBlob = await zip.generateAsync({ type: 'blob' }); - downloadFile( - zipBlob, - getCleanPdfFilename(files[0].name) + '_tiffs.zip' - ); } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'converted_images.zip'); showAlert('Success', 'PDF converted to TIFFs successfully!', 'success', () => { resetState(); }); @@ -120,59 +131,6 @@ async function convert() { } } -async function renderPage( - page: PDFPageProxy, - pageNumber: number -): Promise<{ blobData: Blob | null; ending: string; }> { - const viewport = page.getViewport({ scale: 2.0 }); - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - canvas.height = viewport.height; - canvas.width = viewport.width; - - await page.render({ - canvasContext: context!, - viewport: viewport, - canvas, - }).promise; - - const imageData = context!.getImageData( - 0, - 0, - canvas.width, - canvas.height - ); - const rgba = imageData.data; - - try { - const tiffData = UTIF.encodeImage( - new Uint8Array(rgba), - canvas.width, - canvas.height - ); - const tiffBlob = new Blob([tiffData], { type: 'image/tiff' }); - return { - blobData: tiffBlob, - ending: 'tiff' - } - } catch (encodeError: any) { - console.warn( - `TIFF encoding failed for page ${pageNumber}, using PNG fallback:`, - encodeError - ); - // Fallback to PNG if TIFF encoding fails (e.g., PackBits compression issues) - const pngBlob = await new Promise((resolve) => - canvas.toBlob(resolve, 'image/png') - ); - if (pngBlob) { - return { - blobData: pngBlob, - ending: 'png' - } - } - } -} - document.addEventListener('DOMContentLoaded', () => { const fileInput = document.getElementById('file-input') as HTMLInputElement; const dropZone = document.getElementById('drop-zone'); diff --git a/src/js/logic/pdf-to-webp-page.ts b/src/js/logic/pdf-to-webp-page.ts index ad76de6..cdafdf7 100644 --- a/src/js/logic/pdf-to-webp-page.ts +++ b/src/js/logic/pdf-to-webp-page.ts @@ -1,9 +1,8 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument, getCleanPdfFilename } from '../utils/helpers.js'; +import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js'; import { createIcons, icons } from 'lucide'; import JSZip from 'jszip'; import * as pdfjsLib from 'pdfjs-dist'; -import { PDFPageProxy } from 'pdfjs-dist'; pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); @@ -88,27 +87,31 @@ async function convert() { const pdf = await getPDFDocument( await readFileAsArrayBuffer(files[0]) ).promise; + const zip = new JSZip(); const qualityInput = document.getElementById('webp-quality') as HTMLInputElement; const quality = qualityInput ? parseFloat(qualityInput.value) : 0.85; - if (pdf.numPages === 1) { - const page = await pdf.getPage(1); - const blob = await renderPage(page, quality); - downloadFile(blob, getCleanPdfFilename(files[0].name) + '.webp'); - } else { - const zip = new JSZip(); - for (let i = 1; i <= pdf.numPages; i++) { - const page = await pdf.getPage(i); - const blob = await renderPage(page, quality); - if (blob) { - zip.file(`page_${i}.webp`, blob); - } - } + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const viewport = page.getViewport({ scale: 2.0 }); + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; - const zipBlob = await zip.generateAsync({ type: 'blob' }); - downloadFile(zipBlob, getCleanPdfFilename(files[0].name) + '_webps.zip'); + await page.render({ canvasContext: context!, viewport: viewport, canvas }).promise; + + const blob = await new Promise((resolve) => + canvas.toBlob(resolve, 'image/webp', quality) + ); + if (blob) { + zip.file(`page_${i}.webp`, blob); + } } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'converted_images.zip'); showAlert('Success', 'PDF converted to WebPs successfully!', 'success', () => { resetState(); }); @@ -123,28 +126,6 @@ async function convert() { } } -async function renderPage( - page: PDFPageProxy, - quality: number -): Promise { - const viewport = page.getViewport({ scale: 2.0 }); - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - canvas.height = viewport.height; - canvas.width = viewport.width; - - await page.render({ - canvasContext: context!, - viewport: viewport, - canvas, - }).promise; - - const blob = await new Promise((resolve) => - canvas.toBlob(resolve, 'image/webp', quality) - ); - return blob; -} - document.addEventListener('DOMContentLoaded', () => { const fileInput = document.getElementById('file-input') as HTMLInputElement; const dropZone = document.getElementById('drop-zone'); diff --git a/src/js/logic/pdf-workflow-page.ts b/src/js/logic/pdf-workflow-page.ts new file mode 100644 index 0000000..c4ab6bb --- /dev/null +++ b/src/js/logic/pdf-workflow-page.ts @@ -0,0 +1,1578 @@ +import { showAlert } from '../ui.js'; +import { tesseractLanguages } from '../config/tesseract-languages.js'; +import { createWorkflowEditor, updateNodeDisplay } from '../workflow/editor'; +import { executeWorkflow } from '../workflow/engine'; +import { + nodeRegistry, + getNodesByCategory, + createNodeByType, +} from '../workflow/nodes/registry'; +import type { BaseWorkflowNode } from '../workflow/nodes/base-node'; +import type { WorkflowEditor } from '../workflow/editor'; +import { + PDFInputNode, + EncryptedPDFError, +} from '../workflow/nodes/pdf-input-node'; +import { ImageInputNode } from '../workflow/nodes/image-input-node'; +import { WordToPdfNode } from '../workflow/nodes/word-to-pdf-node'; +import { ExcelToPdfNode } from '../workflow/nodes/excel-to-pdf-node'; +import { PowerPointToPdfNode } from '../workflow/nodes/powerpoint-to-pdf-node'; +import { TextToPdfNode } from '../workflow/nodes/text-to-pdf-node'; +import { SvgToPdfNode } from '../workflow/nodes/svg-to-pdf-node'; +import { EpubToPdfNode } from '../workflow/nodes/epub-to-pdf-node'; +import { EmailToPdfNode } from '../workflow/nodes/email-to-pdf-node'; +import { DigitalSignNode } from '../workflow/nodes/digital-sign-node'; +import { XpsToPdfNode } from '../workflow/nodes/xps-to-pdf-node'; +import { MobiToPdfNode } from '../workflow/nodes/mobi-to-pdf-node'; +import { Fb2ToPdfNode } from '../workflow/nodes/fb2-to-pdf-node'; +import { CbzToPdfNode } from '../workflow/nodes/cbz-to-pdf-node'; +import { MarkdownToPdfNode } from '../workflow/nodes/markdown-to-pdf-node'; +import { JsonToPdfNode } from '../workflow/nodes/json-to-pdf-node'; +import { XmlToPdfNode } from '../workflow/nodes/xml-to-pdf-node'; +import { WpdToPdfNode } from '../workflow/nodes/wpd-to-pdf-node'; +import { WpsToPdfNode } from '../workflow/nodes/wps-to-pdf-node'; +import { PagesToPdfNode } from '../workflow/nodes/pages-to-pdf-node'; +import { OdgToPdfNode } from '../workflow/nodes/odg-to-pdf-node'; +import { PubToPdfNode } from '../workflow/nodes/pub-to-pdf-node'; +import { VsdToPdfNode } from '../workflow/nodes/vsd-to-pdf-node'; +import { + saveWorkflow, + loadWorkflow, + exportWorkflow, + importWorkflow, + getSavedTemplateNames, + templateNameExists, + deleteTemplate, +} from '../workflow/serialization'; + +let workflowEditor: WorkflowEditor | null = null; +let selectedNodeId: string | null = null; +let deleteNodeHandler: EventListener | null = null; + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializePage); +} else { + initializePage(); +} + +async function initializePage() { + const container = document.getElementById('rete-container'); + if (!container) return; + + workflowEditor = await createWorkflowEditor(container); + const { editor, area, engine } = workflowEditor; + + buildToolbox(); + + editor.addPipe((context) => { + if (context.type === 'nodecreated' || context.type === 'noderemoved') { + updateNodeCount(); + } + if ( + context.type === 'connectioncreated' || + context.type === 'connectionremoved' + ) { + const conn = context.data; + updateNodeDisplay(conn.source, editor, area); + updateNodeDisplay(conn.target, editor, area); + } + return context; + }); + + document.getElementById('run-btn')?.addEventListener('click', async () => { + const allNodes = editor.getNodes() as BaseWorkflowNode[]; + if (allNodes.length === 0) { + showAlert('Error', 'Add at least one node to run the workflow.'); + return; + } + const hasInput = allNodes.some((n) => n.category === 'Input'); + const hasOutput = allNodes.some((n) => n.category === 'Output'); + if (!hasInput || !hasOutput) { + showAlert( + 'Error', + 'Your workflow needs at least one input node and one output node to run.' + ); + return; + } + + const statusText = document.getElementById('status-text'); + const runBtn = document.getElementById('run-btn') as HTMLButtonElement; + runBtn.disabled = true; + runBtn.classList.add('opacity-50', 'pointer-events-none'); + + try { + await executeWorkflow(editor, engine, area, (progress) => { + const msg = progress.message || `Processing ${progress.nodeName}...`; + if (statusText) statusText.textContent = msg; + }); + if (statusText) statusText.textContent = 'Workflow completed'; + } catch (err) { + if (statusText) statusText.textContent = 'Error during execution'; + showAlert('Error', (err as Error).message); + } finally { + runBtn.disabled = false; + runBtn.classList.remove('opacity-50', 'pointer-events-none'); + } + }); + + document.getElementById('clear-btn')?.addEventListener('click', async () => { + await editor.clear(); + updateNodeCount(); + const statusText = document.getElementById('status-text'); + if (statusText) statusText.textContent = 'Ready'; + document.getElementById('settings-sidebar')?.classList.add('hidden'); + }); + + document.getElementById('close-settings')?.addEventListener('click', () => { + document.getElementById('settings-sidebar')?.classList.add('hidden'); + }); + + document.getElementById('save-btn')?.addEventListener('click', () => { + showSaveTemplateModal(editor, area); + }); + + document.getElementById('load-btn')?.addEventListener('click', () => { + showLoadTemplateModal(editor, area); + }); + + document.getElementById('export-btn')?.addEventListener('click', () => { + exportWorkflow(editor, area); + }); + + document.getElementById('import-btn')?.addEventListener('click', async () => { + await importWorkflow(editor, area); + updateNodeCount(); + }); + + // Mobile toolbox sidebar toggle + const toolboxSidebar = document.getElementById('toolbox-sidebar'); + const toolboxBackdrop = document.getElementById('toolbox-backdrop'); + + function closeToolbox() { + toolboxSidebar?.classList.add('hidden'); + toolboxSidebar?.classList.remove('flex'); + toolboxBackdrop?.classList.add('hidden'); + } + + function openToolbox() { + toolboxSidebar?.classList.remove('hidden'); + toolboxSidebar?.classList.add('flex'); + toolboxBackdrop?.classList.remove('hidden'); + } + + document.getElementById('toolbox-toggle')?.addEventListener('click', () => { + if (toolboxSidebar?.classList.contains('hidden')) { + openToolbox(); + } else { + closeToolbox(); + } + }); + + toolboxBackdrop?.addEventListener('click', closeToolbox); + + document.getElementById('node-search')?.addEventListener('input', (e) => { + const query = (e.target as HTMLInputElement).value.toLowerCase(); + const items = document.querySelectorAll('.toolbox-node-item'); + const categories = + document.querySelectorAll('.toolbox-category'); + + items.forEach((item) => { + const label = item.dataset.label?.toLowerCase() ?? ''; + item.style.display = label.includes(query) ? '' : 'none'; + }); + + categories.forEach((cat) => { + const itemsContainer = cat.querySelector('.toolbox-items'); + if (itemsContainer) itemsContainer.style.display = ''; + const visibleItems = cat.querySelectorAll( + '.toolbox-node-item:not([style*="display: none"])' + ); + cat.style.display = visibleItems.length > 0 ? '' : 'none'; + }); + }); + + let justPicked = false; + let dragDistance = 0; + let pickedNodeId: string | null = null; + + area.addPipe((context) => { + if (context.type === 'nodepicked') { + const nodeId = context.data.id; + selectedNodeId = nodeId; + justPicked = true; + pickedNodeId = nodeId; + dragDistance = 0; + } + if (context.type === 'nodetranslated') { + const dx = context.data.position.x - context.data.previous.x; + const dy = context.data.position.y - context.data.previous.y; + dragDistance += Math.abs(dx) + Math.abs(dy); + } + if (context.type === 'nodedragged') { + if (pickedNodeId && dragDistance < 5) { + const node = editor.getNode(pickedNodeId) as BaseWorkflowNode; + if (node) { + showNodeSettings(node); + } + } + pickedNodeId = null; + } + if (context.type === 'translated') { + container.classList.add('is-panning'); + } + return context; + }); + + container.addEventListener('mouseup', () => + container.classList.remove('is-panning') + ); + container.addEventListener('mouseleave', () => + container.classList.remove('is-panning') + ); + + container.addEventListener('click', (e) => { + if (justPicked) { + justPicked = false; + return; + } + if ((e.target as HTMLElement).closest('[data-testid="node"]')) return; + selectedNodeId = null; + document.getElementById('settings-sidebar')?.classList.add('hidden'); + }); + + document.addEventListener('keydown', (e) => { + if (!selectedNodeId || !workflowEditor) return; + if (e.key === 'Delete' || e.key === 'Backspace') { + const tag = (e.target as HTMLElement).tagName; + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return; + e.preventDefault(); + deleteSelectedNode(); + } + }); + + if (deleteNodeHandler) { + document.removeEventListener('wf-delete-node', deleteNodeHandler); + } + deleteNodeHandler = ((e: CustomEvent) => { + const nodeId = e.detail?.nodeId; + if (nodeId) deleteNodeById(nodeId); + }) as EventListener; + document.addEventListener('wf-delete-node', deleteNodeHandler); +} + +async function deleteNodeById(nodeId: string) { + if (!workflowEditor) return; + const { editor } = workflowEditor; + + const conns = editor + .getConnections() + .filter((c) => c.source === nodeId || c.target === nodeId); + for (const conn of conns) { + await editor.removeConnection(conn.id); + } + await editor.removeNode(nodeId); + + if (selectedNodeId === nodeId) { + selectedNodeId = null; + document.getElementById('settings-sidebar')?.classList.add('hidden'); + } + updateNodeCount(); +} + +async function deleteSelectedNode() { + if (!selectedNodeId) return; + await deleteNodeById(selectedNodeId); +} + +function updateNodeCount() { + if (!workflowEditor) return; + const count = workflowEditor.editor.getNodes().length; + const el = document.getElementById('node-count'); + if (el) el.textContent = `${count} node${count !== 1 ? 's' : ''}`; +} + +function showSaveTemplateModal( + editor: WorkflowEditor['editor'], + area: WorkflowEditor['area'] +) { + const modal = document.getElementById('save-template-modal')!; + const nameInput = document.getElementById( + 'save-template-name' + ) as HTMLInputElement; + const errorEl = document.getElementById('save-template-error')!; + const confirmBtn = document.getElementById('save-template-confirm')!; + const cancelBtn = document.getElementById('save-template-cancel')!; + + nameInput.value = ''; + errorEl.classList.add('hidden'); + modal.classList.remove('hidden'); + nameInput.focus(); + + const cleanup = () => { + modal.classList.add('hidden'); + confirmBtn.replaceWith(confirmBtn.cloneNode(true)); + cancelBtn.replaceWith(cancelBtn.cloneNode(true)); + nameInput.removeEventListener('keydown', onKeydown); + }; + + const doSave = () => { + const name = nameInput.value.trim(); + if (!name) { + errorEl.textContent = 'Please enter a name.'; + errorEl.classList.remove('hidden'); + return; + } + if (templateNameExists(name)) { + errorEl.textContent = 'A template with this name already exists.'; + errorEl.classList.remove('hidden'); + return; + } + saveWorkflow(editor, area, name); + cleanup(); + showAlert('Saved', `Template "${name}" saved.`, 'success'); + }; + + const onKeydown = (e: KeyboardEvent) => { + if (e.key === 'Enter') doSave(); + if (e.key === 'Escape') cleanup(); + }; + + nameInput.addEventListener('keydown', onKeydown); + document + .getElementById('save-template-confirm')! + .addEventListener('click', doSave); + document + .getElementById('save-template-cancel')! + .addEventListener('click', cleanup); +} + +function showLoadTemplateModal( + editor: WorkflowEditor['editor'], + area: WorkflowEditor['area'] +) { + const modal = document.getElementById('load-template-modal')!; + const listEl = document.getElementById('load-template-list')!; + const emptyEl = document.getElementById('load-template-empty')!; + const cancelBtn = document.getElementById('load-template-cancel')!; + + const names = getSavedTemplateNames(); + listEl.innerHTML = ''; + + if (names.length === 0) { + emptyEl.classList.remove('hidden'); + } else { + emptyEl.classList.add('hidden'); + for (const name of names) { + const row = document.createElement('div'); + row.className = + 'group flex items-center gap-2 bg-gray-900/60 hover:bg-gray-700/50 rounded-lg px-3 py-2.5 border border-gray-700/50 transition-colors cursor-pointer'; + + const icon = document.createElement('i'); + icon.className = 'ph ph-file-text text-base text-gray-500 flex-shrink-0'; + row.appendChild(icon); + + const label = document.createElement('span'); + label.className = 'text-gray-200 text-sm truncate flex-1'; + label.textContent = name; + row.appendChild(label); + + const loadBtn = document.createElement('button'); + loadBtn.className = + 'bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-medium px-3 py-1.5 rounded-md transition-colors flex-shrink-0'; + loadBtn.textContent = 'Load'; + loadBtn.addEventListener('click', async () => { + const loaded = await loadWorkflow(editor, area, name); + cleanup(); + if (loaded) { + updateNodeCount(); + showAlert('Loaded', `Template "${name}" loaded.`, 'success'); + } else { + showAlert('Error', 'Failed to load template.'); + } + }); + row.appendChild(loadBtn); + + const delBtn = document.createElement('button'); + delBtn.className = + 'text-gray-600 hover:text-red-400 transition-colors flex-shrink-0'; + delBtn.innerHTML = ''; + delBtn.addEventListener('click', () => { + deleteTemplate(name); + row.remove(); + const remaining = getSavedTemplateNames(); + if (remaining.length === 0) emptyEl.classList.remove('hidden'); + }); + row.appendChild(delBtn); + + listEl.appendChild(row); + } + } + + modal.classList.remove('hidden'); + + const cleanup = () => { + modal.classList.add('hidden'); + cancelBtn.replaceWith(cancelBtn.cloneNode(true)); + }; + + document + .getElementById('load-template-cancel')! + .addEventListener('click', cleanup); +} + +function buildToolbox() { + const container = document.getElementById('toolbox-categories'); + if (!container) return; + + const categorized = getNodesByCategory(); + const categoryOrder: Array<{ key: string; label: string; color: string }> = [ + { key: 'Input', label: 'Input', color: 'text-blue-400' }, + { + key: 'Edit & Annotate', + label: 'Edit & Annotate', + color: 'text-indigo-300', + }, + { + key: 'Organize & Manage', + label: 'Organize & Manage', + color: 'text-violet-300', + }, + { + key: 'Optimize & Repair', + label: 'Optimize & Repair', + color: 'text-amber-300', + }, + { key: 'Secure PDF', label: 'Secure PDF', color: 'text-rose-300' }, + { key: 'Output', label: 'Output', color: 'text-teal-300' }, + ]; + + for (const cat of categoryOrder) { + const entries = categorized[cat.key as keyof typeof categorized] ?? []; + if (entries.length === 0) continue; + + const section = document.createElement('div'); + section.className = 'toolbox-category'; + + const header = document.createElement('button'); + header.className = `w-full flex items-center justify-between text-xs font-bold uppercase tracking-wider ${cat.color} mb-1.5 px-1 hover:opacity-80 transition-opacity`; + header.type = 'button'; + + const headerLabel = document.createElement('span'); + headerLabel.textContent = cat.label; + header.appendChild(headerLabel); + + const chevronWrap = document.createElement('span'); + chevronWrap.className = 'flex-shrink-0'; + chevronWrap.innerHTML = ''; + header.appendChild(chevronWrap); + + const itemsContainer = document.createElement('div'); + itemsContainer.className = 'toolbox-items'; + + header.addEventListener('click', () => { + const collapsed = itemsContainer.style.display === 'none'; + itemsContainer.style.display = collapsed ? '' : 'none'; + const iconName = collapsed ? 'ph-caret-down' : 'ph-caret-up'; + chevronWrap.innerHTML = ``; + }); + + section.appendChild(header); + + for (const entry of entries) { + const item = document.createElement('button'); + item.className = + 'toolbox-node-item w-full text-left px-2 py-1.5 rounded-md text-gray-300 hover:bg-gray-700 hover:text-white transition-colors text-xs flex items-center gap-2'; + item.dataset.label = entry.label; + item.dataset.type = Object.keys(nodeRegistry).find( + (k) => nodeRegistry[k] === entry + )!; + + const iconEl = document.createElement('i'); + iconEl.className = `ph ${entry.icon} text-sm flex-shrink-0`; + item.appendChild(iconEl); + + const labelEl = document.createElement('span'); + labelEl.textContent = entry.label; + item.appendChild(labelEl); + + item.addEventListener('click', () => { + addNodeToCanvas(item.dataset.type!); + if (window.innerWidth < 768) { + document.getElementById('toolbox-sidebar')?.classList.add('hidden'); + document.getElementById('toolbox-sidebar')?.classList.remove('flex'); + document.getElementById('toolbox-backdrop')?.classList.add('hidden'); + } + }); + + item.draggable = true; + item.addEventListener('dragstart', (e) => { + e.dataTransfer?.setData( + 'application/rete-node-type', + item.dataset.type! + ); + e.dataTransfer!.effectAllowed = 'copy'; + }); + + itemsContainer.appendChild(item); + } + + section.appendChild(itemsContainer); + container.appendChild(section); + } + + const reteContainer = document.getElementById('rete-container'); + if (reteContainer) { + reteContainer.addEventListener('dragover', (e) => { + e.preventDefault(); + e.dataTransfer!.dropEffect = 'copy'; + }); + reteContainer.addEventListener('drop', (e) => { + e.preventDefault(); + const nodeType = e.dataTransfer?.getData('application/rete-node-type'); + if (!nodeType || !workflowEditor) return; + + const { area } = workflowEditor; + const rect = reteContainer.getBoundingClientRect(); + const { x: tx, y: ty, k } = area.area.transform; + const x = (e.clientX - rect.left - tx) / k; + const y = (e.clientY - rect.top - ty) / k; + addNodeToCanvas(nodeType, { x, y }); + }); + } +} + +async function addNodeToCanvas( + type: string, + position?: { x: number; y: number } +) { + if (!workflowEditor) return; + const { editor, area } = workflowEditor; + + try { + const node = createNodeByType(type); + if (!node) { + console.error('Node type not found in registry:', type); + return; + } + await editor.addNode(node); + + const pos = position || getCanvasCenter(area); + await area.translate(node.id, pos); + } catch (err) { + console.error('Failed to add node to canvas:', err); + } +} + +function getCanvasCenter(area: WorkflowEditor['area']): { + x: number; + y: number; +} { + const container = area.container; + const rect = container.getBoundingClientRect(); + const { x: tx, y: ty, k } = area.area.transform; + const cx = (rect.width / 2 - tx) / k; + const cy = (rect.height / 2 - ty) / k; + return { + x: cx + (Math.random() - 0.5) * 100, + y: cy + (Math.random() - 0.5) * 100, + }; +} + +function buildFileList( + container: HTMLElement, + filenames: string[], + onRemove: (index: number) => void +) { + const list = document.createElement('div'); + list.className = 'flex flex-col gap-1.5 mb-2'; + + filenames.forEach((name, i) => { + const row = document.createElement('div'); + row.className = + 'flex items-center justify-between bg-gray-900 rounded-lg px-3 py-2'; + + const nameEl = document.createElement('span'); + nameEl.className = 'text-sm text-white truncate flex-1 mr-2'; + nameEl.textContent = name; + row.appendChild(nameEl); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'text-gray-500 hover:text-red-400 text-lg leading-none flex-shrink-0'; + removeBtn.innerHTML = '×'; + removeBtn.addEventListener('click', () => onRemove(i)); + row.appendChild(removeBtn); + + list.appendChild(row); + }); + + container.appendChild(list); +} + +function promptPdfPassword(filename: string): Promise { + return new Promise((resolve) => { + const modal = document.getElementById('pdf-password-modal')!; + const filenameEl = document.getElementById('pdf-password-filename')!; + const input = document.getElementById( + 'pdf-password-input' + ) as HTMLInputElement; + const errorEl = document.getElementById('pdf-password-error')!; + const skipBtn = document.getElementById('pdf-password-skip')!; + const unlockBtn = document.getElementById('pdf-password-unlock')!; + + filenameEl.textContent = filename; + input.value = ''; + errorEl.classList.add('hidden'); + modal.classList.remove('hidden'); + input.focus(); + + const cleanup = () => { + modal.classList.add('hidden'); + skipBtn.replaceWith(skipBtn.cloneNode(true)); + unlockBtn.replaceWith(unlockBtn.cloneNode(true)); + input.removeEventListener('keydown', onKeydown); + }; + + const onKeydown = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + cleanup(); + resolve(input.value || null); + } + if (e.key === 'Escape') { + cleanup(); + resolve(null); + } + }; + + input.addEventListener('keydown', onKeydown); + document + .getElementById('pdf-password-skip')! + .addEventListener('click', () => { + cleanup(); + resolve(null); + }); + document + .getElementById('pdf-password-unlock')! + .addEventListener('click', () => { + cleanup(); + resolve(input.value || null); + }); + }); +} + +function showNodeSettings(node: BaseWorkflowNode) { + const sidebar = document.getElementById('settings-sidebar'); + const title = document.getElementById('settings-title'); + const content = document.getElementById('settings-content'); + if (!sidebar || !title || !content) return; + + sidebar.classList.remove('hidden'); + title.textContent = node.label; + content.innerHTML = ''; + + if (node instanceof PDFInputNode) { + const fileSection = document.createElement('div'); + + const label = document.createElement('label'); + label.className = 'block text-xs text-gray-400 mb-1'; + label.textContent = 'PDF Files'; + fileSection.appendChild(label); + + if (node.hasFile()) { + buildFileList(fileSection, node.getFilenames(), (index) => { + node.removeFile(index); + showNodeSettings(node); + }); + } + + const uploadBtn = document.createElement('button'); + uploadBtn.className = + 'w-full bg-gray-700 hover:bg-gray-600 text-white text-xs px-3 py-2 rounded-lg transition-colors'; + uploadBtn.textContent = node.hasFile() ? 'Add More Files' : 'Upload PDFs'; + + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.pdf'; + fileInput.multiple = true; + fileInput.className = 'hidden'; + fileInput.addEventListener('change', async (e) => { + const files = Array.from((e.target as HTMLInputElement).files ?? []); + if (files.length === 0) return; + for (const file of files) { + try { + await node.addFile(file); + } catch (err) { + if (err instanceof EncryptedPDFError) { + const password = await promptPdfPassword(file.name); + if (password) { + try { + await node.addDecryptedFile(file, password); + } catch { + showAlert( + 'Error', + `Wrong password or failed to decrypt "${file.name}".` + ); + } + } + } else { + showAlert('Error', 'Failed to load PDF: ' + (err as Error).message); + } + } + } + showNodeSettings(node); + }); + + uploadBtn.addEventListener('click', () => fileInput.click()); + fileSection.appendChild(uploadBtn); + fileSection.appendChild(fileInput); + content.appendChild(fileSection); + return; + } + + if (node instanceof ImageInputNode) { + const fileSection = document.createElement('div'); + + const label = document.createElement('label'); + label.className = 'block text-xs text-gray-400 mb-1'; + label.textContent = 'Images'; + fileSection.appendChild(label); + + const formatHint = document.createElement('p'); + formatHint.className = 'text-[10px] text-gray-500 mb-2'; + formatHint.textContent = + 'Supported: JPG, PNG, BMP, GIF, TIFF, WebP, HEIC, PSD, SVG, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2'; + fileSection.appendChild(formatHint); + + if (node.hasFile()) { + buildFileList(fileSection, node.getFilenames(), (index) => { + node.removeFile(index); + showNodeSettings(node); + }); + } + + const uploadBtn = document.createElement('button'); + uploadBtn.className = + 'w-full bg-gray-700 hover:bg-gray-600 text-white text-xs px-3 py-2 rounded-lg transition-colors'; + uploadBtn.textContent = node.hasFile() + ? 'Add More Images' + : 'Upload Images'; + + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = 'image/*'; + fileInput.multiple = true; + fileInput.className = 'hidden'; + fileInput.addEventListener('change', async (e) => { + const files = Array.from((e.target as HTMLInputElement).files ?? []); + if (files.length === 0) return; + try { + await node.addFiles(files); + showNodeSettings(node); + } catch (err) { + showAlert('Error', 'Failed to load images: ' + (err as Error).message); + } + }); + + uploadBtn.addEventListener('click', () => fileInput.click()); + fileSection.appendChild(uploadBtn); + fileSection.appendChild(fileInput); + content.appendChild(fileSection); + return; + } + + if (node instanceof DigitalSignNode) { + const certSection = document.createElement('div'); + + const certLabel = document.createElement('label'); + certLabel.className = 'block text-xs text-gray-400 mb-1'; + certLabel.textContent = 'Certificate (.pfx, .p12, .pem)'; + certSection.appendChild(certLabel); + + if (node.hasCertFile()) { + const certFileDiv = document.createElement('div'); + certFileDiv.className = + 'flex items-center justify-between bg-gray-700 px-3 py-2 rounded-lg mb-2'; + + const certName = document.createElement('span'); + certName.className = 'text-xs text-gray-200 truncate flex-1'; + certName.textContent = node.getCertFilename(); + + const statusDot = document.createElement('span'); + statusDot.className = `w-2 h-2 rounded-full flex-shrink-0 mx-2 ${node.hasCert() ? 'bg-green-400' : 'bg-yellow-400'}`; + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'text-red-400 hover:text-red-300 text-xs flex-shrink-0'; + removeBtn.textContent = 'Remove'; + removeBtn.addEventListener('click', () => { + node.removeCert(); + showNodeSettings(node); + }); + + certFileDiv.append(certName, statusDot, removeBtn); + certSection.appendChild(certFileDiv); + + if (node.needsPassword()) { + const pwSection = document.createElement('div'); + pwSection.className = 'mb-2'; + + const pwLabel = document.createElement('label'); + pwLabel.className = 'block text-xs text-gray-400 mb-1'; + pwLabel.textContent = 'Certificate Password'; + pwSection.appendChild(pwLabel); + + const pwRow = document.createElement('div'); + pwRow.className = 'flex gap-2'; + + const pwInput = document.createElement('input'); + pwInput.type = 'password'; + pwInput.placeholder = 'Enter password...'; + pwInput.className = + 'flex-1 bg-gray-700 border border-gray-600 text-white text-xs px-3 py-2 rounded-lg focus:outline-none focus:border-indigo-500'; + + const unlockBtn = document.createElement('button'); + unlockBtn.className = + 'bg-indigo-600 hover:bg-indigo-500 text-white text-xs px-3 py-2 rounded-lg transition-colors flex-shrink-0'; + unlockBtn.textContent = 'Unlock'; + + const statusMsg = document.createElement('div'); + statusMsg.className = 'text-xs mt-1 hidden'; + + const doUnlock = async () => { + const pw = pwInput.value; + if (!pw) return; + unlockBtn.textContent = 'Unlocking...'; + unlockBtn.disabled = true; + const success = await node.unlockCert(pw); + if (success) { + showNodeSettings(node); + } else { + unlockBtn.textContent = 'Unlock'; + unlockBtn.disabled = false; + statusMsg.textContent = 'Incorrect password'; + statusMsg.className = 'text-xs mt-1 text-red-400'; + statusMsg.classList.remove('hidden'); + } + }; + + unlockBtn.addEventListener('click', doUnlock); + pwInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') doUnlock(); + }); + + pwRow.append(pwInput, unlockBtn); + pwSection.append(pwRow, statusMsg); + certSection.appendChild(pwSection); + } else if (node.hasCert()) { + const okMsg = document.createElement('div'); + okMsg.className = 'text-xs text-green-400 mb-2'; + okMsg.textContent = 'Certificate unlocked'; + certSection.appendChild(okMsg); + } + } + + const uploadBtn = document.createElement('button'); + uploadBtn.className = + 'w-full bg-gray-700 hover:bg-gray-600 text-white text-xs px-3 py-2 rounded-lg transition-colors'; + uploadBtn.textContent = node.hasCertFile() + ? 'Change Certificate' + : 'Upload Certificate'; + + const certInput = document.createElement('input'); + certInput.type = 'file'; + certInput.accept = '.pfx,.p12,.pem'; + certInput.className = 'hidden'; + certInput.addEventListener('change', (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (!file) return; + node.setCertFile(file); + + const isPem = file.name.toLowerCase().endsWith('.pem'); + if (isPem) { + file.text().then(async (pemContent) => { + const isEncrypted = pemContent.includes('ENCRYPTED'); + if (!isEncrypted) { + await node.unlockCert(''); + } + showNodeSettings(node); + }); + } else { + showNodeSettings(node); + } + }); + + uploadBtn.addEventListener('click', () => certInput.click()); + certSection.append(uploadBtn, certInput); + content.appendChild(certSection); + + const divider = document.createElement('div'); + divider.className = 'border-t border-gray-700 my-3'; + content.appendChild(divider); + } + + const fileInputConfigs: { + cls: any; + label: string; + accept: string; + btnLabel: string; + hint?: string; + }[] = [ + { + cls: WordToPdfNode, + label: 'Word Documents', + accept: '.doc,.docx,.odt,.rtf', + btnLabel: 'Documents', + hint: 'Supported: DOC, DOCX, ODT, RTF', + }, + { + cls: ExcelToPdfNode, + label: 'Spreadsheets', + accept: '.xlsx,.xls,.ods,.csv', + btnLabel: 'Spreadsheets', + hint: 'Supported: XLSX, XLS, ODS, CSV', + }, + { + cls: PowerPointToPdfNode, + label: 'Presentations', + accept: '.ppt,.pptx,.odp', + btnLabel: 'Presentations', + hint: 'Supported: PPT, PPTX, ODP', + }, + { + cls: TextToPdfNode, + label: 'Text Files', + accept: '.txt', + btnLabel: 'Text Files', + }, + { + cls: SvgToPdfNode, + label: 'SVG Files', + accept: '.svg', + btnLabel: 'SVG Files', + }, + { + cls: EpubToPdfNode, + label: 'EPUB Files', + accept: '.epub', + btnLabel: 'EPUB Files', + }, + { + cls: EmailToPdfNode, + label: 'Email Files', + accept: '.eml,.msg', + btnLabel: 'Email Files', + hint: 'Supported: EML, MSG', + }, + { + cls: XpsToPdfNode, + label: 'XPS Files', + accept: '.xps,.oxps', + btnLabel: 'XPS Files', + hint: 'Supported: XPS, OXPS', + }, + { + cls: MobiToPdfNode, + label: 'MOBI Files', + accept: '.mobi', + btnLabel: 'MOBI Files', + }, + { + cls: Fb2ToPdfNode, + label: 'FB2 Files', + accept: '.fb2', + btnLabel: 'FB2 Files', + }, + { + cls: CbzToPdfNode, + label: 'Comic Archives', + accept: '.cbz,.cbr', + btnLabel: 'Comics', + hint: 'Supported: CBZ, CBR', + }, + { + cls: MarkdownToPdfNode, + label: 'Markdown Files', + accept: '.md,.markdown', + btnLabel: 'Markdown Files', + }, + { + cls: JsonToPdfNode, + label: 'JSON Files', + accept: '.json', + btnLabel: 'JSON Files', + }, + { + cls: XmlToPdfNode, + label: 'XML Files', + accept: '.xml', + btnLabel: 'XML Files', + }, + { + cls: WpdToPdfNode, + label: 'WordPerfect Files', + accept: '.wpd', + btnLabel: 'WPD Files', + }, + { + cls: WpsToPdfNode, + label: 'WPS Files', + accept: '.wps', + btnLabel: 'WPS Files', + }, + { + cls: PagesToPdfNode, + label: 'Pages Files', + accept: '.pages', + btnLabel: 'Pages Files', + }, + { + cls: OdgToPdfNode, + label: 'ODG Files', + accept: '.odg', + btnLabel: 'ODG Files', + }, + { + cls: PubToPdfNode, + label: 'Publisher Files', + accept: '.pub', + btnLabel: 'PUB Files', + }, + { + cls: VsdToPdfNode, + label: 'Visio Files', + accept: '.vsd,.vsdx', + btnLabel: 'Visio Files', + hint: 'Supported: VSD, VSDX', + }, + ]; + + const fileInputConfig = fileInputConfigs.find((c) => node instanceof c.cls); + if (fileInputConfig) { + const fileNode = node as InstanceType; + const fileSection = document.createElement('div'); + + const label = document.createElement('label'); + label.className = 'block text-xs text-gray-400 mb-1'; + label.textContent = fileInputConfig.label; + fileSection.appendChild(label); + + if (fileInputConfig.hint) { + const hint = document.createElement('p'); + hint.className = 'text-[10px] text-gray-500 mb-2'; + hint.textContent = fileInputConfig.hint; + fileSection.appendChild(hint); + } + + if (fileNode.hasFile()) { + buildFileList(fileSection, fileNode.getFilenames(), (index) => { + fileNode.removeFile(index); + showNodeSettings(node); + }); + } + + const uploadBtn = document.createElement('button'); + uploadBtn.className = + 'w-full bg-gray-700 hover:bg-gray-600 text-white text-xs px-3 py-2 rounded-lg transition-colors'; + uploadBtn.textContent = fileNode.hasFile() + ? `Add More ${fileInputConfig.btnLabel}` + : `Upload ${fileInputConfig.btnLabel}`; + + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = fileInputConfig.accept; + fileInput.multiple = true; + fileInput.className = 'hidden'; + fileInput.addEventListener('change', async (e) => { + const files = Array.from((e.target as HTMLInputElement).files ?? []); + if (files.length === 0) return; + try { + await fileNode.addFiles(files); + showNodeSettings(node); + } catch (err) { + showAlert('Error', `Failed to load files: ${(err as Error).message}`); + } + }); + + uploadBtn.addEventListener('click', () => fileInput.click()); + fileSection.appendChild(uploadBtn); + fileSection.appendChild(fileInput); + content.appendChild(fileSection); + + const controlEntries = Object.entries(node.controls); + if (controlEntries.length > 0) { + const divider = document.createElement('div'); + divider.className = 'border-t border-gray-700 my-3'; + content.appendChild(divider); + } else { + return; + } + } + + const controlEntries = Object.entries(node.controls); + if (controlEntries.length === 0) { + const empty = document.createElement('p'); + empty.className = 'text-xs text-gray-500'; + empty.textContent = 'No configurable settings for this node.'; + content.appendChild(empty); + return; + } + + const dropdownOptions: Record = { + format: [ + { label: 'JPG', value: 'jpg' }, + { label: 'PNG', value: 'png' }, + { label: 'WebP', value: 'webp' }, + { label: 'SVG', value: 'svg' }, + ], + position: [ + { label: 'Bottom Center', value: 'bottom-center' }, + { label: 'Bottom Left', value: 'bottom-left' }, + { label: 'Bottom Right', value: 'bottom-right' }, + { label: 'Top Center', value: 'top-center' }, + { label: 'Top Left', value: 'top-left' }, + { label: 'Top Right', value: 'top-right' }, + ], + orientation: [ + { label: 'Auto (Keep Original)', value: 'auto' }, + { label: 'Portrait', value: 'portrait' }, + { label: 'Landscape', value: 'landscape' }, + ], + direction: [ + { label: 'Vertical', value: 'vertical' }, + { label: 'Horizontal', value: 'horizontal' }, + ], + pagesPerSheet: [ + { label: '2', value: '2' }, + { label: '4', value: '4' }, + { label: '9', value: '9' }, + { label: '16', value: '16' }, + ], + fontFamily: [ + { label: 'Helvetica', value: 'helv' }, + { label: 'Times Roman', value: 'times' }, + { label: 'Courier', value: 'cour' }, + { label: 'Times Italic', value: 'tiro' }, + ], + pageSize: [ + { label: 'A4', value: 'a4' }, + { label: 'Letter', value: 'letter' }, + { label: 'Legal', value: 'legal' }, + ], + targetSize: [ + { label: 'A4', value: 'A4' }, + { label: 'Letter', value: 'Letter' }, + { label: 'Legal', value: 'Legal' }, + { label: 'A3', value: 'A3' }, + { label: 'A5', value: 'A5' }, + { label: 'Tabloid', value: 'Tabloid' }, + { label: 'Custom', value: 'Custom' }, + ], + scalingMode: [ + { label: 'Fit (keep full page visible)', value: 'fit' }, + { label: 'Fill (cover full target page)', value: 'fill' }, + ], + customUnits: [ + { label: 'Millimeters (mm)', value: 'mm' }, + { label: 'Inches (in)', value: 'in' }, + ], + numberFormat: [ + { label: 'Simple (1, 2, 3)', value: 'simple' }, + { label: 'Page X of Y', value: 'page_x_of_y' }, + ], + angle: [ + { label: '90° Clockwise', value: '90' }, + { label: '180°', value: '180' }, + { label: '90° Counter-clockwise', value: '270' }, + ], + blankPosition: [ + { label: 'End', value: 'end' }, + { label: 'Beginning', value: 'start' }, + { label: 'After Page...', value: 'after' }, + ], + resolution: [ + { label: 'Standard (192 DPI)', value: '2.0' }, + { label: 'High (288 DPI)', value: '3.0' }, + { label: 'Ultra (384 DPI)', value: '4.0' }, + ], + language: Object.entries(tesseractLanguages).map(([code, name]) => ({ + label: name, + value: code, + })), + gridMode: [ + { label: '1x2 (Booklet)', value: '1x2' }, + { label: '2x2 (4-up)', value: '2x2' }, + { label: '2x4 (8-up)', value: '2x4' }, + { label: '4x4 (16-up)', value: '4x4' }, + ], + paperSize: [ + { label: 'Letter', value: 'Letter' }, + { label: 'A4', value: 'A4' }, + { label: 'A3', value: 'A3' }, + { label: 'Tabloid', value: 'Tabloid' }, + { label: 'Legal', value: 'Legal' }, + ], + rasterizeDpi: [ + { label: '72 (Screen)', value: '72' }, + { label: '150 (Default)', value: '150' }, + { label: '200 (Good)', value: '200' }, + { label: '300 (Print)', value: '300' }, + { label: '600 (High Quality)', value: '600' }, + ], + imageFormat: [ + { label: 'PNG (Lossless)', value: 'png' }, + { label: 'JPEG (Smaller file size)', value: 'jpeg' }, + ], + skewThreshold: [ + { label: '0.1° (Very Sensitive)', value: '0.1' }, + { label: '0.5° (Default)', value: '0.5' }, + { label: '1.0° (Normal)', value: '1.0' }, + { label: '2.0° (Less Sensitive)', value: '2.0' }, + ], + processingDpi: [ + { label: '100 (Fast)', value: '100' }, + { label: '150 (Default)', value: '150' }, + { label: '200 (Better)', value: '200' }, + { label: '300 (Best Quality)', value: '300' }, + ], + level: [ + { label: 'PDF/A-1b (Strict, no transparency)', value: 'PDF/A-1b' }, + { label: 'PDF/A-2b (Recommended)', value: 'PDF/A-2b' }, + { label: 'PDF/A-3b (Modern, allows attachments)', value: 'PDF/A-3b' }, + ], + algorithm: [ + { label: 'Condense (Smart, requires PyMuPDF)', value: 'condense' }, + { label: 'Photon (Rasterize pages)', value: 'photon' }, + ], + compressionLevel: [ + { label: 'Light', value: 'light' }, + { label: 'Balanced', value: 'balanced' }, + { label: 'Aggressive', value: 'aggressive' }, + { label: 'Extreme', value: 'extreme' }, + ], + redactMode: [ + { label: 'Search Text', value: 'text' }, + { label: 'Area (Coordinates)', value: 'area' }, + ], + }; + + const booleanControls = new Set([ + 'grayscale', + 'border', + 'margins', + 'separator', + 'sepia', + 'includeCcBcc', + 'includeAttachments', + 'binarize', + 'preFlatten', + 'flattenForms', + 'removeMetadata', + 'removeAnnotations', + 'removeJavascript', + 'removeEmbeddedFiles', + 'removeLayers', + 'removeLinks', + 'removeStructureTree', + 'removeMarkInfo', + 'removeFonts', + 'subsetFonts', + 'convertToGrayscale', + 'removeThumbnails', + ]); + const multiSelectDropdowns = new Set(['language']); + const advancedControls = new Set(['resolution', 'binarize', 'whitelist']); + + const colorControls = new Set([ + 'color', + 'borderColor', + 'backgroundColor', + 'separatorColor', + 'fontColor', + 'fillColor', + ]); + + const controlHints: Record = { + pages: 'e.g. 1-3, 5, 7-9', + whitelist: 'Limit recognized characters (leave empty for all)', + afterPage: 'Insert blank pages after this page number', + x0: 'Left edge in points (1 inch = 72 pts)', + y0: 'Top edge in points', + x1: 'Right edge in points', + y1: 'Bottom edge in points', + }; + + const inputClass = + 'w-full bg-gray-900 border border-gray-600 text-white rounded-md px-2 py-1.5 text-xs focus:border-indigo-500 focus:outline-none'; + + const conditionalVisibility: Record> = { + redactMode: { + text: ['text'], + area: ['x0', 'y0', 'x1', 'y1'], + }, + targetSize: { + Custom: ['customWidth', 'customHeight', 'customUnits'], + }, + }; + + const controlWrappers: Record = {}; + const hasAdvanced = controlEntries.some(([key]) => advancedControls.has(key)); + const advancedWrappers: HTMLElement[] = []; + + for (const [key, control] of controlEntries) { + const wrapper = document.createElement('div'); + controlWrappers[key] = wrapper; + const ctrl = control as { value?: unknown; type?: string }; + const currentValue = String(ctrl.value ?? ''); + + const controlLabel = document.createElement('label'); + controlLabel.className = 'block text-xs text-gray-400 mb-1'; + controlLabel.textContent = formatLabel(key); + wrapper.appendChild(controlLabel); + + if (dropdownOptions[key] && multiSelectDropdowns.has(key)) { + const selectedValues = new Set( + currentValue ? currentValue.split('+') : [] + ); + const container = document.createElement('div'); + container.className = 'flex flex-col gap-1'; + + const searchInput = document.createElement('input'); + searchInput.type = 'text'; + searchInput.placeholder = 'Search languages...'; + searchInput.className = inputClass; + container.appendChild(searchInput); + + const tagsDiv = document.createElement('div'); + tagsDiv.className = 'flex flex-wrap gap-1 min-h-[24px]'; + container.appendChild(tagsDiv); + + const listDiv = document.createElement('div'); + listDiv.className = + 'max-h-32 overflow-y-auto bg-gray-800 rounded border border-gray-600 mt-1'; + container.appendChild(listDiv); + + function updateTags() { + tagsDiv.innerHTML = ''; + for (const val of selectedValues) { + const opt = dropdownOptions[key].find((o) => o.value === val); + if (!opt) continue; + const tag = document.createElement('span'); + tag.className = + 'inline-flex items-center gap-1 px-2 py-0.5 rounded bg-indigo-600 text-white text-[10px]'; + tag.textContent = opt.label; + const removeBtn = document.createElement('button'); + removeBtn.type = 'button'; + removeBtn.textContent = '\u00d7'; + removeBtn.className = + 'text-white/70 hover:text-white text-xs leading-none'; + removeBtn.addEventListener('click', () => { + selectedValues.delete(val); + updateTags(); + updateCtrl(); + renderList(searchInput.value); + }); + tag.appendChild(removeBtn); + tagsDiv.appendChild(tag); + } + } + + function updateCtrl() { + (ctrl as { value: string }).value = + Array.from(selectedValues).join('+'); + } + + function renderList(filter: string) { + listDiv.innerHTML = ''; + const lowerFilter = filter.toLowerCase(); + for (const opt of dropdownOptions[key]) { + if (lowerFilter && !opt.label.toLowerCase().includes(lowerFilter)) + continue; + const label = document.createElement('label'); + label.className = + 'flex items-center gap-2 px-2 py-1 hover:bg-gray-700 cursor-pointer text-xs text-gray-300'; + const cb = document.createElement('input'); + cb.type = 'checkbox'; + cb.checked = selectedValues.has(opt.value); + cb.className = + 'w-3 h-3 rounded text-indigo-600 bg-gray-700 border-gray-600'; + cb.addEventListener('change', () => { + if (cb.checked) { + selectedValues.add(opt.value); + } else { + selectedValues.delete(opt.value); + } + updateTags(); + updateCtrl(); + }); + label.appendChild(cb); + label.appendChild(document.createTextNode(opt.label)); + listDiv.appendChild(label); + } + } + + searchInput.addEventListener('input', () => { + renderList(searchInput.value); + }); + + updateTags(); + renderList(''); + wrapper.appendChild(container); + } else if (dropdownOptions[key]) { + const select = document.createElement('select'); + select.className = inputClass; + for (const opt of dropdownOptions[key]) { + const option = document.createElement('option'); + option.value = opt.value; + option.textContent = opt.label; + if (currentValue === opt.value) option.selected = true; + select.appendChild(option); + } + select.addEventListener('change', () => { + (ctrl as { value: string }).value = select.value; + if (conditionalVisibility[key]) { + applyConditionalVisibility(key, select.value); + } + }); + wrapper.appendChild(select); + } else if (booleanControls.has(key)) { + const toggle = document.createElement('button'); + toggle.type = 'button'; + const isOn = currentValue === 'true'; + toggle.className = `relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full transition-colors duration-200 ${isOn ? 'bg-indigo-500' : 'bg-gray-600'}`; + const dot = document.createElement('span'); + dot.className = `pointer-events-none absolute top-[3px] left-[3px] h-[18px] w-[18px] rounded-full bg-white shadow-md transition-transform duration-200 ${isOn ? 'translate-x-5' : 'translate-x-0'}`; + toggle.appendChild(dot); + toggle.addEventListener('click', () => { + const newVal = + (ctrl as { value: string }).value === 'true' ? 'false' : 'true'; + (ctrl as { value: string }).value = newVal; + const on = newVal === 'true'; + toggle.className = `relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full transition-colors duration-200 ${on ? 'bg-indigo-500' : 'bg-gray-600'}`; + dot.className = `pointer-events-none absolute top-[3px] left-[3px] h-[18px] w-[18px] rounded-full bg-white shadow-md transition-transform duration-200 ${on ? 'translate-x-5' : 'translate-x-0'}`; + }); + wrapper.appendChild(toggle); + } else if (colorControls.has(key)) { + const colorRow = document.createElement('div'); + colorRow.className = 'flex items-center gap-2'; + const colorInput = document.createElement('input'); + colorInput.type = 'color'; + colorInput.value = currentValue || '#000000'; + colorInput.className = 'w-8 h-8 rounded bg-transparent cursor-pointer'; + const hexInput = document.createElement('input'); + hexInput.type = 'text'; + hexInput.value = currentValue || '#000000'; + hexInput.className = inputClass + ' flex-1'; + colorInput.addEventListener('input', () => { + hexInput.value = colorInput.value; + (ctrl as { value: string }).value = colorInput.value; + }); + hexInput.addEventListener('input', () => { + if (/^#[0-9a-fA-F]{6}$/.test(hexInput.value)) { + colorInput.value = hexInput.value; + } + (ctrl as { value: string }).value = hexInput.value; + }); + colorRow.appendChild(colorInput); + colorRow.appendChild(hexInput); + wrapper.appendChild(colorRow); + } else if (ctrl.type === 'number' || typeof ctrl.value === 'number') { + const input = document.createElement('input'); + input.type = 'number'; + input.className = inputClass; + input.value = currentValue; + input.addEventListener('input', () => { + const num = parseFloat(input.value); + if (!isNaN(num)) { + (ctrl as { value: number }).value = num; + } + }); + wrapper.appendChild(input); + } else { + const input = document.createElement('input'); + const isPasswordField = key === 'password' || key === 'ownerPassword'; + input.type = isPasswordField ? 'password' : 'text'; + input.className = inputClass; + input.value = currentValue; + input.addEventListener('input', () => { + (ctrl as { value: string }).value = input.value; + }); + wrapper.appendChild(input); + } + + if (controlHints[key]) { + const hint = document.createElement('p'); + hint.className = 'text-[10px] text-gray-500 mt-1'; + hint.textContent = controlHints[key]; + wrapper.appendChild(hint); + } + + if (advancedControls.has(key)) { + advancedWrappers.push(wrapper); + } else { + content.appendChild(wrapper); + } + } + + function applyConditionalVisibility( + dropdownKey: string, + selectedValue: string + ) { + const mapping = conditionalVisibility[dropdownKey]; + if (!mapping) return; + const allControlled = new Set(Object.values(mapping).flat()); + for (const controlKey of allControlled) { + const el = controlWrappers[controlKey]; + if (el) el.style.display = 'none'; + } + const visible = mapping[selectedValue] ?? []; + for (const controlKey of visible) { + const el = controlWrappers[controlKey]; + if (el) el.style.display = ''; + } + } + + for (const [dropdownKey, mapping] of Object.entries(conditionalVisibility)) { + const ctrl = controlEntries.find(([k]) => k === dropdownKey)?.[1] as + | { value?: unknown } + | undefined; + if (ctrl) { + applyConditionalVisibility(dropdownKey, String(ctrl.value ?? '')); + } + } + + if (hasAdvanced && advancedWrappers.length > 0) { + const details = document.createElement('details'); + details.className = + 'bg-gray-800/50 border border-gray-700 rounded-lg p-2 mt-1'; + const summary = document.createElement('summary'); + summary.className = + 'text-xs font-medium text-gray-400 cursor-pointer select-none flex items-center justify-between'; + const summaryText = document.createElement('span'); + summaryText.textContent = 'Advanced Settings'; + summary.appendChild(summaryText); + const chevron = document.createElement('i'); + chevron.className = + 'ph ph-caret-down text-xs text-gray-500 transition-transform duration-200'; + summary.appendChild(chevron); + details.addEventListener('toggle', () => { + chevron.style.transform = details.open + ? 'rotate(180deg)' + : 'rotate(0deg)'; + }); + details.appendChild(summary); + const advancedContent = document.createElement('div'); + advancedContent.className = 'mt-2 space-y-3'; + for (const w of advancedWrappers) { + advancedContent.appendChild(w); + } + details.appendChild(advancedContent); + content.appendChild(details); + } +} + +function formatLabel(key: string): string { + return key + .replace(/([A-Z])/g, ' $1') + .replace(/^./, (c) => c.toUpperCase()) + .trim(); +} diff --git a/src/js/logic/png-to-pdf-page.ts b/src/js/logic/png-to-pdf-page.ts index eb9e34a..a286e67 100644 --- a/src/js/logic/png-to-pdf-page.ts +++ b/src/js/logic/png-to-pdf-page.ts @@ -1,248 +1,257 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; -import { downloadFile, readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, +} from '../utils/helpers.js'; import { PDFDocument as PDFLibDocument } from 'pdf-lib'; +import { + getSelectedQuality, + compressImageBytes, +} from '../utils/image-compress.js'; let files: File[] = []; if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } function initializePage() { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const addMoreBtn = document.getElementById('add-more-btn'); - const clearFilesBtn = document.getElementById('clear-files-btn'); - const processBtn = document.getElementById('process-btn'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const processBtn = document.getElementById('process-btn'); - if (fileInput) { - fileInput.addEventListener('change', handleFileUpload); - } + if (fileInput) { + fileInput.addEventListener('change', handleFileUpload); + } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); - - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handleFiles(droppedFiles); - } - }); - - // Clear value on click to allow re-selecting the same file - fileInput?.addEventListener('click', () => { - if (fileInput) fileInput.value = ''; - }); - } - - if (addMoreBtn) { - addMoreBtn.addEventListener('click', () => { - fileInput?.click(); - }); - } - - if (clearFilesBtn) { - clearFilesBtn.addEventListener('click', () => { - files = []; - updateUI(); - }); - } - - if (processBtn) { - processBtn.addEventListener('click', convertToPdf); - } - - document.getElementById('back-to-tools')?.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleFiles(droppedFiles); + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput?.addEventListener('click', () => { + if (fileInput) fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput?.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + files = []; + updateUI(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } + + document.getElementById('back-to-tools')?.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); } function handleFileUpload(e: Event) { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handleFiles(input.files); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleFiles(input.files); + } } function handleFiles(newFiles: FileList) { - const validFiles = Array.from(newFiles).filter(file => - file.type === 'image/png' || file.name.toLowerCase().endsWith('.png') + const validFiles = Array.from(newFiles).filter( + (file) => + file.type === 'image/png' || file.name.toLowerCase().endsWith('.png') + ); + + if (validFiles.length < newFiles.length) { + showAlert( + 'Invalid Files', + 'Some files were skipped. Only PNG images are allowed.' ); + } - if (validFiles.length < newFiles.length) { - showAlert('Invalid Files', 'Some files were skipped. Only PNG images are allowed.'); - } - - if (validFiles.length > 0) { - files = [...files, ...validFiles]; - updateUI(); - } + if (validFiles.length > 0) { + files = [...files, ...validFiles]; + updateUI(); + } } const resetState = () => { - files = []; - updateUI(); + files = []; + updateUI(); }; function updateUI() { - const fileDisplayArea = document.getElementById('file-display-area'); - const fileControls = document.getElementById('file-controls'); - const optionsDiv = document.getElementById('jpg-to-pdf-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const optionsDiv = document.getElementById('jpg-to-pdf-options'); - if (!fileDisplayArea || !fileControls || !optionsDiv) return; + if (!fileDisplayArea || !fileControls || !optionsDiv) return; - fileDisplayArea.innerHTML = ''; + fileDisplayArea.innerHTML = ''; - if (files.length > 0) { - fileControls.classList.remove('hidden'); - optionsDiv.classList.remove('hidden'); + if (files.length > 0) { + fileControls.classList.remove('hidden'); + optionsDiv.classList.remove('hidden'); - files.forEach((file, index) => { - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + files.forEach((file, index) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex items-center gap-2 overflow-hidden'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex items-center gap-2 overflow-hidden'; - const nameSpan = document.createElement('span'); - nameSpan.className = 'truncate font-medium text-gray-200'; - nameSpan.textContent = file.name; + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; - const sizeSpan = document.createElement('span'); - sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; - sizeSpan.textContent = `(${formatBytes(file.size)})`; + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; + sizeSpan.textContent = `(${formatBytes(file.size)})`; - infoContainer.append(nameSpan, sizeSpan); + infoContainer.append(nameSpan, sizeSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - files = files.filter((_, i) => i !== index); - updateUI(); - }; + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + files = files.filter((_, i) => i !== index); + updateUI(); + }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - }); - createIcons({ icons }); - } else { - fileControls.classList.add('hidden'); - optionsDiv.classList.add('hidden'); - } + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + }); + createIcons({ icons }); + } else { + fileControls.classList.add('hidden'); + optionsDiv.classList.add('hidden'); + } } function sanitizeImageAsJpeg(imageBytes: any) { - return new Promise((resolve, reject) => { - const blob = new Blob([imageBytes]); - const imageUrl = URL.createObjectURL(blob); - const img = new Image(); + return new Promise((resolve, reject) => { + const blob = new Blob([imageBytes]); + const imageUrl = URL.createObjectURL(blob); + const img = new Image(); - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.naturalWidth; - canvas.height = img.naturalHeight; - const ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0); + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); - canvas.toBlob( - async (jpegBlob) => { - if (!jpegBlob) { - return reject(new Error('Canvas toBlob conversion failed.')); - } - const arrayBuffer = await jpegBlob.arrayBuffer(); - resolve(new Uint8Array(arrayBuffer)); - }, - 'image/jpeg', - 0.9 - ); - URL.revokeObjectURL(imageUrl); - }; + canvas.toBlob( + async (jpegBlob) => { + if (!jpegBlob) { + return reject(new Error('Canvas toBlob conversion failed.')); + } + const arrayBuffer = await jpegBlob.arrayBuffer(); + resolve(new Uint8Array(arrayBuffer)); + }, + 'image/jpeg', + 0.9 + ); + URL.revokeObjectURL(imageUrl); + }; - img.onerror = () => { - URL.revokeObjectURL(imageUrl); - reject( - new Error( - 'The provided file could not be loaded as an image. It may be corrupted.' - ) - ); - }; + img.onerror = () => { + URL.revokeObjectURL(imageUrl); + reject( + new Error( + 'The provided file could not be loaded as an image. It may be corrupted.' + ) + ); + }; - img.src = imageUrl; - }); + img.src = imageUrl; + }); } async function convertToPdf() { - if (files.length === 0) { - showAlert('No Files', 'Please select at least one JPG file.'); - return; - } + if (files.length === 0) { + showAlert('No Files', 'Please select at least one JPG file.'); + return; + } - showLoader('Creating PDF from JPGs...'); + showLoader('Creating PDF from JPGs...'); - try { - const pdfDoc = await PDFLibDocument.create(); + try { + const pdfDoc = await PDFLibDocument.create(); + const quality = getSelectedQuality(); - for (const file of files) { - const originalBytes = await readFileAsArrayBuffer(file); - let jpgImage; + for (const file of files) { + const originalBytes = await readFileAsArrayBuffer(file); + const compressed = await compressImageBytes( + new Uint8Array(originalBytes as ArrayBuffer), + quality + ); + let embeddedImage; - try { - jpgImage = await pdfDoc.embedJpg(originalBytes as Uint8Array); - } catch (e) { - showAlert( - 'Warning', - `Direct JPG embedding failed for ${file.name}, attempting to sanitize...` - ); - try { - const sanitizedBytes = await sanitizeImageAsJpeg(originalBytes); - jpgImage = await pdfDoc.embedJpg(sanitizedBytes as Uint8Array); - } catch (fallbackError) { - console.error( - `Failed to process ${file.name} after sanitization:`, - fallbackError - ); - throw new Error( - `Could not process "${file.name}". The file may be corrupted.` - ); - } - } - - const page = pdfDoc.addPage([jpgImage.width, jpgImage.height]); - page.drawImage(jpgImage, { - x: 0, - y: 0, - width: jpgImage.width, - height: jpgImage.height, - }); + if (compressed.type === 'jpeg') { + embeddedImage = await pdfDoc.embedJpg(compressed.bytes); + } else { + try { + embeddedImage = await pdfDoc.embedPng(compressed.bytes); + } catch { + const fallback = await sanitizeImageAsJpeg(originalBytes); + embeddedImage = await pdfDoc.embedJpg(fallback as Uint8Array); } + } - const pdfBytes = await pdfDoc.save(); - downloadFile( - new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), - 'from_jpgs.pdf' - ); - showAlert('Success', 'PDF created successfully!', 'success', () => { - resetState(); - }); - } catch (e: any) { - console.error(e); - showAlert('Conversion Error', e.message); - } finally { - hideLoader(); + const page = pdfDoc.addPage([embeddedImage.width, embeddedImage.height]); + page.drawImage(embeddedImage, { + x: 0, + y: 0, + width: embeddedImage.width, + height: embeddedImage.height, + }); } + + const pdfBytes = await pdfDoc.save(); + downloadFile( + new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), + 'from_jpgs.pdf' + ); + showAlert('Success', 'PDF created successfully!', 'success', () => { + resetState(); + }); + } catch (e: any) { + console.error(e); + showAlert('Conversion Error', e.message); + } finally { + hideLoader(); + } } diff --git a/src/js/logic/posterize-page.ts b/src/js/logic/posterize-page.ts index 52b687f..ffc12d9 100644 --- a/src/js/logic/posterize-page.ts +++ b/src/js/logic/posterize-page.ts @@ -3,17 +3,10 @@ import { downloadFile, parsePageRanges, getPDFDocument, formatBytes } from '../u import { PDFDocument, PageSizes } from 'pdf-lib'; import * as pdfjsLib from 'pdfjs-dist'; import { createIcons, icons } from 'lucide'; +import { PosterizeState } from '@/types'; pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); -interface PosterizeState { - file: File | null; - pdfJsDoc: pdfjsLib.PDFDocumentProxy | null; - pdfBytes: Uint8Array | null; - pageSnapshots: Record; - currentPage: number; -} - const pageState: PosterizeState = { file: null, pdfJsDoc: null, @@ -143,7 +136,7 @@ async function posterize() { const rows = parseInt((document.getElementById('posterize-rows') as HTMLInputElement).value) || 1; const cols = parseInt((document.getElementById('posterize-cols') as HTMLInputElement).value) || 1; const pageSizeKey = (document.getElementById('output-page-size') as HTMLSelectElement).value as keyof typeof PageSizes; - let orientation = (document.getElementById('output-orientation') as HTMLSelectElement).value; + const orientation = (document.getElementById('output-orientation') as HTMLSelectElement).value; const scalingMode = (document.querySelector('input[name="scaling-mode"]:checked') as HTMLInputElement).value; const overlap = parseFloat((document.getElementById('overlap') as HTMLInputElement).value) || 0; const overlapUnits = (document.getElementById('overlap-units') as HTMLSelectElement).value; diff --git a/src/js/logic/powerpoint-to-pdf-page.ts b/src/js/logic/powerpoint-to-pdf-page.ts new file mode 100644 index 0000000..130fac5 --- /dev/null +++ b/src/js/logic/powerpoint-to-pdf-page.ts @@ -0,0 +1,218 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + const convertToPdf = async () => { + try { + if (state.files.length === 0) { + showAlert('No Files', 'Please select at least one PowerPoint file.'); + hideLoader(); + return; + } + + const converter = getLibreOfficeConverter(); + + // Initialize LibreOffice if not already done + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + + if (state.files.length === 1) { + const originalFile = state.files[0]; + + showLoader('Processing...'); + + const pdfBlob = await converter.convertToPdf(originalFile); + + const fileName = originalFile.name.replace(/\.(ppt|pptx|odp)$/i, '') + '.pdf'; + + downloadFile(pdfBlob, fileName); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${originalFile.name} to PDF.`, + 'success', + () => resetState() + ); + } else { + showLoader('Processing...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.(ppt|pptx|odp)$/i, ''); + const pdfBuffer = await pdfBlob.arrayBuffer(); + zip.file(`${baseName}.pdf`, pdfBuffer); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + + downloadFile(zipBlob, 'powerpoint-converted.zip'); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${state.files.length} PowerPoint file(s) to PDF.`, + 'success', + () => resetState() + ); + } + } catch (e: any) { + hideLoader(); + showAlert( + 'Error', + `An error occurred during conversion. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + state.files = [...state.files, ...Array.from(files)]; + updateUI(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const pptFiles = Array.from(files).filter(f => { + const name = f.name.toLowerCase(); + return name.endsWith('.ppt') || name.endsWith('.pptx') || name.endsWith('.odp'); + }); + if (pptFiles.length > 0) { + const dataTransfer = new DataTransfer(); + pptFiles.forEach(f => dataTransfer.items.add(f)); + handleFileSelect(dataTransfer.files); + } + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + resetState(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } + + updateUI(); +}); diff --git a/src/js/logic/prepare-pdf-for-ai-page.ts b/src/js/logic/prepare-pdf-for-ai-page.ts new file mode 100644 index 0000000..464f522 --- /dev/null +++ b/src/js/logic/prepare-pdf-for-ai-page.ts @@ -0,0 +1,237 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, + getPDFDocument, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const fileDisplayArea = document.getElementById('file-display-area'); + const extractOptions = document.getElementById('extract-options'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!fileDisplayArea || !extractOptions || !processBtn || !fileControls) + return; + + if (state.files.length > 0) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + + try { + const arrayBuffer = await readFileAsArrayBuffer(file); + const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; + metaSpan.textContent = `${formatBytes(file.size)} • ${pdfDoc.numPages} pages`; + } catch (error) { + console.error('Error loading PDF:', error); + metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`; + } + } + + createIcons({ icons }); + fileControls.classList.remove('hidden'); + extractOptions.classList.remove('hidden'); + (processBtn as HTMLButtonElement).disabled = false; + } else { + fileDisplayArea.innerHTML = ''; + fileControls.classList.add('hidden'); + extractOptions.classList.add('hidden'); + (processBtn as HTMLButtonElement).disabled = true; + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + const extractForAI = async () => { + try { + if (state.files.length === 0) { + showAlert('No Files', 'Please select at least one PDF file.'); + return; + } + + showLoader('Loading engine...'); + const pymupdf = await loadPyMuPDF(); + + const total = state.files.length; + let completed = 0; + let failed = 0; + + if (total === 1) { + const file = state.files[0]; + showLoader(`Extracting ${file.name} for AI...`); + + const llamaDocs = await (pymupdf as any).pdfToLlamaIndex(file); + const outName = file.name.replace(/\.pdf$/i, '') + '_llm.json'; + const jsonContent = JSON.stringify(llamaDocs, null, 2); + downloadFile( + new Blob([jsonContent], { type: 'application/json' }), + outName + ); + + hideLoader(); + showAlert( + 'Extraction Complete', + `Successfully extracted PDF for AI/LLM use.`, + 'success', + () => resetState() + ); + } else { + // Multiple files - create ZIP + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (const file of state.files) { + try { + showLoader( + `Extracting ${file.name} for AI (${completed + 1}/${total})...` + ); + + const llamaDocs = await (pymupdf as any).pdfToLlamaIndex(file); + const outName = file.name.replace(/\.pdf$/i, '') + '_llm.json'; + const jsonContent = JSON.stringify(llamaDocs, null, 2); + zip.file(outName, jsonContent); + + completed++; + } catch (error) { + console.error(`Failed to extract ${file.name}:`, error); + failed++; + } + } + + showLoader('Creating ZIP archive...'); + const zipBlob = await zip.generateAsync({ type: 'blob' }); + + downloadFile(zipBlob, 'pdf-for-ai.zip'); + + hideLoader(); + + if (failed === 0) { + showAlert( + 'Extraction Complete', + `Successfully extracted ${completed} PDF(s) for AI/LLM use.`, + 'success', + () => resetState() + ); + } else { + showAlert( + 'Extraction Partial', + `Extracted ${completed} PDF(s), failed ${failed}.`, + 'warning', + () => resetState() + ); + } + } + } catch (e: any) { + hideLoader(); + showAlert( + 'Error', + `An error occurred during extraction. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const pdfFiles = Array.from(files).filter( + (f) => + f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf') + ); + if (pdfFiles.length > 0) { + state.files = [...state.files, ...pdfFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', resetState); + } + + if (processBtn) { + processBtn.addEventListener('click', extractForAI); + } +}); diff --git a/src/js/logic/psd-to-pdf-page.ts b/src/js/logic/psd-to-pdf-page.ts new file mode 100644 index 0000000..6a17d23 --- /dev/null +++ b/src/js/logic/psd-to-pdf-page.ts @@ -0,0 +1,166 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; + +const ACCEPTED_EXTENSIONS = ['.psd']; +const FILETYPE_NAME = 'PSD'; + +let pymupdf: any = null; + +async function ensurePyMuPDF(): Promise { + if (!pymupdf) { + pymupdf = await loadPyMuPDF(); + } + return pymupdf; +} + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!fileDisplayArea || !processBtn || !fileControls) return; + if (state.files.length > 0) { + fileDisplayArea.innerHTML = ''; + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + infoContainer.append(nameSpan, metaSpan); + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + createIcons({ icons }); + fileControls.classList.remove('hidden'); + processBtn.classList.remove('hidden'); + } else { + fileDisplayArea.innerHTML = ''; + fileControls.classList.add('hidden'); + processBtn.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + updateUI(); + }; + + const convert = async () => { + if (state.files.length === 0) { + showAlert( + 'No Files', + `Please select at least one ${FILETYPE_NAME} file.` + ); + return; + } + try { + showLoader('Loading engine...'); + const mupdf = await ensurePyMuPDF(); + + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + const pdfBlob = await mupdf.imageToPdf(file, { imageType: 'psd' }); + const baseName = file.name.replace(/\.[^/.]+$/, ''); + downloadFile(pdfBlob, `${baseName}.pdf`); + hideLoader(); + showAlert( + 'Conversion Complete', + `Successfully converted ${file.name} to PDF.`, + 'success', + () => resetState() + ); + } else { + showLoader('Converting multiple files...'); + const pdfBlob = await mupdf.imagesToPdf(state.files); + downloadFile(pdfBlob, 'psd_to_pdf.pdf'); + hideLoader(); + showAlert( + 'Conversion Complete', + `Successfully converted ${state.files.length} PSD files to a single PDF.`, + 'success', + () => resetState() + ); + } + } catch (err) { + hideLoader(); + const message = err instanceof Error ? err.message : 'Unknown error'; + console.error(`[${FILETYPE_NAME}ToPDF] Error:`, err); + showAlert( + 'Error', + `An error occurred during conversion. Error: ${message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter((file) => { + const ext = '.' + file.name.split('.').pop()?.toLowerCase(); + return ACCEPTED_EXTENSIONS.includes(ext); + }); + if (validFiles.length > 0) { + state.files = [...state.files, ...validFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => + handleFileSelect((e.target as HTMLInputElement).files) + ); + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + if (addMoreBtn) addMoreBtn.addEventListener('click', () => fileInput.click()); + if (clearFilesBtn) clearFilesBtn.addEventListener('click', resetState); + if (processBtn) processBtn.addEventListener('click', convert); +}); diff --git a/src/js/logic/pub-to-pdf-page.ts b/src/js/logic/pub-to-pdf-page.ts new file mode 100644 index 0000000..1c2e682 --- /dev/null +++ b/src/js/logic/pub-to-pdf-page.ts @@ -0,0 +1,142 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +const ACCEPTED_EXTENSIONS = ['.pub']; +const FILETYPE_NAME = 'PUB'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + infoContainer.append(nameSpan, metaSpan); + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + updateUI(); + }; + + const convert = async () => { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${FILETYPE_NAME} file.`); + return; + } + try { + const converter = getLibreOfficeConverter(); + showLoader('Loading engine...'); + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + const baseName = file.name.replace(/\.[^/.]+$/, ''); + downloadFile(pdfBlob, `${baseName}.pdf`); + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${file.name} to PDF.`, 'success', () => resetState()); + } else { + showLoader('Converting multiple files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + const baseName = file.name.replace(/\.[^/.]+$/, ''); + zip.file(`${baseName}.pdf`, pdfBlob); + } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE_NAME.toLowerCase()}-to-pdf.zip`); + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${state.files.length} files to PDF.`, 'success', () => resetState()); + } + } catch (err) { + hideLoader(); + const message = err instanceof Error ? err.message : 'Unknown error'; + console.error(`[${FILETYPE_NAME}ToPDF] Error:`, err); + showAlert('Error', `An error occurred during conversion. Error: ${message}`); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter(file => { + const ext = '.' + file.name.split('.').pop()?.toLowerCase(); + return ACCEPTED_EXTENSIONS.includes(ext); + }); + if (validFiles.length > 0) { + state.files = [...state.files, ...validFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => handleFileSelect((e.target as HTMLInputElement).files)); + dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('bg-gray-700'); }); + dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('bg-gray-700'); }); + dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('bg-gray-700'); handleFileSelect(e.dataTransfer?.files ?? null); }); + fileInput.addEventListener('click', () => { fileInput.value = ''; }); + } + if (addMoreBtn) addMoreBtn.addEventListener('click', () => fileInput.click()); + if (clearFilesBtn) clearFilesBtn.addEventListener('click', resetState); + if (processBtn) processBtn.addEventListener('click', convert); + + updateUI(); +}); diff --git a/src/js/logic/rasterize-pdf-page.ts b/src/js/logic/rasterize-pdf-page.ts new file mode 100644 index 0000000..fc9960b --- /dev/null +++ b/src/js/logic/rasterize-pdf-page.ts @@ -0,0 +1,262 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, + getPDFDocument, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const fileDisplayArea = document.getElementById('file-display-area'); + const rasterizeOptions = document.getElementById('rasterize-options'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!fileDisplayArea || !rasterizeOptions || !processBtn || !fileControls) + return; + + if (state.files.length > 0) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + + try { + const arrayBuffer = await readFileAsArrayBuffer(file); + const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; + metaSpan.textContent = `${formatBytes(file.size)} • ${pdfDoc.numPages} pages`; + } catch (error) { + console.error('Error loading PDF:', error); + metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`; + } + } + + createIcons({ icons }); + fileControls.classList.remove('hidden'); + rasterizeOptions.classList.remove('hidden'); + (processBtn as HTMLButtonElement).disabled = false; + } else { + fileDisplayArea.innerHTML = ''; + fileControls.classList.add('hidden'); + rasterizeOptions.classList.add('hidden'); + (processBtn as HTMLButtonElement).disabled = true; + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + const rasterize = async () => { + try { + if (state.files.length === 0) { + showAlert('No Files', 'Please select at least one PDF file.'); + return; + } + + if (!isPyMuPDFAvailable()) { + showWasmRequiredDialog('pymupdf'); + return; + } + + showLoader('Loading engine...'); + const pymupdf = await loadPyMuPDF(); + + // Get options from UI + const dpi = + parseInt( + (document.getElementById('rasterize-dpi') as HTMLSelectElement).value + ) || 150; + const format = ( + document.getElementById('rasterize-format') as HTMLSelectElement + ).value as 'png' | 'jpeg'; + const grayscale = ( + document.getElementById('rasterize-grayscale') as HTMLInputElement + ).checked; + + const total = state.files.length; + let completed = 0; + let failed = 0; + + if (total === 1) { + const file = state.files[0]; + showLoader(`Rasterizing ${file.name}...`); + + const rasterizedBlob = await (pymupdf as any).rasterizePdf(file, { + dpi, + format, + grayscale, + quality: 95, + }); + + const outName = file.name.replace(/\.pdf$/i, '') + '_rasterized.pdf'; + downloadFile(rasterizedBlob, outName); + + hideLoader(); + showAlert( + 'Rasterization Complete', + `Successfully rasterized PDF at ${dpi} DPI.`, + 'success', + () => resetState() + ); + } else { + // Multiple files - create ZIP + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (const file of state.files) { + try { + showLoader( + `Rasterizing ${file.name} (${completed + 1}/${total})...` + ); + + const rasterizedBlob = await (pymupdf as any).rasterizePdf(file, { + dpi, + format, + grayscale, + quality: 95, + }); + + const outName = + file.name.replace(/\.pdf$/i, '') + '_rasterized.pdf'; + zip.file(outName, rasterizedBlob); + + completed++; + } catch (error) { + console.error(`Failed to rasterize ${file.name}:`, error); + failed++; + } + } + + showLoader('Creating ZIP archive...'); + const zipBlob = await zip.generateAsync({ type: 'blob' }); + + downloadFile(zipBlob, 'rasterized-pdfs.zip'); + + hideLoader(); + + if (failed === 0) { + showAlert( + 'Rasterization Complete', + `Successfully rasterized ${completed} PDF(s) at ${dpi} DPI.`, + 'success', + () => resetState() + ); + } else { + showAlert( + 'Rasterization Partial', + `Rasterized ${completed} PDF(s), failed ${failed}.`, + 'warning', + () => resetState() + ); + } + } + } catch (e: any) { + hideLoader(); + showAlert( + 'Error', + `An error occurred during rasterization. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const pdfFiles = Array.from(files).filter( + (f) => + f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf') + ); + if (pdfFiles.length > 0) { + state.files = [...state.files, ...pdfFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', resetState); + } + + if (processBtn) { + processBtn.addEventListener('click', rasterize); + } +}); diff --git a/src/js/logic/remove-blank-pages-page.ts b/src/js/logic/remove-blank-pages-page.ts index f320e63..dea1fa8 100644 --- a/src/js/logic/remove-blank-pages-page.ts +++ b/src/js/logic/remove-blank-pages-page.ts @@ -1,65 +1,79 @@ import { PDFDocument } from 'pdf-lib'; import * as pdfjsLib from 'pdfjs-dist'; import { createIcons, icons } from 'lucide'; +import { initPagePreview } from '../utils/page-preview.js'; -pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); // State const pageState: { - pdfDoc: PDFDocument | null; - file: File | null; - detectedBlankPages: number[]; - pageThumbnails: Map; + pdfDoc: PDFDocument | null; + file: File | null; + detectedBlankPages: number[]; + pageThumbnails: Map; } = { - pdfDoc: null, - file: null, - detectedBlankPages: [], - pageThumbnails: new Map() + pdfDoc: null, + file: null, + detectedBlankPages: [], + pageThumbnails: new Map(), }; function showLoader(msg = 'Processing...') { - document.getElementById('loader-modal')?.classList.remove('hidden'); - const txt = document.getElementById('loader-text'); - if (txt) txt.textContent = msg; + document.getElementById('loader-modal')?.classList.remove('hidden'); + const txt = document.getElementById('loader-text'); + if (txt) txt.textContent = msg; } -function hideLoader() { document.getElementById('loader-modal')?.classList.add('hidden'); } +function hideLoader() { + document.getElementById('loader-modal')?.classList.add('hidden'); +} -function showAlert(title: string, msg: string, type = 'error', cb?: () => void) { - const modal = document.getElementById('alert-modal'); - const t = document.getElementById('alert-title'); - const m = document.getElementById('alert-message'); - if (t) t.textContent = title; - if (m) m.textContent = msg; - modal?.classList.remove('hidden'); - const okBtn = document.getElementById('alert-ok'); - if (okBtn) { - const newBtn = okBtn.cloneNode(true) as HTMLElement; - okBtn.replaceWith(newBtn); - newBtn.addEventListener('click', () => { - modal?.classList.add('hidden'); - if (cb) cb(); - }); - } +function showAlert( + title: string, + msg: string, + type = 'error', + cb?: () => void +) { + const modal = document.getElementById('alert-modal'); + const t = document.getElementById('alert-title'); + const m = document.getElementById('alert-message'); + if (t) t.textContent = title; + if (m) m.textContent = msg; + modal?.classList.remove('hidden'); + const okBtn = document.getElementById('alert-ok'); + if (okBtn) { + const newBtn = okBtn.cloneNode(true) as HTMLElement; + okBtn.replaceWith(newBtn); + newBtn.addEventListener('click', () => { + modal?.classList.add('hidden'); + if (cb) cb(); + }); + } } function downloadFile(blob: Blob, filename: string) { - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; a.download = filename; a.click(); - URL.revokeObjectURL(url); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); } function updateFileDisplay() { - const area = document.getElementById('file-display-area'); - if (!area || !pageState.file || !pageState.pdfDoc) return; + const area = document.getElementById('file-display-area'); + if (!area || !pageState.file || !pageState.pdfDoc) return; - const fileSize = pageState.file.size < 1024 * 1024 - ? `${(pageState.file.size / 1024).toFixed(1)} KB` - : `${(pageState.file.size / 1024 / 1024).toFixed(2)} MB`; - const pageCount = pageState.pdfDoc.getPageCount(); + const fileSize = + pageState.file.size < 1024 * 1024 + ? `${(pageState.file.size / 1024).toFixed(1)} KB` + : `${(pageState.file.size / 1024 / 1024).toFixed(2)} MB`; + const pageCount = pageState.pdfDoc.getPageCount(); - area.innerHTML = ` + area.innerHTML = `
@@ -72,257 +86,285 @@ function updateFileDisplay() {
`; - createIcons({ icons }); - document.getElementById('remove-file')?.addEventListener('click', resetState); + createIcons({ icons }); + document.getElementById('remove-file')?.addEventListener('click', resetState); } function resetState() { - pageState.pdfDoc = null; - pageState.file = null; - pageState.detectedBlankPages = []; - pageState.pageThumbnails.forEach(url => URL.revokeObjectURL(url)); - pageState.pageThumbnails.clear(); + pageState.pdfDoc = null; + pageState.file = null; + pageState.detectedBlankPages = []; + pageState.pageThumbnails.forEach((url) => URL.revokeObjectURL(url)); + pageState.pageThumbnails.clear(); - const area = document.getElementById('file-display-area'); - if (area) area.innerHTML = ''; - document.getElementById('options-panel')?.classList.add('hidden'); - document.getElementById('preview-panel')?.classList.add('hidden'); - const inp = document.getElementById('file-input') as HTMLInputElement; - if (inp) inp.value = ''; + const area = document.getElementById('file-display-area'); + if (area) area.innerHTML = ''; + document.getElementById('options-panel')?.classList.add('hidden'); + document.getElementById('preview-panel')?.classList.add('hidden'); + const inp = document.getElementById('file-input') as HTMLInputElement; + if (inp) inp.value = ''; + const slider = document.getElementById( + 'sensitivity-slider' + ) as HTMLInputElement; + if (slider) slider.value = '80'; + const sliderLabel = document.getElementById('sensitivity-value'); + if (sliderLabel) sliderLabel.textContent = '80'; } async function handleFileUpload(file: File) { - if (!file || file.type !== 'application/pdf') { - showAlert('Error', 'Please upload a valid PDF file.'); - return; - } - showLoader('Loading PDF...'); - try { - const buf = await file.arrayBuffer(); - pageState.pdfDoc = await PDFDocument.load(buf); - pageState.file = file; - pageState.detectedBlankPages = []; - updateFileDisplay(); - document.getElementById('options-panel')?.classList.remove('hidden'); - document.getElementById('preview-panel')?.classList.add('hidden'); - } catch (e) { - console.error(e); - showAlert('Error', 'Failed to load PDF file.'); - } finally { - hideLoader(); - } + if (!file || file.type !== 'application/pdf') { + showAlert('Error', 'Please upload a valid PDF file.'); + return; + } + showLoader('Loading PDF...'); + try { + const buf = await file.arrayBuffer(); + pageState.pdfDoc = await PDFDocument.load(buf); + pageState.file = file; + pageState.detectedBlankPages = []; + updateFileDisplay(); + document.getElementById('options-panel')?.classList.remove('hidden'); + document.getElementById('preview-panel')?.classList.add('hidden'); + } catch (e) { + console.error(e); + showAlert('Error', 'Failed to load PDF file.'); + } finally { + hideLoader(); + } } -async function isPageBlank(page: any, threshold = 250): Promise { - const viewport = page.getViewport({ scale: 0.5 }); // Lower scale for faster processing - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - if (!ctx) return false; +async function isPageBlank( + page: any, + maxNonWhitePercent = 0.5 +): Promise { + const viewport = page.getViewport({ scale: 0.5 }); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (!ctx) return false; - canvas.width = viewport.width; - canvas.height = viewport.height; + canvas.width = viewport.width; + canvas.height = viewport.height; - await page.render({ canvasContext: ctx, viewport }).promise; + await page.render({ canvasContext: ctx, viewport }).promise; - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const data = imageData.data; + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + const totalPixels = data.length / 4; - let totalBrightness = 0; - for (let i = 0; i < data.length; i += 4) { - const r = data[i], g = data[i + 1], b = data[i + 2]; - totalBrightness += (r + g + b) / 3; - } + let nonWhitePixels = 0; + for (let i = 0; i < data.length; i += 4) { + const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3; + if (brightness < 240) nonWhitePixels++; + } - const avgBrightness = totalBrightness / (data.length / 4); - return avgBrightness > threshold; + const nonWhitePercent = (nonWhitePixels / totalPixels) * 100; + return nonWhitePercent <= maxNonWhitePercent; } async function generateThumbnail(page: any): Promise { - const viewport = page.getViewport({ scale: 0.3 }); - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - if (!ctx) return ''; + const viewport = page.getViewport({ scale: 1 }); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (!ctx) return ''; - canvas.width = viewport.width; - canvas.height = viewport.height; + canvas.width = viewport.width; + canvas.height = viewport.height; - await page.render({ canvasContext: ctx, viewport }).promise; - return canvas.toDataURL('image/jpeg', 0.7); + await page.render({ canvasContext: ctx, viewport }).promise; + return canvas.toDataURL('image/jpeg', 0.7); } async function detectBlankPages() { - if (!pageState.pdfDoc || !pageState.file) return showAlert('Error', 'Please upload a PDF first.'); + if (!pageState.pdfDoc || !pageState.file) + return showAlert('Error', 'Please upload a PDF first.'); - const sensitivitySlider = document.getElementById('sensitivity-slider') as HTMLInputElement; - const sensitivityPercent = parseInt(sensitivitySlider?.value || '80'); - const threshold = Math.round(255 - (sensitivityPercent * 2.55)); + const sensitivitySlider = document.getElementById( + 'sensitivity-slider' + ) as HTMLInputElement; + const sensitivityPercent = parseInt(sensitivitySlider?.value || '80'); + const maxNonWhitePercent = 5 - (sensitivityPercent / 100) * 4.9; - showLoader('Detecting blank pages...'); - try { - const pdfData = await pageState.file.arrayBuffer(); - const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise; - const totalPages = pdfDoc.numPages; + showLoader('Detecting blank pages...'); + try { + const pdfData = await pageState.file.arrayBuffer(); + const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise; + const totalPages = pdfDoc.numPages; - pageState.detectedBlankPages = []; - pageState.pageThumbnails.forEach(url => URL.revokeObjectURL(url)); - pageState.pageThumbnails.clear(); + pageState.detectedBlankPages = []; + pageState.pageThumbnails.forEach((url) => URL.revokeObjectURL(url)); + pageState.pageThumbnails.clear(); - for (let i = 1; i <= totalPages; i++) { - const page = await pdfDoc.getPage(i); - if (await isPageBlank(page, threshold)) { - pageState.detectedBlankPages.push(i - 1); // 0-indexed - const thumbnail = await generateThumbnail(page); - pageState.pageThumbnails.set(i - 1, thumbnail); - } - } - - if (pageState.detectedBlankPages.length === 0) { - showAlert('Info', 'No blank pages detected in this PDF.'); - hideLoader(); - return; - } - - // Show preview panel - updatePreviewPanel(); - document.getElementById('preview-panel')?.classList.remove('hidden'); - hideLoader(); - } catch (e) { - console.error(e); - showAlert('Error', 'Could not detect blank pages.'); - hideLoader(); + for (let i = 1; i <= totalPages; i++) { + const page = await pdfDoc.getPage(i); + if (await isPageBlank(page, maxNonWhitePercent)) { + pageState.detectedBlankPages.push(i - 1); // 0-indexed + const thumbnail = await generateThumbnail(page); + pageState.pageThumbnails.set(i - 1, thumbnail); + } } + + if (pageState.detectedBlankPages.length === 0) { + showAlert('Info', 'No blank pages detected in this PDF.'); + hideLoader(); + return; + } + + // Show preview panel + updatePreviewPanel(); + document.getElementById('preview-panel')?.classList.remove('hidden'); + + const previewContainer = document.getElementById('blank-pages-preview'); + if (previewContainer) initPagePreview(previewContainer, pdfDoc); + + hideLoader(); + } catch (e) { + console.error(e); + showAlert('Error', 'Could not detect blank pages.'); + hideLoader(); + } } function updatePreviewPanel() { - const previewInfo = document.getElementById('preview-info'); - const previewContainer = document.getElementById('blank-pages-preview'); + const previewInfo = document.getElementById('preview-info'); + const previewContainer = document.getElementById('blank-pages-preview'); - if (!previewInfo || !previewContainer) return; + if (!previewInfo || !previewContainer) return; - previewInfo.textContent = `Found ${pageState.detectedBlankPages.length} blank page(s). Click on a page to deselect it.`; - previewContainer.innerHTML = ''; + previewInfo.textContent = `Found ${pageState.detectedBlankPages.length} blank page(s). Click on a page to deselect it.`; + previewContainer.innerHTML = ''; - pageState.detectedBlankPages.forEach((pageIndex) => { - const thumbnail = pageState.pageThumbnails.get(pageIndex) || ''; - const div = document.createElement('div'); - div.className = 'relative cursor-pointer group'; - div.dataset.pageIndex = String(pageIndex); - div.dataset.selected = 'true'; + pageState.detectedBlankPages.forEach((pageIndex) => { + const thumbnail = pageState.pageThumbnails.get(pageIndex) || ''; + const div = document.createElement('div'); + div.className = + 'relative cursor-pointer flex flex-col items-center gap-1 p-2 border-2 border-red-500 rounded-lg bg-gray-700 transition-colors group'; + div.dataset.pageIndex = String(pageIndex); + div.dataset.selected = 'true'; - div.innerHTML = ` -
- Page ${pageIndex + 1} -
- Page ${pageIndex + 1} + div.innerHTML = ` +
+ Page ${pageIndex + 1} +
+ ${pageIndex + 1}
-
+
`; - div.addEventListener('click', () => togglePageSelection(div, pageIndex)); - previewContainer.appendChild(div); - }); + div.addEventListener('click', () => togglePageSelection(div, pageIndex)); + previewContainer.appendChild(div); + }); - createIcons({ icons }); + createIcons({ icons }); } function togglePageSelection(div: HTMLElement, pageIndex: number) { - const isSelected = div.dataset.selected === 'true'; - const border = div.querySelector('.border-2') as HTMLElement; - const checkMark = div.querySelector('.check-mark') as HTMLElement; + const isSelected = div.dataset.selected === 'true'; + const checkMark = div.querySelector('.check-mark') as HTMLElement; - if (isSelected) { - div.dataset.selected = 'false'; - border?.classList.remove('border-red-500'); - border?.classList.add('border-gray-500', 'opacity-50'); - checkMark?.classList.add('hidden'); - } else { - div.dataset.selected = 'true'; - border?.classList.add('border-red-500'); - border?.classList.remove('border-gray-500', 'opacity-50'); - checkMark?.classList.remove('hidden'); - } + if (isSelected) { + div.dataset.selected = 'false'; + div.classList.remove('border-red-500'); + div.classList.add('border-gray-600', 'opacity-50'); + checkMark?.classList.add('hidden'); + } else { + div.dataset.selected = 'true'; + div.classList.add('border-red-500'); + div.classList.remove('border-gray-600', 'opacity-50'); + checkMark?.classList.remove('hidden'); + } } async function processRemoveBlankPages() { - if (!pageState.pdfDoc || !pageState.file) return showAlert('Error', 'Please upload a PDF first.'); + if (!pageState.pdfDoc || !pageState.file) + return showAlert('Error', 'Please upload a PDF first.'); - // Get selected pages to remove - const previewContainer = document.getElementById('blank-pages-preview'); - const selectedPages: number[] = []; - previewContainer?.querySelectorAll('[data-selected="true"]').forEach(el => { - const pageIndex = parseInt((el as HTMLElement).dataset.pageIndex || '-1'); - if (pageIndex >= 0) selectedPages.push(pageIndex); - }); + // Get selected pages to remove + const previewContainer = document.getElementById('blank-pages-preview'); + const selectedPages: number[] = []; + previewContainer?.querySelectorAll('[data-selected="true"]').forEach((el) => { + const pageIndex = parseInt((el as HTMLElement).dataset.pageIndex || '-1'); + if (pageIndex >= 0) selectedPages.push(pageIndex); + }); - if (selectedPages.length === 0) { - showAlert('Info', 'No pages selected for removal.'); - return; + if (selectedPages.length === 0) { + showAlert('Info', 'No pages selected for removal.'); + return; + } + + showLoader(`Removing ${selectedPages.length} blank page(s)...`); + try { + const newPdf = await PDFDocument.create(); + const pages = pageState.pdfDoc.getPages(); + + for (let i = 0; i < pages.length; i++) { + if (!selectedPages.includes(i)) { + const [copiedPage] = await newPdf.copyPages(pageState.pdfDoc, [i]); + newPdf.addPage(copiedPage); + } } - showLoader(`Removing ${selectedPages.length} blank page(s)...`); - try { - const newPdf = await PDFDocument.create(); - const pages = pageState.pdfDoc.getPages(); - - for (let i = 0; i < pages.length; i++) { - if (!selectedPages.includes(i)) { - const [copiedPage] = await newPdf.copyPages(pageState.pdfDoc, [i]); - newPdf.addPage(copiedPage); - } - } - - const newPdfBytes = await newPdf.save(); - downloadFile(new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }), 'blank-pages-removed.pdf'); - showAlert('Success', `Removed ${selectedPages.length} blank page(s) successfully!`, 'success', resetState); - } catch (e) { - console.error(e); - showAlert('Error', 'Could not remove blank pages.'); - } finally { - hideLoader(); - } + const newPdfBytes = await newPdf.save(); + downloadFile( + new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }), + 'blank-pages-removed.pdf' + ); + showAlert( + 'Success', + `Removed ${selectedPages.length} blank page(s) successfully!`, + 'success', + resetState + ); + } catch (e) { + console.error(e); + showAlert('Error', 'Could not remove blank pages.'); + } finally { + hideLoader(); + } } document.addEventListener('DOMContentLoaded', () => { - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const detectBtn = document.getElementById('detect-btn'); - const processBtn = document.getElementById('process-btn'); - const sensitivitySlider = document.getElementById('sensitivity-slider') as HTMLInputElement; - const sensitivityValue = document.getElementById('sensitivity-value'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const detectBtn = document.getElementById('detect-btn'); + const processBtn = document.getElementById('process-btn'); + const sensitivitySlider = document.getElementById( + 'sensitivity-slider' + ) as HTMLInputElement; + const sensitivityValue = document.getElementById('sensitivity-value'); - sensitivitySlider?.addEventListener('input', (e) => { - const value = (e.target as HTMLInputElement).value; - if (sensitivityValue) sensitivityValue.textContent = value; - }); + sensitivitySlider?.addEventListener('input', (e) => { + const value = (e.target as HTMLInputElement).value; + if (sensitivityValue) sensitivityValue.textContent = value; + }); - fileInput?.addEventListener('change', (e) => { - const f = (e.target as HTMLInputElement).files?.[0]; - if (f) handleFileUpload(f); - }); + fileInput?.addEventListener('change', (e) => { + const f = (e.target as HTMLInputElement).files?.[0]; + if (f) handleFileUpload(f); + }); - dropZone?.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('border-indigo-500'); - }); + dropZone?.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('border-indigo-500'); + }); - dropZone?.addEventListener('dragleave', () => { - dropZone.classList.remove('border-indigo-500'); - }); + dropZone?.addEventListener('dragleave', () => { + dropZone.classList.remove('border-indigo-500'); + }); - dropZone?.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('border-indigo-500'); - const f = e.dataTransfer?.files[0]; - if (f) handleFileUpload(f); - }); + dropZone?.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('border-indigo-500'); + const f = e.dataTransfer?.files[0]; + if (f) handleFileUpload(f); + }); - detectBtn?.addEventListener('click', detectBlankPages); - processBtn?.addEventListener('click', processRemoveBlankPages); + detectBtn?.addEventListener('click', detectBlankPages); + processBtn?.addEventListener('click', processRemoveBlankPages); - document.getElementById('back-to-tools')?.addEventListener('click', () => { - window.location.href = '../../index.html'; - }); + document.getElementById('back-to-tools')?.addEventListener('click', () => { + window.location.href = '../../index.html'; + }); }); diff --git a/src/js/logic/remove-restrictions-page.ts b/src/js/logic/remove-restrictions-page.ts index 3cb9176..98404e2 100644 --- a/src/js/logic/remove-restrictions-page.ts +++ b/src/js/logic/remove-restrictions-page.ts @@ -1,12 +1,9 @@ import { showAlert } from '../ui.js'; import { downloadFile, formatBytes, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js'; import { icons, createIcons } from 'lucide'; +import { RemoveRestrictionsState } from '@/types'; -interface PageState { - file: File | null; -} - -const pageState: PageState = { +const pageState: RemoveRestrictionsState = { file: null, }; diff --git a/src/js/logic/rotate-custom-page.ts b/src/js/logic/rotate-custom-page.ts new file mode 100644 index 0000000..2d5ebec --- /dev/null +++ b/src/js/logic/rotate-custom-page.ts @@ -0,0 +1,386 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { downloadFile, formatBytes, getPDFDocument } from '../utils/helpers.js'; +import { createIcons, icons } from 'lucide'; +import { PDFDocument as PDFLibDocument, degrees } from 'pdf-lib'; +import { renderPagesProgressively, cleanupLazyRendering } from '../utils/render-utils.js'; +import * as pdfjsLib from 'pdfjs-dist'; + +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); + +interface RotateState { + file: File | null; + pdfDoc: PDFLibDocument | null; + pdfJsDoc: pdfjsLib.PDFDocumentProxy | null; + rotations: number[]; +} + +const pageState: RotateState = { + file: null, + pdfDoc: null, + pdfJsDoc: null, + rotations: [], +}; + +function resetState() { + cleanupLazyRendering(); + pageState.file = null; + pageState.pdfDoc = null; + pageState.pdfJsDoc = null; + pageState.rotations = []; + + const fileDisplayArea = document.getElementById('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + + const toolOptions = document.getElementById('tool-options'); + if (toolOptions) toolOptions.classList.add('hidden'); + + const pageThumbnails = document.getElementById('page-thumbnails'); + if (pageThumbnails) pageThumbnails.innerHTML = ''; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; + + const batchAngle = document.getElementById('batch-custom-angle') as HTMLInputElement; + if (batchAngle) batchAngle.value = '0'; +} + +function updateAllRotationDisplays() { + for (let i = 0; i < pageState.rotations.length; i++) { + const input = document.getElementById(`page-angle-${i}`) as HTMLInputElement; + if (input) input.value = pageState.rotations[i].toString(); + const container = document.querySelector(`[data-page-index="${i}"]`); + if (container) { + const wrapper = container.querySelector('.thumbnail-wrapper') as HTMLElement; + if (wrapper) wrapper.style.transform = `rotate(${-pageState.rotations[i]}deg)`; + } + } +} + +function createPageWrapper(canvas: HTMLCanvasElement, pageNumber: number): HTMLElement { + const pageIndex = pageNumber - 1; + + const container = document.createElement('div'); + container.className = 'page-thumbnail relative bg-gray-700 rounded-lg overflow-hidden'; + container.dataset.pageIndex = pageIndex.toString(); + container.dataset.pageNumber = pageNumber.toString(); + + const canvasWrapper = document.createElement('div'); + canvasWrapper.className = 'thumbnail-wrapper flex items-center justify-center p-2 h-36'; + canvasWrapper.style.transition = 'transform 0.3s ease'; + // Apply initial rotation if it exists (negated for canvas display) + const initialRotation = pageState.rotations[pageIndex] || 0; + canvasWrapper.style.transform = `rotate(${-initialRotation}deg)`; + + canvas.className = 'max-w-full max-h-full object-contain'; + canvasWrapper.appendChild(canvas); + + const pageLabel = document.createElement('div'); + pageLabel.className = 'absolute top-1 left-1 bg-black bg-opacity-60 text-white text-xs px-2 py-1 rounded'; + pageLabel.textContent = `${pageNumber}`; + + container.appendChild(canvasWrapper); + container.appendChild(pageLabel); + + // Per-page rotation controls - Custom angle input + const controls = document.createElement('div'); + controls.className = 'flex items-center justify-center gap-1 p-2 bg-gray-800'; + + const decrementBtn = document.createElement('button'); + decrementBtn.className = 'w-8 h-8 flex items-center justify-center bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600 text-sm'; + decrementBtn.textContent = '-'; + decrementBtn.onclick = function (e) { + e.stopPropagation(); + const input = document.getElementById(`page-angle-${pageIndex}`) as HTMLInputElement; + const current = parseInt(input.value) || 0; + input.value = (current - 1).toString(); + }; + + const angleInput = document.createElement('input'); + angleInput.type = 'number'; + angleInput.id = `page-angle-${pageIndex}`; + angleInput.value = pageState.rotations[pageIndex]?.toString() || '0'; + angleInput.className = 'w-12 h-8 text-center bg-gray-700 border border-gray-600 text-white rounded text-xs'; + + const incrementBtn = document.createElement('button'); + incrementBtn.className = 'w-8 h-8 flex items-center justify-center bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600 text-sm'; + incrementBtn.textContent = '+'; + incrementBtn.onclick = function (e) { + e.stopPropagation(); + const input = document.getElementById(`page-angle-${pageIndex}`) as HTMLInputElement; + const current = parseInt(input.value) || 0; + input.value = (current + 1).toString(); + }; + + const applyBtn = document.createElement('button'); + applyBtn.className = 'w-8 h-8 flex items-center justify-center bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600'; + applyBtn.innerHTML = ''; + applyBtn.onclick = function (e) { + e.stopPropagation(); + const input = document.getElementById(`page-angle-${pageIndex}`) as HTMLInputElement; + const angle = parseInt(input.value) || 0; + pageState.rotations[pageIndex] = angle; + const wrapper = container.querySelector('.thumbnail-wrapper') as HTMLElement; + if (wrapper) wrapper.style.transform = `rotate(${-angle}deg)`; + }; + + controls.append(decrementBtn, angleInput, incrementBtn, applyBtn); + container.appendChild(controls); + + // Re-create icons for the new element + setTimeout(function () { + createIcons({ icons }); + }, 0); + + return container; +} + +async function renderThumbnails() { + const pageThumbnails = document.getElementById('page-thumbnails'); + if (!pageThumbnails || !pageState.pdfJsDoc) return; + + pageThumbnails.innerHTML = ''; + + await renderPagesProgressively( + pageState.pdfJsDoc, + pageThumbnails, + createPageWrapper, + { + batchSize: 8, + useLazyLoading: true, + lazyLoadMargin: '200px', + eagerLoadBatches: 2, + onBatchComplete: function () { + createIcons({ icons }); + } + } + ); + + createIcons({ icons }); +} + +async function updateUI() { + const fileDisplayArea = document.getElementById('file-display-area'); + const toolOptions = document.getElementById('tool-options'); + + if (!fileDisplayArea) return; + + fileDisplayArea.innerHTML = ''; + + if (pageState.file) { + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = pageState.file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(pageState.file.size)} • Loading...`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = function () { + resetState(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); + + try { + showLoader('Loading PDF...'); + const arrayBuffer = await pageState.file.arrayBuffer(); + + pageState.pdfDoc = await PDFLibDocument.load(arrayBuffer.slice(0), { + ignoreEncryption: true, + throwOnInvalidObject: false + }); + + pageState.pdfJsDoc = await getPDFDocument({ data: arrayBuffer.slice(0) }).promise; + + const pageCount = pageState.pdfDoc.getPageCount(); + pageState.rotations = new Array(pageCount).fill(0); + + metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageCount} pages`; + + await renderThumbnails(); + hideLoader(); + + if (toolOptions) toolOptions.classList.remove('hidden'); + } catch (error) { + console.error('Error loading PDF:', error); + hideLoader(); + showAlert('Error', 'Failed to load PDF file.'); + resetState(); + } + } else { + if (toolOptions) toolOptions.classList.add('hidden'); + } +} + +async function applyRotations() { + if (!pageState.pdfDoc || !pageState.file) { + showAlert('Error', 'Please upload a PDF first.'); + return; + } + + showLoader('Applying rotations...'); + + try { + const pageCount = pageState.pdfDoc.getPageCount(); + const newPdfDoc = await PDFLibDocument.create(); + + for (let i = 0; i < pageCount; i++) { + const rotation = pageState.rotations[i] || 0; + const originalPage = pageState.pdfDoc.getPage(i); + const currentRotation = originalPage.getRotation().angle; + const totalRotation = currentRotation + rotation; + + console.log(`Page ${i}: rotation=${rotation}, currentRotation=${currentRotation}, totalRotation=${totalRotation}, applying=${-totalRotation}`); + + if (totalRotation % 90 === 0) { + const [copiedPage] = await newPdfDoc.copyPages(pageState.pdfDoc, [i]); + copiedPage.setRotation(degrees(totalRotation)); + newPdfDoc.addPage(copiedPage); + } else { + const embeddedPage = await newPdfDoc.embedPage(originalPage); + const { width, height } = embeddedPage.scale(1); + + const angleRad = (totalRotation * Math.PI) / 180; + const absCos = Math.abs(Math.cos(angleRad)); + const absSin = Math.abs(Math.sin(angleRad)); + + const newWidth = width * absCos + height * absSin; + const newHeight = width * absSin + height * absCos; + + const newPage = newPdfDoc.addPage([newWidth, newHeight]); + + const x = newWidth / 2 - (width / 2 * Math.cos(angleRad) - height / 2 * Math.sin(angleRad)); + const y = newHeight / 2 - (width / 2 * Math.sin(angleRad) + height / 2 * Math.cos(angleRad)); + + newPage.drawPage(embeddedPage, { + x, + y, + width, + height, + rotate: degrees(totalRotation), + }); + } + } + + const rotatedPdfBytes = await newPdfDoc.save(); + const originalName = pageState.file.name.replace(/\.pdf$/i, ''); + + downloadFile( + new Blob([new Uint8Array(rotatedPdfBytes)], { type: 'application/pdf' }), + `${originalName}_rotated.pdf` + ); + + showAlert('Success', 'Rotations applied successfully!', 'success', function () { + resetState(); + }); + } catch (e) { + console.error(e); + showAlert('Error', 'Could not apply rotations.'); + } finally { + hideLoader(); + } +} + +function handleFileSelect(files: FileList | null) { + if (files && files.length > 0) { + const file = files[0]; + if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) { + pageState.file = file; + updateUI(); + } + } +} + +document.addEventListener('DOMContentLoaded', function () { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const backBtn = document.getElementById('back-to-tools'); + const batchDecrement = document.getElementById('batch-decrement'); + const batchIncrement = document.getElementById('batch-increment'); + const batchApply = document.getElementById('batch-apply'); + const batchAngleInput = document.getElementById('batch-custom-angle') as HTMLInputElement; + + if (backBtn) { + backBtn.addEventListener('click', function () { + window.location.href = import.meta.env.BASE_URL; + }); + } + + if (batchDecrement && batchAngleInput) { + batchDecrement.addEventListener('click', function () { + const current = parseInt(batchAngleInput.value) || 0; + batchAngleInput.value = (current - 1).toString(); + }); + } + + if (batchIncrement && batchAngleInput) { + batchIncrement.addEventListener('click', function () { + const current = parseInt(batchAngleInput.value) || 0; + batchAngleInput.value = (current + 1).toString(); + }); + } + + if (batchApply && batchAngleInput) { + batchApply.addEventListener('click', function () { + const angle = parseInt(batchAngleInput.value) || 0; + for (let i = 0; i < pageState.rotations.length; i++) { + pageState.rotations[i] = angle; + } + updateAllRotationDisplays(); + }); + } + + if (fileInput && dropZone) { + fileInput.addEventListener('change', function (e) { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', function (e) { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', function (e) { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', function (e) { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const pdfFiles = Array.from(files).filter(function (f) { + return f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf'); + }); + if (pdfFiles.length > 0) { + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(pdfFiles[0]); + handleFileSelect(dataTransfer.files); + } + } + }); + + fileInput.addEventListener('click', function () { + fileInput.value = ''; + }); + } + + if (processBtn) { + processBtn.addEventListener('click', applyRotations); + } +}); diff --git a/src/js/logic/rotate-pdf-page.ts b/src/js/logic/rotate-pdf-page.ts index 64b8830..1c347b7 100644 --- a/src/js/logic/rotate-pdf-page.ts +++ b/src/js/logic/rotate-pdf-page.ts @@ -1,403 +1,359 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; import { downloadFile, formatBytes, getPDFDocument } from '../utils/helpers.js'; import { createIcons, icons } from 'lucide'; -import { PDFDocument as PDFLibDocument, degrees } from 'pdf-lib'; -import { renderPagesProgressively, cleanupLazyRendering } from '../utils/render-utils.js'; +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; +import { + renderPagesProgressively, + cleanupLazyRendering, +} from '../utils/render-utils.js'; +import { rotatePdfPages } from '../utils/pdf-operations.js'; import * as pdfjsLib from 'pdfjs-dist'; -pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); interface RotateState { - file: File | null; - pdfDoc: PDFLibDocument | null; - pdfJsDoc: pdfjsLib.PDFDocumentProxy | null; - rotations: number[]; + file: File | null; + pdfDoc: PDFLibDocument | null; + pdfJsDoc: pdfjsLib.PDFDocumentProxy | null; + rotations: number[]; } const pageState: RotateState = { - file: null, - pdfDoc: null, - pdfJsDoc: null, - rotations: [], + file: null, + pdfDoc: null, + pdfJsDoc: null, + rotations: [], }; function resetState() { - cleanupLazyRendering(); - pageState.file = null; - pageState.pdfDoc = null; - pageState.pdfJsDoc = null; - pageState.rotations = []; + cleanupLazyRendering(); + pageState.file = null; + pageState.pdfDoc = null; + pageState.pdfJsDoc = null; + pageState.rotations = []; - const fileDisplayArea = document.getElementById('file-display-area'); - if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + const fileDisplayArea = document.getElementById('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; - const toolOptions = document.getElementById('tool-options'); - if (toolOptions) toolOptions.classList.add('hidden'); + const toolOptions = document.getElementById('tool-options'); + if (toolOptions) toolOptions.classList.add('hidden'); - const pageThumbnails = document.getElementById('page-thumbnails'); - if (pageThumbnails) pageThumbnails.innerHTML = ''; + const pageThumbnails = document.getElementById('page-thumbnails'); + if (pageThumbnails) pageThumbnails.innerHTML = ''; - const fileInput = document.getElementById('file-input') as HTMLInputElement; - if (fileInput) fileInput.value = ''; - - const batchAngle = document.getElementById('batch-custom-angle') as HTMLInputElement; - if (batchAngle) batchAngle.value = '0'; + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; } function updateAllRotationDisplays() { - for (let i = 0; i < pageState.rotations.length; i++) { - const input = document.getElementById(`page-angle-${i}`) as HTMLInputElement; - if (input) input.value = pageState.rotations[i].toString(); - const container = document.querySelector(`[data-page-index="${i}"]`); - if (container) { - const wrapper = container.querySelector('.thumbnail-wrapper') as HTMLElement; - if (wrapper) wrapper.style.transform = `rotate(${-pageState.rotations[i]}deg)`; - } + for (let i = 0; i < pageState.rotations.length; i++) { + const container = document.querySelector(`[data-page-index="${i}"]`); + if (container) { + const wrapper = container.querySelector( + '.thumbnail-wrapper' + ) as HTMLElement; + if (wrapper) + wrapper.style.transform = `rotate(${pageState.rotations[i]}deg)`; } + } } -function createPageWrapper(canvas: HTMLCanvasElement, pageNumber: number): HTMLElement { - const pageIndex = pageNumber - 1; +function createPageWrapper( + canvas: HTMLCanvasElement, + pageNumber: number +): HTMLElement { + const pageIndex = pageNumber - 1; - const container = document.createElement('div'); - container.className = 'page-thumbnail relative bg-gray-700 rounded-lg overflow-hidden'; - container.dataset.pageIndex = pageIndex.toString(); - container.dataset.pageNumber = pageNumber.toString(); + const container = document.createElement('div'); + container.className = + 'page-thumbnail relative bg-gray-700 rounded-lg overflow-hidden'; + container.dataset.pageIndex = pageIndex.toString(); + container.dataset.pageNumber = pageNumber.toString(); - const canvasWrapper = document.createElement('div'); - canvasWrapper.className = 'thumbnail-wrapper flex items-center justify-center p-2 h-36'; - canvasWrapper.style.transition = 'transform 0.3s ease'; + const canvasWrapper = document.createElement('div'); + canvasWrapper.className = + 'thumbnail-wrapper flex items-center justify-center p-2 h-36'; + canvasWrapper.style.transition = 'transform 0.3s ease'; + // Apply initial rotation if it exists + const initialRotation = pageState.rotations[pageIndex] || 0; + canvasWrapper.style.transform = `rotate(${initialRotation}deg)`; - canvas.className = 'max-w-full max-h-full object-contain'; - canvasWrapper.appendChild(canvas); + canvas.className = 'max-w-full max-h-full object-contain'; + canvasWrapper.appendChild(canvas); - const pageLabel = document.createElement('div'); - pageLabel.className = 'absolute top-1 left-1 bg-black bg-opacity-60 text-white text-xs px-2 py-1 rounded'; - pageLabel.textContent = `${pageNumber}`; + const pageLabel = document.createElement('div'); + pageLabel.className = + 'absolute top-1 left-1 bg-black bg-opacity-60 text-white text-xs px-2 py-1 rounded'; + pageLabel.textContent = `${pageNumber}`; - container.appendChild(canvasWrapper); - container.appendChild(pageLabel); + container.appendChild(canvasWrapper); + container.appendChild(pageLabel); - // Per-page rotation controls - const controls = document.createElement('div'); - controls.className = 'flex items-center justify-center gap-1 p-2 bg-gray-800'; + // Per-page rotation controls - Left and Right buttons only + const controls = document.createElement('div'); + controls.className = 'flex items-center justify-center gap-2 p-2 bg-gray-800'; - const decrementBtn = document.createElement('button'); - decrementBtn.className = 'w-8 h-8 flex items-center justify-center bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600 text-sm'; - decrementBtn.textContent = '−'; - decrementBtn.onclick = function (e) { - e.stopPropagation(); - const input = document.getElementById(`page-angle-${pageIndex}`) as HTMLInputElement; - const current = parseInt(input.value) || 0; - input.value = (current - 1).toString(); - }; + const rotateLeftBtn = document.createElement('button'); + rotateLeftBtn.className = + 'flex items-center gap-1 px-3 py-1.5 bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600 text-xs cursor-pointer'; + rotateLeftBtn.innerHTML = ''; + rotateLeftBtn.addEventListener('click', function (e) { + e.stopPropagation(); + e.preventDefault(); + pageState.rotations[pageIndex] = pageState.rotations[pageIndex] - 90; + const wrapper = container.querySelector( + '.thumbnail-wrapper' + ) as HTMLElement; + if (wrapper) + wrapper.style.transform = `rotate(${pageState.rotations[pageIndex]}deg)`; + }); - const angleInput = document.createElement('input'); - angleInput.type = 'number'; - angleInput.id = `page-angle-${pageIndex}`; - angleInput.value = pageState.rotations[pageIndex]?.toString() || '0'; - angleInput.className = 'w-12 h-8 text-center bg-gray-700 border border-gray-600 text-white rounded text-xs'; + const rotateRightBtn = document.createElement('button'); + rotateRightBtn.className = + 'flex items-center gap-1 px-3 py-1.5 bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600 text-xs cursor-pointer'; + rotateRightBtn.innerHTML = ''; + rotateRightBtn.addEventListener('click', function (e) { + e.stopPropagation(); + e.preventDefault(); + pageState.rotations[pageIndex] = pageState.rotations[pageIndex] + 90; + const wrapper = container.querySelector( + '.thumbnail-wrapper' + ) as HTMLElement; + if (wrapper) + wrapper.style.transform = `rotate(${pageState.rotations[pageIndex]}deg)`; + }); - const incrementBtn = document.createElement('button'); - incrementBtn.className = 'w-8 h-8 flex items-center justify-center bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600 text-sm'; - incrementBtn.textContent = '+'; - incrementBtn.onclick = function (e) { - e.stopPropagation(); - const input = document.getElementById(`page-angle-${pageIndex}`) as HTMLInputElement; - const current = parseInt(input.value) || 0; - input.value = (current + 1).toString(); - }; + controls.append(rotateLeftBtn, rotateRightBtn); + container.appendChild(controls); - const applyBtn = document.createElement('button'); - applyBtn.className = 'w-8 h-8 flex items-center justify-center bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600'; - applyBtn.innerHTML = ''; - applyBtn.onclick = function (e) { - e.stopPropagation(); - const input = document.getElementById(`page-angle-${pageIndex}`) as HTMLInputElement; - const angle = parseInt(input.value) || 0; - pageState.rotations[pageIndex] = angle; - const wrapper = container.querySelector('.thumbnail-wrapper') as HTMLElement; - if (wrapper) wrapper.style.transform = `rotate(${-angle}deg)`; - }; + // Re-create icons scoped to this container only + setTimeout(function () { + createIcons({ icons, nameAttr: 'data-lucide', attrs: {} }); + }, 0); - controls.append(decrementBtn, angleInput, incrementBtn, applyBtn); - container.appendChild(controls); - - // Re-create icons for the new element - setTimeout(function () { - createIcons({ icons }); - }, 0); - - return container; + return container; } async function renderThumbnails() { - const pageThumbnails = document.getElementById('page-thumbnails'); - if (!pageThumbnails || !pageState.pdfJsDoc) return; + const pageThumbnails = document.getElementById('page-thumbnails'); + if (!pageThumbnails || !pageState.pdfJsDoc) return; - pageThumbnails.innerHTML = ''; + pageThumbnails.innerHTML = ''; - await renderPagesProgressively( - pageState.pdfJsDoc, - pageThumbnails, - createPageWrapper, - { - batchSize: 8, - useLazyLoading: true, - lazyLoadMargin: '200px', - eagerLoadBatches: 2, - onBatchComplete: function () { - createIcons({ icons }); - } - } - ); + await renderPagesProgressively( + pageState.pdfJsDoc, + pageThumbnails, + createPageWrapper, + { + batchSize: 8, + useLazyLoading: true, + lazyLoadMargin: '200px', + eagerLoadBatches: 2, + onBatchComplete: function () { + createIcons({ icons }); + }, + } + ); - createIcons({ icons }); + createIcons({ icons }); } async function updateUI() { - const fileDisplayArea = document.getElementById('file-display-area'); - const toolOptions = document.getElementById('tool-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const toolOptions = document.getElementById('tool-options'); - if (!fileDisplayArea) return; + if (!fileDisplayArea) return; - fileDisplayArea.innerHTML = ''; + fileDisplayArea.innerHTML = ''; - if (pageState.file) { - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + if (pageState.file) { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col overflow-hidden'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = pageState.file.name; + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = pageState.file.name; - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(pageState.file.size)} • Loading...`; + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(pageState.file.size)} • Loading...`; - infoContainer.append(nameSpan, metaSpan); + infoContainer.append(nameSpan, metaSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = function () { - resetState(); - }; + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = function () { + resetState(); + }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); - try { - showLoader('Loading PDF...'); - const arrayBuffer = await pageState.file.arrayBuffer(); + try { + showLoader('Loading PDF...'); + const arrayBuffer = await pageState.file.arrayBuffer(); - pageState.pdfDoc = await PDFLibDocument.load(arrayBuffer.slice(0), { - ignoreEncryption: true, - throwOnInvalidObject: false - }); + pageState.pdfDoc = await PDFLibDocument.load(arrayBuffer.slice(0), { + ignoreEncryption: true, + throwOnInvalidObject: false, + }); - pageState.pdfJsDoc = await getPDFDocument({ data: arrayBuffer.slice(0) }).promise; + pageState.pdfJsDoc = await getPDFDocument({ data: arrayBuffer.slice(0) }) + .promise; - const pageCount = pageState.pdfDoc.getPageCount(); - pageState.rotations = new Array(pageCount).fill(0); + const pageCount = pageState.pdfDoc.getPageCount(); + pageState.rotations = new Array(pageCount).fill(0); - metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageCount} pages`; + metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageCount} pages`; - await renderThumbnails(); - hideLoader(); + await renderThumbnails(); + hideLoader(); - if (toolOptions) toolOptions.classList.remove('hidden'); - } catch (error) { - console.error('Error loading PDF:', error); - hideLoader(); - showAlert('Error', 'Failed to load PDF file.'); - resetState(); - } - } else { - if (toolOptions) toolOptions.classList.add('hidden'); + if (toolOptions) toolOptions.classList.remove('hidden'); + } catch (error) { + console.error('Error loading PDF:', error); + hideLoader(); + showAlert('Error', 'Failed to load PDF file.'); + resetState(); } + } else { + if (toolOptions) toolOptions.classList.add('hidden'); + } } async function applyRotations() { - if (!pageState.pdfDoc || !pageState.file) { - showAlert('Error', 'Please upload a PDF first.'); - return; - } + if (!pageState.pdfDoc || !pageState.file) { + showAlert('Error', 'Please upload a PDF first.'); + return; + } - showLoader('Applying rotations...'); + showLoader('Applying rotations...'); - try { - const pageCount = pageState.pdfDoc.getPageCount(); - const newPdfDoc = await PDFLibDocument.create(); + try { + const pdfBytes = await pageState.pdfDoc.save(); + const rotatedPdfBytes = await rotatePdfPages( + new Uint8Array(pdfBytes), + pageState.rotations + ); + const originalName = pageState.file.name.replace(/\.pdf$/i, ''); - for (let i = 0; i < pageCount; i++) { - const rotation = pageState.rotations[i] || 0; - const originalPage = pageState.pdfDoc.getPage(i); - const currentRotation = originalPage.getRotation().angle; - const totalRotation = currentRotation + rotation; + downloadFile( + new Blob([rotatedPdfBytes as unknown as BlobPart], { + type: 'application/pdf', + }), + `${originalName}_rotated.pdf` + ); - if (totalRotation % 90 === 0) { - const [copiedPage] = await newPdfDoc.copyPages(pageState.pdfDoc, [i]); - copiedPage.setRotation(degrees(totalRotation)); - newPdfDoc.addPage(copiedPage); - } else { - const embeddedPage = await newPdfDoc.embedPage(originalPage); - const { width, height } = embeddedPage.scale(1); - - const angleRad = (totalRotation * Math.PI) / 180; - const absCos = Math.abs(Math.cos(angleRad)); - const absSin = Math.abs(Math.sin(angleRad)); - - const newWidth = width * absCos + height * absSin; - const newHeight = width * absSin + height * absCos; - - const newPage = newPdfDoc.addPage([newWidth, newHeight]); - - const x = newWidth / 2 - (width / 2 * Math.cos(angleRad) - height / 2 * Math.sin(angleRad)); - const y = newHeight / 2 - (width / 2 * Math.sin(angleRad) + height / 2 * Math.cos(angleRad)); - - newPage.drawPage(embeddedPage, { - x, - y, - width, - height, - rotate: degrees(totalRotation), - }); - } - } - - const rotatedPdfBytes = await newPdfDoc.save(); - const originalName = pageState.file.name.replace(/\.pdf$/i, ''); - - downloadFile( - new Blob([new Uint8Array(rotatedPdfBytes)], { type: 'application/pdf' }), - `${originalName}_rotated.pdf` - ); - - showAlert('Success', 'Rotations applied successfully!', 'success', function () { - resetState(); - }); - } catch (e) { - console.error(e); - showAlert('Error', 'Could not apply rotations.'); - } finally { - hideLoader(); - } + showAlert( + 'Success', + 'Rotations applied successfully!', + 'success', + function () { + resetState(); + } + ); + } catch (e) { + console.error(e); + showAlert('Error', 'Could not apply rotations.'); + } finally { + hideLoader(); + } } function handleFileSelect(files: FileList | null) { - if (files && files.length > 0) { - const file = files[0]; - if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) { - pageState.file = file; - updateUI(); - } + if (files && files.length > 0) { + const file = files[0]; + if ( + file.type === 'application/pdf' || + file.name.toLowerCase().endsWith('.pdf') + ) { + pageState.file = file; + updateUI(); } + } } document.addEventListener('DOMContentLoaded', function () { - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const processBtn = document.getElementById('process-btn'); - const backBtn = document.getElementById('back-to-tools'); - const rotateAllLeft = document.getElementById('rotate-all-left'); - const rotateAllRight = document.getElementById('rotate-all-right'); - const batchDecrement = document.getElementById('batch-decrement'); - const batchIncrement = document.getElementById('batch-increment'); - const batchApply = document.getElementById('batch-apply'); - const batchAngleInput = document.getElementById('batch-custom-angle') as HTMLInputElement; + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const backBtn = document.getElementById('back-to-tools'); + const rotateAllLeft = document.getElementById('rotate-all-left'); + const rotateAllRight = document.getElementById('rotate-all-right'); - if (backBtn) { - backBtn.addEventListener('click', function () { - window.location.href = import.meta.env.BASE_URL; + if (backBtn) { + backBtn.addEventListener('click', function () { + window.location.href = import.meta.env.BASE_URL; + }); + } + + if (rotateAllLeft) { + rotateAllLeft.addEventListener('click', function () { + for (let i = 0; i < pageState.rotations.length; i++) { + pageState.rotations[i] = pageState.rotations[i] - 90; + } + updateAllRotationDisplays(); + }); + } + + if (rotateAllRight) { + rotateAllRight.addEventListener('click', function () { + for (let i = 0; i < pageState.rotations.length; i++) { + pageState.rotations[i] = pageState.rotations[i] + 90; + } + updateAllRotationDisplays(); + }); + } + + if (fileInput && dropZone) { + fileInput.addEventListener('change', function (e) { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', function (e) { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', function (e) { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', function (e) { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const pdfFiles = Array.from(files).filter(function (f) { + return ( + f.type === 'application/pdf' || + f.name.toLowerCase().endsWith('.pdf') + ); }); - } + if (pdfFiles.length > 0) { + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(pdfFiles[0]); + handleFileSelect(dataTransfer.files); + } + } + }); - if (rotateAllLeft) { - rotateAllLeft.addEventListener('click', function () { - for (let i = 0; i < pageState.rotations.length; i++) { - pageState.rotations[i] = pageState.rotations[i] + 90; - } - updateAllRotationDisplays(); - }); - } + fileInput.addEventListener('click', function () { + fileInput.value = ''; + }); + } - if (rotateAllRight) { - rotateAllRight.addEventListener('click', function () { - for (let i = 0; i < pageState.rotations.length; i++) { - pageState.rotations[i] = pageState.rotations[i] - 90; - } - updateAllRotationDisplays(); - }); - } - - if (batchDecrement && batchAngleInput) { - batchDecrement.addEventListener('click', function () { - const current = parseInt(batchAngleInput.value) || 0; - batchAngleInput.value = (current - 1).toString(); - }); - } - - if (batchIncrement && batchAngleInput) { - batchIncrement.addEventListener('click', function () { - const current = parseInt(batchAngleInput.value) || 0; - batchAngleInput.value = (current + 1).toString(); - }); - } - - if (batchApply && batchAngleInput) { - batchApply.addEventListener('click', function () { - const angle = parseInt(batchAngleInput.value) || 0; - if (angle !== 0) { - for (let i = 0; i < pageState.rotations.length; i++) { - pageState.rotations[i] = pageState.rotations[i] + angle; - } - updateAllRotationDisplays(); - } - }); - } - - if (fileInput && dropZone) { - fileInput.addEventListener('change', function (e) { - handleFileSelect((e.target as HTMLInputElement).files); - }); - - dropZone.addEventListener('dragover', function (e) { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); - - dropZone.addEventListener('dragleave', function (e) { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', function (e) { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const files = e.dataTransfer?.files; - if (files && files.length > 0) { - const pdfFiles = Array.from(files).filter(function (f) { - return f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf'); - }); - if (pdfFiles.length > 0) { - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(pdfFiles[0]); - handleFileSelect(dataTransfer.files); - } - } - }); - - fileInput.addEventListener('click', function () { - fileInput.value = ''; - }); - } - - if (processBtn) { - processBtn.addEventListener('click', applyRotations); - } + if (processBtn) { + processBtn.addEventListener('click', applyRotations); + } }); diff --git a/src/js/logic/rtf-to-pdf-page.ts b/src/js/logic/rtf-to-pdf-page.ts new file mode 100644 index 0000000..443fc3e --- /dev/null +++ b/src/js/logic/rtf-to-pdf-page.ts @@ -0,0 +1,215 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + const convertToPdf = async () => { + try { + if (state.files.length === 0) { + showAlert('No Files', 'Please select at least one RTF file.'); + hideLoader(); + return; + } + + const converter = getLibreOfficeConverter(); + + // Initialize LibreOffice if not already done + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + + if (state.files.length === 1) { + const originalFile = state.files[0]; + + showLoader('Processing...'); + + const pdfBlob = await converter.convertToPdf(originalFile); + + const fileName = originalFile.name.replace(/\.rtf$/i, '') + '.pdf'; + + downloadFile(pdfBlob, fileName); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${originalFile.name} to PDF.`, + 'success', + () => resetState() + ); + } else { + showLoader('Converting multiple RTF files to PDF...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.rtf$/i, ''); + const pdfBuffer = await pdfBlob.arrayBuffer(); + zip.file(`${baseName}.pdf`, pdfBuffer); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + + downloadFile(zipBlob, 'rtf-converted.zip'); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${state.files.length} RTF file(s) to PDF.`, + 'success', + () => resetState() + ); + } + } catch (e: any) { + hideLoader(); + showAlert( + 'Error', + `An error occurred during conversion. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + state.files = [...state.files, ...Array.from(files)]; + updateUI(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const rtfFiles = Array.from(files).filter(f => f.name.toLowerCase().endsWith('.rtf') || f.type === 'text/rtf' || f.type === 'application/rtf'); + if (rtfFiles.length > 0) { + const dataTransfer = new DataTransfer(); + rtfFiles.forEach(f => dataTransfer.items.add(f)); + handleFileSelect(dataTransfer.files); + } + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + resetState(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } + + updateUI(); +}); diff --git a/src/js/logic/sanitize-pdf-page.ts b/src/js/logic/sanitize-pdf-page.ts index 09a6339..76ea679 100644 --- a/src/js/logic/sanitize-pdf-page.ts +++ b/src/js/logic/sanitize-pdf-page.ts @@ -1,747 +1,199 @@ import { showAlert } from '../ui.js'; import { downloadFile, formatBytes } from '../utils/helpers.js'; -import { PDFDocument, PDFName } from 'pdf-lib'; import { icons, createIcons } from 'lucide'; +import { SanitizePdfState } from '@/types'; +import { sanitizePdf } from '../utils/sanitize.js'; -interface PageState { - file: File | null; - pdfDoc: PDFDocument | null; -} - -const pageState: PageState = { - file: null, - pdfDoc: null, +const pageState: SanitizePdfState = { + file: null, + pdfDoc: null, }; -function removeMetadataFromDoc(pdfDoc: PDFDocument) { - const infoDict = (pdfDoc as any).getInfoDict(); - const allKeys = infoDict.keys(); - allKeys.forEach((key: any) => { - infoDict.delete(key); - }); - - pdfDoc.setTitle(''); - pdfDoc.setAuthor(''); - pdfDoc.setSubject(''); - pdfDoc.setKeywords([]); - pdfDoc.setCreator(''); - pdfDoc.setProducer(''); - - try { - const catalogDict = (pdfDoc.catalog as any).dict; - if (catalogDict.has(PDFName.of('Metadata'))) { - catalogDict.delete(PDFName.of('Metadata')); - } - } catch (e: any) { - console.warn('Could not remove XMP metadata:', e.message); - } - - try { - const context = pdfDoc.context; - if ((context as any).trailerInfo) { - delete (context as any).trailerInfo.ID; - } - } catch (e: any) { - console.warn('Could not remove document IDs:', e.message); - } - - try { - const catalogDict = (pdfDoc.catalog as any).dict; - if (catalogDict.has(PDFName.of('PieceInfo'))) { - catalogDict.delete(PDFName.of('PieceInfo')); - } - } catch (e: any) { - console.warn('Could not remove PieceInfo:', e.message); - } -} - -function removeAnnotationsFromDoc(pdfDoc: PDFDocument) { - const pages = pdfDoc.getPages(); - for (const page of pages) { - try { - page.node.delete(PDFName.of('Annots')); - } catch (e: any) { - console.warn('Could not remove annotations from page:', e.message); - } - } -} - -function flattenFormsInDoc(pdfDoc: PDFDocument) { - const form = pdfDoc.getForm(); - form.flatten(); -} - function resetState() { - pageState.file = null; - pageState.pdfDoc = null; + pageState.file = null; + pageState.pdfDoc = null; - const fileDisplayArea = document.getElementById('file-display-area'); - if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + const fileDisplayArea = document.getElementById('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; - const toolOptions = document.getElementById('tool-options'); - if (toolOptions) toolOptions.classList.add('hidden'); + const toolOptions = document.getElementById('tool-options'); + if (toolOptions) toolOptions.classList.add('hidden'); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - if (fileInput) fileInput.value = ''; + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; } async function updateUI() { - const fileDisplayArea = document.getElementById('file-display-area'); - const toolOptions = document.getElementById('tool-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const toolOptions = document.getElementById('tool-options'); - if (!fileDisplayArea) return; + if (!fileDisplayArea) return; - fileDisplayArea.innerHTML = ''; + fileDisplayArea.innerHTML = ''; - if (pageState.file) { - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + if (pageState.file) { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col overflow-hidden'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = pageState.file.name; + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = pageState.file.name; - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = formatBytes(pageState.file.size); + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(pageState.file.size); - infoContainer.append(nameSpan, metaSpan); + infoContainer.append(nameSpan, metaSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = function () { - resetState(); - }; + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = function () { + resetState(); + }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); - if (toolOptions) toolOptions.classList.remove('hidden'); - } else { - if (toolOptions) toolOptions.classList.add('hidden'); - } + if (toolOptions) toolOptions.classList.remove('hidden'); + } else { + if (toolOptions) toolOptions.classList.add('hidden'); + } } async function handleFileSelect(files: FileList | null) { - if (files && files.length > 0) { - const file = files[0]; - if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) { - pageState.file = file; - - // Load PDF for sanitization - try { - const arrayBuffer = await file.arrayBuffer(); - pageState.pdfDoc = await PDFDocument.load(arrayBuffer); - updateUI(); - } catch (e) { - console.error('Error loading PDF:', e); - showAlert('Error', 'Failed to load PDF file.'); - } - } + if (files && files.length > 0) { + const file = files[0]; + if ( + file.type === 'application/pdf' || + file.name.toLowerCase().endsWith('.pdf') + ) { + pageState.file = file; + updateUI(); } + } } -async function sanitizePdf() { - if (!pageState.pdfDoc) { - showAlert('Error', 'No PDF document loaded.'); - return; +async function runSanitize() { + if (!pageState.file) { + showAlert('Error', 'No PDF document loaded.'); + return; + } + + const loaderModal = document.getElementById('loader-modal'); + const loaderText = document.getElementById('loader-text'); + if (loaderModal) loaderModal.classList.remove('hidden'); + if (loaderText) loaderText.textContent = 'Sanitizing PDF...'; + + try { + const options = { + flattenForms: ( + document.getElementById('flatten-forms') as HTMLInputElement + ).checked, + removeMetadata: ( + document.getElementById('remove-metadata') as HTMLInputElement + ).checked, + removeAnnotations: ( + document.getElementById('remove-annotations') as HTMLInputElement + ).checked, + removeJavascript: ( + document.getElementById('remove-javascript') as HTMLInputElement + ).checked, + removeEmbeddedFiles: ( + document.getElementById('remove-embedded-files') as HTMLInputElement + ).checked, + removeLayers: ( + document.getElementById('remove-layers') as HTMLInputElement + ).checked, + removeLinks: (document.getElementById('remove-links') as HTMLInputElement) + .checked, + removeStructureTree: ( + document.getElementById('remove-structure-tree') as HTMLInputElement + ).checked, + removeMarkInfo: ( + document.getElementById('remove-markinfo') as HTMLInputElement + ).checked, + removeFonts: (document.getElementById('remove-fonts') as HTMLInputElement) + .checked, + }; + + const hasAnyOption = Object.values(options).some(Boolean); + if (!hasAnyOption) { + showAlert( + 'No Changes', + 'No items were selected for removal or none were found in the PDF.' + ); + if (loaderModal) loaderModal.classList.add('hidden'); + return; } - const loaderModal = document.getElementById('loader-modal'); - const loaderText = document.getElementById('loader-text'); - if (loaderModal) loaderModal.classList.remove('hidden'); - if (loaderText) loaderText.textContent = 'Sanitizing PDF...'; - - try { - const pdfDoc = pageState.pdfDoc; - - const shouldFlattenForms = (document.getElementById('flatten-forms') as HTMLInputElement).checked; - const shouldRemoveMetadata = (document.getElementById('remove-metadata') as HTMLInputElement).checked; - const shouldRemoveAnnotations = (document.getElementById('remove-annotations') as HTMLInputElement).checked; - const shouldRemoveJavascript = (document.getElementById('remove-javascript') as HTMLInputElement).checked; - const shouldRemoveEmbeddedFiles = (document.getElementById('remove-embedded-files') as HTMLInputElement).checked; - const shouldRemoveLayers = (document.getElementById('remove-layers') as HTMLInputElement).checked; - const shouldRemoveLinks = (document.getElementById('remove-links') as HTMLInputElement).checked; - const shouldRemoveStructureTree = (document.getElementById('remove-structure-tree') as HTMLInputElement).checked; - const shouldRemoveMarkInfo = (document.getElementById('remove-markinfo') as HTMLInputElement).checked; - const shouldRemoveFonts = (document.getElementById('remove-fonts') as HTMLInputElement).checked; - - let changesMade = false; - - if (shouldFlattenForms) { - try { - flattenFormsInDoc(pdfDoc); - changesMade = true; - } catch (e: any) { - console.warn(`Could not flatten forms: ${e.message}`); - try { - const catalogDict = (pdfDoc.catalog as any).dict; - if (catalogDict.has(PDFName.of('AcroForm'))) { - catalogDict.delete(PDFName.of('AcroForm')); - changesMade = true; - } - } catch (removeError: any) { - console.warn('Could not remove AcroForm:', removeError.message); - } - } - } - - if (shouldRemoveMetadata) { - removeMetadataFromDoc(pdfDoc); - changesMade = true; - } - - if (shouldRemoveAnnotations) { - removeAnnotationsFromDoc(pdfDoc); - changesMade = true; - } - - if (shouldRemoveJavascript) { - try { - if ((pdfDoc as any).javaScripts && (pdfDoc as any).javaScripts.length > 0) { - (pdfDoc as any).javaScripts = []; - changesMade = true; - } - - const catalogDict = (pdfDoc.catalog as any).dict; - - const namesRef = catalogDict.get(PDFName.of('Names')); - if (namesRef) { - try { - const namesDict = pdfDoc.context.lookup(namesRef) as any; - if (namesDict.has(PDFName.of('JavaScript'))) { - namesDict.delete(PDFName.of('JavaScript')); - changesMade = true; - } - } catch (e: any) { - console.warn('Could not access Names/JavaScript:', e.message); - } - } - - if (catalogDict.has(PDFName.of('OpenAction'))) { - catalogDict.delete(PDFName.of('OpenAction')); - changesMade = true; - } - - if (catalogDict.has(PDFName.of('AA'))) { - catalogDict.delete(PDFName.of('AA')); - changesMade = true; - } - - const pages = pdfDoc.getPages(); - for (const page of pages) { - try { - const pageDict = page.node; - - if (pageDict.has(PDFName.of('AA'))) { - pageDict.delete(PDFName.of('AA')); - changesMade = true; - } - - const annotRefs = pageDict.Annots()?.asArray() || []; - for (const annotRef of annotRefs) { - try { - const annot = pdfDoc.context.lookup(annotRef) as any; - - if (annot.has(PDFName.of('A'))) { - const actionRef = annot.get(PDFName.of('A')); - try { - const actionDict = pdfDoc.context.lookup(actionRef) as any; - const actionType = actionDict - .get(PDFName.of('S')) - ?.toString() - .substring(1); - - if (actionType === 'JavaScript') { - annot.delete(PDFName.of('A')); - changesMade = true; - } - } catch (e: any) { - console.warn('Could not read action:', e.message); - } - } - - if (annot.has(PDFName.of('AA'))) { - annot.delete(PDFName.of('AA')); - changesMade = true; - } - } catch (e: any) { - console.warn('Could not process annotation for JS:', e.message); - } - } - } catch (e: any) { - console.warn('Could not remove page actions:', e.message); - } - } - - try { - const acroFormRef = catalogDict.get(PDFName.of('AcroForm')); - if (acroFormRef) { - const acroFormDict = pdfDoc.context.lookup(acroFormRef) as any; - const fieldsRef = acroFormDict.get(PDFName.of('Fields')); - - if (fieldsRef) { - const fieldsArray = pdfDoc.context.lookup(fieldsRef) as any; - const fields = fieldsArray.asArray(); - - for (const fieldRef of fields) { - try { - const field = pdfDoc.context.lookup(fieldRef) as any; - - if (field.has(PDFName.of('A'))) { - field.delete(PDFName.of('A')); - changesMade = true; - } - - if (field.has(PDFName.of('AA'))) { - field.delete(PDFName.of('AA')); - changesMade = true; - } - } catch (e: any) { - console.warn('Could not process field for JS:', e.message); - } - } - } - } - } catch (e: any) { - console.warn('Could not process form fields for JS:', e.message); - } - } catch (e: any) { - console.warn(`Could not remove JavaScript: ${e.message}`); - } - } - - if (shouldRemoveEmbeddedFiles) { - try { - const catalogDict = (pdfDoc.catalog as any).dict; - - const namesRef = catalogDict.get(PDFName.of('Names')); - if (namesRef) { - try { - const namesDict = pdfDoc.context.lookup(namesRef) as any; - if (namesDict.has(PDFName.of('EmbeddedFiles'))) { - namesDict.delete(PDFName.of('EmbeddedFiles')); - changesMade = true; - } - } catch (e: any) { - console.warn('Could not access Names/EmbeddedFiles:', e.message); - } - } - - if (catalogDict.has(PDFName.of('EmbeddedFiles'))) { - catalogDict.delete(PDFName.of('EmbeddedFiles')); - changesMade = true; - } - - const pages = pdfDoc.getPages(); - for (const page of pages) { - try { - const annotRefs = page.node.Annots()?.asArray() || []; - const annotsToKeep = []; - - for (const ref of annotRefs) { - try { - const annot = pdfDoc.context.lookup(ref) as any; - const subtype = annot - .get(PDFName.of('Subtype')) - ?.toString() - .substring(1); - - if (subtype !== 'FileAttachment') { - annotsToKeep.push(ref); - } else { - changesMade = true; - } - } catch (e) { - annotsToKeep.push(ref); - } - } - - if (annotsToKeep.length !== annotRefs.length) { - if (annotsToKeep.length > 0) { - const newAnnotsArray = pdfDoc.context.obj(annotsToKeep); - page.node.set(PDFName.of('Annots'), newAnnotsArray); - } else { - page.node.delete(PDFName.of('Annots')); - } - } - } catch (pageError: any) { - console.warn( - `Could not process page for attachments: ${pageError.message}` - ); - } - } - - if ((pdfDoc as any).embeddedFiles && (pdfDoc as any).embeddedFiles.length > 0) { - (pdfDoc as any).embeddedFiles = []; - changesMade = true; - } - - if (catalogDict.has(PDFName.of('Collection'))) { - catalogDict.delete(PDFName.of('Collection')); - changesMade = true; - } - } catch (e: any) { - console.warn(`Could not remove embedded files: ${e.message}`); - } - } - - if (shouldRemoveLayers) { - try { - const catalogDict = (pdfDoc.catalog as any).dict; - - if (catalogDict.has(PDFName.of('OCProperties'))) { - catalogDict.delete(PDFName.of('OCProperties')); - changesMade = true; - } - - const pages = pdfDoc.getPages(); - for (const page of pages) { - try { - const pageDict = page.node; - - if (pageDict.has(PDFName.of('OCProperties'))) { - pageDict.delete(PDFName.of('OCProperties')); - changesMade = true; - } - - const resourcesRef = pageDict.get(PDFName.of('Resources')); - if (resourcesRef) { - try { - const resourcesDict = pdfDoc.context.lookup(resourcesRef) as any; - if (resourcesDict.has(PDFName.of('Properties'))) { - resourcesDict.delete(PDFName.of('Properties')); - changesMade = true; - } - } catch (e: any) { - console.warn('Could not access Resources:', e.message); - } - } - } catch (e: any) { - console.warn('Could not remove page layers:', e.message); - } - } - } catch (e: any) { - console.warn(`Could not remove layers: ${e.message}`); - } - } - - if (shouldRemoveLinks) { - try { - const pages = pdfDoc.getPages(); - - for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) { - try { - const page = pages[pageIndex]; - const pageDict = page.node; - - const annotsRef = pageDict.get(PDFName.of('Annots')); - if (!annotsRef) continue; - - const annotsArray = pdfDoc.context.lookup(annotsRef) as any; - const annotRefs = annotsArray.asArray(); - - if (annotRefs.length === 0) continue; - - const annotsToKeep = []; - let linksRemoved = 0; - - for (const ref of annotRefs) { - try { - const annot = pdfDoc.context.lookup(ref) as any; - const subtype = annot - .get(PDFName.of('Subtype')) - ?.toString() - .substring(1); - - let isLink = false; - - if (subtype === 'Link') { - isLink = true; - linksRemoved++; - } else { - const actionRef = annot.get(PDFName.of('A')); - if (actionRef) { - try { - const actionDict = pdfDoc.context.lookup(actionRef) as any; - const actionType = actionDict - .get(PDFName.of('S')) - ?.toString() - .substring(1); - - if ( - actionType === 'URI' || - actionType === 'Launch' || - actionType === 'GoTo' || - actionType === 'GoToR' - ) { - isLink = true; - linksRemoved++; - } - } catch (e: any) { - console.warn('Could not read action:', e.message); - } - } - - const dest = annot.get(PDFName.of('Dest')); - if (dest && !isLink) { - isLink = true; - linksRemoved++; - } - } - - if (!isLink) { - annotsToKeep.push(ref); - } - } catch (e: any) { - console.warn('Could not process annotation:', e.message); - annotsToKeep.push(ref); - } - } - - if (linksRemoved > 0) { - if (annotsToKeep.length > 0) { - const newAnnotsArray = pdfDoc.context.obj(annotsToKeep); - pageDict.set(PDFName.of('Annots'), newAnnotsArray); - } else { - pageDict.delete(PDFName.of('Annots')); - } - changesMade = true; - } - } catch (pageError: any) { - console.warn( - `Could not process page ${pageIndex + 1} for links: ${pageError.message}` - ); - } - } - - try { - const catalogDict = (pdfDoc.catalog as any).dict; - const namesRef = catalogDict.get(PDFName.of('Names')); - if (namesRef) { - try { - const namesDict = pdfDoc.context.lookup(namesRef) as any; - if (namesDict.has(PDFName.of('Dests'))) { - namesDict.delete(PDFName.of('Dests')); - changesMade = true; - } - } catch (e: any) { - console.warn('Could not access Names/Dests:', e.message); - } - } - - if (catalogDict.has(PDFName.of('Dests'))) { - catalogDict.delete(PDFName.of('Dests')); - changesMade = true; - } - } catch (e: any) { - console.warn('Could not remove named destinations:', e.message); - } - } catch (e: any) { - console.warn(`Could not remove links: ${e.message}`); - } - } - - if (shouldRemoveStructureTree) { - try { - const catalogDict = (pdfDoc.catalog as any).dict; - - if (catalogDict.has(PDFName.of('StructTreeRoot'))) { - catalogDict.delete(PDFName.of('StructTreeRoot')); - changesMade = true; - } - - const pages = pdfDoc.getPages(); - for (const page of pages) { - try { - const pageDict = page.node; - if (pageDict.has(PDFName.of('StructParents'))) { - pageDict.delete(PDFName.of('StructParents')); - changesMade = true; - } - } catch (e: any) { - console.warn('Could not remove page StructParents:', e.message); - } - } - - if (catalogDict.has(PDFName.of('ParentTree'))) { - catalogDict.delete(PDFName.of('ParentTree')); - changesMade = true; - } - } catch (e: any) { - console.warn(`Could not remove structure tree: ${e.message}`); - } - } - - if (shouldRemoveMarkInfo) { - try { - const catalogDict = (pdfDoc.catalog as any).dict; - - if (catalogDict.has(PDFName.of('MarkInfo'))) { - catalogDict.delete(PDFName.of('MarkInfo')); - changesMade = true; - } - - if (catalogDict.has(PDFName.of('Marked'))) { - catalogDict.delete(PDFName.of('Marked')); - changesMade = true; - } - } catch (e: any) { - console.warn(`Could not remove MarkInfo: ${e.message}`); - } - } - - if (shouldRemoveFonts) { - try { - const pages = pdfDoc.getPages(); - - for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) { - try { - const page = pages[pageIndex]; - const pageDict = page.node; - const resourcesRef = pageDict.get(PDFName.of('Resources')); - - if (resourcesRef) { - try { - const resourcesDict = pdfDoc.context.lookup(resourcesRef) as any; - - if (resourcesDict.has(PDFName.of('Font'))) { - const fontRef = resourcesDict.get(PDFName.of('Font')); - - try { - const fontDict = pdfDoc.context.lookup(fontRef) as any; - const fontKeys = fontDict.keys(); - - for (const fontKey of fontKeys) { - try { - const specificFontRef = fontDict.get(fontKey); - const specificFont = - pdfDoc.context.lookup(specificFontRef) as any; - - if (specificFont.has(PDFName.of('FontDescriptor'))) { - const descriptorRef = specificFont.get( - PDFName.of('FontDescriptor') - ); - const descriptor = - pdfDoc.context.lookup(descriptorRef) as any; - - const fontFileKeys = [ - 'FontFile', - 'FontFile2', - 'FontFile3', - ]; - for (const key of fontFileKeys) { - if (descriptor.has(PDFName.of(key))) { - descriptor.delete(PDFName.of(key)); - changesMade = true; - } - } - } - } catch (e: any) { - console.warn( - `Could not process font ${fontKey}:`, - e.message - ); - } - } - } catch (e: any) { - console.warn( - 'Could not access font dictionary:', - e.message - ); - } - } - } catch (e: any) { - console.warn( - 'Could not access Resources for fonts:', - e.message - ); - } - } - } catch (e: any) { - console.warn( - `Could not remove fonts from page ${pageIndex + 1}:`, - e.message - ); - } - } - - if ((pdfDoc as any).fonts && (pdfDoc as any).fonts.length > 0) { - (pdfDoc as any).fonts = []; - changesMade = true; - } - } catch (e: any) { - console.warn(`Could not remove fonts: ${e.message}`); - } - } - - if (!changesMade) { - showAlert( - 'No Changes', - 'No items were selected for removal or none were found in the PDF.' - ); - if (loaderModal) loaderModal.classList.add('hidden'); - return; - } - - const sanitizedPdfBytes = await pdfDoc.save(); - downloadFile( - new Blob([sanitizedPdfBytes as BlobPart], { type: 'application/pdf' }), - 'sanitized.pdf' - ); - showAlert('Success', 'PDF has been sanitized and downloaded.', 'success', () => { resetState(); }); - } catch (e: any) { - console.error('Sanitization Error:', e); - showAlert('Error', `An error occurred during sanitization: ${e.message}`); - } finally { - if (loaderModal) loaderModal.classList.add('hidden'); - } + const arrayBuffer = await pageState.file.arrayBuffer(); + const result = await sanitizePdf(new Uint8Array(arrayBuffer), options); + + downloadFile( + new Blob([new Uint8Array(result.bytes)], { type: 'application/pdf' }), + 'sanitized.pdf' + ); + showAlert( + 'Success', + 'PDF has been sanitized and downloaded.', + 'success', + () => { + resetState(); + } + ); + } catch (e: any) { + console.error('Sanitization Error:', e); + showAlert('Error', `An error occurred during sanitization: ${e.message}`); + } finally { + if (loaderModal) loaderModal.classList.add('hidden'); + } } document.addEventListener('DOMContentLoaded', function () { - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const processBtn = document.getElementById('process-btn'); - const backBtn = document.getElementById('back-to-tools'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const backBtn = document.getElementById('back-to-tools'); - if (backBtn) { - backBtn.addEventListener('click', function () { - window.location.href = import.meta.env.BASE_URL; - }); - } + if (backBtn) { + backBtn.addEventListener('click', function () { + window.location.href = import.meta.env.BASE_URL; + }); + } - if (fileInput && dropZone) { - fileInput.addEventListener('change', function (e) { - handleFileSelect((e.target as HTMLInputElement).files); - }); + if (fileInput && dropZone) { + fileInput.addEventListener('change', function (e) { + handleFileSelect((e.target as HTMLInputElement).files); + }); - dropZone.addEventListener('dragover', function (e) { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); + dropZone.addEventListener('dragover', function (e) { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); - dropZone.addEventListener('dragleave', function (e) { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - }); + dropZone.addEventListener('dragleave', function (e) { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); - dropZone.addEventListener('drop', function (e) { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - handleFileSelect(e.dataTransfer?.files); - }); + dropZone.addEventListener('drop', function (e) { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files); + }); - fileInput.addEventListener('click', function () { - fileInput.value = ''; - }); - } + fileInput.addEventListener('click', function () { + fileInput.value = ''; + }); + } - if (processBtn) { - processBtn.addEventListener('click', sanitizePdf); - } + if (processBtn) { + processBtn.addEventListener('click', runSanitize); + } }); diff --git a/src/js/logic/scanner-effect-page.ts b/src/js/logic/scanner-effect-page.ts new file mode 100644 index 0000000..a93bb8c --- /dev/null +++ b/src/js/logic/scanner-effect-page.ts @@ -0,0 +1,451 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + formatBytes, + readFileAsArrayBuffer, + getPDFDocument, +} from '../utils/helpers.js'; +import { createIcons, icons } from 'lucide'; +import { PDFDocument } from 'pdf-lib'; +import { applyScannerEffect } from '../utils/image-effects.js'; +import * as pdfjsLib from 'pdfjs-dist'; +import type { ScanSettings } from '../types/scanner-effect-type.js'; + +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); + +let files: File[] = []; +let cachedBaselineData: ImageData | null = null; +let cachedBaselineWidth = 0; +let cachedBaselineHeight = 0; +let pdfjsDoc: pdfjsLib.PDFDocumentProxy | null = null; + +function getSettings(): ScanSettings { + return { + grayscale: + (document.getElementById('setting-grayscale') as HTMLInputElement) + ?.checked ?? false, + border: + (document.getElementById('setting-border') as HTMLInputElement) + ?.checked ?? false, + rotate: parseFloat( + (document.getElementById('setting-rotate') as HTMLInputElement)?.value ?? + '0' + ), + rotateVariance: parseFloat( + (document.getElementById('setting-rotate-variance') as HTMLInputElement) + ?.value ?? '0' + ), + brightness: parseInt( + (document.getElementById('setting-brightness') as HTMLInputElement) + ?.value ?? '0' + ), + contrast: parseInt( + (document.getElementById('setting-contrast') as HTMLInputElement) + ?.value ?? '0' + ), + blur: parseFloat( + (document.getElementById('setting-blur') as HTMLInputElement)?.value ?? + '0' + ), + noise: parseInt( + (document.getElementById('setting-noise') as HTMLInputElement)?.value ?? + '10' + ), + yellowish: parseInt( + (document.getElementById('setting-yellowish') as HTMLInputElement) + ?.value ?? '0' + ), + resolution: parseInt( + (document.getElementById('setting-resolution') as HTMLInputElement) + ?.value ?? '150' + ), + }; +} + +const applyEffects = applyScannerEffect; + +function updatePreview(): void { + if (!cachedBaselineData) return; + + const previewCanvas = document.getElementById( + 'preview-canvas' + ) as HTMLCanvasElement; + if (!previewCanvas) return; + + const settings = getSettings(); + const baselineCopy = new ImageData( + new Uint8ClampedArray(cachedBaselineData.data), + cachedBaselineWidth, + cachedBaselineHeight + ); + + applyEffects(baselineCopy, previewCanvas, settings, settings.rotate); +} + +async function renderPreview(): Promise { + if (!pdfjsDoc) return; + + const page = await pdfjsDoc.getPage(1); + const viewport = page.getViewport({ scale: 1.0 }); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d')!; + canvas.width = viewport.width; + canvas.height = viewport.height; + + await page.render({ canvasContext: ctx, viewport, canvas }).promise; + + cachedBaselineData = ctx.getImageData(0, 0, canvas.width, canvas.height); + cachedBaselineWidth = canvas.width; + cachedBaselineHeight = canvas.height; + + updatePreview(); +} + +const updateUI = () => { + const fileDisplayArea = document.getElementById('file-display-area'); + const optionsPanel = document.getElementById('options-panel'); + + if (!fileDisplayArea || !optionsPanel) return; + + fileDisplayArea.innerHTML = ''; + + if (files.length > 0) { + optionsPanel.classList.remove('hidden'); + + files.forEach((file) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + files = []; + pdfjsDoc = null; + cachedBaselineData = null; + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + + readFileAsArrayBuffer(file) + .then((buffer: ArrayBuffer) => { + return getPDFDocument(buffer).promise; + }) + .then((pdf: pdfjsLib.PDFDocumentProxy) => { + metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`; + }) + .catch(() => { + metaSpan.textContent = formatBytes(file.size); + }); + }); + + createIcons({ icons }); + } else { + optionsPanel.classList.add('hidden'); + } +}; + +const resetState = () => { + files = []; + pdfjsDoc = null; + cachedBaselineData = null; + const fileInput = document.getElementById('file-input') as HTMLInputElement; + if (fileInput) fileInput.value = ''; + updateUI(); +}; + +async function processAllPages(): Promise { + if (files.length === 0) { + showAlert('No File', 'Please upload a PDF file first.'); + return; + } + + showLoader('Applying scanner effect...'); + + try { + const settings = getSettings(); + const pdfBytes = (await readFileAsArrayBuffer(files[0])) as ArrayBuffer; + const doc = await getPDFDocument({ data: pdfBytes }).promise; + const newPdfDoc = await PDFDocument.create(); + const dpiScale = settings.resolution / 72; + + for (let i = 1; i <= doc.numPages; i++) { + showLoader(`Processing page ${i} of ${doc.numPages}...`); + + const page = await doc.getPage(i); + const viewport = page.getViewport({ scale: dpiScale }); + const renderCanvas = document.createElement('canvas'); + const renderCtx = renderCanvas.getContext('2d')!; + renderCanvas.width = viewport.width; + renderCanvas.height = viewport.height; + + await page.render({ + canvasContext: renderCtx, + viewport, + canvas: renderCanvas, + }).promise; + + const baseData = renderCtx.getImageData( + 0, + 0, + renderCanvas.width, + renderCanvas.height + ); + const baselineCopy = new ImageData( + new Uint8ClampedArray(baseData.data), + baseData.width, + baseData.height + ); + + const outputCanvas = document.createElement('canvas'); + const pageRotation = + settings.rotate + + (settings.rotateVariance > 0 + ? (Math.random() - 0.5) * 2 * settings.rotateVariance + : 0); + + applyEffects( + baselineCopy, + outputCanvas, + settings, + pageRotation, + dpiScale + ); + + const jpegBlob = await new Promise((resolve) => + outputCanvas.toBlob(resolve, 'image/jpeg', 0.85) + ); + + if (jpegBlob) { + const jpegBytes = await jpegBlob.arrayBuffer(); + const jpegImage = await newPdfDoc.embedJpg(jpegBytes); + const newPage = newPdfDoc.addPage([ + outputCanvas.width, + outputCanvas.height, + ]); + newPage.drawImage(jpegImage, { + x: 0, + y: 0, + width: outputCanvas.width, + height: outputCanvas.height, + }); + } + } + + const resultBytes = await newPdfDoc.save(); + downloadFile( + new Blob([new Uint8Array(resultBytes)], { type: 'application/pdf' }), + 'scanned.pdf' + ); + showAlert( + 'Success', + 'Scanner effect applied successfully!', + 'success', + () => { + resetState(); + } + ); + } catch (e) { + console.error(e); + showAlert( + 'Error', + 'Failed to apply scanner effect. The file might be corrupted.' + ); + } finally { + hideLoader(); + } +} + +const sliderDefaults: { + id: string; + display: string; + suffix: string; + defaultValue: string; +}[] = [ + { + id: 'setting-rotate', + display: 'rotate-value', + suffix: '°', + defaultValue: '0', + }, + { + id: 'setting-rotate-variance', + display: 'rotate-variance-value', + suffix: '°', + defaultValue: '0', + }, + { + id: 'setting-brightness', + display: 'brightness-value', + suffix: '', + defaultValue: '0', + }, + { + id: 'setting-contrast', + display: 'contrast-value', + suffix: '', + defaultValue: '0', + }, + { + id: 'setting-blur', + display: 'blur-value', + suffix: 'px', + defaultValue: '0', + }, + { + id: 'setting-noise', + display: 'noise-value', + suffix: '', + defaultValue: '10', + }, + { + id: 'setting-yellowish', + display: 'yellowish-value', + suffix: '', + defaultValue: '0', + }, + { + id: 'setting-resolution', + display: 'resolution-value', + suffix: ' DPI', + defaultValue: '150', + }, +]; + +function resetSettings(): void { + sliderDefaults.forEach(({ id, display, suffix, defaultValue }) => { + const slider = document.getElementById(id) as HTMLInputElement; + const label = document.getElementById(display); + if (slider) slider.value = defaultValue; + if (label) label.textContent = defaultValue + suffix; + }); + + const grayscale = document.getElementById( + 'setting-grayscale' + ) as HTMLInputElement; + const border = document.getElementById('setting-border') as HTMLInputElement; + if (grayscale) grayscale.checked = false; + if (border) border.checked = false; + + updatePreview(); +} + +function setupSettingsListeners(): void { + sliderDefaults.forEach(({ id, display, suffix }) => { + const slider = document.getElementById(id) as HTMLInputElement; + const label = document.getElementById(display); + if (slider && label) { + slider.addEventListener('input', () => { + label.textContent = slider.value + suffix; + if (id !== 'setting-resolution') { + updatePreview(); + } + }); + } + }); + + const toggleIds = ['setting-grayscale', 'setting-border']; + toggleIds.forEach((id) => { + const toggle = document.getElementById(id) as HTMLInputElement; + if (toggle) { + toggle.addEventListener('change', updatePreview); + } + }); + + const resetBtn = document.getElementById('reset-settings-btn'); + if (resetBtn) { + resetBtn.addEventListener('click', resetSettings); + } +} + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const handleFileSelect = async (newFiles: FileList | null) => { + if (!newFiles || newFiles.length === 0) return; + const validFiles = Array.from(newFiles).filter( + (file) => file.type === 'application/pdf' + ); + + if (validFiles.length === 0) { + showAlert('Invalid File', 'Please upload a PDF file.'); + return; + } + + files = [validFiles[0]]; + updateUI(); + + showLoader('Loading preview...'); + try { + const buffer = await readFileAsArrayBuffer(validFiles[0]); + pdfjsDoc = await getPDFDocument({ data: buffer }).promise; + await renderPreview(); + } catch (e) { + console.error(e); + showAlert('Error', 'Failed to load PDF for preview.'); + } finally { + hideLoader(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (processBtn) { + processBtn.addEventListener('click', processAllPages); + } + + setupSettingsListeners(); +}); diff --git a/src/js/logic/split-pdf-page.ts b/src/js/logic/split-pdf-page.ts index 774b803..699c248 100644 --- a/src/js/logic/split-pdf-page.ts +++ b/src/js/logic/split-pdf-page.ts @@ -1,538 +1,656 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; import { createIcons, icons } from 'lucide'; import * as pdfjsLib from 'pdfjs-dist'; -import { downloadFile, getPDFDocument, readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js'; +import { + downloadFile, + getPDFDocument, + readFileAsArrayBuffer, + formatBytes, +} from '../utils/helpers.js'; import { state } from '../state.js'; -import { renderPagesProgressively, cleanupLazyRendering } from '../utils/render-utils.js'; +import { + renderPagesProgressively, + cleanupLazyRendering, +} from '../utils/render-utils.js'; +import { initPagePreview } from '../utils/page-preview.js'; +import { isCpdfAvailable } from '../utils/cpdf-helper.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; import JSZip from 'jszip'; import { PDFDocument as PDFLibDocument } from 'pdf-lib'; // @ts-ignore -pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); document.addEventListener('DOMContentLoaded', () => { - let visualSelectorRendered = false; + let visualSelectorRendered = false; - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const processBtn = document.getElementById('process-btn'); - const fileDisplayArea = document.getElementById('file-display-area'); - const splitOptions = document.getElementById('split-options'); - const backBtn = document.getElementById('back-to-tools'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const fileDisplayArea = document.getElementById('file-display-area'); + const splitOptions = document.getElementById('split-options'); + const backBtn = document.getElementById('back-to-tools'); - // Split Mode Elements - const splitModeSelect = document.getElementById('split-mode') as HTMLSelectElement; - const rangePanel = document.getElementById('range-panel'); - const visualPanel = document.getElementById('visual-select-panel'); - const evenOddPanel = document.getElementById('even-odd-panel'); - const zipOptionWrapper = document.getElementById('zip-option-wrapper'); - const allPagesPanel = document.getElementById('all-pages-panel'); - const bookmarksPanel = document.getElementById('bookmarks-panel'); - const nTimesPanel = document.getElementById('n-times-panel'); - const nTimesWarning = document.getElementById('n-times-warning'); + // Split Mode Elements + const splitModeSelect = document.getElementById( + 'split-mode' + ) as HTMLSelectElement; + const rangePanel = document.getElementById('range-panel'); + const visualPanel = document.getElementById('visual-select-panel'); + const evenOddPanel = document.getElementById('even-odd-panel'); + const zipOptionWrapper = document.getElementById('zip-option-wrapper'); + const allPagesPanel = document.getElementById('all-pages-panel'); + const bookmarksPanel = document.getElementById('bookmarks-panel'); + const nTimesPanel = document.getElementById('n-times-panel'); + const nTimesWarning = document.getElementById('n-times-warning'); - if (backBtn) { - backBtn.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (state.files.length > 0) { + const file = state.files[0]; + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Placeholder + + infoContainer.append(nameSpan, metaSpan); + + // Add remove button + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); + + // Load PDF Document + try { + if (!state.pdfDoc) { + showLoader('Loading PDF...'); + const arrayBuffer = (await readFileAsArrayBuffer( + file + )) as ArrayBuffer; + state.pdfDoc = await PDFLibDocument.load(arrayBuffer); + hideLoader(); + } + // Update page count + metaSpan.textContent = `${formatBytes(file.size)} • ${state.pdfDoc.getPageCount()} pages`; + } catch (error) { + console.error('Error loading PDF:', error); + showAlert('Error', 'Failed to load PDF file.'); + state.files = []; + updateUI(); + return; + } + } + + if (splitOptions) splitOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (splitOptions) splitOptions.classList.add('hidden'); + state.pdfDoc = null; + } + }; + + const renderVisualSelector = async () => { + if (visualSelectorRendered) return; + + const container = document.getElementById('page-selector-grid'); + if (!container) return; + + visualSelectorRendered = true; + container.textContent = ''; + + // Cleanup any previous lazy loading observers + cleanupLazyRendering(); + + showLoader('Rendering page previews...'); + + try { + if (!state.pdfDoc) { + // If pdfDoc is not loaded yet (e.g. page refresh), try to load it from the first file + if (state.files.length > 0) { + const file = state.files[0]; + const arrayBuffer = (await readFileAsArrayBuffer( + file + )) as ArrayBuffer; + state.pdfDoc = await PDFLibDocument.load(arrayBuffer); + } else { + throw new Error('No PDF document loaded'); + } + } + + const pdfData = await state.pdfDoc.save(); + const pdf = await getPDFDocument({ data: pdfData }).promise; + + // Function to create wrapper element for each page + const createWrapper = (canvas: HTMLCanvasElement, pageNumber: number) => { + const wrapper = document.createElement('div'); + wrapper.className = + 'page-thumbnail-wrapper p-2 border-2 border-gray-600 rounded-lg cursor-pointer hover:border-indigo-500 bg-gray-700 transition-colors relative group flex flex-col items-center gap-1'; + wrapper.dataset.pageIndex = (pageNumber - 1).toString(); + wrapper.dataset.pageNumber = pageNumber.toString(); + + const imgContainer = document.createElement('div'); + imgContainer.className = 'relative'; + + const img = document.createElement('img'); + img.src = canvas.toDataURL(); + img.className = 'rounded-md shadow-md max-w-full h-auto'; + + const pageNumDiv = document.createElement('div'); + pageNumDiv.className = + 'absolute top-1 left-1 bg-indigo-600 text-white text-xs px-2 py-1 rounded-md font-semibold shadow-lg z-10 pointer-events-none'; + pageNumDiv.textContent = pageNumber.toString(); + + imgContainer.append(img, pageNumDiv); + wrapper.appendChild(imgContainer); + + const handleSelection = (e: any) => { + e.preventDefault(); + e.stopPropagation(); + + const isSelected = wrapper.classList.contains('selected'); + + if (isSelected) { + wrapper.classList.remove('selected', 'border-indigo-500'); + wrapper.classList.add('border-gray-600'); + } else { + wrapper.classList.add('selected', 'border-indigo-500'); + wrapper.classList.remove('border-gray-600'); + } + }; + + wrapper.addEventListener('click', handleSelection); + wrapper.addEventListener('touchend', handleSelection); + + wrapper.addEventListener('touchstart', (e) => { + e.preventDefault(); }); + + return wrapper; + }; + + // Render pages progressively with lazy loading + await renderPagesProgressively(pdf, container, createWrapper, { + batchSize: 8, + useLazyLoading: true, + lazyLoadMargin: '400px', + onProgress: (current, total) => { + showLoader(`Rendering page previews: ${current}/${total}`); + }, + onBatchComplete: () => { + createIcons({ icons }); + }, + }); + + initPagePreview(container, pdf); + } catch (error) { + console.error('Error rendering visual selector:', error); + showAlert('Error', 'Failed to render page previews.'); + // Reset the flag on error so the user can try again. + visualSelectorRendered = false; + } finally { + hideLoader(); + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + + // Reset visual selection + document + .querySelectorAll('.page-thumbnail-wrapper.selected') + .forEach((el) => { + el.classList.remove('selected', 'border-indigo-500'); + el.classList.add('border-transparent'); + }); + visualSelectorRendered = false; + const container = document.getElementById('page-selector-grid'); + if (container) container.innerHTML = ''; + + // Reset inputs + const pageRangeInput = document.getElementById( + 'page-range' + ) as HTMLInputElement; + if (pageRangeInput) pageRangeInput.value = ''; + + const nValueInput = document.getElementById( + 'split-n-value' + ) as HTMLInputElement; + if (nValueInput) nValueInput.value = '5'; + + // Reset radio buttons to default (range) + const rangeRadio = document.querySelector( + 'input[name="split-mode"][value="range"]' + ) as HTMLInputElement; + if (rangeRadio) { + rangeRadio.checked = true; + rangeRadio.dispatchEvent(new Event('change')); } - const updateUI = async () => { - if (state.files.length > 0) { - const file = state.files[0]; - if (fileDisplayArea) { - fileDisplayArea.innerHTML = ''; - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + // Reset split mode select + if (splitModeSelect) { + splitModeSelect.value = 'range'; + splitModeSelect.dispatchEvent(new Event('change')); + } - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col overflow-hidden'; + updateUI(); + }; - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = file.name; + const split = async () => { + const splitMode = splitModeSelect.value; + const downloadAsZip = + (document.getElementById('download-as-zip') as HTMLInputElement) + ?.checked || false; - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Placeholder + showLoader('Splitting PDF...'); - infoContainer.append(nameSpan, metaSpan); + try { + if (!state.pdfDoc) throw new Error('No PDF document loaded.'); - // Add remove button - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - state.files = []; - state.pdfDoc = null; - updateUI(); - }; + const totalPages = state.pdfDoc.getPageCount(); + let indicesToExtract: number[] = []; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); + switch (splitMode) { + case 'range': + const pageRangeInput = ( + document.getElementById('page-range') as HTMLInputElement + ).value; + if (!pageRangeInput) throw new Error('Choose a valid page range.'); + const ranges = pageRangeInput.split(','); - // Load PDF Document - try { - if (!state.pdfDoc) { - showLoader('Loading PDF...'); - const arrayBuffer = await readFileAsArrayBuffer(file) as ArrayBuffer; - state.pdfDoc = await PDFLibDocument.load(arrayBuffer); - hideLoader(); - } - // Update page count - metaSpan.textContent = `${formatBytes(file.size)} • ${state.pdfDoc.getPageCount()} pages`; - } catch (error) { - console.error('Error loading PDF:', error); - showAlert('Error', 'Failed to load PDF file.'); - state.files = []; - updateUI(); - return; - } + const rangeGroups: number[][] = []; + for (const range of ranges) { + const trimmedRange = range.trim(); + if (!trimmedRange) continue; + + const groupIndices: number[] = []; + if (trimmedRange.includes('-')) { + const [start, end] = trimmedRange.split('-').map(Number); + if ( + isNaN(start) || + isNaN(end) || + start < 1 || + end > totalPages || + start > end + ) + continue; + for (let i = start; i <= end; i++) groupIndices.push(i - 1); + } else { + const pageNum = Number(trimmedRange); + if (isNaN(pageNum) || pageNum < 1 || pageNum > totalPages) + continue; + groupIndices.push(pageNum - 1); } - if (splitOptions) splitOptions.classList.remove('hidden'); + if (groupIndices.length > 0) { + rangeGroups.push(groupIndices); + indicesToExtract.push(...groupIndices); + } + } - } else { - if (fileDisplayArea) fileDisplayArea.innerHTML = ''; - if (splitOptions) splitOptions.classList.add('hidden'); - state.pdfDoc = null; - } - }; + if (rangeGroups.length > 1) { + showLoader('Creating separate PDFs for each range...'); + const zip = new JSZip(); - const renderVisualSelector = async () => { - if (visualSelectorRendered) return; + for (let i = 0; i < rangeGroups.length; i++) { + const group = rangeGroups[i]; + const newPdf = await PDFLibDocument.create(); + const copiedPages = await newPdf.copyPages(state.pdfDoc, group); + copiedPages.forEach((page: any) => newPdf.addPage(page)); + const pdfBytes = await newPdf.save(); - const container = document.getElementById('page-selector-grid'); - if (!container) return; - - visualSelectorRendered = true; - container.textContent = ''; - - // Cleanup any previous lazy loading observers - cleanupLazyRendering(); - - showLoader('Rendering page previews...'); - - try { - if (!state.pdfDoc) { - // If pdfDoc is not loaded yet (e.g. page refresh), try to load it from the first file - if (state.files.length > 0) { - const file = state.files[0]; - const arrayBuffer = await readFileAsArrayBuffer(file) as ArrayBuffer; - state.pdfDoc = await PDFLibDocument.load(arrayBuffer); - } else { - throw new Error('No PDF document loaded'); - } + const minPage = Math.min(...group) + 1; + const maxPage = Math.max(...group) + 1; + const filename = + minPage === maxPage + ? `page-${minPage}.pdf` + : `pages-${minPage}-${maxPage}.pdf`; + zip.file(filename, pdfBytes); } - const pdfData = await state.pdfDoc.save(); - const pdf = await getPDFDocument({ data: pdfData }).promise; - - // Function to create wrapper element for each page - const createWrapper = (canvas: HTMLCanvasElement, pageNumber: number) => { - const wrapper = document.createElement('div'); - wrapper.className = - 'page-thumbnail-wrapper p-1 border-2 border-transparent rounded-lg cursor-pointer hover:border-indigo-500 relative'; - wrapper.dataset.pageIndex = (pageNumber - 1).toString(); - - const img = document.createElement('img'); - img.src = canvas.toDataURL(); - img.className = 'rounded-md w-full h-auto'; - - const p = document.createElement('p'); - p.className = 'text-center text-xs mt-1 text-gray-300'; - p.textContent = `Page ${pageNumber}`; - - wrapper.append(img, p); - - const handleSelection = (e: any) => { - e.preventDefault(); - e.stopPropagation(); - - const isSelected = wrapper.classList.contains('selected'); - - if (isSelected) { - wrapper.classList.remove('selected', 'border-indigo-500'); - wrapper.classList.add('border-transparent'); - } else { - wrapper.classList.add('selected', 'border-indigo-500'); - wrapper.classList.remove('border-transparent'); - } - }; - - wrapper.addEventListener('click', handleSelection); - wrapper.addEventListener('touchend', handleSelection); - - wrapper.addEventListener('touchstart', (e) => { - e.preventDefault(); - }); - - return wrapper; - }; - - // Render pages progressively with lazy loading - await renderPagesProgressively( - pdf, - container, - createWrapper, - { - batchSize: 8, - useLazyLoading: true, - lazyLoadMargin: '400px', - onProgress: (current, total) => { - showLoader(`Rendering page previews: ${current}/${total}`); - }, - onBatchComplete: () => { - createIcons({ icons }); - } - } - ); - } catch (error) { - console.error('Error rendering visual selector:', error); - showAlert('Error', 'Failed to render page previews.'); - // Reset the flag on error so the user can try again. - visualSelectorRendered = false; - } finally { + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'split-pages.zip'); hideLoader(); + showAlert( + 'Success', + `PDF split into ${rangeGroups.length} files successfully!`, + 'success', + () => { + resetState(); + } + ); + return; + } + break; + + case 'even-odd': + const choiceElement = document.querySelector( + 'input[name="even-odd-choice"]:checked' + ) as HTMLInputElement; + if (!choiceElement) + throw new Error('Please select even or odd pages.'); + const choice = choiceElement.value; + for (let i = 0; i < totalPages; i++) { + if (choice === 'even' && (i + 1) % 2 === 0) + indicesToExtract.push(i); + if (choice === 'odd' && (i + 1) % 2 !== 0) indicesToExtract.push(i); + } + break; + case 'all': + indicesToExtract = Array.from({ length: totalPages }, (_, i) => i); + break; + case 'visual': + indicesToExtract = Array.from( + document.querySelectorAll('.page-thumbnail-wrapper.selected') + ).map((el) => parseInt((el as HTMLElement).dataset.pageIndex || '0')); + break; + case 'bookmarks': + // Check if CPDF is configured + if (!isCpdfAvailable()) { + showWasmRequiredDialog('cpdf'); + hideLoader(); + return; + } + const { getCpdf } = await import('../utils/cpdf-helper.js'); + const cpdf = await getCpdf(); + const pdfBytes = await state.pdfDoc.save(); + const pdf = cpdf.fromMemory(new Uint8Array(pdfBytes), ''); + + cpdf.startGetBookmarkInfo(pdf); + const bookmarkCount = cpdf.numberBookmarks(); + const bookmarkLevel = ( + document.getElementById('bookmark-level') as HTMLSelectElement + )?.value; + + const splitPages: number[] = []; + for (let i = 0; i < bookmarkCount; i++) { + const level = cpdf.getBookmarkLevel(i); + const page = cpdf.getBookmarkPage(pdf, i); + + if (bookmarkLevel === 'all' || level === parseInt(bookmarkLevel)) { + if (page > 1 && !splitPages.includes(page - 1)) { + splitPages.push(page - 1); // Convert to 0-based index + } + } + } + cpdf.endGetBookmarkInfo(); + cpdf.deletePdf(pdf); + + if (splitPages.length === 0) { + throw new Error('No bookmarks found at the selected level.'); + } + + splitPages.sort((a, b) => a - b); + const zip = new JSZip(); + + for (let i = 0; i < splitPages.length; i++) { + const startPage = i === 0 ? 0 : splitPages[i]; + const endPage = + i < splitPages.length - 1 + ? splitPages[i + 1] - 1 + : totalPages - 1; + + const newPdf = await PDFLibDocument.create(); + const pageIndices = Array.from( + { length: endPage - startPage + 1 }, + (_, idx) => startPage + idx + ); + const copiedPages = await newPdf.copyPages( + state.pdfDoc, + pageIndices + ); + copiedPages.forEach((page: any) => newPdf.addPage(page)); + const pdfBytes2 = await newPdf.save(); + zip.file(`split-${i + 1}.pdf`, pdfBytes2); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'split-by-bookmarks.zip'); + hideLoader(); + showAlert('Success', 'PDF split successfully!', 'success', () => { + resetState(); + }); + return; + + case 'n-times': + const nValue = parseInt( + (document.getElementById('split-n-value') as HTMLInputElement) + ?.value || '5' + ); + if (nValue < 1) throw new Error('N must be at least 1.'); + + const zip2 = new JSZip(); + const numSplits = Math.ceil(totalPages / nValue); + + for (let i = 0; i < numSplits; i++) { + const startPage = i * nValue; + const endPage = Math.min(startPage + nValue - 1, totalPages - 1); + const pageIndices = Array.from( + { length: endPage - startPage + 1 }, + (_, idx) => startPage + idx + ); + + const newPdf = await PDFLibDocument.create(); + const copiedPages = await newPdf.copyPages( + state.pdfDoc, + pageIndices + ); + copiedPages.forEach((page: any) => newPdf.addPage(page)); + const pdfBytes3 = await newPdf.save(); + zip2.file(`split-${i + 1}.pdf`, pdfBytes3); + } + + const zipBlob2 = await zip2.generateAsync({ type: 'blob' }); + downloadFile(zipBlob2, 'split-n-times.zip'); + hideLoader(); + showAlert('Success', 'PDF split successfully!', 'success', () => { + resetState(); + }); + return; + } + + const uniqueIndices = [...new Set(indicesToExtract)]; + if ( + uniqueIndices.length === 0 && + splitMode !== 'bookmarks' && + splitMode !== 'n-times' + ) { + throw new Error('No pages were selected for splitting.'); + } + + if ( + splitMode === 'all' || + (['range', 'visual'].includes(splitMode) && downloadAsZip) + ) { + showLoader('Creating ZIP file...'); + const zip = new JSZip(); + for (const index of uniqueIndices) { + const newPdf = await PDFLibDocument.create(); + const [copiedPage] = await newPdf.copyPages(state.pdfDoc, [ + index as number, + ]); + newPdf.addPage(copiedPage); + const pdfBytes = await newPdf.save(); + // @ts-ignore + zip.file(`page-${index + 1}.pdf`, pdfBytes); } - }; + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'split-pages.zip'); + } else { + const newPdf = await PDFLibDocument.create(); + const copiedPages = await newPdf.copyPages( + state.pdfDoc, + uniqueIndices as number[] + ); + copiedPages.forEach((page: any) => newPdf.addPage(page)); + const pdfBytes = await newPdf.save(); + downloadFile( + new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), + 'split-document.pdf' + ); + } - const resetState = () => { - state.files = []; - state.pdfDoc = null; + if (splitMode === 'visual') { + visualSelectorRendered = false; + } - // Reset visual selection - document.querySelectorAll('.page-thumbnail-wrapper.selected').forEach(el => { - el.classList.remove('selected', 'border-indigo-500'); - el.classList.add('border-transparent'); - }); + showAlert('Success', 'PDF split successfully!', 'success', () => { + resetState(); + }); + } catch (e: any) { + console.error(e); + showAlert( + 'Error', + e.message || 'Failed to split PDF. Please check your selection.' + ); + } finally { + hideLoader(); + } + }; + + const handleFileSelect = async (files: FileList | null) => { + if (files && files.length > 0) { + // Split tool only supports one file at a time + state.files = [files[0]]; + await updateUI(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files) { + const pdfFiles = Array.from(files).filter( + (f) => + f.type === 'application/pdf' || + f.name.toLowerCase().endsWith('.pdf') + ); + if (pdfFiles.length > 0) { + // Take only the first PDF + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(pdfFiles[0]); + handleFileSelect(dataTransfer.files); + } + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (splitModeSelect) { + splitModeSelect.addEventListener('change', (e) => { + const mode = (e.target as HTMLSelectElement).value; + + if (mode !== 'visual') { visualSelectorRendered = false; const container = document.getElementById('page-selector-grid'); if (container) container.innerHTML = ''; + } - // Reset inputs - const pageRangeInput = document.getElementById('page-range') as HTMLInputElement; - if (pageRangeInput) pageRangeInput.value = ''; + rangePanel?.classList.add('hidden'); + visualPanel?.classList.add('hidden'); + evenOddPanel?.classList.add('hidden'); + allPagesPanel?.classList.add('hidden'); + bookmarksPanel?.classList.add('hidden'); + nTimesPanel?.classList.add('hidden'); + zipOptionWrapper?.classList.add('hidden'); + if (nTimesWarning) nTimesWarning.classList.add('hidden'); - const nValueInput = document.getElementById('split-n-value') as HTMLInputElement; - if (nValueInput) nValueInput.value = '5'; + if (mode === 'range') { + rangePanel?.classList.remove('hidden'); + zipOptionWrapper?.classList.remove('hidden'); + } else if (mode === 'visual') { + visualPanel?.classList.remove('hidden'); + zipOptionWrapper?.classList.remove('hidden'); + renderVisualSelector(); + } else if (mode === 'even-odd') { + evenOddPanel?.classList.remove('hidden'); + } else if (mode === 'all') { + allPagesPanel?.classList.remove('hidden'); + } else if (mode === 'bookmarks') { + bookmarksPanel?.classList.remove('hidden'); + zipOptionWrapper?.classList.remove('hidden'); + } else if (mode === 'n-times') { + nTimesPanel?.classList.remove('hidden'); + zipOptionWrapper?.classList.remove('hidden'); - // Reset radio buttons to default (range) - const rangeRadio = document.querySelector('input[name="split-mode"][value="range"]') as HTMLInputElement; - if (rangeRadio) { - rangeRadio.checked = true; - rangeRadio.dispatchEvent(new Event('change')); - } - - // Reset split mode select - if (splitModeSelect) { - splitModeSelect.value = 'range'; - splitModeSelect.dispatchEvent(new Event('change')); - } - - updateUI(); - }; - - const split = async () => { - const splitMode = splitModeSelect.value; - const downloadAsZip = - (document.getElementById('download-as-zip') as HTMLInputElement)?.checked || - false; - - showLoader('Splitting PDF...'); - - try { - if (!state.pdfDoc) throw new Error('No PDF document loaded.'); - - const totalPages = state.pdfDoc.getPageCount(); - let indicesToExtract: number[] = []; - - switch (splitMode) { - case 'range': - const pageRangeInput = (document.getElementById('page-range') as HTMLInputElement).value; - if (!pageRangeInput) throw new Error('Choose a valid page range.'); - const ranges = pageRangeInput.split(','); - for (const range of ranges) { - const trimmedRange = range.trim(); - if (trimmedRange.includes('-')) { - const [start, end] = trimmedRange.split('-').map(Number); - if ( - isNaN(start) || - isNaN(end) || - start < 1 || - end > totalPages || - start > end - ) - continue; - for (let i = start; i <= end; i++) indicesToExtract.push(i - 1); - } else { - const pageNum = Number(trimmedRange); - if (isNaN(pageNum) || pageNum < 1 || pageNum > totalPages) continue; - indicesToExtract.push(pageNum - 1); - } - } - break; - - case 'even-odd': - const choiceElement = document.querySelector( - 'input[name="even-odd-choice"]:checked' - ) as HTMLInputElement; - if (!choiceElement) throw new Error('Please select even or odd pages.'); - const choice = choiceElement.value; - for (let i = 0; i < totalPages; i++) { - if (choice === 'even' && (i + 1) % 2 === 0) indicesToExtract.push(i); - if (choice === 'odd' && (i + 1) % 2 !== 0) indicesToExtract.push(i); - } - break; - case 'all': - indicesToExtract = Array.from({ length: totalPages }, (_, i) => i); - break; - case 'visual': - indicesToExtract = Array.from( - document.querySelectorAll('.page-thumbnail-wrapper.selected') - ) - .map((el) => parseInt((el as HTMLElement).dataset.pageIndex || '0')); - break; - case 'bookmarks': - const { getCpdf } = await import('../utils/cpdf-helper.js'); - const cpdf = await getCpdf(); - const pdfBytes = await state.pdfDoc.save(); - const pdf = cpdf.fromMemory(new Uint8Array(pdfBytes), ''); - - cpdf.startGetBookmarkInfo(pdf); - const bookmarkCount = cpdf.numberBookmarks(); - const bookmarkLevel = (document.getElementById('bookmark-level') as HTMLSelectElement)?.value; - - const splitPages: number[] = []; - for (let i = 0; i < bookmarkCount; i++) { - const level = cpdf.getBookmarkLevel(i); - const page = cpdf.getBookmarkPage(pdf, i); - - if (bookmarkLevel === 'all' || level === parseInt(bookmarkLevel)) { - if (page > 1 && !splitPages.includes(page - 1)) { - splitPages.push(page - 1); // Convert to 0-based index - } - } - } - cpdf.endGetBookmarkInfo(); - cpdf.deletePdf(pdf); - - if (splitPages.length === 0) { - throw new Error('No bookmarks found at the selected level.'); - } - - splitPages.sort((a, b) => a - b); - const zip = new JSZip(); - - for (let i = 0; i < splitPages.length; i++) { - const startPage = i === 0 ? 0 : splitPages[i]; - const endPage = i < splitPages.length - 1 ? splitPages[i + 1] - 1 : totalPages - 1; - - const newPdf = await PDFLibDocument.create(); - const pageIndices = Array.from({ length: endPage - startPage + 1 }, (_, idx) => startPage + idx); - const copiedPages = await newPdf.copyPages(state.pdfDoc, pageIndices); - copiedPages.forEach((page: any) => newPdf.addPage(page)); - const pdfBytes2 = await newPdf.save(); - zip.file(`split-${i + 1}.pdf`, pdfBytes2); - } - - const zipBlob = await zip.generateAsync({ type: 'blob' }); - downloadFile(zipBlob, 'split-by-bookmarks.zip'); - hideLoader(); - showAlert('Success', 'PDF split successfully!', 'success', () => { - resetState(); - }); - return; - - case 'n-times': - const nValue = parseInt((document.getElementById('split-n-value') as HTMLInputElement)?.value || '5'); - if (nValue < 1) throw new Error('N must be at least 1.'); - - const zip2 = new JSZip(); - const numSplits = Math.ceil(totalPages / nValue); - - for (let i = 0; i < numSplits; i++) { - const startPage = i * nValue; - const endPage = Math.min(startPage + nValue - 1, totalPages - 1); - const pageIndices = Array.from({ length: endPage - startPage + 1 }, (_, idx) => startPage + idx); - - const newPdf = await PDFLibDocument.create(); - const copiedPages = await newPdf.copyPages(state.pdfDoc, pageIndices); - copiedPages.forEach((page: any) => newPdf.addPage(page)); - const pdfBytes3 = await newPdf.save(); - zip2.file(`split-${i + 1}.pdf`, pdfBytes3); - } - - const zipBlob2 = await zip2.generateAsync({ type: 'blob' }); - downloadFile(zipBlob2, 'split-n-times.zip'); - hideLoader(); - showAlert('Success', 'PDF split successfully!', 'success', () => { - resetState(); - }); - return; + const updateWarning = () => { + if (!state.pdfDoc) return; + const totalPages = state.pdfDoc.getPageCount(); + const nValue = parseInt( + (document.getElementById('split-n-value') as HTMLInputElement) + ?.value || '5' + ); + const remainder = totalPages % nValue; + if (remainder !== 0 && nTimesWarning) { + nTimesWarning.classList.remove('hidden'); + const warningText = document.getElementById('n-times-warning-text'); + if (warningText) { + warningText.textContent = `The PDF has ${totalPages} pages, which is not evenly divisible by ${nValue}. The last PDF will contain ${remainder} page(s).`; } + } else if (nTimesWarning) { + nTimesWarning.classList.add('hidden'); + } + }; - const uniqueIndices = [...new Set(indicesToExtract)]; - if (uniqueIndices.length === 0 && splitMode !== 'bookmarks' && splitMode !== 'n-times') { - throw new Error('No pages were selected for splitting.'); - } + updateWarning(); + document + .getElementById('split-n-value') + ?.addEventListener('input', updateWarning); + } + }); + } - if ( - splitMode === 'all' || - (['range', 'visual'].includes(splitMode) && downloadAsZip) - ) { - showLoader('Creating ZIP file...'); - const zip = new JSZip(); - for (const index of uniqueIndices) { - const newPdf = await PDFLibDocument.create(); - const [copiedPage] = await newPdf.copyPages(state.pdfDoc, [ - index as number, - ]); - newPdf.addPage(copiedPage); - const pdfBytes = await newPdf.save(); - // @ts-ignore - zip.file(`page-${index + 1}.pdf`, pdfBytes); - } - const zipBlob = await zip.generateAsync({ type: 'blob' }); - downloadFile(zipBlob, 'split-pages.zip'); - } else { - const newPdf = await PDFLibDocument.create(); - const copiedPages = await newPdf.copyPages( - state.pdfDoc, - uniqueIndices as number[] - ); - copiedPages.forEach((page: any) => newPdf.addPage(page)); - const pdfBytes = await newPdf.save(); - downloadFile( - new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), - 'split-document.pdf' - ); - } - - if (splitMode === 'visual') { - visualSelectorRendered = false; - } - - showAlert('Success', 'PDF split successfully!', 'success', () => { - resetState(); - }); - - } catch (e: any) { - console.error(e); - showAlert( - 'Error', - e.message || 'Failed to split PDF. Please check your selection.' - ); - } finally { - hideLoader(); - } - }; - - const handleFileSelect = async (files: FileList | null) => { - if (files && files.length > 0) { - // Split tool only supports one file at a time - state.files = [files[0]]; - await updateUI(); - } - }; - - if (fileInput && dropZone) { - fileInput.addEventListener('change', (e) => { - handleFileSelect((e.target as HTMLInputElement).files); - }); - - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); - - dropZone.addEventListener('dragleave', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const files = e.dataTransfer?.files; - if (files) { - const pdfFiles = Array.from(files).filter(f => f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf')); - if (pdfFiles.length > 0) { - // Take only the first PDF - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(pdfFiles[0]); - handleFileSelect(dataTransfer.files); - } - } - }); - - // Clear value on click to allow re-selecting the same file - fileInput.addEventListener('click', () => { - fileInput.value = ''; - }); - } - - if (splitModeSelect) { - splitModeSelect.addEventListener('change', (e) => { - const mode = (e.target as HTMLSelectElement).value; - - if (mode !== 'visual') { - visualSelectorRendered = false; - const container = document.getElementById('page-selector-grid'); - if (container) container.innerHTML = ''; - } - - rangePanel?.classList.add('hidden'); - visualPanel?.classList.add('hidden'); - evenOddPanel?.classList.add('hidden'); - allPagesPanel?.classList.add('hidden'); - bookmarksPanel?.classList.add('hidden'); - nTimesPanel?.classList.add('hidden'); - zipOptionWrapper?.classList.add('hidden'); - if (nTimesWarning) nTimesWarning.classList.add('hidden'); - - if (mode === 'range') { - rangePanel?.classList.remove('hidden'); - zipOptionWrapper?.classList.remove('hidden'); - } else if (mode === 'visual') { - visualPanel?.classList.remove('hidden'); - zipOptionWrapper?.classList.remove('hidden'); - renderVisualSelector(); - } else if (mode === 'even-odd') { - evenOddPanel?.classList.remove('hidden'); - } else if (mode === 'all') { - allPagesPanel?.classList.remove('hidden'); - } else if (mode === 'bookmarks') { - bookmarksPanel?.classList.remove('hidden'); - zipOptionWrapper?.classList.remove('hidden'); - } else if (mode === 'n-times') { - nTimesPanel?.classList.remove('hidden'); - zipOptionWrapper?.classList.remove('hidden'); - - const updateWarning = () => { - if (!state.pdfDoc) return; - const totalPages = state.pdfDoc.getPageCount(); - const nValue = parseInt((document.getElementById('split-n-value') as HTMLInputElement)?.value || '5'); - const remainder = totalPages % nValue; - if (remainder !== 0 && nTimesWarning) { - nTimesWarning.classList.remove('hidden'); - const warningText = document.getElementById('n-times-warning-text'); - if (warningText) { - warningText.textContent = `The PDF has ${totalPages} pages, which is not evenly divisible by ${nValue}. The last PDF will contain ${remainder} page(s).`; - } - } else if (nTimesWarning) { - nTimesWarning.classList.add('hidden'); - } - }; - - updateWarning(); - document.getElementById('split-n-value')?.addEventListener('input', updateWarning); - } - }); - } - - if (processBtn) { - processBtn.addEventListener('click', split); - } + if (processBtn) { + processBtn.addEventListener('click', split); + } }); diff --git a/src/js/logic/svg-to-pdf-page.ts b/src/js/logic/svg-to-pdf-page.ts index e88f7bd..a65fe08 100644 --- a/src/js/logic/svg-to-pdf-page.ts +++ b/src/js/logic/svg-to-pdf-page.ts @@ -1,246 +1,269 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; -import { downloadFile, readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, +} from '../utils/helpers.js'; import { PDFDocument as PDFLibDocument } from 'pdf-lib'; +import { + getSelectedQuality, + compressImageBytes, +} from '../utils/image-compress.js'; let files: File[] = []; if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } function initializePage() { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const addMoreBtn = document.getElementById('add-more-btn'); - const clearFilesBtn = document.getElementById('clear-files-btn'); - const processBtn = document.getElementById('process-btn'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const processBtn = document.getElementById('process-btn'); - if (fileInput) { - fileInput.addEventListener('change', handleFileUpload); - } + if (fileInput) { + fileInput.addEventListener('change', handleFileUpload); + } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); - - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handleFiles(droppedFiles); - } - }); - - // Clear value on click to allow re-selecting the same file - fileInput?.addEventListener('click', () => { - if (fileInput) fileInput.value = ''; - }); - } - - if (addMoreBtn) { - addMoreBtn.addEventListener('click', () => { - fileInput?.click(); - }); - } - - if (clearFilesBtn) { - clearFilesBtn.addEventListener('click', () => { - files = []; - updateUI(); - }); - } - - if (processBtn) { - processBtn.addEventListener('click', convertToPdf); - } - - document.getElementById('back-to-tools')?.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleFiles(droppedFiles); + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput?.addEventListener('click', () => { + if (fileInput) fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput?.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + files = []; + updateUI(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } + + document.getElementById('back-to-tools')?.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); } function handleFileUpload(e: Event) { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handleFiles(input.files); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleFiles(input.files); + } } function handleFiles(newFiles: FileList) { - const validFiles = Array.from(newFiles).filter(file => - file.type === 'image/svg+xml' || file.name.toLowerCase().endsWith('.svg') + const validFiles = Array.from(newFiles).filter( + (file) => + file.type === 'image/svg+xml' || file.name.toLowerCase().endsWith('.svg') + ); + + if (validFiles.length < newFiles.length) { + showAlert( + 'Invalid Files', + 'Some files were skipped. Only SVG graphics are allowed.' ); + } - if (validFiles.length < newFiles.length) { - showAlert('Invalid Files', 'Some files were skipped. Only SVG graphics are allowed.'); - } - - if (validFiles.length > 0) { - files = [...files, ...validFiles]; - updateUI(); - } + if (validFiles.length > 0) { + files = [...files, ...validFiles]; + updateUI(); + } } const resetState = () => { - files = []; - updateUI(); + files = []; + updateUI(); }; function updateUI() { - const fileDisplayArea = document.getElementById('file-display-area'); - const fileControls = document.getElementById('file-controls'); - const optionsDiv = document.getElementById('jpg-to-pdf-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const optionsDiv = document.getElementById('jpg-to-pdf-options'); - if (!fileDisplayArea || !fileControls || !optionsDiv) return; + if (!fileDisplayArea || !fileControls || !optionsDiv) return; - fileDisplayArea.innerHTML = ''; + fileDisplayArea.innerHTML = ''; - if (files.length > 0) { - fileControls.classList.remove('hidden'); - optionsDiv.classList.remove('hidden'); + if (files.length > 0) { + fileControls.classList.remove('hidden'); + optionsDiv.classList.remove('hidden'); - files.forEach((file, index) => { - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + files.forEach((file, index) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex items-center gap-2 overflow-hidden'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex items-center gap-2 overflow-hidden'; - const nameSpan = document.createElement('span'); - nameSpan.className = 'truncate font-medium text-gray-200'; - nameSpan.textContent = file.name; + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; - const sizeSpan = document.createElement('span'); - sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; - sizeSpan.textContent = `(${formatBytes(file.size)})`; + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; + sizeSpan.textContent = `(${formatBytes(file.size)})`; - infoContainer.append(nameSpan, sizeSpan); + infoContainer.append(nameSpan, sizeSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - files = files.filter((_, i) => i !== index); - updateUI(); - }; + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + files = files.filter((_, i) => i !== index); + updateUI(); + }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - }); - createIcons({ icons }); - } else { - fileControls.classList.add('hidden'); - optionsDiv.classList.add('hidden'); - } + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + }); + createIcons({ icons }); + } else { + fileControls.classList.add('hidden'); + optionsDiv.classList.add('hidden'); + } } function svgToPng(svgText: string): Promise { - return new Promise((resolve, reject) => { - const img = new Image(); + return new Promise((resolve, reject) => { + const img = new Image(); - // Create a proper SVG data URL from the SVG text - const svgBlob = new Blob([svgText], { type: 'image/svg+xml;charset=utf-8' }); - const url = URL.createObjectURL(svgBlob); - - img.onload = () => { - const canvas = document.createElement('canvas'); - // Use a reasonable default size if SVG has no explicit dimensions - const width = img.naturalWidth || img.width || 800; - const height = img.naturalHeight || img.height || 600; - - canvas.width = width; - canvas.height = height; - - const ctx = canvas.getContext('2d'); - if (!ctx) { - URL.revokeObjectURL(url); - return reject(new Error('Could not get canvas context')); - } - - // Fill with white background for transparency - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, width, height); - ctx.drawImage(img, 0, 0, width, height); - - canvas.toBlob( - async (pngBlob) => { - URL.revokeObjectURL(url); - if (!pngBlob) { - return reject(new Error('Canvas toBlob conversion failed.')); - } - const arrayBuffer = await pngBlob.arrayBuffer(); - resolve(new Uint8Array(arrayBuffer)); - }, - 'image/png' - ); - }; - - img.onerror = () => { - URL.revokeObjectURL(url); - reject(new Error('Failed to load SVG image')); - }; - - img.src = url; + // Create a proper SVG data URL from the SVG text + const svgBlob = new Blob([svgText], { + type: 'image/svg+xml;charset=utf-8', }); + const url = URL.createObjectURL(svgBlob); + + img.onload = () => { + const canvas = document.createElement('canvas'); + // Use a reasonable default size if SVG has no explicit dimensions + const width = img.naturalWidth || img.width || 800; + const height = img.naturalHeight || img.height || 600; + + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext('2d'); + if (!ctx) { + URL.revokeObjectURL(url); + return reject(new Error('Could not get canvas context')); + } + + // Fill with white background for transparency + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, width, height); + ctx.drawImage(img, 0, 0, width, height); + + canvas.toBlob(async (pngBlob) => { + URL.revokeObjectURL(url); + if (!pngBlob) { + return reject(new Error('Canvas toBlob conversion failed.')); + } + const arrayBuffer = await pngBlob.arrayBuffer(); + resolve(new Uint8Array(arrayBuffer)); + }, 'image/png'); + }; + + img.onerror = () => { + URL.revokeObjectURL(url); + reject(new Error('Failed to load SVG image')); + }; + + img.src = url; + }); } async function convertToPdf() { - if (files.length === 0) { - showAlert('No Files', 'Please select at least one SVG file.'); - return; - } + if (files.length === 0) { + showAlert('No Files', 'Please select at least one SVG file.'); + return; + } - showLoader('Creating PDF from SVG files...'); + showLoader('Creating PDF from SVG files...'); - try { - const pdfDoc = await PDFLibDocument.create(); + try { + const pdfDoc = await PDFLibDocument.create(); - for (const file of files) { - try { - // Read SVG as text (not binary) - const svgText = await file.text(); + for (const file of files) { + try { + // Read SVG as text (not binary) + const svgText = await file.text(); - // Convert SVG to PNG via canvas - const pngBytes = await svgToPng(svgText); - const pngImage = await pdfDoc.embedPng(pngBytes); + // Convert SVG to PNG via canvas + const pngBytes = await svgToPng(svgText); + const quality = getSelectedQuality(); + const compressed = await compressImageBytes(pngBytes, quality); + const embeddedImage = + compressed.type === 'jpeg' + ? await pdfDoc.embedJpg(compressed.bytes) + : await pdfDoc.embedPng(compressed.bytes); - const page = pdfDoc.addPage([pngImage.width, pngImage.height]); - page.drawImage(pngImage, { - x: 0, - y: 0, - width: pngImage.width, - height: pngImage.height, - }); - } catch (error) { - console.error(`Failed to process ${file.name}:`, error); - throw new Error(`Could not process "${file.name}". The file may be corrupted.`); - } - } - - const pdfBytes = await pdfDoc.save(); - downloadFile( - new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), - 'from_svgs.pdf' - ); - showAlert('Success', 'PDF created successfully!', 'success', () => { - resetState(); + const page = pdfDoc.addPage([ + embeddedImage.width, + embeddedImage.height, + ]); + page.drawImage(embeddedImage, { + x: 0, + y: 0, + width: embeddedImage.width, + height: embeddedImage.height, }); - } catch (e: any) { - console.error(e); - showAlert('Conversion Error', e.message); - } finally { - hideLoader(); + } catch (error) { + console.error(`Failed to process ${file.name}:`, error); + throw new Error( + `Could not process "${file.name}". The file may be corrupted.` + ); + } } + + const pdfBytes = await pdfDoc.save(); + downloadFile( + new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), + 'from_svgs.pdf' + ); + showAlert('Success', 'PDF created successfully!', 'success', () => { + resetState(); + }); + } catch (e: any) { + console.error(e); + showAlert('Conversion Error', e.message); + } finally { + hideLoader(); + } } diff --git a/src/js/logic/table-of-contents.ts b/src/js/logic/table-of-contents.ts index 1850d74..1aa46fa 100644 --- a/src/js/logic/table-of-contents.ts +++ b/src/js/logic/table-of-contents.ts @@ -1,8 +1,14 @@ -import { downloadFile, formatBytes } from "../utils/helpers"; -import { initializeGlobalShortcuts } from "../utils/shortcuts-init.js"; +import { downloadFile, formatBytes } from '../utils/helpers'; +import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'; +import { isCpdfAvailable } from '../utils/cpdf-helper.js'; +import { + showWasmRequiredDialog, + WasmProvider, +} from '../utils/wasm-provider.js'; - -const worker = new Worker(import.meta.env.BASE_URL + 'workers/table-of-contents.worker.js'); +const worker = new Worker( + import.meta.env.BASE_URL + 'workers/table-of-contents.worker.js' +); let pdfFile: File | null = null; @@ -55,12 +61,13 @@ function showStatus( type: 'success' | 'error' | 'info' = 'info' ) { statusMessage.textContent = message; - statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${type === 'success' - ? 'bg-green-900 text-green-200' - : type === 'error' - ? 'bg-red-900 text-red-200' - : 'bg-blue-900 text-blue-200' - }`; + statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${ + type === 'success' + ? 'bg-green-900 text-green-200' + : type === 'error' + ? 'bg-red-900 text-red-200' + : 'bg-blue-900 text-blue-200' + }`; statusMessage.classList.remove('hidden'); } @@ -130,6 +137,12 @@ async function generateTableOfContents() { return; } + // Check if CPDF is configured + if (!isCpdfAvailable()) { + showWasmRequiredDialog('cpdf'); + return; + } + try { generateBtn.disabled = true; showStatus('Reading file (Main Thread)...', 'info'); @@ -143,13 +156,14 @@ async function generateTableOfContents() { const fontFamily = parseInt(fontFamilySelect.value, 10); const addBookmark = addBookmarkCheckbox.checked; - const message: GenerateTOCMessage = { + const message = { command: 'generate-toc', pdfData: arrayBuffer, title, fontSize, fontFamily, addBookmark, + cpdfUrl: WasmProvider.getUrl('cpdf')! + 'coherentpdf.browser.min.js', }; worker.postMessage(message, [arrayBuffer]); @@ -171,7 +185,10 @@ worker.onmessage = (e: MessageEvent) => { const pdfBytes = new Uint8Array(pdfBytesBuffer); const blob = new Blob([pdfBytes], { type: 'application/pdf' }); - downloadFile(blob, pdfFile?.name.replace('.pdf', '_with_toc.pdf') || 'output_with_toc.pdf'); + downloadFile( + blob, + pdfFile?.name.replace('.pdf', '_with_toc.pdf') || 'output_with_toc.pdf' + ); showStatus( 'Table of contents generated successfully! Download started.', diff --git a/src/js/logic/text-color-page.ts b/src/js/logic/text-color-page.ts index 955a61e..a471ca7 100644 --- a/src/js/logic/text-color-page.ts +++ b/src/js/logic/text-color-page.ts @@ -3,11 +3,11 @@ import { showAlert, showLoader, hideLoader } from '../ui.js'; import { downloadFile, hexToRgb, formatBytes, getPDFDocument, readFileAsArrayBuffer } from '../utils/helpers.js'; import { PDFDocument as PDFLibDocument } from 'pdf-lib'; import * as pdfjsLib from 'pdfjs-dist'; +import { TextColorState } from '@/types'; pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); -interface PageState { file: File | null; pdfDoc: PDFLibDocument | null; } -const pageState: PageState = { file: null, pdfDoc: null }; +const pageState: TextColorState = { file: null, pdfDoc: null }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializePage); } else { initializePage(); } diff --git a/src/js/logic/txt-to-pdf-page.ts b/src/js/logic/txt-to-pdf-page.ts index 59e66a4..be41b94 100644 --- a/src/js/logic/txt-to-pdf-page.ts +++ b/src/js/logic/txt-to-pdf-page.ts @@ -1,465 +1,280 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, formatBytes, hexToRgb } from '../utils/helpers.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; import { createIcons, icons } from 'lucide'; -import { PDFDocument, rgb, StandardFonts, PageSizes } from 'pdf-lib'; -import { getFontForLanguage, getLanguageForChar } from '../utils/font-loader.js'; -import { languageToFontFamily } from '../config/font-mappings.js'; -import fontkit from '@pdf-lib/fontkit'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; let files: File[] = []; let currentMode: 'upload' | 'text' = 'upload'; -let selectedLanguages: string[] = ['eng']; -const allLanguages = Object.keys(languageToFontFamily).sort().map(code => { - let name = code; - try { - const displayNames = new Intl.DisplayNames(['en'], { type: 'language' }); - name = displayNames.of(code) || code; - } catch (e) { - console.warn(`Failed to get language name for ${code}`, e); - } - return { code, name: `${name} (${code})` }; -}); +// RTL character detection pattern (Arabic, Hebrew, Persian, etc.) +const RTL_PATTERN = + /[\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u08A0-\u08FF\uFB1D-\uFB4F\uFB50-\uFDFF\uFE70-\uFEFF]/; + +function hasRtlCharacters(text: string): boolean { + return RTL_PATTERN.test(text); +} const updateUI = () => { - const fileDisplayArea = document.getElementById('file-display-area'); - const fileControls = document.getElementById('file-controls'); - const dropZone = document.getElementById('drop-zone'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const dropZone = document.getElementById('drop-zone'); - if (!fileDisplayArea || !fileControls || !dropZone) return; + if (!fileDisplayArea || !fileControls || !dropZone) return; - fileDisplayArea.innerHTML = ''; + fileDisplayArea.innerHTML = ''; - if (files.length > 0 && currentMode === 'upload') { - dropZone.classList.add('hidden'); - fileControls.classList.remove('hidden'); + if (files.length > 0 && currentMode === 'upload') { + dropZone.classList.add('hidden'); + fileControls.classList.remove('hidden'); - files.forEach((file, index) => { - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + files.forEach((file, index) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const infoSpan = document.createElement('span'); - infoSpan.className = 'truncate font-medium text-gray-200'; - infoSpan.textContent = file.name; + const infoSpan = document.createElement('span'); + infoSpan.className = 'truncate font-medium text-gray-200'; + infoSpan.textContent = file.name; - const sizeSpan = document.createElement('span'); - sizeSpan.className = 'text-gray-400 text-xs ml-2'; - sizeSpan.textContent = `(${formatBytes(file.size)})`; + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'text-gray-400 text-xs ml-2'; + sizeSpan.textContent = `(${formatBytes(file.size)})`; - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - files = files.filter((_, i) => i !== index); - updateUI(); - }; + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + files = files.filter((_, i) => i !== index); + updateUI(); + }; - fileDiv.append(infoSpan, sizeSpan, removeBtn); - fileDisplayArea.appendChild(fileDiv); - }); - createIcons({ icons }); - } else { - dropZone.classList.remove('hidden'); - fileControls.classList.add('hidden'); - } + fileDiv.append(infoSpan, sizeSpan, removeBtn); + fileDisplayArea.appendChild(fileDiv); + }); + createIcons({ icons }); + } else { + dropZone.classList.remove('hidden'); + fileControls.classList.add('hidden'); + } }; const resetState = () => { - files = []; - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const textInput = document.getElementById('text-input') as HTMLTextAreaElement; - if (fileInput) fileInput.value = ''; - if (textInput) textInput.value = ''; - updateUI(); + files = []; + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const textInput = document.getElementById( + 'text-input' + ) as HTMLTextAreaElement; + if (fileInput) fileInput.value = ''; + if (textInput) textInput.value = ''; + updateUI(); }; -async function createPdfFromText( - text: string, - fontSize: number, - pageSizeKey: string, - colorHex: string, - orientation: string, - customWidth?: number, - customHeight?: number -): Promise { - const pdfDoc = await PDFDocument.create(); - pdfDoc.registerFontkit(fontkit); - - const fontMap = new Map(); - const fallbackFont = await pdfDoc.embedFont(StandardFonts.Helvetica); - - if (!selectedLanguages.includes('eng')) { - selectedLanguages.push('eng'); - } - - for (const lang of selectedLanguages) { - try { - const fontBytes = await getFontForLanguage(lang); - const font = await pdfDoc.embedFont(fontBytes, { subset: false }); - fontMap.set(lang, font); - } catch (e) { - console.warn(`Failed to load font for ${lang}, using fallback`, e); - fontMap.set(lang, fallbackFont); - } - } - - let pageSize = pageSizeKey === 'Custom' - ? [customWidth || 595, customHeight || 842] as [number, number] - : (PageSizes as any)[pageSizeKey]; - - if (orientation === 'landscape') { - pageSize = [pageSize[1], pageSize[0]] as [number, number]; - } - - const margin = 72; - const textColor = hexToRgb(colorHex); - - let page = pdfDoc.addPage(pageSize); - let { width, height } = page.getSize(); - const textWidth = width - margin * 2; - const lineHeight = fontSize * 1.3; - let y = height - margin; - - const paragraphs = text.split('\n'); - - for (const paragraph of paragraphs) { - if (paragraph.trim() === '') { - y -= lineHeight; - if (y < margin) { - page = pdfDoc.addPage(pageSize); - y = page.getHeight() - margin; - } - continue; - } - - const words = paragraph.split(' '); - let currentLineWords: { text: string; font: any }[] = []; - let currentLineWidth = 0; - - for (const word of words) { - let wordLang = 'eng'; - - for (const char of word) { - const charLang = getLanguageForChar(char); - if (selectedLanguages.includes(charLang)) { - wordLang = charLang; - break; - } - } - - const font = fontMap.get(wordLang) || fontMap.get('eng') || fallbackFont; - const wordWidth = font.widthOfTextAtSize(word + ' ', fontSize); - - if (currentLineWidth + wordWidth > textWidth && currentLineWords.length > 0) { - currentLineWords.forEach((item, idx) => { - const x = margin + (currentLineWidth * idx / currentLineWords.length); - page.drawText(item.text, { - x: margin + (currentLineWidth - textWidth) / 2, - y: y, - size: fontSize, - font: item.font, - color: rgb(textColor.r / 255, textColor.g / 255, textColor.b / 255), - }); - }); - - currentLineWords = []; - currentLineWidth = 0; - y -= lineHeight; - - if (y < margin) { - page = pdfDoc.addPage(pageSize); - y = page.getHeight() - margin; - } - } - - currentLineWords.push({ text: word + ' ', font }); - currentLineWidth += wordWidth; - } - - if (currentLineWords.length > 0) { - let x = margin; - currentLineWords.forEach((item) => { - page.drawText(item.text, { - x: x, - y: y, - size: fontSize, - font: item.font, - color: rgb(textColor.r / 255, textColor.g / 255, textColor.b / 255), - }); - x += item.font.widthOfTextAtSize(item.text, fontSize); - }); - - y -= lineHeight; - if (y < margin) { - page = pdfDoc.addPage(pageSize); - y = page.getHeight() - margin; - } - } - } - - return await pdfDoc.save(); -} - async function convert() { - const fontSize = parseInt((document.getElementById('font-size') as HTMLInputElement).value) || 12; - const pageSizeKey = (document.getElementById('page-size') as HTMLSelectElement).value; - const colorHex = (document.getElementById('text-color') as HTMLInputElement).value; - const orientation = (document.getElementById('page-orientation') as HTMLSelectElement).value; - const customWidth = parseInt((document.getElementById('custom-width') as HTMLInputElement)?.value); - const customHeight = parseInt((document.getElementById('custom-height') as HTMLInputElement)?.value); + const fontSize = + parseInt( + (document.getElementById('font-size') as HTMLInputElement).value + ) || 12; + const pageSizeKey = ( + document.getElementById('page-size') as HTMLSelectElement + ).value; + const fontName = + (document.getElementById('font-family') as HTMLSelectElement)?.value || + 'helv'; + const textColor = + (document.getElementById('text-color') as HTMLInputElement)?.value || + '#000000'; - if (currentMode === 'upload' && files.length === 0) { - showAlert('No Files', 'Please select at least one text file.'); - return; + if (currentMode === 'upload' && files.length === 0) { + showAlert('No Files', 'Please select at least one text file.'); + return; + } + + if (currentMode === 'text') { + const textInput = document.getElementById( + 'text-input' + ) as HTMLTextAreaElement; + if (!textInput.value.trim()) { + showAlert('No Text', 'Please enter some text to convert.'); + return; } + } - if (currentMode === 'text') { - const textInput = document.getElementById('text-input') as HTMLTextAreaElement; - if (!textInput.value.trim()) { - showAlert('No Text', 'Please enter some text to convert.'); - return; - } + showLoader('Loading engine...'); + + try { + const pymupdf = await loadPyMuPDF(); + + let textContent = ''; + + if (currentMode === 'upload') { + for (const file of files) { + const text = await file.text(); + textContent += text + '\n\n'; + } + } else { + const textInput = document.getElementById( + 'text-input' + ) as HTMLTextAreaElement; + textContent = textInput.value; } showLoader('Creating PDF...'); - try { - if (currentMode === 'upload') { - let combinedText = ''; - for (const file of files) { - const text = await file.text(); - combinedText += text + '\n\n'; - } - const pdfBytes = await createPdfFromText( - combinedText, - fontSize, - pageSizeKey, - colorHex, - orientation, - customWidth, - customHeight - ); + const pdfBlob = await pymupdf.textToPdf(textContent, { + fontSize, + pageSize: pageSizeKey as 'a4' | 'letter' | 'legal' | 'a3' | 'a5', + fontName: fontName as 'helv' | 'tiro' | 'cour' | 'times', + textColor, + margins: 72, + }); - downloadFile( - new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), - 'from_text.pdf' - ); - } else { - const textInput = document.getElementById('text-input') as HTMLTextAreaElement; - const pdfBytes = await createPdfFromText( - textInput.value, - fontSize, - pageSizeKey, - colorHex, - orientation, - customWidth, - customHeight - ); + downloadFile(pdfBlob, 'text_to_pdf.pdf'); - downloadFile( - new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), - 'from_text.pdf' - ); - } + showAlert( + 'Success', + 'Text converted to PDF successfully!', + 'success', + () => { + resetState(); + } + ); + } catch (e: any) { + console.error('[TxtToPDF] Error:', e); + showAlert('Error', `Failed to convert text to PDF. ${e.message || ''}`); + } finally { + hideLoader(); + } +} - showAlert('Success', 'Text converted to PDF successfully!', 'success', () => { - resetState(); - }); - } catch (e) { - console.error(e); - showAlert('Error', 'Failed to convert text to PDF.'); - } finally { - hideLoader(); - } +// Update textarea direction based on RTL detection +function updateTextareaDirection(textarea: HTMLTextAreaElement) { + const text = textarea.value; + if (hasRtlCharacters(text)) { + textarea.style.direction = 'rtl'; + textarea.style.textAlign = 'right'; + } else { + textarea.style.direction = 'ltr'; + textarea.style.textAlign = 'left'; + } } document.addEventListener('DOMContentLoaded', () => { - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const addMoreBtn = document.getElementById('add-more-btn'); - const clearFilesBtn = document.getElementById('clear-files-btn'); - const processBtn = document.getElementById('process-btn'); - const backBtn = document.getElementById('back-to-tools'); - const uploadModeBtn = document.getElementById('txt-mode-upload-btn'); - const textModeBtn = document.getElementById('txt-mode-text-btn'); - const uploadPanel = document.getElementById('txt-upload-panel'); - const textPanel = document.getElementById('txt-text-panel'); - const pageSizeSelect = document.getElementById('page-size') as HTMLSelectElement; - const customSizeContainer = document.getElementById('custom-size-container'); - const langDropdownBtn = document.getElementById('lang-dropdown-btn'); - const langDropdownContent = document.getElementById('lang-dropdown-content'); - const langSearch = document.getElementById('lang-search') as HTMLInputElement; - const langContainer = document.getElementById('language-list-container'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const processBtn = document.getElementById('process-btn'); + const backBtn = document.getElementById('back-to-tools'); + const uploadModeBtn = document.getElementById('txt-mode-upload-btn'); + const textModeBtn = document.getElementById('txt-mode-text-btn'); + const uploadPanel = document.getElementById('txt-upload-panel'); + const textPanel = document.getElementById('txt-text-panel'); + const textInput = document.getElementById( + 'text-input' + ) as HTMLTextAreaElement; - // Back to Tools - if (backBtn) { - backBtn.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; - }); + // Back to Tools + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + // Mode switching + if (uploadModeBtn && textModeBtn && uploadPanel && textPanel) { + uploadModeBtn.addEventListener('click', () => { + currentMode = 'upload'; + uploadModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); + uploadModeBtn.classList.add('bg-indigo-600', 'text-white'); + textModeBtn.classList.remove('bg-indigo-600', 'text-white'); + textModeBtn.classList.add('bg-gray-700', 'text-gray-300'); + uploadPanel.classList.remove('hidden'); + textPanel.classList.add('hidden'); + }); + + textModeBtn.addEventListener('click', () => { + currentMode = 'text'; + textModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); + textModeBtn.classList.add('bg-indigo-600', 'text-white'); + uploadModeBtn.classList.remove('bg-indigo-600', 'text-white'); + uploadModeBtn.classList.add('bg-gray-700', 'text-gray-300'); + textPanel.classList.remove('hidden'); + uploadPanel.classList.add('hidden'); + }); + } + + // RTL auto-detection for textarea + if (textInput) { + textInput.addEventListener('input', () => { + updateTextareaDirection(textInput); + }); + } + + // File handling + const handleFileSelect = (newFiles: FileList | null) => { + if (!newFiles || newFiles.length === 0) return; + const validFiles = Array.from(newFiles).filter( + (file) => + file.name.toLowerCase().endsWith('.txt') || file.type === 'text/plain' + ); + + if (validFiles.length < newFiles.length) { + showAlert( + 'Invalid Files', + 'Some files were skipped. Only text files are allowed.' + ); } - // Mode switching - if (uploadModeBtn && textModeBtn && uploadPanel && textPanel) { - uploadModeBtn.addEventListener('click', () => { - currentMode = 'upload'; - uploadModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); - uploadModeBtn.classList.add('bg-indigo-600', 'text-white'); - textModeBtn.classList.remove('bg-indigo-600', 'text-white'); - textModeBtn.classList.add('bg-gray-700', 'text-gray-300'); - uploadPanel.classList.remove('hidden'); - textPanel.classList.add('hidden'); - }); - - textModeBtn.addEventListener('click', () => { - currentMode = 'text'; - textModeBtn.classList.remove('bg-gray-700', 'text-gray-300'); - textModeBtn.classList.add('bg-indigo-600', 'text-white'); - uploadModeBtn.classList.remove('bg-indigo-600', 'text-white'); - uploadModeBtn.classList.add('bg-gray-700', 'text-gray-300'); - textPanel.classList.remove('hidden'); - uploadPanel.classList.add('hidden'); - }); + if (validFiles.length > 0) { + files = [...files, ...validFiles]; + updateUI(); } + }; - // Custom page size toggle - if (pageSizeSelect && customSizeContainer) { - pageSizeSelect.addEventListener('change', () => { - if (pageSizeSelect.value === 'Custom') { - customSizeContainer.classList.remove('hidden'); - } else { - customSizeContainer.classList.add('hidden'); - } - }); - } + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); - // Language dropdown - if (langDropdownBtn && langDropdownContent && langContainer) { - // Populate language list - allLanguages.forEach(lang => { - const label = document.createElement('label'); - label.className = 'flex items-center gap-2 p-2 hover:bg-gray-700 rounded cursor-pointer'; + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); - const checkbox = document.createElement('input'); - checkbox.type = 'checkbox'; - checkbox.value = lang.code; - checkbox.className = 'w-4 h-4'; - checkbox.checked = lang.code === 'eng'; + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); - checkbox.addEventListener('change', () => { - if (checkbox.checked) { - if (!selectedLanguages.includes(lang.code)) { - selectedLanguages.push(lang.code); - } - } else { - selectedLanguages = selectedLanguages.filter(l => l !== lang.code); - } - updateLanguageDisplay(); - }); + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); - const span = document.createElement('span'); - span.textContent = lang.name; - span.className = 'text-sm text-gray-300'; + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } - label.append(checkbox, span); - langContainer.appendChild(label); - }); + if (addMoreBtn && fileInput) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } - langDropdownBtn.addEventListener('click', () => { - langDropdownContent.classList.toggle('hidden'); - }); + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + files = []; + updateUI(); + }); + } - document.addEventListener('click', (e) => { - if (!langDropdownBtn.contains(e.target as Node) && !langDropdownContent.contains(e.target as Node)) { - langDropdownContent.classList.add('hidden'); - } - }); + if (processBtn) { + processBtn.addEventListener('click', convert); + } - if (langSearch) { - langSearch.addEventListener('input', () => { - const searchTerm = langSearch.value.toLowerCase(); - const labels = langContainer.querySelectorAll('label'); - labels.forEach(label => { - const text = label.textContent?.toLowerCase() || ''; - if (text.includes(searchTerm)) { - (label as HTMLElement).style.display = 'flex'; - } else { - (label as HTMLElement).style.display = 'none'; - } - }); - }); - } - } - - function updateLanguageDisplay() { - const langDropdownText = document.getElementById('lang-dropdown-text'); - if (langDropdownText) { - const selectedNames = selectedLanguages.map(code => { - const lang = allLanguages.find(l => l.code === code); - return lang?.name || code; - }); - langDropdownText.textContent = selectedNames.length > 0 ? selectedNames.join(', ') : 'Select Languages'; - } - } - - // File handling - const handleFileSelect = (newFiles: FileList | null) => { - if (!newFiles || newFiles.length === 0) return; - const validFiles = Array.from(newFiles).filter( - (file) => file.name.toLowerCase().endsWith('.txt') || file.type === 'text/plain' - ); - - if (validFiles.length < newFiles.length) { - showAlert('Invalid Files', 'Some files were skipped. Only text files are allowed.'); - } - - if (validFiles.length > 0) { - files = [...files, ...validFiles]; - updateUI(); - } - }; - - if (fileInput && dropZone) { - fileInput.addEventListener('change', (e) => { - handleFileSelect((e.target as HTMLInputElement).files); - }); - - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); - - dropZone.addEventListener('dragleave', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - handleFileSelect(e.dataTransfer?.files ?? null); - }); - - fileInput.addEventListener('click', () => { - fileInput.value = ''; - }); - } - - if (addMoreBtn && fileInput) { - addMoreBtn.addEventListener('click', () => { - fileInput.click(); - }); - } - - if (clearFilesBtn) { - clearFilesBtn.addEventListener('click', () => { - files = []; - updateUI(); - }); - } - - if (processBtn) { - processBtn.addEventListener('click', convert); - } - - createIcons({ icons }); + createIcons({ icons }); }); diff --git a/src/js/logic/validate-signature-pdf-page.ts b/src/js/logic/validate-signature-pdf-page.ts new file mode 100644 index 0000000..ded88c4 --- /dev/null +++ b/src/js/logic/validate-signature-pdf-page.ts @@ -0,0 +1,469 @@ +import { createIcons, icons } from 'lucide'; +import { showAlert, showLoader, hideLoader } from '../ui.js'; +import { readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js'; +import { validatePdfSignatures } from './validate-signature-pdf.js'; +import forge from 'node-forge'; +import { SignatureValidationResult, ValidateSignatureState } from '@/types'; + +const state: ValidateSignatureState = { + pdfFile: null, + pdfBytes: null, + results: [], + trustedCertFile: null, + trustedCert: null, +}; + +function getElement(id: string): T | null { + return document.getElementById(id) as T | null; +} + +function resetState(): void { + state.pdfFile = null; + state.pdfBytes = null; + state.results = []; + + const fileDisplayArea = getElement('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + + const resultsSection = getElement('results-section'); + if (resultsSection) resultsSection.classList.add('hidden'); + + const resultsContainer = getElement('results-container'); + if (resultsContainer) resultsContainer.innerHTML = ''; + + const fileInput = getElement('file-input'); + if (fileInput) fileInput.value = ''; + + const customCertSection = getElement('custom-cert-section'); + if (customCertSection) customCertSection.classList.add('hidden'); +} + +function resetCertState(): void { + state.trustedCertFile = null; + state.trustedCert = null; + + const certDisplayArea = getElement('cert-display-area'); + if (certDisplayArea) certDisplayArea.innerHTML = ''; + + const certInput = getElement('cert-input'); + if (certInput) certInput.value = ''; +} + +function initializePage(): void { + createIcons({ icons }); + + const fileInput = getElement('file-input'); + const dropZone = getElement('drop-zone'); + const backBtn = getElement('back-to-tools'); + const certInput = getElement('cert-input'); + const certDropZone = getElement('cert-drop-zone'); + + if (fileInput) { + fileInput.addEventListener('change', handlePdfUpload); + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handlePdfFile(droppedFiles[0]); + } + }); + } + + if (certInput) { + certInput.addEventListener('change', handleCertUpload); + certInput.addEventListener('click', () => { + certInput.value = ''; + }); + } + + if (certDropZone) { + certDropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + certDropZone.classList.add('bg-gray-700'); + }); + + certDropZone.addEventListener('dragleave', () => { + certDropZone.classList.remove('bg-gray-700'); + }); + + certDropZone.addEventListener('drop', (e) => { + e.preventDefault(); + certDropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleCertFile(droppedFiles[0]); + } + }); + } + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } +} + +function handlePdfUpload(e: Event): void { + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handlePdfFile(input.files[0]); + } +} + +async function handlePdfFile(file: File): Promise { + if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { + showAlert('Invalid File', 'Please select a PDF file.'); + return; + } + + resetState(); + state.pdfFile = file; + state.pdfBytes = new Uint8Array(await readFileAsArrayBuffer(file) as ArrayBuffer); + + updatePdfDisplay(); + + const customCertSection = getElement('custom-cert-section'); + if (customCertSection) customCertSection.classList.remove('hidden'); + createIcons({ icons }); + + await validateSignatures(); +} + +function updatePdfDisplay(): void { + const fileDisplayArea = getElement('file-display-area'); + if (!fileDisplayArea || !state.pdfFile) return; + + fileDisplayArea.innerHTML = ''; + + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = state.pdfFile.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(state.pdfFile.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => resetState(); + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); +} + +function handleCertUpload(e: Event): void { + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleCertFile(input.files[0]); + } +} + +async function handleCertFile(file: File): Promise { + const validExtensions = ['.pem', '.crt', '.cer', '.der']; + const hasValidExtension = validExtensions.some(ext => file.name.toLowerCase().endsWith(ext)); + + if (!hasValidExtension) { + showAlert('Invalid Certificate', 'Please select a .pem, .crt, .cer, or .der certificate file.'); + return; + } + + resetCertState(); + state.trustedCertFile = file; + + try { + const content = await file.text(); + + if (content.includes('-----BEGIN CERTIFICATE-----')) { + state.trustedCert = forge.pki.certificateFromPem(content); + } else { + const bytes = new Uint8Array(await readFileAsArrayBuffer(file) as ArrayBuffer); + const derString = String.fromCharCode.apply(null, Array.from(bytes)); + const asn1 = forge.asn1.fromDer(derString); + state.trustedCert = forge.pki.certificateFromAsn1(asn1); + } + + updateCertDisplay(); + + if (state.pdfBytes) { + await validateSignatures(); + } + } catch (error) { + console.error('Error parsing certificate:', error); + showAlert('Invalid Certificate', 'Failed to parse the certificate file.'); + resetCertState(); + } +} + +function updateCertDisplay(): void { + const certDisplayArea = getElement('cert-display-area'); + if (!certDisplayArea || !state.trustedCertFile || !state.trustedCert) return; + + certDisplayArea.innerHTML = ''; + + const certDiv = document.createElement('div'); + certDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + + const cn = state.trustedCert.subject.getField('CN'); + nameSpan.textContent = cn?.value as string || state.trustedCertFile.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-green-400'; + metaSpan.innerHTML = 'Trusted certificate loaded'; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = async () => { + resetCertState(); + if (state.pdfBytes) { + await validateSignatures(); + } + }; + + certDiv.append(infoContainer, removeBtn); + certDisplayArea.appendChild(certDiv); + createIcons({ icons }); +} + +async function validateSignatures(): Promise { + if (!state.pdfBytes) return; + + showLoader('Analyzing signatures...'); + + try { + state.results = await validatePdfSignatures(state.pdfBytes, state.trustedCert ?? undefined); + displayResults(); + } catch (error) { + console.error('Validation error:', error); + showAlert('Error', 'Failed to validate signatures. The file may be corrupted.'); + } finally { + hideLoader(); + } +} + +function displayResults(): void { + const resultsSection = getElement('results-section'); + const resultsContainer = getElement('results-container'); + + if (!resultsSection || !resultsContainer) return; + + resultsContainer.innerHTML = ''; + resultsSection.classList.remove('hidden'); + + if (state.results.length === 0) { + resultsContainer.innerHTML = ` +
+ +

No Signatures Found

+

This PDF does not contain any digital signatures.

+
+ `; + createIcons({ icons }); + return; + } + + const summaryDiv = document.createElement('div'); + summaryDiv.className = 'mb-4 p-3 bg-gray-700 rounded-lg border border-gray-600'; + + const validCount = state.results.filter(r => r.isValid && !r.isExpired).length; + const trustVerified = state.trustedCert ? state.results.filter(r => r.isTrusted).length : 0; + + let summaryHtml = ` +

+ ${state.results.length} + signature${state.results.length > 1 ? 's' : ''} found + + ${validCount} valid +

+ `; + + if (state.trustedCert) { + summaryHtml += ` +

+ + Trust verification: ${trustVerified}/${state.results.length} signatures verified against custom certificate +

+ `; + } + + summaryDiv.innerHTML = summaryHtml; + resultsContainer.appendChild(summaryDiv); + + state.results.forEach((result, index) => { + const card = createSignatureCard(result, index); + resultsContainer.appendChild(card); + }); + + createIcons({ icons }); +} + +function createSignatureCard(result: SignatureValidationResult, index: number): HTMLElement { + const card = document.createElement('div'); + card.className = 'bg-gray-700 rounded-lg p-4 border border-gray-600 mb-4'; + + let statusColor = 'text-green-400'; + let statusIcon = 'check-circle'; + let statusText = 'Valid Signature'; + + if (!result.isValid) { + statusColor = 'text-red-400'; + statusIcon = 'x-circle'; + statusText = 'Invalid Signature'; + } else if (result.isExpired) { + statusColor = 'text-yellow-400'; + statusIcon = 'alert-triangle'; + statusText = 'Certificate Expired'; + } else if (result.isSelfSigned) { + statusColor = 'text-yellow-400'; + statusIcon = 'alert-triangle'; + statusText = 'Self-Signed Certificate'; + } + + const formatDate = (date: Date) => { + if (!date || date.getTime() === 0) return 'Unknown'; + return date.toLocaleDateString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + }; + + let trustBadge = ''; + if (state.trustedCert) { + if (result.isTrusted) { + trustBadge = 'Trusted'; + } else { + trustBadge = 'Not in trust chain'; + } + } + + card.innerHTML = ` +
+
+ +
+

Signature ${index + 1}

+

${statusText}

+
+
+
+ ${result.coverageStatus === 'full' + ? 'Full Coverage' + : result.coverageStatus === 'partial' + ? 'Partial Coverage' + : '' + }${trustBadge} +
+
+ +
+
+
+

Signed By

+

${escapeHtml(result.signerName)}

+ ${result.signerOrg ? `

${escapeHtml(result.signerOrg)}

` : ''} + ${result.signerEmail ? `

${escapeHtml(result.signerEmail)}

` : ''} +
+
+

Issuer

+

${escapeHtml(result.issuer)}

+ ${result.issuerOrg ? `

${escapeHtml(result.issuerOrg)}

` : ''} +
+
+ + ${result.signatureDate ? ` +
+

Signed On

+

${formatDate(result.signatureDate)}

+
+ ` : ''} + +
+
+

Valid From

+

${formatDate(result.validFrom)}

+
+
+

Valid Until

+

${formatDate(result.validTo)}

+
+
+ + ${result.reason ? ` +
+

Reason

+

${escapeHtml(result.reason)}

+
+ ` : ''} + + ${result.location ? ` +
+

Location

+

${escapeHtml(result.location)}

+
+ ` : ''} + +
+ + Technical Details + +
+

Serial Number: ${escapeHtml(result.serialNumber)}

+

Digest Algorithm: ${escapeHtml(result.algorithms.digest)}

+

Signature Algorithm: ${escapeHtml(result.algorithms.signature)}

+ ${result.errorMessage ? `

Error: ${escapeHtml(result.errorMessage)}

` : ''} +
+
+
+ `; + + return card; +} + +function escapeHtml(str: string): string { + const div = document.createElement('div'); + div.textContent = str; + return div.innerHTML; +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializePage); +} else { + initializePage(); +} diff --git a/src/js/logic/validate-signature-pdf.ts b/src/js/logic/validate-signature-pdf.ts new file mode 100644 index 0000000..59ee5f5 --- /dev/null +++ b/src/js/logic/validate-signature-pdf.ts @@ -0,0 +1,238 @@ +import forge from 'node-forge'; +import { ExtractedSignature, SignatureValidationResult } from '@/types'; + + +export function extractSignatures(pdfBytes: Uint8Array): ExtractedSignature[] { + const signatures: ExtractedSignature[] = []; + const pdfString = new TextDecoder('latin1').decode(pdfBytes); + + // Find all signature objects for /Type /Sig + const sigRegex = /\/Type\s*\/Sig\b/g; + let sigMatch; + let sigIndex = 0; + + while ((sigMatch = sigRegex.exec(pdfString)) !== null) { + try { + const searchStart = Math.max(0, sigMatch.index - 5000); + const searchEnd = Math.min(pdfString.length, sigMatch.index + 10000); + const context = pdfString.substring(searchStart, searchEnd); + const byteRangeMatch = context.match(/\/ByteRange\s*\[\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\]/); + if (!byteRangeMatch) continue; + + const byteRange = [ + parseInt(byteRangeMatch[1], 10), + parseInt(byteRangeMatch[2], 10), + parseInt(byteRangeMatch[3], 10), + parseInt(byteRangeMatch[4], 10), + ]; + + const contentsMatch = context.match(/\/Contents\s*<([0-9A-Fa-f]+)>/); + if (!contentsMatch) continue; + + const hexContents = contentsMatch[1]; + const contentsBytes = hexToBytes(hexContents); + + const reasonMatch = context.match(/\/Reason\s*\(([^)]*)\)/); + const locationMatch = context.match(/\/Location\s*\(([^)]*)\)/); + const contactMatch = context.match(/\/ContactInfo\s*\(([^)]*)\)/); + const nameMatch = context.match(/\/Name\s*\(([^)]*)\)/); + const timeMatch = context.match(/\/M\s*\(D:(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/); + + let signingTime: string | undefined; + if (timeMatch) { + signingTime = `${timeMatch[1]}-${timeMatch[2]}-${timeMatch[3]}T${timeMatch[4]}:${timeMatch[5]}:${timeMatch[6]}`; + } + + signatures.push({ + index: sigIndex++, + contents: contentsBytes, + byteRange, + reason: reasonMatch ? decodeURIComponent(escape(reasonMatch[1])) : undefined, + location: locationMatch ? decodeURIComponent(escape(locationMatch[1])) : undefined, + contactInfo: contactMatch ? decodeURIComponent(escape(contactMatch[1])) : undefined, + name: nameMatch ? decodeURIComponent(escape(nameMatch[1])) : undefined, + signingTime, + }); + } catch (e) { + console.warn('Error extracting signature at index', sigIndex, e); + } + } + + return signatures; +} + +export function validateSignature( + signature: ExtractedSignature, + pdfBytes: Uint8Array, + trustedCert?: forge.pki.Certificate +): SignatureValidationResult { + const result: SignatureValidationResult = { + signatureIndex: signature.index, + isValid: false, + signerName: 'Unknown', + issuer: 'Unknown', + validFrom: new Date(0), + validTo: new Date(0), + isExpired: false, + isSelfSigned: false, + isTrusted: false, + algorithms: { digest: 'Unknown', signature: 'Unknown' }, + serialNumber: '', + byteRange: signature.byteRange, + coverageStatus: 'unknown', + reason: signature.reason, + location: signature.location, + contactInfo: signature.contactInfo, + }; + + try { + const binaryString = String.fromCharCode.apply(null, Array.from(signature.contents)); + const asn1 = forge.asn1.fromDer(binaryString); + const p7 = forge.pkcs7.messageFromAsn1(asn1) as any; + + if (!p7.certificates || p7.certificates.length === 0) { + result.errorMessage = 'No certificates found in signature'; + return result; + } + + const signerCert = p7.certificates[0] as forge.pki.Certificate; + + const subjectCN = signerCert.subject.getField('CN'); + const subjectO = signerCert.subject.getField('O'); + const subjectE = signerCert.subject.getField('E') || signerCert.subject.getField('emailAddress'); + const issuerCN = signerCert.issuer.getField('CN'); + const issuerO = signerCert.issuer.getField('O'); + + result.signerName = (subjectCN?.value as string) ?? 'Unknown'; + result.signerOrg = subjectO?.value as string | undefined; + result.signerEmail = subjectE?.value as string | undefined; + result.issuer = (issuerCN?.value as string) ?? 'Unknown'; + result.issuerOrg = issuerO?.value as string | undefined; + result.validFrom = signerCert.validity.notBefore; + result.validTo = signerCert.validity.notAfter; + result.serialNumber = signerCert.serialNumber; + + const now = new Date(); + result.isExpired = now > result.validTo || now < result.validFrom; + + result.isSelfSigned = signerCert.isIssuer(signerCert); + + // Check trust against provided certificate + if (trustedCert) { + try { + const isTrustedIssuer = trustedCert.isIssuer(signerCert); + const isSameCert = signerCert.serialNumber === trustedCert.serialNumber; + + let chainTrusted = false; + for (const cert of p7.certificates) { + if (trustedCert.isIssuer(cert) || + (cert as forge.pki.Certificate).serialNumber === trustedCert.serialNumber) { + chainTrusted = true; + break; + } + } + + result.isTrusted = isTrustedIssuer || isSameCert || chainTrusted; + } catch { + result.isTrusted = false; + } + } + + result.algorithms = { + digest: getDigestAlgorithmName(signerCert.siginfo?.algorithmOid || ''), + signature: getSignatureAlgorithmName(signerCert.signatureOid || ''), + }; + + // Parse signing time if available in signature + if (signature.signingTime) { + result.signatureDate = new Date(signature.signingTime); + } else { + // Try to extract from authenticated attributes + try { + const signedData = p7 as any; + if (signedData.rawCapture?.authenticatedAttributes) { + // Look for signing time attribute + for (const attr of signedData.rawCapture.authenticatedAttributes) { + if (attr.type === forge.pki.oids.signingTime) { + result.signatureDate = attr.value; + break; + } + } + } + } catch { /* ignore */ } + } + + if (signature.byteRange && signature.byteRange.length === 4) { + const [start1, len1, start2, len2] = signature.byteRange; + const totalCovered = len1 + len2; + const expectedEnd = start2 + len2; + + if (expectedEnd === pdfBytes.length) { + result.coverageStatus = 'full'; + } else if (expectedEnd < pdfBytes.length) { + result.coverageStatus = 'partial'; + } + } + + result.isValid = true; + + } catch (e) { + result.errorMessage = e instanceof Error ? e.message : 'Failed to parse signature'; + } + + return result; +} + +export async function validatePdfSignatures( + pdfBytes: Uint8Array, + trustedCert?: forge.pki.Certificate +): Promise { + const signatures = extractSignatures(pdfBytes); + return signatures.map(sig => validateSignature(sig, pdfBytes, trustedCert)); +} + +export function countSignatures(pdfBytes: Uint8Array): number { + return extractSignatures(pdfBytes).length; +} + +function hexToBytes(hex: string): Uint8Array { + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) { + bytes[i / 2] = parseInt(hex.substr(i, 2), 16); + } + + let actualLength = bytes.length; + while (actualLength > 0 && bytes[actualLength - 1] === 0) { + actualLength--; + } + + return bytes.slice(0, actualLength); +} + +function getDigestAlgorithmName(oid: string): string { + const digestAlgorithms: Record = { + '1.2.840.113549.2.5': 'MD5', + '1.3.14.3.2.26': 'SHA-1', + '2.16.840.1.101.3.4.2.1': 'SHA-256', + '2.16.840.1.101.3.4.2.2': 'SHA-384', + '2.16.840.1.101.3.4.2.3': 'SHA-512', + '2.16.840.1.101.3.4.2.4': 'SHA-224', + }; + return digestAlgorithms[oid] || oid || 'Unknown'; +} + +function getSignatureAlgorithmName(oid: string): string { + const signatureAlgorithms: Record = { + '1.2.840.113549.1.1.1': 'RSA', + '1.2.840.113549.1.1.5': 'RSA with SHA-1', + '1.2.840.113549.1.1.11': 'RSA with SHA-256', + '1.2.840.113549.1.1.12': 'RSA with SHA-384', + '1.2.840.113549.1.1.13': 'RSA with SHA-512', + '1.2.840.10045.2.1': 'ECDSA', + '1.2.840.10045.4.1': 'ECDSA with SHA-1', + '1.2.840.10045.4.3.2': 'ECDSA with SHA-256', + '1.2.840.10045.4.3.3': 'ECDSA with SHA-384', + '1.2.840.10045.4.3.4': 'ECDSA with SHA-512', + }; + return signatureAlgorithms[oid] || oid || 'Unknown'; +} diff --git a/src/js/logic/view-metadata-page.ts b/src/js/logic/view-metadata-page.ts index fc00e66..240dcce 100644 --- a/src/js/logic/view-metadata-page.ts +++ b/src/js/logic/view-metadata-page.ts @@ -1,11 +1,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; import { formatBytes, formatIsoDate, getPDFDocument } from '../utils/helpers.js'; import { createIcons, icons } from 'lucide'; - -interface ViewMetadataState { - file: File | null; - metadata: Record; -} +import { ViewMetadataState } from '@/types'; const pageState: ViewMetadataState = { file: null, diff --git a/src/js/logic/vsd-to-pdf-page.ts b/src/js/logic/vsd-to-pdf-page.ts new file mode 100644 index 0000000..01430d6 --- /dev/null +++ b/src/js/logic/vsd-to-pdf-page.ts @@ -0,0 +1,142 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +const ACCEPTED_EXTENSIONS = ['.vsd', '.vsdx']; +const FILETYPE_NAME = 'VSD'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + infoContainer.append(nameSpan, metaSpan); + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + updateUI(); + }; + + const convert = async () => { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${FILETYPE_NAME} file.`); + return; + } + try { + const converter = getLibreOfficeConverter(); + showLoader('Loading engine...'); + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + const baseName = file.name.replace(/\.[^/.]+$/, ''); + downloadFile(pdfBlob, `${baseName}.pdf`); + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${file.name} to PDF.`, 'success', () => resetState()); + } else { + showLoader('Converting multiple files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + const baseName = file.name.replace(/\.[^/.]+$/, ''); + zip.file(`${baseName}.pdf`, pdfBlob); + } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE_NAME.toLowerCase()}-to-pdf.zip`); + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${state.files.length} files to PDF.`, 'success', () => resetState()); + } + } catch (err) { + hideLoader(); + const message = err instanceof Error ? err.message : 'Unknown error'; + console.error(`[${FILETYPE_NAME}ToPDF] Error:`, err); + showAlert('Error', `An error occurred during conversion. Error: ${message}`); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter(file => { + const ext = '.' + file.name.split('.').pop()?.toLowerCase(); + return ACCEPTED_EXTENSIONS.includes(ext); + }); + if (validFiles.length > 0) { + state.files = [...state.files, ...validFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => handleFileSelect((e.target as HTMLInputElement).files)); + dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('bg-gray-700'); }); + dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('bg-gray-700'); }); + dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('bg-gray-700'); handleFileSelect(e.dataTransfer?.files ?? null); }); + fileInput.addEventListener('click', () => { fileInput.value = ''; }); + } + if (addMoreBtn) addMoreBtn.addEventListener('click', () => fileInput.click()); + if (clearFilesBtn) clearFilesBtn.addEventListener('click', resetState); + if (processBtn) processBtn.addEventListener('click', convert); + + updateUI(); +}); diff --git a/src/js/logic/wasm-settings-page.ts b/src/js/logic/wasm-settings-page.ts new file mode 100644 index 0000000..4e22310 --- /dev/null +++ b/src/js/logic/wasm-settings-page.ts @@ -0,0 +1,235 @@ +import { createIcons, icons } from 'lucide'; +import { showAlert, showLoader, hideLoader } from '../ui.js'; +import { WasmProvider, type WasmPackage } from '../utils/wasm-provider.js'; +import { clearPyMuPDFCache } from '../utils/pymupdf-loader.js'; +import { clearGhostscriptCache } from '../utils/ghostscript-dynamic-loader.js'; + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializePage); +} else { + initializePage(); +} + +function initializePage() { + createIcons({ icons }); + + document.querySelectorAll('.copy-btn').forEach((btn) => { + btn.addEventListener('click', async () => { + const url = btn.getAttribute('data-copy'); + if (url) { + await navigator.clipboard.writeText(url); + const svg = btn.querySelector('svg'); + if (svg) { + const checkIcon = document.createElement('i'); + checkIcon.setAttribute('data-lucide', 'check'); + checkIcon.className = 'w-3.5 h-3.5'; + svg.replaceWith(checkIcon); + createIcons({ icons }); + + setTimeout(() => { + const newSvg = btn.querySelector('svg'); + if (newSvg) { + const copyIcon = document.createElement('i'); + copyIcon.setAttribute('data-lucide', 'copy'); + copyIcon.className = 'w-3.5 h-3.5'; + newSvg.replaceWith(copyIcon); + createIcons({ icons }); + } + }, 1500); + } + } + }); + }); + + const pymupdfUrl = document.getElementById('pymupdf-url') as HTMLInputElement; + const pymupdfTest = document.getElementById( + 'pymupdf-test' + ) as HTMLButtonElement; + const pymupdfStatus = document.getElementById( + 'pymupdf-status' + ) as HTMLSpanElement; + + const ghostscriptUrl = document.getElementById( + 'ghostscript-url' + ) as HTMLInputElement; + const ghostscriptTest = document.getElementById( + 'ghostscript-test' + ) as HTMLButtonElement; + const ghostscriptStatus = document.getElementById( + 'ghostscript-status' + ) as HTMLSpanElement; + + const cpdfUrl = document.getElementById('cpdf-url') as HTMLInputElement; + const cpdfTest = document.getElementById('cpdf-test') as HTMLButtonElement; + const cpdfStatus = document.getElementById('cpdf-status') as HTMLSpanElement; + + const saveBtn = document.getElementById('save-btn') as HTMLButtonElement; + const clearBtn = document.getElementById('clear-btn') as HTMLButtonElement; + const backBtn = document.getElementById('back-to-tools'); + + backBtn?.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + + loadConfiguration(); + + function loadConfiguration() { + const packages: { name: WasmPackage; input: HTMLInputElement }[] = [ + { name: 'pymupdf', input: pymupdfUrl }, + { name: 'ghostscript', input: ghostscriptUrl }, + { name: 'cpdf', input: cpdfUrl }, + ]; + + const config = WasmProvider.getAllProviders(); + + for (const { name, input } of packages) { + const url = config[name]; + if (!url) continue; + + if (WasmProvider.isUserConfigured(name)) { + input.value = url; + } else { + input.value = ''; + input.placeholder = `Using default: ${url}`; + } + updateStatus(name, true); + } + } + + function updateStatus( + packageName: WasmPackage, + configured: boolean, + testing = false + ) { + const statusMap: Record = { + pymupdf: pymupdfStatus, + ghostscript: ghostscriptStatus, + cpdf: cpdfStatus, + }; + + const statusEl = statusMap[packageName]; + if (!statusEl) return; + + if (testing) { + statusEl.textContent = 'Testing...'; + statusEl.className = + 'text-xs px-2 py-1 rounded-full bg-yellow-600/30 text-yellow-300'; + } else if (configured && WasmProvider.isUserConfigured(packageName)) { + statusEl.textContent = 'Custom Override'; + statusEl.className = + 'text-xs px-2 py-1 rounded-full bg-blue-600/30 text-blue-300'; + } else if (configured || WasmProvider.hasEnvDefault(packageName)) { + statusEl.textContent = 'Pre-configured'; + statusEl.className = + 'text-xs px-2 py-1 rounded-full bg-green-600/30 text-green-300'; + } else { + statusEl.textContent = 'Not Configured'; + statusEl.className = + 'text-xs px-2 py-1 rounded-full bg-gray-600 text-gray-300'; + } + } + + async function testConnection(packageName: WasmPackage, url: string) { + if (!url.trim()) { + showAlert('Empty URL', 'Please enter a URL to test.'); + return; + } + + updateStatus(packageName, false, true); + + const result = await WasmProvider.validateUrl(packageName, url); + + if (result.valid) { + updateStatus(packageName, true); + showAlert( + 'Success', + `Connection to ${WasmProvider.getPackageDisplayName(packageName)} successful!`, + 'success' + ); + } else { + updateStatus(packageName, false); + showAlert( + 'Connection Failed', + result.error || 'Could not connect to the URL.' + ); + } + } + + pymupdfTest?.addEventListener('click', () => { + testConnection('pymupdf', pymupdfUrl.value); + }); + + ghostscriptTest?.addEventListener('click', () => { + testConnection('ghostscript', ghostscriptUrl.value); + }); + + cpdfTest?.addEventListener('click', () => { + testConnection('cpdf', cpdfUrl.value); + }); + + saveBtn?.addEventListener('click', async () => { + showLoader('Saving configuration...'); + + try { + if (pymupdfUrl.value.trim()) { + WasmProvider.setUrl('pymupdf', pymupdfUrl.value.trim()); + updateStatus('pymupdf', true); + } else { + WasmProvider.removeUrl('pymupdf'); + updateStatus('pymupdf', false); + } + + if (ghostscriptUrl.value.trim()) { + WasmProvider.setUrl('ghostscript', ghostscriptUrl.value.trim()); + updateStatus('ghostscript', true); + } else { + WasmProvider.removeUrl('ghostscript'); + updateStatus('ghostscript', false); + } + + if (cpdfUrl.value.trim()) { + WasmProvider.setUrl('cpdf', cpdfUrl.value.trim()); + updateStatus('cpdf', true); + } else { + WasmProvider.removeUrl('cpdf'); + updateStatus('cpdf', false); + } + + hideLoader(); + showAlert('Saved', 'Configuration saved successfully!', 'success'); + } catch (e: unknown) { + hideLoader(); + const errorMessage = e instanceof Error ? e.message : 'Unknown error'; + showAlert('Error', `Failed to save configuration: ${errorMessage}`); + } + }); + + clearBtn?.addEventListener('click', () => { + WasmProvider.clearAll(); + + clearPyMuPDFCache(); + clearGhostscriptCache(); + + const defaults = WasmProvider.getAllProviders(); + pymupdfUrl.value = defaults.pymupdf || ''; + ghostscriptUrl.value = defaults.ghostscript || ''; + cpdfUrl.value = defaults.cpdf || ''; + + updateStatus('pymupdf', WasmProvider.isConfigured('pymupdf')); + updateStatus('ghostscript', WasmProvider.isConfigured('ghostscript')); + updateStatus('cpdf', WasmProvider.isConfigured('cpdf')); + + const hasDefaults = + WasmProvider.hasEnvDefault('pymupdf') || + WasmProvider.hasEnvDefault('ghostscript') || + WasmProvider.hasEnvDefault('cpdf'); + + showAlert( + 'Reset', + hasDefaults + ? 'Custom overrides cleared. Pre-configured defaults are active.' + : 'All configurations and cached modules have been cleared.', + 'success' + ); + }); +} diff --git a/src/js/logic/webp-to-pdf-page.ts b/src/js/logic/webp-to-pdf-page.ts index 087c8b2..bcf38ae 100644 --- a/src/js/logic/webp-to-pdf-page.ts +++ b/src/js/logic/webp-to-pdf-page.ts @@ -1,248 +1,257 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; -import { downloadFile, readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, +} from '../utils/helpers.js'; import { PDFDocument as PDFLibDocument } from 'pdf-lib'; +import { + getSelectedQuality, + compressImageBytes, +} from '../utils/image-compress.js'; let files: File[] = []; if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } function initializePage() { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const addMoreBtn = document.getElementById('add-more-btn'); - const clearFilesBtn = document.getElementById('clear-files-btn'); - const processBtn = document.getElementById('process-btn'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const processBtn = document.getElementById('process-btn'); - if (fileInput) { - fileInput.addEventListener('change', handleFileUpload); - } + if (fileInput) { + fileInput.addEventListener('change', handleFileUpload); + } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); - - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handleFiles(droppedFiles); - } - }); - - // Clear value on click to allow re-selecting the same file - fileInput?.addEventListener('click', () => { - if (fileInput) fileInput.value = ''; - }); - } - - if (addMoreBtn) { - addMoreBtn.addEventListener('click', () => { - fileInput?.click(); - }); - } - - if (clearFilesBtn) { - clearFilesBtn.addEventListener('click', () => { - files = []; - updateUI(); - }); - } - - if (processBtn) { - processBtn.addEventListener('click', convertToPdf); - } - - document.getElementById('back-to-tools')?.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleFiles(droppedFiles); + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput?.addEventListener('click', () => { + if (fileInput) fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput?.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + files = []; + updateUI(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } + + document.getElementById('back-to-tools')?.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); } function handleFileUpload(e: Event) { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handleFiles(input.files); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleFiles(input.files); + } } function handleFiles(newFiles: FileList) { - const validFiles = Array.from(newFiles).filter(file => - file.type === 'image/webp' || file.name.toLowerCase().endsWith('.webp') + const validFiles = Array.from(newFiles).filter( + (file) => + file.type === 'image/webp' || file.name.toLowerCase().endsWith('.webp') + ); + + if (validFiles.length < newFiles.length) { + showAlert( + 'Invalid Files', + 'Some files were skipped. Only WebP images are allowed.' ); + } - if (validFiles.length < newFiles.length) { - showAlert('Invalid Files', 'Some files were skipped. Only WebP images are allowed.'); - } - - if (validFiles.length > 0) { - files = [...files, ...validFiles]; - updateUI(); - } + if (validFiles.length > 0) { + files = [...files, ...validFiles]; + updateUI(); + } } const resetState = () => { - files = []; - updateUI(); + files = []; + updateUI(); }; function updateUI() { - const fileDisplayArea = document.getElementById('file-display-area'); - const fileControls = document.getElementById('file-controls'); - const optionsDiv = document.getElementById('jpg-to-pdf-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const optionsDiv = document.getElementById('jpg-to-pdf-options'); - if (!fileDisplayArea || !fileControls || !optionsDiv) return; + if (!fileDisplayArea || !fileControls || !optionsDiv) return; - fileDisplayArea.innerHTML = ''; + fileDisplayArea.innerHTML = ''; - if (files.length > 0) { - fileControls.classList.remove('hidden'); - optionsDiv.classList.remove('hidden'); + if (files.length > 0) { + fileControls.classList.remove('hidden'); + optionsDiv.classList.remove('hidden'); - files.forEach((file, index) => { - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + files.forEach((file, index) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex items-center gap-2 overflow-hidden'; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex items-center gap-2 overflow-hidden'; - const nameSpan = document.createElement('span'); - nameSpan.className = 'truncate font-medium text-gray-200'; - nameSpan.textContent = file.name; + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; - const sizeSpan = document.createElement('span'); - sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; - sizeSpan.textContent = `(${formatBytes(file.size)})`; + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 text-gray-400 text-xs'; + sizeSpan.textContent = `(${formatBytes(file.size)})`; - infoContainer.append(nameSpan, sizeSpan); + infoContainer.append(nameSpan, sizeSpan); - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - files = files.filter((_, i) => i !== index); - updateUI(); - }; + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + files = files.filter((_, i) => i !== index); + updateUI(); + }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - }); - createIcons({ icons }); - } else { - fileControls.classList.add('hidden'); - optionsDiv.classList.add('hidden'); - } + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + }); + createIcons({ icons }); + } else { + fileControls.classList.add('hidden'); + optionsDiv.classList.add('hidden'); + } } function sanitizeImageAsJpeg(imageBytes: any) { - return new Promise((resolve, reject) => { - const blob = new Blob([imageBytes]); - const imageUrl = URL.createObjectURL(blob); - const img = new Image(); + return new Promise((resolve, reject) => { + const blob = new Blob([imageBytes]); + const imageUrl = URL.createObjectURL(blob); + const img = new Image(); - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.naturalWidth; - canvas.height = img.naturalHeight; - const ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0); + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); - canvas.toBlob( - async (jpegBlob) => { - if (!jpegBlob) { - return reject(new Error('Canvas toBlob conversion failed.')); - } - const arrayBuffer = await jpegBlob.arrayBuffer(); - resolve(new Uint8Array(arrayBuffer)); - }, - 'image/jpeg', - 0.9 - ); - URL.revokeObjectURL(imageUrl); - }; + canvas.toBlob( + async (jpegBlob) => { + if (!jpegBlob) { + return reject(new Error('Canvas toBlob conversion failed.')); + } + const arrayBuffer = await jpegBlob.arrayBuffer(); + resolve(new Uint8Array(arrayBuffer)); + }, + 'image/jpeg', + 0.9 + ); + URL.revokeObjectURL(imageUrl); + }; - img.onerror = () => { - URL.revokeObjectURL(imageUrl); - reject( - new Error( - 'The provided file could not be loaded as an image. It may be corrupted.' - ) - ); - }; + img.onerror = () => { + URL.revokeObjectURL(imageUrl); + reject( + new Error( + 'The provided file could not be loaded as an image. It may be corrupted.' + ) + ); + }; - img.src = imageUrl; - }); + img.src = imageUrl; + }); } async function convertToPdf() { - if (files.length === 0) { - showAlert('No Files', 'Please select at least one JPG file.'); - return; - } + if (files.length === 0) { + showAlert('No Files', 'Please select at least one JPG file.'); + return; + } - showLoader('Creating PDF from JPGs...'); + showLoader('Creating PDF from JPGs...'); - try { - const pdfDoc = await PDFLibDocument.create(); + try { + const pdfDoc = await PDFLibDocument.create(); + const quality = getSelectedQuality(); - for (const file of files) { - const originalBytes = await readFileAsArrayBuffer(file); - let jpgImage; + for (const file of files) { + const originalBytes = await readFileAsArrayBuffer(file); + const compressed = await compressImageBytes( + new Uint8Array(originalBytes as ArrayBuffer), + quality + ); + let embeddedImage; - try { - jpgImage = await pdfDoc.embedJpg(originalBytes as Uint8Array); - } catch (e) { - showAlert( - 'Warning', - `Direct JPG embedding failed for ${file.name}, attempting to sanitize...` - ); - try { - const sanitizedBytes = await sanitizeImageAsJpeg(originalBytes); - jpgImage = await pdfDoc.embedJpg(sanitizedBytes as Uint8Array); - } catch (fallbackError) { - console.error( - `Failed to process ${file.name} after sanitization:`, - fallbackError - ); - throw new Error( - `Could not process "${file.name}". The file may be corrupted.` - ); - } - } - - const page = pdfDoc.addPage([jpgImage.width, jpgImage.height]); - page.drawImage(jpgImage, { - x: 0, - y: 0, - width: jpgImage.width, - height: jpgImage.height, - }); + if (compressed.type === 'jpeg') { + embeddedImage = await pdfDoc.embedJpg(compressed.bytes); + } else { + try { + embeddedImage = await pdfDoc.embedPng(compressed.bytes); + } catch { + const fallback = await sanitizeImageAsJpeg(originalBytes); + embeddedImage = await pdfDoc.embedJpg(fallback as Uint8Array); } + } - const pdfBytes = await pdfDoc.save(); - downloadFile( - new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), - 'from_jpgs.pdf' - ); - showAlert('Success', 'PDF created successfully!', 'success', () => { - resetState(); - }); - } catch (e: any) { - console.error(e); - showAlert('Conversion Error', e.message); - } finally { - hideLoader(); + const page = pdfDoc.addPage([embeddedImage.width, embeddedImage.height]); + page.drawImage(embeddedImage, { + x: 0, + y: 0, + width: embeddedImage.width, + height: embeddedImage.height, + }); } + + const pdfBytes = await pdfDoc.save(); + downloadFile( + new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), + 'from_webps.pdf' + ); + showAlert('Success', 'PDF created successfully!', 'success', () => { + resetState(); + }); + } catch (e: any) { + console.error(e); + showAlert('Conversion Error', e.message); + } finally { + hideLoader(); + } } diff --git a/src/js/logic/word-to-pdf-page.ts b/src/js/logic/word-to-pdf-page.ts new file mode 100644 index 0000000..e198e3b --- /dev/null +++ b/src/js/logic/word-to-pdf-page.ts @@ -0,0 +1,236 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + readFileAsArrayBuffer, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + const convertToPdf = async () => { + try { + console.log('[Word2PDF] Starting conversion...'); + console.log('[Word2PDF] Number of files:', state.files.length); + + if (state.files.length === 0) { + showAlert('No Files', 'Please select at least one Word document.'); + hideLoader(); + return; + } + + const converter = getLibreOfficeConverter(); + console.log('[Word2PDF] Got converter instance'); + + // Initialize LibreOffice if not already done + console.log('[Word2PDF] Initializing LibreOffice...'); + await converter.initialize((progress: LoadProgress) => { + console.log('[Word2PDF] Init progress:', progress.percent + '%', progress.message); + showLoader(progress.message, progress.percent); + }); + console.log('[Word2PDF] LibreOffice initialized successfully!'); + + if (state.files.length === 1) { + const originalFile = state.files[0]; + console.log('[Word2PDF] Converting single file:', originalFile.name); + + showLoader('Processing...'); + + const pdfBlob = await converter.convertToPdf(originalFile); + console.log('[Word2PDF] Conversion complete! PDF size:', pdfBlob.size); + + const fileName = originalFile.name.replace(/\.(doc|docx|odt|rtf)$/i, '') + '.pdf'; + + downloadFile(pdfBlob, fileName); + console.log('[Word2PDF] File downloaded:', fileName); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${originalFile.name} to PDF.`, + 'success', + () => resetState() + ); + } else { + console.log('[Word2PDF] Converting multiple files:', state.files.length); + showLoader('Processing...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + console.log(`[Word2PDF] Converting file ${i + 1}/${state.files.length}:`, file.name); + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + + const pdfBlob = await converter.convertToPdf(file); + console.log(`[Word2PDF] Converted ${file.name}, PDF size:`, pdfBlob.size); + + const baseName = file.name.replace(/\.(doc|docx|odt|rtf)$/i, ''); + const pdfBuffer = await pdfBlob.arrayBuffer(); + zip.file(`${baseName}.pdf`, pdfBuffer); + } + + console.log('[Word2PDF] Generating ZIP file...'); + const zipBlob = await zip.generateAsync({ type: 'blob' }); + console.log('[Word2PDF] ZIP size:', zipBlob.size); + + downloadFile(zipBlob, 'word-converted.zip'); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${state.files.length} Word document(s) to PDF.`, + 'success', + () => resetState() + ); + } + } catch (e: any) { + console.error('[Word2PDF] ERROR:', e); + console.error('[Word2PDF] Error stack:', e.stack); + hideLoader(); + showAlert( + 'Error', + `An error occurred during conversion. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + state.files = [...state.files, ...Array.from(files)]; + updateUI(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const wordFiles = Array.from(files).filter(f => { + const name = f.name.toLowerCase(); + return name.endsWith('.doc') || name.endsWith('.docx') || name.endsWith('.odt') || name.endsWith('.rtf'); + }); + if (wordFiles.length > 0) { + const dataTransfer = new DataTransfer(); + wordFiles.forEach(f => dataTransfer.items.add(f)); + handleFileSelect(dataTransfer.files); + } + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + resetState(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } + + // Initialize UI state (ensures button is hidden when no files) + updateUI(); +}); diff --git a/src/js/logic/wpd-to-pdf-page.ts b/src/js/logic/wpd-to-pdf-page.ts new file mode 100644 index 0000000..9d41536 --- /dev/null +++ b/src/js/logic/wpd-to-pdf-page.ts @@ -0,0 +1,188 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +const ACCEPTED_EXTENSIONS = ['.wpd']; +const FILETYPE_NAME = 'WPD'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + updateUI(); + }; + + const convert = async () => { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${FILETYPE_NAME} file.`); + return; + } + + try { + const converter = getLibreOfficeConverter(); + + showLoader('Loading engine...'); + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + downloadFile(pdfBlob, `${baseName}.pdf`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${file.name} to PDF.`, 'success', () => resetState()); + } else { + showLoader('Converting multiple files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + zip.file(`${baseName}.pdf`, pdfBlob); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE_NAME.toLowerCase()}-to-pdf.zip`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${state.files.length} files to PDF.`, 'success', () => resetState()); + } + } catch (e: any) { + hideLoader(); + console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e); + showAlert('Error', `An error occurred during conversion. Error: ${e.message}`); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter(file => { + const ext = '.' + file.name.split('.').pop()?.toLowerCase(); + return ACCEPTED_EXTENSIONS.includes(ext); + }); + if (validFiles.length > 0) { + state.files = [...state.files, ...validFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => fileInput.click()); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', resetState); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } + + updateUI(); +}); diff --git a/src/js/logic/wps-to-pdf-page.ts b/src/js/logic/wps-to-pdf-page.ts new file mode 100644 index 0000000..641bae2 --- /dev/null +++ b/src/js/logic/wps-to-pdf-page.ts @@ -0,0 +1,188 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js'; + +const ACCEPTED_EXTENSIONS = ['.wps']; +const FILETYPE_NAME = 'WPS'; + +document.addEventListener('DOMContentLoaded', () => { + state.files = []; + + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const convertOptions = document.getElementById('convert-options'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + const processBtn = document.getElementById('process-btn'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!convertOptions) return; + + if (state.files.length > 0) { + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + } + if (fileControls) fileControls.classList.remove('hidden'); + convertOptions.classList.remove('hidden'); + } else { + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + if (fileControls) fileControls.classList.add('hidden'); + convertOptions.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + updateUI(); + }; + + const convert = async () => { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${FILETYPE_NAME} file.`); + return; + } + + try { + const converter = getLibreOfficeConverter(); + + showLoader('Loading engine...'); + await converter.initialize((progress: LoadProgress) => { + showLoader(progress.message, progress.percent); + }); + + if (state.files.length === 1) { + const file = state.files[0]; + showLoader(`Converting ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + downloadFile(pdfBlob, `${baseName}.pdf`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${file.name} to PDF.`, 'success', () => resetState()); + } else { + showLoader('Converting multiple files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); + const pdfBlob = await converter.convertToPdf(file); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + zip.file(`${baseName}.pdf`, pdfBlob); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE_NAME.toLowerCase()}-to-pdf.zip`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${state.files.length} files to PDF.`, 'success', () => resetState()); + } + } catch (e: any) { + hideLoader(); + console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e); + showAlert('Error', `An error occurred during conversion. Error: ${e.message}`); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter(file => { + const ext = '.' + file.name.split('.').pop()?.toLowerCase(); + return ACCEPTED_EXTENSIONS.includes(ext); + }); + if (validFiles.length > 0) { + state.files = [...state.files, ...validFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => fileInput.click()); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', resetState); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } + + updateUI(); +}); diff --git a/src/js/logic/xml-to-pdf-page.ts b/src/js/logic/xml-to-pdf-page.ts new file mode 100644 index 0000000..382ab5a --- /dev/null +++ b/src/js/logic/xml-to-pdf-page.ts @@ -0,0 +1,181 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + formatBytes, +} from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { convertXmlToPdf } from '../utils/xml-to-pdf.js'; + +const ACCEPTED_EXTENSIONS = ['.xml']; +const FILETYPE_NAME = 'XML'; + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!fileDisplayArea || !processBtn || !fileControls) return; + + if (state.files.length > 0) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + fileControls.classList.remove('hidden'); + processBtn.classList.remove('hidden'); + } else { + fileDisplayArea.innerHTML = ''; + fileControls.classList.add('hidden'); + processBtn.classList.add('hidden'); + } + }; + + const resetState = () => { + state.files = []; + updateUI(); + }; + + const convert = async () => { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${FILETYPE_NAME} file.`); + return; + } + + try { + if (state.files.length === 1) { + const file = state.files[0]; + const pdfBlob = await convertXmlToPdf(file, { + onProgress: (percent, message) => { + showLoader(message, percent); + } + }); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + downloadFile(pdfBlob, `${baseName}.pdf`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${file.name} to PDF.`, 'success', () => resetState()); + } else { + showLoader('Converting multiple files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + const pdfBlob = await convertXmlToPdf(file, { + onProgress: (percent, message) => { + showLoader(`File ${i + 1}/${state.files.length}: ${message}`, percent); + } + }); + + const baseName = file.name.replace(/\.[^/.]+$/, ''); + zip.file(`${baseName}.pdf`, pdfBlob); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE_NAME.toLowerCase()}-to-pdf.zip`); + + hideLoader(); + showAlert('Conversion Complete', `Successfully converted ${state.files.length} files to PDF.`, 'success', () => resetState()); + } + } catch (err) { + hideLoader(); + const message = err instanceof Error ? err.message : 'Unknown error'; + console.error(`[${FILETYPE_NAME}ToPDF] Error:`, err); + showAlert('Error', `An error occurred during conversion. Error: ${message}`); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter(file => { + const ext = '.' + file.name.split('.').pop()?.toLowerCase(); + return ACCEPTED_EXTENSIONS.includes(ext); + }); + if (validFiles.length > 0) { + state.files = [...state.files, ...validFiles]; + updateUI(); + } + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + handleFileSelect(e.dataTransfer?.files ?? null); + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => fileInput.click()); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', resetState); + } + + if (processBtn) { + processBtn.addEventListener('click', convert); + } +}); diff --git a/src/js/logic/xps-to-pdf-page.ts b/src/js/logic/xps-to-pdf-page.ts new file mode 100644 index 0000000..a0af021 --- /dev/null +++ b/src/js/logic/xps-to-pdf-page.ts @@ -0,0 +1,213 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { downloadFile, formatBytes } from '../utils/helpers.js'; +import { state } from '../state.js'; +import { createIcons, icons } from 'lucide'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; +import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; + +const FILETYPE = 'xps'; +const EXTENSIONS = ['.xps', '.oxps']; +const TOOL_NAME = 'XPS'; + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); + const fileDisplayArea = document.getElementById('file-display-area'); + const fileControls = document.getElementById('file-controls'); + const addMoreBtn = document.getElementById('add-more-btn'); + const clearFilesBtn = document.getElementById('clear-files-btn'); + const backBtn = document.getElementById('back-to-tools'); + + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } + + const updateUI = async () => { + if (!fileDisplayArea || !processBtn || !fileControls) return; + + if (state.files.length > 0) { + fileDisplayArea.innerHTML = ''; + + for (let index = 0; index < state.files.length; index++) { + const file = state.files[index]; + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col overflow-hidden'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = formatBytes(file.size); + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = + 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.files = state.files.filter((_, i) => i !== index); + updateUI(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + } + + createIcons({ icons }); + fileControls.classList.remove('hidden'); + processBtn.classList.remove('hidden'); + (processBtn as HTMLButtonElement).disabled = false; + } else { + fileDisplayArea.innerHTML = ''; + fileControls.classList.add('hidden'); + processBtn.classList.add('hidden'); + (processBtn as HTMLButtonElement).disabled = true; + } + }; + + const resetState = () => { + state.files = []; + state.pdfDoc = null; + updateUI(); + }; + + const convertToPdf = async () => { + try { + if (state.files.length === 0) { + showAlert('No Files', `Please select at least one ${TOOL_NAME} file.`); + return; + } + + showLoader('Loading engine...'); + const pymupdf = await loadPyMuPDF(); + + if (state.files.length === 1) { + const originalFile = state.files[0]; + showLoader(`Converting ${originalFile.name}...`); + + const pdfBlob = await pymupdf.convertToPdf(originalFile, { + filetype: FILETYPE, + }); + const fileName = originalFile.name.replace(/\.[^.]+$/, '') + '.pdf'; + + downloadFile(pdfBlob, fileName); + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${originalFile.name} to PDF.`, + 'success', + () => resetState() + ); + } else { + showLoader('Converting files...'); + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (let i = 0; i < state.files.length; i++) { + const file = state.files[i]; + showLoader( + `Converting ${i + 1}/${state.files.length}: ${file.name}...` + ); + + const pdfBlob = await pymupdf.convertToPdf(file, { + filetype: FILETYPE, + }); + const baseName = file.name.replace(/\.[^.]+$/, ''); + const pdfBuffer = await pdfBlob.arrayBuffer(); + zip.file(`${baseName}.pdf`, pdfBuffer); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, `${FILETYPE}-converted.zip`); + + hideLoader(); + + showAlert( + 'Conversion Complete', + `Successfully converted ${state.files.length} ${TOOL_NAME} file(s) to PDF.`, + 'success', + () => resetState() + ); + } + } catch (e: any) { + console.error(`[${TOOL_NAME}2PDF] ERROR:`, e); + hideLoader(); + showAlert( + 'Error', + `An error occurred during conversion. Error: ${e.message}` + ); + } + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0) { + state.files = [...state.files, ...Array.from(files)]; + updateUI(); + } + }; + + if (fileInput && dropZone) { + fileInput.addEventListener('change', (e) => { + handleFileSelect((e.target as HTMLInputElement).files); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const files = e.dataTransfer?.files; + if (files && files.length > 0) { + const validFiles = Array.from(files).filter((f) => { + const name = f.name.toLowerCase(); + return EXTENSIONS.some((ext) => name.endsWith(ext)); + }); + if (validFiles.length > 0) { + const dataTransfer = new DataTransfer(); + validFiles.forEach((f) => dataTransfer.items.add(f)); + handleFileSelect(dataTransfer.files); + } + } + }); + + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } + + if (addMoreBtn) { + addMoreBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + if (clearFilesBtn) { + clearFilesBtn.addEventListener('click', () => { + resetState(); + }); + } + + if (processBtn) { + processBtn.addEventListener('click', convertToPdf); + } +}); diff --git a/src/js/main.ts b/src/js/main.ts index 5c00592..8474670 100644 --- a/src/js/main.ts +++ b/src/js/main.ts @@ -1,50 +1,41 @@ import { categories } from './config/tools.js'; -import { dom, switchView, hideAlert, showLoader, hideLoader, showAlert } from './ui.js'; -import { state, resetState } from './state.js'; +import { dom, switchView, hideAlert } from './ui.js'; import { ShortcutsManager } from './logic/shortcuts.js'; import { createIcons, icons } from 'lucide'; +import '@phosphor-icons/web/regular'; import * as pdfjsLib from 'pdfjs-dist'; import '../css/styles.css'; import { formatShortcutDisplay, formatStars } from './utils/helpers.js'; -import { APP_VERSION, injectVersion } from '../version.js'; -import { initI18n, applyTranslations, rewriteLinks, injectLanguageSwitcher, createLanguageSwitcher, t } from './i18n/index.js'; +import { APP_VERSION } from '../version.js'; +import { + initI18n, + applyTranslations, + rewriteLinks, + injectLanguageSwitcher, + createLanguageSwitcher, + t, +} from './i18n/index.js'; +declare const __BRAND_NAME__: string; const init = async () => { await initI18n(); injectLanguageSwitcher(); applyTranslations(); - pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); + pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url + ).toString(); if (__SIMPLE_MODE__) { const hideBrandingSections = () => { - const nav = document.querySelector('nav'); - if (nav) { - nav.style.display = 'none'; - - const simpleNav = document.createElement('nav'); - simpleNav.className = - 'bg-gray-800 border-b border-gray-700 sticky top-0 z-30'; - simpleNav.innerHTML = ` -
-
- -
-
- `; - document.body.insertBefore(simpleNav, document.body.firstChild); - } - const heroSection = document.getElementById('hero-section'); if (heroSection) { heroSection.style.display = 'none'; } - const githubLink = document.querySelector('a[href*="github.com/alam00000/bentopdf"]'); + const githubLink = document.querySelector( + 'a[href*="github.com/alam00000/bentopdf"]' + ); if (githubLink) { (githubLink as HTMLElement).style.display = 'none'; } @@ -79,68 +70,31 @@ const init = async () => { } // Hide "Used by companies" section - const usedBySection = document.querySelector('.hide-section') as HTMLElement; + const usedBySection = document.querySelector( + '.hide-section' + ) as HTMLElement; if (usedBySection) { usedBySection.style.display = 'none'; } - const footer = document.querySelector('footer'); - if (footer && !document.querySelector('[data-simple-footer]')) { - footer.style.display = 'none'; - - const simpleFooter = document.createElement('footer'); - simpleFooter.className = 'mt-16 border-t-2 border-gray-700 py-8'; - simpleFooter.setAttribute('data-simple-footer', 'true'); - simpleFooter.innerHTML = ` -
-
-
-
- Bento PDF Logo - BentoPDF -
-

- © 2025 BentoPDF. All rights reserved. -

-

- Version ${APP_VERSION} -

-
-
-
-
- `; - document.body.appendChild(simpleFooter); - - const langContainer = simpleFooter.querySelector('#simple-mode-lang-switcher'); - if (langContainer) { - const switcher = createLanguageSwitcher(); - const dropdown = switcher.querySelector('div[role="menu"]'); - if (dropdown) { - dropdown.classList.remove('mt-2'); - dropdown.classList.add('bottom-full', 'mb-2'); - } - langContainer.appendChild(switcher); - } - } - const sectionDividers = document.querySelectorAll('.section-divider'); sectionDividers.forEach((divider) => { (divider as HTMLElement).style.display = 'none'; }); - document.title = 'BentoPDF - PDF Tools'; + const brandName = __BRAND_NAME__ || 'BentoPDF'; + document.title = `${brandName} - ${t('simpleMode.title')}`; const toolsHeader = document.getElementById('tools-header'); if (toolsHeader) { const title = toolsHeader.querySelector('h2'); const subtitle = toolsHeader.querySelector('p'); if (title) { - title.textContent = 'PDF Tools'; + title.textContent = t('simpleMode.title'); title.className = 'text-4xl md:text-5xl font-bold text-white mb-3'; } if (subtitle) { - subtitle.textContent = 'Select a tool to get started'; + subtitle.textContent = t('simpleMode.subtitle'); subtitle.className = 'text-lg text-gray-400'; } } @@ -165,13 +119,14 @@ const init = async () => { if (shortcutSettingsBtn) shortcutSettingsBtn.style.display = 'none'; } else { if (keyboardShortcutBtn) { - keyboardShortcutBtn.textContent = navigator.userAgent.toUpperCase().includes('MAC') + keyboardShortcutBtn.textContent = navigator.userAgent + .toUpperCase() + .includes('MAC') ? '⌘ + K' : 'Ctrl + K'; } } - const categoryTranslationKeys: Record = { 'Popular Tools': 'tools:categories.popularTools', 'Edit & Annotate': 'tools:categories.editAnnotate', @@ -183,6 +138,7 @@ const init = async () => { }; const toolTranslationKeys: Record = { + 'PDF Workflow Builder': 'tools:pdfWorkflow', 'PDF Multi Tool': 'tools:pdfMultiTool', 'Merge PDF': 'tools:mergePdf', 'Split PDF': 'tools:splitPdf', @@ -203,11 +159,12 @@ const init = async () => { 'Background Color': 'tools:backgroundColor', 'Change Text Color': 'tools:changeTextColor', 'Add Stamps': 'tools:addStamps', + 'Bates Numbering': 'tools:batesNumbering', 'Remove Annotations': 'tools:removeAnnotations', 'PDF Form Filler': 'tools:pdfFormFiller', 'Create PDF Form': 'tools:createPdfForm', 'Remove Blank Pages': 'tools:removeBlankPages', - 'Image to PDF': 'tools:imageToPdf', + 'Images to PDF': 'tools:imageToPdf', 'PNG to PDF': 'tools:pngToPdf', 'WebP to PDF': 'tools:webpToPdf', 'SVG to PDF': 'tools:svgToPdf', @@ -233,6 +190,7 @@ const init = async () => { 'Add Blank Page': 'tools:addBlankPage', 'Reverse Pages': 'tools:reversePages', 'Rotate PDF': 'tools:rotatePdf', + 'Rotate by Custom Degrees': 'tools:rotateCustom', 'N-Up PDF': 'tools:nUpPdf', 'Combine to Single Page': 'tools:combineToSinglePage', 'View Metadata': 'tools:viewMetadata', @@ -251,24 +209,126 @@ const init = async () => { 'Flatten PDF': 'tools:flattenPdf', 'Remove Metadata': 'tools:removeMetadata', 'Change Permissions': 'tools:changePermissions', + 'Email to PDF': 'tools:emailToPdf', + 'Font to Outline': 'tools:fontToOutline', + 'Deskew PDF': 'tools:deskewPdf', + 'Digital Signature': 'tools:digitalSignPdf', + 'Validate Signature': 'tools:validateSignaturePdf', + 'Scanner Effect': 'tools:scannerEffect', + 'Adjust Colors': 'tools:adjustColors', + 'Markdown to PDF': 'tools:markdownToPdf', + 'PDF Booklet': 'tools:pdfBooklet', + 'Word to PDF': 'tools:wordToPdf', + 'Excel to PDF': 'tools:excelToPdf', + 'PowerPoint to PDF': 'tools:powerpointToPdf', + 'XPS to PDF': 'tools:xpsToPdf', + 'MOBI to PDF': 'tools:mobiToPdf', + 'EPUB to PDF': 'tools:epubToPdf', + 'FB2 to PDF': 'tools:fb2ToPdf', + 'CBZ to PDF': 'tools:cbzToPdf', + 'WPD to PDF': 'tools:wpdToPdf', + 'WPS to PDF': 'tools:wpsToPdf', + 'XML to PDF': 'tools:xmlToPdf', + 'Pages to PDF': 'tools:pagesToPdf', + 'ODG to PDF': 'tools:odgToPdf', + 'ODS to PDF': 'tools:odsToPdf', + 'ODP to PDF': 'tools:odpToPdf', + 'PUB to PDF': 'tools:pubToPdf', + 'VSD to PDF': 'tools:vsdToPdf', + 'PSD to PDF': 'tools:psdToPdf', + 'ODT to PDF': 'tools:odtToPdf', + 'CSV to PDF': 'tools:csvToPdf', + 'RTF to PDF': 'tools:rtfToPdf', + 'PDF to SVG': 'tools:pdfToSvg', + 'PDF to CSV': 'tools:pdfToCsv', + 'PDF to Excel': 'tools:pdfToExcel', + 'PDF to Text': 'tools:pdfToText', + 'Extract Tables': 'tools:extractTables', + 'PDF to Word': 'tools:pdfToWord', + 'Extract Images': 'tools:extractImages', + 'PDF to Markdown': 'tools:pdfToMarkdown', + 'Prepare PDF for AI': 'tools:preparePdfForAi', + 'PDF OCG': 'tools:pdfOcg', + 'PDF to PDF/A': 'tools:pdfToPdfa', + 'Rasterize PDF': 'tools:rasterizePdf', }; // Homepage-only tool grid rendering (not used on individual tool pages) if (dom.toolGrid) { dom.toolGrid.textContent = ''; + let collapsedCategories: string[] = []; + try { + const stored = localStorage.getItem('collapsedCategories'); + if (stored) collapsedCategories = JSON.parse(stored); + } catch { + localStorage.removeItem('collapsedCategories'); + } + + function saveCollapsedCategories() { + localStorage.setItem( + 'collapsedCategories', + JSON.stringify(collapsedCategories) + ); + } + categories.forEach((category) => { const categoryGroup = document.createElement('div'); categoryGroup.className = 'category-group col-span-full'; - const title = document.createElement('h2'); - title.className = 'text-xl font-bold text-indigo-400 mb-4 mt-8 first:mt-0 text-white'; + const header = document.createElement('button'); + header.className = 'category-header'; + header.type = 'button'; + + const title = document.createElement('span'); const categoryKey = categoryTranslationKeys[category.name]; title.textContent = categoryKey ? t(categoryKey) : category.name; + const chevron = document.createElement('i'); + chevron.setAttribute('data-lucide', 'chevron-down'); + chevron.className = + 'category-chevron w-5 h-5 text-gray-400 transition-transform duration-300'; + + header.append(title, chevron); + const toolsContainer = document.createElement('div'); toolsContainer.className = - 'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-6'; + 'category-tools grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-6'; + + const isCollapsed = collapsedCategories.includes(category.name); + if (isCollapsed) { + categoryGroup.classList.add('collapsed'); + toolsContainer.style.maxHeight = '0px'; + } + + toolsContainer.addEventListener('transitionend', (e) => { + if ((e as TransitionEvent).propertyName !== 'max-height') return; + if (!categoryGroup.classList.contains('collapsed')) { + toolsContainer.style.maxHeight = 'none'; + toolsContainer.style.overflow = 'visible'; + } + }); + + header.addEventListener('click', () => { + const collapsed = categoryGroup.classList.toggle('collapsed'); + if (collapsed) { + toolsContainer.style.maxHeight = toolsContainer.scrollHeight + 'px'; + toolsContainer.style.overflow = 'hidden'; + requestAnimationFrame(() => { + toolsContainer.style.maxHeight = '0px'; + }); + if (!collapsedCategories.includes(category.name)) { + collapsedCategories.push(category.name); + } + } else { + toolsContainer.style.overflow = 'hidden'; + toolsContainer.style.maxHeight = toolsContainer.scrollHeight + 'px'; + collapsedCategories = collapsedCategories.filter( + (n) => n !== category.name + ); + } + saveCollapsedCategories(); + }); category.tools.forEach((tool) => { let toolCard: HTMLDivElement | HTMLAnchorElement; @@ -287,7 +347,12 @@ const init = async () => { const icon = document.createElement('i'); icon.className = 'w-10 h-10 mb-3 text-indigo-400'; - icon.setAttribute('data-lucide', tool.icon); + + if (tool.icon.startsWith('ph-')) { + icon.className = `ph ${tool.icon} text-4xl mb-3 text-indigo-400`; + } else { + icon.setAttribute('data-lucide', tool.icon); + } const toolName = document.createElement('h3'); toolName.className = 'font-semibold text-white'; @@ -299,60 +364,94 @@ const init = async () => { if (tool.subtitle) { const toolSubtitle = document.createElement('p'); toolSubtitle.className = 'text-xs text-gray-400 mt-1 px-2'; - toolSubtitle.textContent = toolKey ? t(`${toolKey}.subtitle`) : tool.subtitle; + toolSubtitle.textContent = toolKey + ? t(`${toolKey}.subtitle`) + : tool.subtitle; toolCard.appendChild(toolSubtitle); } toolsContainer.appendChild(toolCard); }); - categoryGroup.append(title, toolsContainer); + categoryGroup.append(header, toolsContainer); dom.toolGrid.appendChild(categoryGroup); + + if (!isCollapsed) { + toolsContainer.style.maxHeight = 'none'; + toolsContainer.style.overflow = 'visible'; + } }); const searchBar = document.getElementById('search-bar'); const categoryGroups = dom.toolGrid.querySelectorAll('.category-group'); - const fuzzyMatch = (searchTerm: string, targetText: string): boolean => { - if (!searchTerm) return true; - - let searchIndex = 0; - let targetIndex = 0; - - while (searchIndex < searchTerm.length && targetIndex < targetText.length) { - if (searchTerm[searchIndex] === targetText[targetIndex]) { - searchIndex++; - } - targetIndex++; - } - - return searchIndex === searchTerm.length; - }; + const searchResultsContainer = document.createElement('div'); + searchResultsContainer.id = 'search-results'; + searchResultsContainer.className = + 'hidden grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-6 col-span-full'; + dom.toolGrid.insertBefore(searchResultsContainer, dom.toolGrid.firstChild); searchBar.addEventListener('input', () => { // @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message const searchTerm = searchBar.value.toLowerCase().trim(); + if (!searchTerm) { + searchResultsContainer.classList.add('hidden'); + searchResultsContainer.innerHTML = ''; + categoryGroups.forEach((group) => { + (group as HTMLElement).style.display = ''; + const toolCards = group.querySelectorAll('.tool-card'); + toolCards.forEach((card) => { + (card as HTMLElement).style.display = ''; + }); + }); + return; + } + categoryGroups.forEach((group) => { - const toolCards = group.querySelectorAll('.tool-card'); - let visibleToolsInCategory = 0; + (group as HTMLElement).style.display = 'none'; + }); + + searchResultsContainer.innerHTML = ''; + searchResultsContainer.classList.remove('hidden'); + + const seenToolIds = new Set(); + const allTools: HTMLElement[] = []; + + categoryGroups.forEach((group) => { + const toolCards = Array.from(group.querySelectorAll('.tool-card')); toolCards.forEach((card) => { - const toolName = card.querySelector('h3').textContent.toLowerCase(); - const toolSubtitle = - card.querySelector('p')?.textContent.toLowerCase() || ''; + const toolName = ( + card.querySelector('h3')?.textContent || '' + ).toLowerCase(); + const toolSubtitle = ( + card.querySelector('p')?.textContent || '' + ).toLowerCase(); + const toolHref = + (card as HTMLAnchorElement).href || + (card as HTMLElement).dataset.toolId || + ''; + + const toolId = + toolHref.split('/').pop()?.replace('.html', '') || toolName; const isMatch = - fuzzyMatch(searchTerm, toolName) || fuzzyMatch(searchTerm, toolSubtitle); + toolName.includes(searchTerm) || toolSubtitle.includes(searchTerm); + const isDuplicate = seenToolIds.has(toolId); - card.classList.toggle('hidden', !isMatch); - if (isMatch) { - visibleToolsInCategory++; + if (isMatch && !isDuplicate) { + seenToolIds.add(toolId); + allTools.push(card.cloneNode(true) as HTMLElement); } }); - - group.classList.toggle('hidden', visibleToolsInCategory === 0); }); + + allTools.forEach((tool) => { + searchResultsContainer.appendChild(tool); + }); + + createIcons({ icons }); }); window.addEventListener('keydown', function (e) { @@ -403,31 +502,29 @@ const init = async () => { createIcons({ icons }); console.log('Please share our tool and share the love!'); - const githubStarsElements = [ document.getElementById('github-stars-desktop'), - document.getElementById('github-stars-mobile') + document.getElementById('github-stars-mobile'), ]; - if (githubStarsElements.some(el => el) && !__SIMPLE_MODE__) { + if (githubStarsElements.some((el) => el) && !__SIMPLE_MODE__) { fetch('https://api.github.com/repos/alam00000/bentopdf') .then((response) => response.json()) .then((data) => { if (data.stargazers_count !== undefined) { const formattedStars = formatStars(data.stargazers_count); - githubStarsElements.forEach(el => { + githubStarsElements.forEach((el) => { if (el) el.textContent = formattedStars; }); } }) .catch(() => { - githubStarsElements.forEach(el => { + githubStarsElements.forEach((el) => { if (el) el.textContent = '-'; }); }); } - // Initialize Shortcuts System ShortcutsManager.init(); @@ -435,9 +532,13 @@ const init = async () => { const shortcutsTabBtn = document.getElementById('shortcuts-tab-btn'); const preferencesTabBtn = document.getElementById('preferences-tab-btn'); const shortcutsTabContent = document.getElementById('shortcuts-tab-content'); - const preferencesTabContent = document.getElementById('preferences-tab-content'); + const preferencesTabContent = document.getElementById( + 'preferences-tab-content' + ); const shortcutsTabFooter = document.getElementById('shortcuts-tab-footer'); - const preferencesTabFooter = document.getElementById('preferences-tab-footer'); + const preferencesTabFooter = document.getElementById( + 'preferences-tab-footer' + ); const resetShortcutsBtn = document.getElementById('reset-shortcuts-btn'); if (shortcutsTabBtn && preferencesTabBtn) { @@ -467,11 +568,12 @@ const init = async () => { } // Full-width toggle functionality - const fullWidthToggle = document.getElementById('full-width-toggle') as HTMLInputElement; + const fullWidthToggle = document.getElementById( + 'full-width-toggle' + ) as HTMLInputElement; const toolInterface = document.getElementById('tool-interface'); - // Load saved preference - const savedFullWidth = localStorage.getItem('fullWidthMode') === 'true'; + const savedFullWidth = localStorage.getItem('fullWidthMode') !== 'false'; if (fullWidthToggle) { fullWidthToggle.checked = savedFullWidth; applyFullWidthMode(savedFullWidth); @@ -493,7 +595,10 @@ const init = async () => { uploader.classList.remove('max-w-2xl', 'max-w-5xl'); } else { // Restore original max-width (most are max-w-2xl, add-stamps is max-w-5xl) - if (!uploader.classList.contains('max-w-2xl') && !uploader.classList.contains('max-w-5xl')) { + if ( + !uploader.classList.contains('max-w-2xl') && + !uploader.classList.contains('max-w-5xl') + ) { uploader.classList.add('max-w-2xl'); } } @@ -508,6 +613,35 @@ const init = async () => { }); } + const compactModeToggle = document.getElementById( + 'compact-mode-toggle' + ) as HTMLInputElement; + + const savedCompactMode = localStorage.getItem('compactMode') === 'true'; + if (compactModeToggle) { + compactModeToggle.checked = savedCompactMode; + } + applyCompactMode(savedCompactMode); + + function applyCompactMode(enabled: boolean) { + if (dom.toolGrid) { + dom.toolGrid.classList.toggle('compact-mode', enabled); + dom.toolGrid + .querySelectorAll('.category-group:not(.collapsed) .category-tools') + .forEach((container) => { + (container as HTMLElement).style.maxHeight = 'none'; + }); + } + } + + if (compactModeToggle) { + compactModeToggle.addEventListener('change', (e) => { + const enabled = (e.target as HTMLInputElement).checked; + localStorage.setItem('compactMode', enabled.toString()); + applyCompactMode(enabled); + }); + } + // Shortcuts UI Handlers if (dom.openShortcutsBtn) { dom.openShortcutsBtn.addEventListener('click', () => { @@ -614,26 +748,36 @@ const init = async () => { } // Reserved shortcuts that commonly conflict with browser/OS functions - const RESERVED_SHORTCUTS: Record = { - 'mod+w': { mac: 'Closes tab', windows: 'Closes tab' }, - 'mod+t': { mac: 'Opens new tab', windows: 'Opens new tab' }, - 'mod+n': { mac: 'Opens new window', windows: 'Opens new window' }, - 'mod+shift+n': { mac: 'Opens incognito window', windows: 'Opens incognito window' }, - 'mod+q': { mac: 'Quits application (cannot be overridden)' }, - 'mod+m': { mac: 'Minimizes window' }, - 'mod+h': { mac: 'Hides window' }, - 'mod+r': { mac: 'Reloads page', windows: 'Reloads page' }, - 'mod+shift+r': { mac: 'Hard reloads page', windows: 'Hard reloads page' }, - 'mod+l': { mac: 'Focuses address bar', windows: 'Focuses address bar' }, - 'mod+d': { mac: 'Bookmarks page', windows: 'Bookmarks page' }, - 'mod+shift+t': { mac: 'Reopens closed tab', windows: 'Reopens closed tab' }, - 'mod+shift+w': { mac: 'Closes window', windows: 'Closes window' }, - 'mod+tab': { mac: 'Switches tabs', windows: 'Switches apps' }, - 'alt+f4': { windows: 'Closes window' }, - 'ctrl+tab': { mac: 'Switches tabs', windows: 'Switches tabs' }, - }; + const RESERVED_SHORTCUTS: Record = + { + 'mod+w': { mac: 'Closes tab', windows: 'Closes tab' }, + 'mod+t': { mac: 'Opens new tab', windows: 'Opens new tab' }, + 'mod+n': { mac: 'Opens new window', windows: 'Opens new window' }, + 'mod+shift+n': { + mac: 'Opens incognito window', + windows: 'Opens incognito window', + }, + 'mod+q': { mac: 'Quits application (cannot be overridden)' }, + 'mod+m': { mac: 'Minimizes window' }, + 'mod+h': { mac: 'Hides window' }, + 'mod+r': { mac: 'Reloads page', windows: 'Reloads page' }, + 'mod+shift+r': { mac: 'Hard reloads page', windows: 'Hard reloads page' }, + 'mod+l': { mac: 'Focuses address bar', windows: 'Focuses address bar' }, + 'mod+d': { mac: 'Bookmarks page', windows: 'Bookmarks page' }, + 'mod+shift+t': { + mac: 'Reopens closed tab', + windows: 'Reopens closed tab', + }, + 'mod+shift+w': { mac: 'Closes window', windows: 'Closes window' }, + 'mod+tab': { mac: 'Switches tabs', windows: 'Switches apps' }, + 'alt+f4': { windows: 'Closes window' }, + 'ctrl+tab': { mac: 'Switches tabs', windows: 'Switches tabs' }, + }; - function getReservedShortcutWarning(combo: string, isMac: boolean): string | null { + function getReservedShortcutWarning( + combo: string, + isMac: boolean + ): string | null { const reserved = RESERVED_SHORTCUTS[combo]; if (!reserved) return null; @@ -643,9 +787,19 @@ const init = async () => { return description; } - function showWarningModal(title: string, message: string, confirmMode: boolean = true): Promise { + function showWarningModal( + title: string, + message: string, + confirmMode: boolean = true + ): Promise { return new Promise((resolve) => { - if (!dom.warningModal || !dom.warningTitle || !dom.warningMessage || !dom.warningCancelBtn || !dom.warningConfirmBtn) { + if ( + !dom.warningModal || + !dom.warningTitle || + !dom.warningMessage || + !dom.warningCancelBtn || + !dom.warningConfirmBtn + ) { resolve(confirmMode ? confirm(message) : (alert(message), true)); return; } @@ -684,15 +838,19 @@ const init = async () => { dom.warningCancelBtn.addEventListener('click', handleCancel); // Close on backdrop click - dom.warningModal.addEventListener('click', (e) => { - if (e.target === dom.warningModal) { - if (confirmMode) { - handleCancel(); - } else { - handleConfirm(); + dom.warningModal.addEventListener( + 'click', + (e) => { + if (e.target === dom.warningModal) { + if (confirmMode) { + handleCancel(); + } else { + handleConfirm(); + } } - } - }, { once: true }); + }, + { once: true } + ); }); } @@ -711,14 +869,15 @@ const init = async () => { const allShortcuts = ShortcutsManager.getAllShortcuts(); const isMac = navigator.userAgent.toUpperCase().includes('MAC'); - const allTools = categories.flatMap(c => c.tools); + const allTools = categories.flatMap((c) => c.tools); - categories.forEach(category => { + categories.forEach((category) => { const section = document.createElement('div'); section.className = 'category-section mb-6 last:mb-0'; const header = document.createElement('h3'); - header.className = 'text-gray-400 text-xs font-bold uppercase tracking-wider mb-3 pl-1'; + header.className = + 'text-gray-400 text-xs font-bold uppercase tracking-wider mb-3 pl-1'; // Translate category name const categoryKey = categoryTranslationKeys[category.name]; header.textContent = categoryKey ? t(categoryKey) : category.name; @@ -730,20 +889,25 @@ const init = async () => { let hasTools = false; - category.tools.forEach(tool => { + category.tools.forEach((tool) => { hasTools = true; const toolId = getToolId(tool); const currentShortcut = allShortcuts.get(toolId) || ''; const item = document.createElement('div'); - item.className = 'shortcut-item flex items-center justify-between p-3 bg-gray-900 rounded-lg border border-gray-700 hover:border-gray-600 transition-colors'; + item.className = + 'shortcut-item flex items-center justify-between p-3 bg-gray-900 rounded-lg border border-gray-700 hover:border-gray-600 transition-colors'; const left = document.createElement('div'); left.className = 'flex items-center gap-3'; const icon = document.createElement('i'); - icon.className = 'w-5 h-5 text-indigo-400'; - icon.setAttribute('data-lucide', tool.icon); + if (tool.icon.startsWith('ph-')) { + icon.className = `ph ${tool.icon} w-5 h-5 text-indigo-400`; + } else { + icon.className = 'w-5 h-5 text-indigo-400'; + icon.setAttribute('data-lucide', tool.icon); + } const name = document.createElement('span'); name.className = 'text-gray-200 font-medium'; @@ -757,13 +921,15 @@ const init = async () => { const input = document.createElement('input'); input.type = 'text'; - input.className = 'shortcut-input w-32 bg-gray-800 border border-gray-600 text-white text-center text-sm rounded px-2 py-1 focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none transition-all'; + input.className = + 'shortcut-input w-32 bg-gray-800 border border-gray-600 text-white text-center text-sm rounded px-2 py-1 focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none transition-all'; input.placeholder = t('settings.clickToSet'); input.value = formatShortcutDisplay(currentShortcut, isMac); input.readOnly = true; const clearBtn = document.createElement('button'); - clearBtn.className = 'absolute -right-2 -top-2 bg-gray-700 hover:bg-red-600 text-white rounded-full p-0.5 hidden group-hover:block shadow-sm'; + clearBtn.className = + 'absolute -right-2 -top-2 bg-gray-700 hover:bg-red-600 text-white rounded-full p-0.5 hidden group-hover:block shadow-sm'; clearBtn.innerHTML = ''; if (currentShortcut) { right.classList.add('group'); @@ -812,7 +978,10 @@ const init = async () => { // Ignore dead keys (used for accented characters on Mac with Option key) if (isDeadKey) { - input.value = formatShortcutDisplay(ShortcutsManager.getShortcut(toolId) || '', isMac); + input.value = formatShortcutDisplay( + ShortcutsManager.getShortcut(toolId) || '', + isMac + ); return; } @@ -828,22 +997,31 @@ const init = async () => { const existingToolId = ShortcutsManager.findToolByShortcut(combo); if (existingToolId && existingToolId !== toolId) { - const existingTool = allTools.find(t => getToolId(t) === existingToolId); + const existingTool = allTools.find( + (t) => getToolId(t) === existingToolId + ); const existingToolName = existingTool?.name || existingToolId; const displayCombo = formatShortcutDisplay(combo, isMac); - const existingToolKey = existingTool ? toolTranslationKeys[existingTool.name] : null; - const translatedToolName = existingToolKey ? t(`${existingToolKey}.name`) : existingToolName; + const existingToolKey = existingTool + ? toolTranslationKeys[existingTool.name] + : null; + const translatedToolName = existingToolKey + ? t(`${existingToolKey}.name`) + : existingToolName; await showWarningModal( t('settings.warnings.alreadyInUse'), `${displayCombo} ${t('settings.warnings.assignedTo')}

` + - `"${translatedToolName}"

` + - t('settings.warnings.chooseDifferent'), + `"${translatedToolName}"

` + + t('settings.warnings.chooseDifferent'), false ); - input.value = formatShortcutDisplay(ShortcutsManager.getShortcut(toolId) || '', isMac); + input.value = formatShortcutDisplay( + ShortcutsManager.getShortcut(toolId) || '', + isMac + ); input.classList.remove('border-indigo-500', 'text-indigo-400'); input.blur(); return; @@ -855,14 +1033,17 @@ const init = async () => { const shouldProceed = await showWarningModal( t('settings.warnings.reserved'), `${displayCombo} ${t('settings.warnings.commonlyUsed')}

` + - `"${reservedWarning}"

` + - `${t('settings.warnings.unreliable')}

` + - t('settings.warnings.useAnyway') + `"${reservedWarning}"

` + + `${t('settings.warnings.unreliable')}

` + + t('settings.warnings.useAnyway') ); if (!shouldProceed) { // Revert display - input.value = formatShortcutDisplay(ShortcutsManager.getShortcut(toolId) || '', isMac); + input.value = formatShortcutDisplay( + ShortcutsManager.getShortcut(toolId) || '', + isMac + ); input.classList.remove('border-indigo-500', 'text-indigo-400'); input.blur(); return; @@ -889,7 +1070,10 @@ const init = async () => { }; input.onblur = () => { - input.value = formatShortcutDisplay(ShortcutsManager.getShortcut(toolId) || '', isMac); + input.value = formatShortcutDisplay( + ShortcutsManager.getShortcut(toolId) || '', + isMac + ); input.classList.remove('border-indigo-500', 'text-indigo-400'); }; @@ -928,7 +1112,7 @@ const init = async () => { scrollToTopBtn.addEventListener('click', () => { window.scrollTo({ top: 0, - behavior: 'instant' + behavior: 'instant', }); }); } diff --git a/src/js/sw-register.ts b/src/js/sw-register.ts new file mode 100644 index 0000000..1c21b7a --- /dev/null +++ b/src/js/sw-register.ts @@ -0,0 +1,55 @@ +/** + * Service Worker Registration + * Registers the service worker to enable offline caching + * + * Note: Service Worker is disabled in development mode to prevent + * conflicts with Vite's HMR (Hot Module Replacement) + */ + +// Skip service worker registration in development mode +const isDevelopment = window.location.hostname === 'localhost' || + window.location.hostname === '127.0.0.1' || + window.location.port !== ''; + +if (isDevelopment) { + console.log('[Dev Mode] Service Worker registration skipped in development'); + console.log('Service Worker will be active in production builds'); +} else if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + const swPath = `${import.meta.env.BASE_URL}sw.js`; + console.log('[SW] Registering Service Worker at:', swPath); + navigator.serviceWorker + .register(swPath) + .then((registration) => { + console.log('[SW] Service Worker registered successfully:', registration.scope); + + setInterval(() => { + registration.update(); + }, 24 * 60 * 60 * 1000); + + registration.addEventListener('updatefound', () => { + const newWorker = registration.installing; + if (newWorker) { + newWorker.addEventListener('statechange', () => { + if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { + console.log('[SW] New version available! Reload to update.'); + + if (confirm('A new version of BentoPDF is available. Reload to update?')) { + newWorker.postMessage({ type: 'SKIP_WAITING' }); + window.location.reload(); + } + } + }); + } + }); + }) + .catch((error) => { + console.error('[SW] Service Worker registration failed:', error); + }); + + navigator.serviceWorker.addEventListener('controllerchange', () => { + console.log('[SW] New service worker activated, reloading...'); + window.location.reload(); + }); + }); +} diff --git a/src/js/types/add-blank-page-type.ts b/src/js/types/add-blank-page-type.ts new file mode 100644 index 0000000..3d2f03c --- /dev/null +++ b/src/js/types/add-blank-page-type.ts @@ -0,0 +1,6 @@ +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; + +export interface AddBlankPageState { + file: File | null; + pdfDoc: PDFLibDocument | null; +} \ No newline at end of file diff --git a/src/js/types/add-watermark-type.ts b/src/js/types/add-watermark-type.ts new file mode 100644 index 0000000..898d5c4 --- /dev/null +++ b/src/js/types/add-watermark-type.ts @@ -0,0 +1,26 @@ +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; + +export interface AddWatermarkState { + file: File | null; + pdfDoc: PDFLibDocument | null; + pdfBytes: Uint8Array | null; + previewCanvas: HTMLCanvasElement | null; + watermarkX: number; // 0–1, percentage from left + watermarkY: number; // 0–1, percentage from top (flipped to bottom for PDF) +} + +export interface PageWatermarkConfig { + type: 'text' | 'image'; + x: number; + y: number; + text: string; + fontSize: number; + color: string; + opacityText: number; + angleText: number; + imageDataUrl: string | null; + imageFile: File | null; + imageScale: number; + opacityImage: number; + angleImage: number; +} diff --git a/src/js/types/adjust-colors-type.ts b/src/js/types/adjust-colors-type.ts new file mode 100644 index 0000000..ddcec5f --- /dev/null +++ b/src/js/types/adjust-colors-type.ts @@ -0,0 +1,10 @@ +export interface AdjustColorsSettings { + brightness: number; + contrast: number; + saturation: number; + hueShift: number; + temperature: number; + tint: number; + gamma: number; + sepia: number; +} diff --git a/src/js/types/alternate-merge-page-type.ts b/src/js/types/alternate-merge-page-type.ts new file mode 100644 index 0000000..488ecb7 --- /dev/null +++ b/src/js/types/alternate-merge-page-type.ts @@ -0,0 +1,5 @@ +export interface AlternateMergeState { + files: File[]; + pdfBytes: Map; + pdfDocs: Map; +} \ No newline at end of file diff --git a/src/js/types/attachment-type.ts b/src/js/types/attachment-type.ts new file mode 100644 index 0000000..a2cc85a --- /dev/null +++ b/src/js/types/attachment-type.ts @@ -0,0 +1,7 @@ +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; + +export interface AddAttachmentState { + file: File | null; + pdfDoc: PDFLibDocument | null; + attachments: File[]; +} \ No newline at end of file diff --git a/src/js/types/background-color-type.ts b/src/js/types/background-color-type.ts new file mode 100644 index 0000000..9235ba8 --- /dev/null +++ b/src/js/types/background-color-type.ts @@ -0,0 +1,6 @@ +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; + +export interface BackgroundColorState { + file: File | null; + pdfDoc: PDFLibDocument | null; +} diff --git a/src/js/types/bates-numbering-type.ts b/src/js/types/bates-numbering-type.ts new file mode 100644 index 0000000..8246dc3 --- /dev/null +++ b/src/js/types/bates-numbering-type.ts @@ -0,0 +1,17 @@ +export interface StylePreset { + template: string; + padding: number; +} + +export type Position = + | 'bottom-center' + | 'bottom-left' + | 'bottom-right' + | 'top-center' + | 'top-left' + | 'top-right'; + +export interface FileEntry { + file: File; + pageCount: number; +} diff --git a/src/js/types/bookmark-pdf-type.ts b/src/js/types/bookmark-pdf-type.ts new file mode 100644 index 0000000..805beb5 --- /dev/null +++ b/src/js/types/bookmark-pdf-type.ts @@ -0,0 +1,179 @@ +import { PDFDocument as PDFLibDocument, PDFRef } from 'pdf-lib'; +import { PDFDocumentProxy, PageViewport } from 'pdfjs-dist'; + +// Core bookmark types +export type BookmarkColor = + | 'red' + | 'blue' + | 'green' + | 'yellow' + | 'purple' + | null; +export type BookmarkStyle = 'bold' | 'italic' | 'bold-italic' | null; + +export interface BookmarkNode { + id: number; + title: string; + page: number; + children: BookmarkNode[]; + color: BookmarkColor | string; + style: BookmarkStyle; + destX: number | null; + destY: number | null; + zoom: string | null; +} + +export type BookmarkTree = BookmarkNode[]; + +// Modal system types +export type ModalFieldType = 'text' | 'select' | 'destination' | 'preview'; + +export interface SelectOption { + value: string; + label: string; +} + +export interface BaseModalField { + name: string; + label: string; +} + +export interface TextModalField extends BaseModalField { + type: 'text'; + placeholder?: string; +} + +export interface SelectModalField extends BaseModalField { + type: 'select'; + options: SelectOption[]; +} + +export interface DestinationModalField extends BaseModalField { + type: 'destination'; + page?: number; + maxPages?: number; +} + +export interface PreviewModalField { + type: 'preview'; + label: string; +} + +export type ModalField = + | TextModalField + | SelectModalField + | DestinationModalField + | PreviewModalField; + +export interface ModalResult { + title?: string; + color?: string; + style?: string; + destPage?: number | null; + destX?: number | null; + destY?: number | null; + zoom?: string | null; + [key: string]: string | number | null | undefined; +} + +export interface ModalDefaultValues { + title?: string; + color?: string; + style?: string; + destPage?: number; + destX?: number | null; + destY?: number | null; + zoom?: string | null; + [key: string]: string | number | null | undefined; +} + +// Destination picking types +export type DestinationCallback = ( + page: number, + pdfX: number, + pdfY: number +) => void; + +export interface DestinationPickingState { + isPickingDestination: boolean; + currentPickingCallback: DestinationCallback | null; + destinationMarker: HTMLDivElement | null; + savedModalOverlay: HTMLDivElement | null; + savedModal: HTMLDivElement | null; + currentViewport: PageViewport | null; +} + +// State types +export interface BookmarkEditorState { + pdfLibDoc: PDFLibDocument | null; + pdfJsDoc: PDFDocumentProxy | null; + currentPage: number; + currentZoom: number; + originalFileName: string; + bookmarkTree: BookmarkTree; + history: BookmarkTree[]; + historyIndex: number; + searchQuery: string; + csvBookmarks: BookmarkTree | null; + jsonBookmarks: BookmarkTree | null; + batchMode: boolean; + selectedBookmarks: Set; + collapsedNodes: Set; +} + +// PDF outline types (from pdfjs-dist) +export interface PDFOutlineItem { + title: string; + dest: string | unknown[] | null; + items?: PDFOutlineItem[]; + color?: Uint8ClampedArray | [number, number, number]; + bold?: boolean; + italic?: boolean; +} + +export interface FlattenedBookmark extends BookmarkNode { + level: number; +} + +// Outline item for PDF creation +export interface OutlineItem { + ref: PDFRef; + dict: { + set: (key: unknown, value: unknown) => void; + }; +} + +// Color mapping types +export type ColorClassMap = Record; + +export const COLOR_CLASSES: ColorClassMap = { + red: 'bg-red-100 border-red-300', + blue: 'bg-blue-100 border-blue-300', + green: 'bg-green-100 border-green-300', + yellow: 'bg-yellow-100 border-yellow-300', + purple: 'bg-purple-100 border-purple-300', +}; + +export const TEXT_COLOR_CLASSES: ColorClassMap = { + red: 'text-red-600', + blue: 'text-blue-600', + green: 'text-green-600', + yellow: 'text-yellow-600', + purple: 'text-purple-600', +}; + +export const HEX_COLOR_MAP: Record = { + red: '#dc2626', + blue: '#2563eb', + green: '#16a34a', + yellow: '#ca8a04', + purple: '#9333ea', +}; + +export const PDF_COLOR_MAP: Record = { + red: [1.0, 0.0, 0.0], + blue: [0.0, 0.0, 1.0], + green: [0.0, 1.0, 0.0], + yellow: [1.0, 1.0, 0.0], + purple: [0.5, 0.0, 0.5], +}; diff --git a/src/js/types/change-permissions-type.ts b/src/js/types/change-permissions-type.ts new file mode 100644 index 0000000..5c1a954 --- /dev/null +++ b/src/js/types/change-permissions-type.ts @@ -0,0 +1,3 @@ +export interface ChangePermissionsState { + file: File | null; +} diff --git a/src/js/types/combine-single-page-type.ts b/src/js/types/combine-single-page-type.ts new file mode 100644 index 0000000..7ea4389 --- /dev/null +++ b/src/js/types/combine-single-page-type.ts @@ -0,0 +1,6 @@ +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; + +export interface CombineSinglePageState { + file: File | null; + pdfDoc: PDFLibDocument | null; +} diff --git a/src/js/types/compare-pdfs-type.ts b/src/js/types/compare-pdfs-type.ts new file mode 100644 index 0000000..d3fa47c --- /dev/null +++ b/src/js/types/compare-pdfs-type.ts @@ -0,0 +1 @@ +export type { CompareState } from '../compare/types.ts'; diff --git a/src/js/types/crop-pdf-type.ts b/src/js/types/crop-pdf-type.ts new file mode 100644 index 0000000..7b8bcf6 --- /dev/null +++ b/src/js/types/crop-pdf-type.ts @@ -0,0 +1,8 @@ +export interface CropperState { + pdfDoc: any; + currentPageNum: number; + cropper: any; + originalPdfBytes: ArrayBuffer | null; + pageCrops: Record; + file: File | null; +} diff --git a/src/js/types/decrypt-pdf-type.ts b/src/js/types/decrypt-pdf-type.ts new file mode 100644 index 0000000..053c177 --- /dev/null +++ b/src/js/types/decrypt-pdf-type.ts @@ -0,0 +1,3 @@ +export interface DecryptPdfState { + files: File[]; +} diff --git a/src/js/types/delete-pages-type.ts b/src/js/types/delete-pages-type.ts new file mode 100644 index 0000000..0f42730 --- /dev/null +++ b/src/js/types/delete-pages-type.ts @@ -0,0 +1,7 @@ +export interface DeletePagesState { + file: File | null; + pdfDoc: any; + pdfJsDoc: any; + totalPages: number; + pagesToDelete: Set; +} diff --git a/src/js/types/digital-sign-type.ts b/src/js/types/digital-sign-type.ts new file mode 100644 index 0000000..84c648e --- /dev/null +++ b/src/js/types/digital-sign-type.ts @@ -0,0 +1,42 @@ +import { forge } from "zgapdfsigner"; + +export interface SignatureInfo { + reason?: string; + location?: string; + contactInfo?: string; + name?: string; +} + +export interface CertificateData { + p12Buffer: ArrayBuffer; + password: string; + certificate: forge.pki.Certificate; +} + +export interface SignPdfOptions { + signatureInfo?: SignatureInfo; + visibleSignature?: VisibleSignatureOptions; +} + +export interface VisibleSignatureOptions { + enabled: boolean; + imageData?: ArrayBuffer; + imageType?: 'png' | 'jpeg' | 'webp'; + x: number; + y: number; + width: number; + height: number; + page: number | string; + text?: string; + textColor?: string; + textSize?: number; +} + +export interface DigitalSignState { + pdfFile: File | null; + pdfBytes: Uint8Array | null; + certFile: File | null; + certData: CertificateData | null; + sigImageData: ArrayBuffer | null; + sigImageType: 'png' | 'jpeg' | 'webp' | null; +} \ No newline at end of file diff --git a/src/js/types/divide-pages-type.ts b/src/js/types/divide-pages-type.ts new file mode 100644 index 0000000..fc8b72a --- /dev/null +++ b/src/js/types/divide-pages-type.ts @@ -0,0 +1,7 @@ +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; + +export interface DividePagesState { + file: File | null; + pdfDoc: PDFLibDocument | null; + totalPages: number; +} \ No newline at end of file diff --git a/src/js/types/edit-attachments-type.ts b/src/js/types/edit-attachments-type.ts new file mode 100644 index 0000000..0c0f438 --- /dev/null +++ b/src/js/types/edit-attachments-type.ts @@ -0,0 +1,12 @@ +export interface AttachmentInfo { + index: number; + name: string; + page: number; + data: Uint8Array; +} + +export interface EditAttachmentState { + file: File | null; + allAttachments: AttachmentInfo[]; + attachmentsToRemove: Set; +} \ No newline at end of file diff --git a/src/js/types/edit-metadata-type.ts b/src/js/types/edit-metadata-type.ts new file mode 100644 index 0000000..b62e6c4 --- /dev/null +++ b/src/js/types/edit-metadata-type.ts @@ -0,0 +1,6 @@ +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; + +export interface EditMetadataState { + file: File | null; + pdfDoc: PDFLibDocument | null; +} \ No newline at end of file diff --git a/src/js/types/email-to-pdf-type.ts b/src/js/types/email-to-pdf-type.ts new file mode 100644 index 0000000..89d8166 --- /dev/null +++ b/src/js/types/email-to-pdf-type.ts @@ -0,0 +1,26 @@ +export interface EmailAttachment { + filename: string; + size: number; + contentType: string; + content?: Uint8Array; + contentId?: string; +} + +export interface ParsedEmail { + subject: string; + from: string; + to: string[]; + cc: string[]; + bcc: string[]; + date: Date | null; + rawDateString: string; + htmlBody: string; + textBody: string; + attachments: EmailAttachment[]; +} + +export interface EmailRenderOptions { + includeCcBcc?: boolean; + includeAttachments?: boolean; + pageSize?: 'a4' | 'letter' | 'legal'; +} diff --git a/src/js/types/encrypt-pdf-type.ts b/src/js/types/encrypt-pdf-type.ts new file mode 100644 index 0000000..8acd7a2 --- /dev/null +++ b/src/js/types/encrypt-pdf-type.ts @@ -0,0 +1,3 @@ +export interface EncryptPdfState { + file: File | null; +} diff --git a/src/js/types/extract-attachments-type.ts b/src/js/types/extract-attachments-type.ts new file mode 100644 index 0000000..a1cbadb --- /dev/null +++ b/src/js/types/extract-attachments-type.ts @@ -0,0 +1,3 @@ +export interface ExtractAttachmentsState { + files: File[]; +} diff --git a/src/js/types/extract-images-type.ts b/src/js/types/extract-images-type.ts new file mode 100644 index 0000000..a436909 --- /dev/null +++ b/src/js/types/extract-images-type.ts @@ -0,0 +1,7 @@ +export interface ExtractedImage { + data: Blob; + width: number; + height: number; + pageNumber: number; + imageIndex: number; +} diff --git a/src/js/types/extract-pages-type.ts b/src/js/types/extract-pages-type.ts new file mode 100644 index 0000000..9791731 --- /dev/null +++ b/src/js/types/extract-pages-type.ts @@ -0,0 +1,5 @@ +export interface ExtractPagesState { + file: File | null; + pdfBytes: ArrayBuffer | null; + totalPages: number; +} diff --git a/src/js/types/fix-page-size-type.ts b/src/js/types/fix-page-size-type.ts new file mode 100644 index 0000000..e286afd --- /dev/null +++ b/src/js/types/fix-page-size-type.ts @@ -0,0 +1,3 @@ +export interface FixPageSizeState { + file: File | null; +} diff --git a/src/js/types/flatten-pdf-type.ts b/src/js/types/flatten-pdf-type.ts new file mode 100644 index 0000000..e8815f5 --- /dev/null +++ b/src/js/types/flatten-pdf-type.ts @@ -0,0 +1,3 @@ +export interface FlattenPdfState { + files: File[]; +} diff --git a/src/js/types/form-creator-type.ts b/src/js/types/form-creator-type.ts new file mode 100644 index 0000000..58460a1 --- /dev/null +++ b/src/js/types/form-creator-type.ts @@ -0,0 +1,52 @@ +export interface FormField { + id: string; + type: + | 'text' + | 'checkbox' + | 'radio' + | 'dropdown' + | 'optionlist' + | 'button' + | 'signature' + | 'date' + | 'image' + | 'barcode'; + x: number; + y: number; + width: number; + height: number; + name: string; + defaultValue: string; + fontSize: number; + alignment: 'left' | 'center' | 'right'; + textColor: string; + required: boolean; + readOnly: boolean; + tooltip: string; + combCells: number; + maxLength: number; + options?: string[]; + checked?: boolean; + exportValue?: string; + groupName?: string; + label?: string; + pageIndex: number; + action?: 'none' | 'reset' | 'print' | 'url' | 'js' | 'showHide'; + actionUrl?: string; + jsScript?: string; + targetFieldName?: string; + visibilityAction?: 'show' | 'hide' | 'toggle'; + dateFormat?: string; + multiline?: boolean; + borderColor?: string; + hideBorder?: boolean; + barcodeFormat?: string; + barcodeValue?: string; +} + +export interface PageData { + index: number; + width: number; + height: number; + pdfPageData?: string; +} diff --git a/src/js/types/form-creator.ts b/src/js/types/form-creator.ts deleted file mode 100644 index f0f7c5b..0000000 --- a/src/js/types/form-creator.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface FormField { - id: string - type: 'text' | 'checkbox' | 'radio' | 'dropdown' | 'optionlist' | 'button' | 'signature' | 'date' | 'image' - x: number - y: number - width: number - height: number - name: string - defaultValue: string - fontSize: number - alignment: 'left' | 'center' | 'right' - textColor: string - required: boolean - readOnly: boolean - tooltip: string - combCells: number - maxLength: number - options?: string[] - checked?: boolean - exportValue?: string - groupName?: string - label?: string - pageIndex: number - action?: 'none' | 'reset' | 'print' | 'url' | 'js' | 'showHide' - actionUrl?: string - jsScript?: string - targetFieldName?: string - visibilityAction?: 'show' | 'hide' | 'toggle' - dateFormat?: string - multiline?: boolean - borderColor?: string - hideBorder?: boolean -} - -export interface PageData { - index: number - width: number - height: number - pdfPageData?: string -} diff --git a/src/js/types/header-footer-type.ts b/src/js/types/header-footer-type.ts new file mode 100644 index 0000000..2a9d1b6 --- /dev/null +++ b/src/js/types/header-footer-type.ts @@ -0,0 +1,6 @@ +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; + +export interface HeaderFooterState { + file: File | null; + pdfDoc: PDFLibDocument | null; +} diff --git a/src/js/types/index.ts b/src/js/types/index.ts index 1e88b7b..aa9dfe9 100644 --- a/src/js/types/index.ts +++ b/src/js/types/index.ts @@ -1,2 +1,53 @@ -export * from './ocr.js'; -export * from './form-creator.js'; +export * from './ocr-type.ts'; +export * from './form-creator-type.ts'; +export * from './digital-sign-type.ts'; +export * from './attachment-type.ts'; +export * from './edit-attachments-type.ts'; +export * from './edit-metadata-type.ts'; +export * from './divide-pages-type.ts'; +export * from './alternate-merge-page-type.ts'; +export * from './add-blank-page-type.ts'; +export * from './compare-pdfs-type.ts'; +export * from './fix-page-size-type.ts'; +export * from './view-metadata-type.ts'; +export * from './header-footer-type.ts'; +export * from './encrypt-pdf-type.ts'; +export * from './flatten-pdf-type.ts'; +export * from './crop-pdf-type.ts'; +export * from './background-color-type.ts'; +export * from './posterize-type.ts'; +export * from './decrypt-pdf-type.ts'; +export * from './combine-single-page-type.ts'; +export * from './change-permissions-type.ts'; +export * from './validate-signature-type.ts'; +export * from './remove-restrictions-type.ts'; +export * from './page-dimensions-type.ts'; +export * from './extract-attachments-type.ts'; +export * from './pdf-multi-tool-type.ts'; +export * from './ocr-pdf-type.ts'; +export * from './delete-pages-type.ts'; +export * from './invert-colors-type.ts'; +export * from './table-of-contents-type.ts'; +export * from './organize-pdf-type.ts'; +export * from './merge-pdf-type.ts'; +export * from './extract-images-type.ts'; +export * from './extract-pages-type.ts'; +export * from './pdf-layers-type.ts'; +export * from './sanitize-pdf-type.ts'; +export * from './reverse-pages-type.ts'; +export * from './text-color-type.ts'; +export * from './n-up-pdf-type.ts'; +export * from './linearize-pdf-type.ts'; +export * from './remove-metadata-type.ts'; +export * from './rotate-pdf-type.ts'; +export * from './pdf-booklet-type.ts'; +export * from './page-numbers-type.ts'; +export * from './pdf-to-zip-type.ts'; +export * from './sign-pdf-type.ts'; +export * from './add-watermark-type.ts'; +export * from './email-to-pdf-type.ts'; +export * from './bookmark-pdf-type.ts'; +export * from './scanner-effect-type.ts'; +export * from './adjust-colors-type.ts'; +export * from './bates-numbering-type.ts'; +export * from './page-preview-type.ts'; diff --git a/src/js/types/invert-colors-type.ts b/src/js/types/invert-colors-type.ts new file mode 100644 index 0000000..cb5abf8 --- /dev/null +++ b/src/js/types/invert-colors-type.ts @@ -0,0 +1,6 @@ +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; + +export interface InvertColorsState { + file: File | null; + pdfDoc: PDFLibDocument | null; +} diff --git a/src/js/types/linearize-pdf-type.ts b/src/js/types/linearize-pdf-type.ts new file mode 100644 index 0000000..50a4c02 --- /dev/null +++ b/src/js/types/linearize-pdf-type.ts @@ -0,0 +1,3 @@ +export interface LinearizePdfState { + files: File[]; +} diff --git a/src/js/types/merge-pdf-type.ts b/src/js/types/merge-pdf-type.ts new file mode 100644 index 0000000..2fbfed9 --- /dev/null +++ b/src/js/types/merge-pdf-type.ts @@ -0,0 +1,5 @@ +export interface MergeState { + files: File[]; + pdfBytes: Map; + pdfDocs: Map; +} diff --git a/src/js/types/n-up-pdf-type.ts b/src/js/types/n-up-pdf-type.ts new file mode 100644 index 0000000..c43d1c3 --- /dev/null +++ b/src/js/types/n-up-pdf-type.ts @@ -0,0 +1,5 @@ +export interface NUpState { + file: File | null; + pdfBytes: ArrayBuffer | null; + totalPages: number; +} diff --git a/src/js/types/ocr-pdf-type.ts b/src/js/types/ocr-pdf-type.ts new file mode 100644 index 0000000..00a340d --- /dev/null +++ b/src/js/types/ocr-pdf-type.ts @@ -0,0 +1,46 @@ +export interface OcrWord { + text: string; + bbox: { x0: number; y0: number; x1: number; y1: number }; + confidence: number; +} + +export interface OcrState { + file: File | null; + searchablePdfBytes: Uint8Array | null; +} + +export interface BBox { + x0: number; // left + y0: number; // top (in hOCR coordinate system, origin at top-left) + x1: number; // right + y1: number; // bottom +} + +export interface Baseline { + slope: number; + intercept: number; +} + +export interface OcrLine { + bbox: BBox; + baseline: Baseline; + textangle: number; + words: OcrWord[]; + direction: 'ltr' | 'rtl'; + injectWordBreaks: boolean; +} + +export interface OcrPage { + width: number; + height: number; + dpi: number; + lines: OcrLine[]; +} + +export interface WordTransform { + x: number; + y: number; + fontSize: number; + horizontalScale: number; + rotation: number; +} diff --git a/src/js/types/ocr.ts b/src/js/types/ocr-type.ts similarity index 100% rename from src/js/types/ocr.ts rename to src/js/types/ocr-type.ts diff --git a/src/js/types/organize-pdf-type.ts b/src/js/types/organize-pdf-type.ts new file mode 100644 index 0000000..c12a9bf --- /dev/null +++ b/src/js/types/organize-pdf-type.ts @@ -0,0 +1,6 @@ +export interface OrganizeState { + file: File | null; + pdfBytes: ArrayBuffer | null; + totalPages: number; + pageOrder: number[]; +} diff --git a/src/js/types/page-dimensions-type.ts b/src/js/types/page-dimensions-type.ts new file mode 100644 index 0000000..9cdccd5 --- /dev/null +++ b/src/js/types/page-dimensions-type.ts @@ -0,0 +1,6 @@ +import { PDFDocument } from 'pdf-lib'; + +export interface PageDimensionsState { + file: File | null; + pdfDoc: PDFDocument | null; +} diff --git a/src/js/types/page-numbers-type.ts b/src/js/types/page-numbers-type.ts new file mode 100644 index 0000000..30fc880 --- /dev/null +++ b/src/js/types/page-numbers-type.ts @@ -0,0 +1,3 @@ +export interface PageNumbersState { + file: File | null; +} diff --git a/src/js/types/page-preview-type.ts b/src/js/types/page-preview-type.ts new file mode 100644 index 0000000..3e4f367 --- /dev/null +++ b/src/js/types/page-preview-type.ts @@ -0,0 +1,10 @@ +import { PDFDocumentProxy } from 'pdfjs-dist'; + +export interface PreviewState { + modal: HTMLElement | null; + pdfjsDoc: PDFDocumentProxy | null; + currentPage: number; + totalPages: number; + isOpen: boolean; + container: HTMLElement | null; +} diff --git a/src/js/types/pdf-booklet-type.ts b/src/js/types/pdf-booklet-type.ts new file mode 100644 index 0000000..b2c7fcc --- /dev/null +++ b/src/js/types/pdf-booklet-type.ts @@ -0,0 +1,5 @@ +export interface BookletState { + file: File | null; + pdfBytes: ArrayBuffer | null; + totalPages: number; +} diff --git a/src/js/types/pdf-layers-type.ts b/src/js/types/pdf-layers-type.ts new file mode 100644 index 0000000..82a485d --- /dev/null +++ b/src/js/types/pdf-layers-type.ts @@ -0,0 +1,5 @@ +export interface LayerData { + name: string; + visible: boolean; + locked: boolean; +} diff --git a/src/js/types/pdf-multi-tool-type.ts b/src/js/types/pdf-multi-tool-type.ts new file mode 100644 index 0000000..69580df --- /dev/null +++ b/src/js/types/pdf-multi-tool-type.ts @@ -0,0 +1,10 @@ +export interface MultiToolPageData { + id: string; + originalPdfId: string; + pageIndex: number; + thumbnail: string; + width: number; + height: number; + rotation: number; + isBlank?: boolean; +} diff --git a/src/js/types/pdf-to-zip-type.ts b/src/js/types/pdf-to-zip-type.ts new file mode 100644 index 0000000..4c4a5de --- /dev/null +++ b/src/js/types/pdf-to-zip-type.ts @@ -0,0 +1,3 @@ +export interface PdfToZipState { + files: File[]; +} diff --git a/src/js/types/posterize-type.ts b/src/js/types/posterize-type.ts new file mode 100644 index 0000000..031c046 --- /dev/null +++ b/src/js/types/posterize-type.ts @@ -0,0 +1,9 @@ +import * as pdfjsLib from 'pdfjs-dist'; + +export interface PosterizeState { + file: File | null; + pdfJsDoc: pdfjsLib.PDFDocumentProxy | null; + pdfBytes: Uint8Array | null; + pageSnapshots: Record; + currentPage: number; +} diff --git a/src/js/types/remove-metadata-type.ts b/src/js/types/remove-metadata-type.ts new file mode 100644 index 0000000..52dc35a --- /dev/null +++ b/src/js/types/remove-metadata-type.ts @@ -0,0 +1,3 @@ +export interface RemoveMetadataState { + file: File | null; +} diff --git a/src/js/types/remove-restrictions-type.ts b/src/js/types/remove-restrictions-type.ts new file mode 100644 index 0000000..772cc00 --- /dev/null +++ b/src/js/types/remove-restrictions-type.ts @@ -0,0 +1,3 @@ +export interface RemoveRestrictionsState { + file: File | null; +} diff --git a/src/js/types/reverse-pages-type.ts b/src/js/types/reverse-pages-type.ts new file mode 100644 index 0000000..02a7840 --- /dev/null +++ b/src/js/types/reverse-pages-type.ts @@ -0,0 +1,5 @@ +export interface ReverseState { + file: File | null; + pdfBytes: ArrayBuffer | null; + totalPages: number; +} diff --git a/src/js/types/rotate-pdf-type.ts b/src/js/types/rotate-pdf-type.ts new file mode 100644 index 0000000..01ec6ed --- /dev/null +++ b/src/js/types/rotate-pdf-type.ts @@ -0,0 +1,6 @@ +export interface RotateState { + file: File | null; + pdfBytes: ArrayBuffer | null; + totalPages: number; + pageRotations: Map; +} diff --git a/src/js/types/sanitize-pdf-type.ts b/src/js/types/sanitize-pdf-type.ts new file mode 100644 index 0000000..0da485c --- /dev/null +++ b/src/js/types/sanitize-pdf-type.ts @@ -0,0 +1,6 @@ +import { PDFDocument } from 'pdf-lib'; + +export interface SanitizePdfState { + file: File | null; + pdfDoc: PDFDocument | null; +} diff --git a/src/js/types/scanner-effect-type.ts b/src/js/types/scanner-effect-type.ts new file mode 100644 index 0000000..c14b4d7 --- /dev/null +++ b/src/js/types/scanner-effect-type.ts @@ -0,0 +1,16 @@ +export interface ScannerEffectState { + file: File | null; +} + +export interface ScanSettings { + grayscale: boolean; + border: boolean; + rotate: number; + rotateVariance: number; + brightness: number; + contrast: number; + blur: number; + noise: number; + yellowish: number; + resolution: number; +} diff --git a/src/js/types/sign-pdf-type.ts b/src/js/types/sign-pdf-type.ts new file mode 100644 index 0000000..3085770 --- /dev/null +++ b/src/js/types/sign-pdf-type.ts @@ -0,0 +1,5 @@ +export interface SignPdfState { + file: File | null; + pdfBytes: ArrayBuffer | null; + signatureData: string | null; +} diff --git a/src/js/types/table-of-contents-type.ts b/src/js/types/table-of-contents-type.ts new file mode 100644 index 0000000..407ddbd --- /dev/null +++ b/src/js/types/table-of-contents-type.ts @@ -0,0 +1,18 @@ +export interface GenerateTOCMessage { + type: 'generateTOC'; + pdfBytes: ArrayBuffer; + title: string; + headerColor: string; + fontColor: string; + fontSize: number; +} + +export interface TOCSuccessResponse { + type: 'success'; + pdfBytes: ArrayBuffer; +} + +export interface TOCErrorResponse { + type: 'error'; + message: string; +} diff --git a/src/js/types/text-color-type.ts b/src/js/types/text-color-type.ts new file mode 100644 index 0000000..fe451b9 --- /dev/null +++ b/src/js/types/text-color-type.ts @@ -0,0 +1,6 @@ +import { PDFDocument as PDFLibDocument } from 'pdf-lib'; + +export interface TextColorState { + file: File | null; + pdfDoc: PDFLibDocument | null; +} diff --git a/src/js/types/validate-signature-type.ts b/src/js/types/validate-signature-type.ts new file mode 100644 index 0000000..4633848 --- /dev/null +++ b/src/js/types/validate-signature-type.ts @@ -0,0 +1,47 @@ +import forge from 'node-forge'; + +export interface SignatureValidationResult { + signatureIndex: number; + isValid: boolean; + signerName: string; + signerOrg?: string; + signerEmail?: string; + issuer: string; + issuerOrg?: string; + signatureDate?: Date; + validFrom: Date; + validTo: Date; + isExpired: boolean; + isSelfSigned: boolean; + isTrusted: boolean; + algorithms: { + digest: string; + signature: string; + }; + serialNumber: string; + reason?: string; + location?: string; + contactInfo?: string; + byteRange?: number[]; + coverageStatus: 'full' | 'partial' | 'unknown'; + errorMessage?: string; +} + +export interface ExtractedSignature { + index: number; + contents: Uint8Array; + byteRange: number[]; + reason?: string; + location?: string; + contactInfo?: string; + name?: string; + signingTime?: string; +} + +export interface ValidateSignatureState { + pdfFile: File | null; + pdfBytes: Uint8Array | null; + results: SignatureValidationResult[]; + trustedCertFile: File | null; + trustedCert: forge.pki.Certificate | null; +} diff --git a/src/js/types/view-metadata-type.ts b/src/js/types/view-metadata-type.ts new file mode 100644 index 0000000..8da1ce0 --- /dev/null +++ b/src/js/types/view-metadata-type.ts @@ -0,0 +1,4 @@ +export interface ViewMetadataState { + file: File | null; + metadata: Record; +} diff --git a/src/js/ui.ts b/src/js/ui.ts index dda62cb..935459d 100644 --- a/src/js/ui.ts +++ b/src/js/ui.ts @@ -1,138 +1,201 @@ import { resetState } from './state.js'; import { formatBytes, getPDFDocument } from './utils/helpers.js'; import { tesseractLanguages } from './config/tesseract-languages.js'; -import { renderPagesProgressively, cleanupLazyRendering } from './utils/render-utils.js'; +import { + renderPagesProgressively, + cleanupLazyRendering, +} from './utils/render-utils.js'; +import { initPagePreview } from './utils/page-preview.js'; import { icons, createIcons } from 'lucide'; import Sortable from 'sortablejs'; -import { getRotationState, updateRotationState } from './utils/rotation-state.js'; +import { + getRotationState, + updateRotationState, +} from './utils/rotation-state.js'; import * as pdfjsLib from 'pdfjs-dist'; import { t } from './i18n/i18n'; -pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); - +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); // Centralizing DOM element selection export const dom = { - gridView: document.getElementById('grid-view'), - toolGrid: document.getElementById('tool-grid'), - toolInterface: document.getElementById('tool-interface'), - toolContent: document.getElementById('tool-content'), - backToGridBtn: document.getElementById('back-to-grid'), - loaderModal: document.getElementById('loader-modal'), - loaderText: document.getElementById('loader-text'), - alertModal: document.getElementById('alert-modal'), - alertTitle: document.getElementById('alert-title'), - alertMessage: document.getElementById('alert-message'), - alertOkBtn: document.getElementById('alert-ok'), - heroSection: document.getElementById('hero-section'), - featuresSection: document.getElementById('features-section'), - toolsHeader: document.getElementById('tools-header'), - dividers: document.querySelectorAll('.section-divider'), - hideSections: document.querySelectorAll('.hide-section'), - shortcutsModal: document.getElementById('shortcuts-modal'), - closeShortcutsModalBtn: document.getElementById('close-shortcuts-modal'), - shortcutsList: document.getElementById('shortcuts-list'), - shortcutSearch: document.getElementById('shortcut-search'), - resetShortcutsBtn: document.getElementById('reset-shortcuts-btn'), - importShortcutsBtn: document.getElementById('import-shortcuts-btn'), - exportShortcutsBtn: document.getElementById('export-shortcuts-btn'), - openShortcutsBtn: document.getElementById('open-shortcuts-btn'), - warningModal: document.getElementById('warning-modal'), - warningTitle: document.getElementById('warning-title'), - warningMessage: document.getElementById('warning-message'), - warningCancelBtn: document.getElementById('warning-cancel-btn'), - warningConfirmBtn: document.getElementById('warning-confirm-btn'), + gridView: document.getElementById('grid-view'), + toolGrid: document.getElementById('tool-grid'), + toolInterface: document.getElementById('tool-interface'), + toolContent: document.getElementById('tool-content'), + backToGridBtn: document.getElementById('back-to-grid'), + loaderModal: document.getElementById('loader-modal'), + loaderText: document.getElementById('loader-text'), + alertModal: document.getElementById('alert-modal'), + alertTitle: document.getElementById('alert-title'), + alertMessage: document.getElementById('alert-message'), + alertOkBtn: document.getElementById('alert-ok'), + heroSection: document.getElementById('hero-section'), + featuresSection: document.getElementById('features-section'), + toolsHeader: document.getElementById('tools-header'), + dividers: document.querySelectorAll('.section-divider'), + hideSections: document.querySelectorAll('.hide-section'), + shortcutsModal: document.getElementById('shortcuts-modal'), + closeShortcutsModalBtn: document.getElementById('close-shortcuts-modal'), + shortcutsList: document.getElementById('shortcuts-list'), + shortcutSearch: document.getElementById('shortcut-search'), + resetShortcutsBtn: document.getElementById('reset-shortcuts-btn'), + importShortcutsBtn: document.getElementById('import-shortcuts-btn'), + exportShortcutsBtn: document.getElementById('export-shortcuts-btn'), + openShortcutsBtn: document.getElementById('open-shortcuts-btn'), + warningModal: document.getElementById('warning-modal'), + warningTitle: document.getElementById('warning-title'), + warningMessage: document.getElementById('warning-message'), + warningCancelBtn: document.getElementById('warning-cancel-btn'), + warningConfirmBtn: document.getElementById('warning-confirm-btn'), }; -export const showLoader = (text = t('common.loading')) => { - if (dom.loaderText) dom.loaderText.textContent = text; - if (dom.loaderModal) dom.loaderModal.classList.remove('hidden'); +export const showLoader = (text = t('common.loading'), progress?: number) => { + if (dom.loaderText) dom.loaderText.textContent = text; + + // Add or update progress bar if progress is provided + const loaderModal = dom.loaderModal; + if (loaderModal) { + let progressBar = loaderModal.querySelector( + '.loader-progress-bar' + ) as HTMLElement; + let progressContainer = loaderModal.querySelector( + '.loader-progress-container' + ) as HTMLElement; + + if (progress !== undefined && progress >= 0) { + // Create progress container if it doesn't exist + if (!progressContainer) { + progressContainer = document.createElement('div'); + progressContainer.className = 'loader-progress-container w-64 mt-4'; + progressContainer.innerHTML = ` +
+
+
+

0%

+ `; + loaderModal + .querySelector('.bg-gray-800') + ?.appendChild(progressContainer); + progressBar = progressContainer.querySelector( + '.loader-progress-bar' + ) as HTMLElement; + } + + // Update progress + if (progressBar) { + progressBar.style.width = `${progress}%`; + } + const progressText = progressContainer.querySelector( + '.loader-progress-text' + ); + if (progressText) { + progressText.textContent = `${Math.round(progress)}%`; + } + progressContainer.classList.remove('hidden'); + } else { + // Hide progress bar if no progress provided + if (progressContainer) { + progressContainer.classList.add('hidden'); + } + } + + loaderModal.classList.remove('hidden'); + } }; export const hideLoader = () => { - if (dom.loaderModal) dom.loaderModal.classList.add('hidden'); + if (dom.loaderModal) dom.loaderModal.classList.add('hidden'); }; -export const showAlert = (title: any, message: any, type: string = 'error', callback?: () => void) => { - if (dom.alertTitle) dom.alertTitle.textContent = title; - if (dom.alertMessage) dom.alertMessage.textContent = message; - if (dom.alertModal) dom.alertModal.classList.remove('hidden'); +export const showAlert = ( + title: any, + message: any, + type: string = 'error', + callback?: () => void +) => { + if (dom.alertTitle) dom.alertTitle.textContent = title; + if (dom.alertMessage) dom.alertMessage.textContent = message; + if (dom.alertModal) dom.alertModal.classList.remove('hidden'); - if (dom.alertOkBtn) { - const newOkBtn = dom.alertOkBtn.cloneNode(true) as HTMLElement; - dom.alertOkBtn.replaceWith(newOkBtn); - dom.alertOkBtn = newOkBtn; + if (dom.alertOkBtn) { + const newOkBtn = dom.alertOkBtn.cloneNode(true) as HTMLElement; + dom.alertOkBtn.replaceWith(newOkBtn); + dom.alertOkBtn = newOkBtn; - newOkBtn.addEventListener('click', () => { - hideAlert(); - if (callback) callback(); - }); - } + newOkBtn.addEventListener('click', () => { + hideAlert(); + if (callback) callback(); + }); + } }; export const hideAlert = () => { - if (dom.alertModal) dom.alertModal.classList.add('hidden'); + if (dom.alertModal) dom.alertModal.classList.add('hidden'); }; export const switchView = (view: any) => { - if (view === 'grid') { - dom.gridView.classList.remove('hidden'); - dom.toolInterface.classList.add('hidden'); - // show hero and features and header - dom.heroSection.classList.remove('hidden'); - dom.featuresSection.classList.remove('hidden'); - dom.toolsHeader.classList.remove('hidden'); - // show dividers - dom.dividers.forEach((divider) => { - divider.classList.remove('hidden'); - }); - // show hideSections - dom.hideSections.forEach((section) => { - section.classList.remove('hidden'); - }); + if (view === 'grid') { + dom.gridView.classList.remove('hidden'); + dom.toolInterface.classList.add('hidden'); + // show hero and features and header + dom.heroSection.classList.remove('hidden'); + dom.featuresSection.classList.remove('hidden'); + dom.toolsHeader.classList.remove('hidden'); + // show dividers + dom.dividers.forEach((divider) => { + divider.classList.remove('hidden'); + }); + // show hideSections + dom.hideSections.forEach((section) => { + section.classList.remove('hidden'); + }); - resetState(); - } else { - dom.gridView.classList.add('hidden'); - dom.toolInterface.classList.remove('hidden'); - dom.featuresSection.classList.add('hidden'); - dom.heroSection.classList.add('hidden'); - dom.toolsHeader.classList.add('hidden'); - dom.dividers.forEach((divider) => { - divider.classList.add('hidden'); - }); - dom.hideSections.forEach((section) => { - section.classList.add('hidden'); - }); - } + resetState(); + } else { + dom.gridView.classList.add('hidden'); + dom.toolInterface.classList.remove('hidden'); + dom.featuresSection.classList.add('hidden'); + dom.heroSection.classList.add('hidden'); + dom.toolsHeader.classList.add('hidden'); + dom.dividers.forEach((divider) => { + divider.classList.add('hidden'); + }); + dom.hideSections.forEach((section) => { + section.classList.add('hidden'); + }); + } }; const thumbnailState = { - sortableInstances: {}, + sortableInstances: {}, }; function initializeOrganizeSortable(containerId: any) { - const container = document.getElementById(containerId); - if (!container) return; + const container = document.getElementById(containerId); + if (!container) return; - if (thumbnailState.sortableInstances[containerId]) { - thumbnailState.sortableInstances[containerId].destroy(); - } + if (thumbnailState.sortableInstances[containerId]) { + thumbnailState.sortableInstances[containerId].destroy(); + } - thumbnailState.sortableInstances[containerId] = Sortable.create(container, { - animation: 150, - ghostClass: 'sortable-ghost', - chosenClass: 'sortable-chosen', - dragClass: 'sortable-drag', - filter: '.delete-page-btn', - preventOnFilter: true, - onStart: function (evt: any) { - evt.item.style.opacity = '0.5'; - }, - onEnd: function (evt: any) { - evt.item.style.opacity = '1'; - }, - }); + thumbnailState.sortableInstances[containerId] = Sortable.create(container, { + animation: 150, + ghostClass: 'sortable-ghost', + chosenClass: 'sortable-chosen', + dragClass: 'sortable-drag', + filter: '.delete-page-btn', + preventOnFilter: true, + onStart: function (evt: any) { + evt.item.style.opacity = '0.5'; + }, + onEnd: function (evt: any) { + evt.item.style.opacity = '1'; + }, + }); } /** @@ -141,243 +204,247 @@ function initializeOrganizeSortable(containerId: any) { * @param {object} pdfDoc The loaded pdf-lib document instance. */ export const renderPageThumbnails = async (toolId: any, pdfDoc: any) => { - const containerId = toolId === 'organize' ? 'page-organizer' : toolId === 'delete-pages' ? 'delete-pages-preview' : 'page-rotator'; - const container = document.getElementById(containerId); - if (!container) return; + const containerId = + toolId === 'organize' + ? 'page-organizer' + : toolId === 'delete-pages' + ? 'delete-pages-preview' + : 'page-rotator'; + const container = document.getElementById(containerId); + if (!container) return; - container.innerHTML = ''; + container.innerHTML = ''; - // Cleanup any previous lazy loading observers - cleanupLazyRendering(); + // Cleanup any previous lazy loading observers + cleanupLazyRendering(); - const currentRenderId = Date.now(); - container.dataset.renderId = currentRenderId.toString(); + const currentRenderId = Date.now(); + container.dataset.renderId = currentRenderId.toString(); - showLoader(t('multiTool.renderingTitle')); + showLoader(t('multiTool.renderingTitle')); - const pdfData = await pdfDoc.save(); - const pdf = await getPDFDocument({ data: pdfData }).promise; + const pdfData = await pdfDoc.save(); + const pdf = await getPDFDocument({ data: pdfData }).promise; - // Function to create wrapper element for each page - const createWrapper = (canvas: HTMLCanvasElement, pageNumber: number) => { - const wrapper = document.createElement('div'); - // @ts-expect-error TS(2322) FIXME: Type 'number' is not assignable to type 'string'. - wrapper.dataset.pageIndex = pageNumber - 1; + // Function to create wrapper element for each page + const createWrapper = (canvas: HTMLCanvasElement, pageNumber: number) => { + const wrapper = document.createElement('div'); + // @ts-expect-error TS(2322) FIXME: Type 'number' is not assignable to type 'string'. + wrapper.dataset.pageIndex = pageNumber - 1; - const imgContainer = document.createElement('div'); - imgContainer.className = - 'w-full h-36 bg-gray-900 rounded-lg flex items-center justify-center overflow-hidden border-2 border-gray-600'; + const imgContainer = document.createElement('div'); + imgContainer.className = 'relative'; - const img = document.createElement('img'); - img.src = canvas.toDataURL(); - img.className = 'max-w-full max-h-full object-contain'; + const img = document.createElement('img'); + img.src = canvas.toDataURL(); + img.className = 'rounded-md shadow-md max-w-full h-auto'; - imgContainer.appendChild(img); + imgContainer.appendChild(img); - if (toolId === 'organize') { - wrapper.className = 'page-thumbnail relative group'; - wrapper.appendChild(imgContainer); + const pageNumSpan = document.createElement('div'); + pageNumSpan.className = + 'absolute top-1 left-1 bg-indigo-600 text-white text-xs px-2 py-1 rounded-md font-semibold shadow-lg z-10 pointer-events-none'; + pageNumSpan.textContent = pageNumber.toString(); - const pageNumSpan = document.createElement('span'); - pageNumSpan.className = - 'absolute top-1 left-1 bg-gray-900 bg-opacity-75 text-white text-xs rounded-full px-2 py-1'; - pageNumSpan.textContent = pageNumber.toString(); + if (toolId === 'organize') { + wrapper.className = + 'page-thumbnail relative cursor-move flex flex-col items-center gap-1 p-2 border-2 border-gray-600 hover:border-indigo-500 rounded-lg bg-gray-700 transition-colors group'; - const deleteBtn = document.createElement('button'); - deleteBtn.className = - 'delete-page-btn absolute top-1 right-1 bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center'; - deleteBtn.innerHTML = '×'; - deleteBtn.addEventListener('click', (e) => { - (e.currentTarget as HTMLElement).parentElement.remove(); + imgContainer.appendChild(pageNumSpan); + wrapper.appendChild(imgContainer); - // Renumber remaining pages - const pages = container.querySelectorAll('.page-thumbnail'); - pages.forEach((page, index) => { - const numSpan = page.querySelector('span'); - if (numSpan) { - numSpan.textContent = (index + 1).toString(); - } - }); + const deleteBtn = document.createElement('button'); + deleteBtn.className = + 'delete-page-btn absolute top-1 right-1 bg-red-600 hover:bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center z-10'; + deleteBtn.innerHTML = '×'; + deleteBtn.addEventListener('click', (e) => { + (e.currentTarget as HTMLElement).parentElement.remove(); - initializeOrganizeSortable(containerId); - }); + // Renumber remaining pages + const pages = container.querySelectorAll('.page-thumbnail'); + pages.forEach((page, index) => { + const numSpan = page.querySelector('.bg-indigo-600'); + if (numSpan) { + numSpan.textContent = (index + 1).toString(); + } + }); - wrapper.append(pageNumSpan, deleteBtn); - } else if (toolId === 'rotate') { - wrapper.className = 'page-rotator-item flex flex-col items-center gap-2 relative group'; + initializeOrganizeSortable(containerId); + }); - // Read rotation from state (handles "Rotate All" on lazy-loaded pages) - const rotationStateArray = getRotationState(); - const pageIndex = pageNumber - 1; - const initialRotation = rotationStateArray[pageIndex] || 0; + wrapper.appendChild(deleteBtn); + } else if (toolId === 'rotate') { + wrapper.className = + 'page-rotator-item flex flex-col items-center gap-2 p-2 border-2 border-gray-600 hover:border-indigo-500 rounded-lg bg-gray-700 transition-colors relative group'; - wrapper.dataset.rotation = initialRotation.toString(); - img.classList.add('transition-transform', 'duration-300'); + // Read rotation from state (handles "Rotate All" on lazy-loaded pages) + const rotationStateArray = getRotationState(); + const pageIndex = pageNumber - 1; + const initialRotation = rotationStateArray[pageIndex] || 0; - // Apply initial rotation if any - if (initialRotation !== 0) { - img.style.transform = `rotate(${initialRotation}deg)`; - } + wrapper.dataset.rotation = initialRotation.toString(); + img.classList.add('transition-transform', 'duration-300'); - wrapper.appendChild(imgContainer); + // Apply initial rotation if any + if (initialRotation !== 0) { + img.style.transform = `rotate(${initialRotation}deg)`; + } - // Page Number Overlay (Top Left) - const pageNumSpan = document.createElement('span'); - pageNumSpan.className = - 'absolute top-2 left-2 bg-gray-900 bg-opacity-75 text-white text-xs font-medium rounded-md px-2 py-1 shadow-sm z-10 pointer-events-none'; - pageNumSpan.textContent = pageNumber.toString(); - wrapper.appendChild(pageNumSpan); + imgContainer.appendChild(pageNumSpan); + wrapper.appendChild(imgContainer); - const controlsDiv = document.createElement('div'); - controlsDiv.className = 'flex flex-col lg:flex-row items-center justify-center w-full gap-2 px-1'; + const controlsDiv = document.createElement('div'); + controlsDiv.className = + 'flex flex-col lg:flex-row items-center justify-center w-full gap-2 px-1'; - // Custom Stepper Component - const stepperContainer = document.createElement('div'); - stepperContainer.className = 'flex items-center border border-gray-600 rounded-md bg-gray-800 overflow-hidden w-24 h-8'; + // Custom Stepper Component + const stepperContainer = document.createElement('div'); + stepperContainer.className = + 'flex items-center border border-gray-600 rounded-md bg-gray-800 overflow-hidden w-24 h-8'; - const decrementBtn = document.createElement('button'); - decrementBtn.className = 'px-2 h-full text-gray-400 hover:text-white hover:bg-gray-700 border-r border-gray-600 transition-colors flex items-center justify-center'; - decrementBtn.innerHTML = ''; + const decrementBtn = document.createElement('button'); + decrementBtn.className = + 'px-2 h-full text-gray-400 hover:text-white hover:bg-gray-700 border-r border-gray-600 transition-colors flex items-center justify-center'; + decrementBtn.innerHTML = ''; - const angleInput = document.createElement('input'); - angleInput.type = 'number'; - angleInput.className = 'no-spinner w-full h-full bg-transparent text-white text-xs text-center focus:outline-none appearance-none m-0 p-0 border-none'; - angleInput.value = initialRotation.toString(); - angleInput.placeholder = "0"; + const angleInput = document.createElement('input'); + angleInput.type = 'number'; + angleInput.className = + 'no-spinner w-full h-full bg-transparent text-white text-xs text-center focus:outline-none appearance-none m-0 p-0 border-none'; + angleInput.value = initialRotation.toString(); + angleInput.placeholder = '0'; - const incrementBtn = document.createElement('button'); - incrementBtn.className = 'px-2 h-full text-gray-400 hover:text-white hover:bg-gray-700 border-l border-gray-600 transition-colors flex items-center justify-center'; - incrementBtn.innerHTML = ''; + const incrementBtn = document.createElement('button'); + incrementBtn.className = + 'px-2 h-full text-gray-400 hover:text-white hover:bg-gray-700 border-l border-gray-600 transition-colors flex items-center justify-center'; + incrementBtn.innerHTML = ''; - // Helper to update rotation - const updateRotation = (newRotation: number) => { - const card = wrapper; // Closure capture - const imgEl = card.querySelector('img'); - const pageIndex = pageNumber - 1; + // Helper to update rotation + const updateRotation = (newRotation: number) => { + const card = wrapper; // Closure capture + const imgEl = card.querySelector('img'); + const pageIndex = pageNumber - 1; - // Update UI - angleInput.value = newRotation.toString(); - card.dataset.rotation = newRotation.toString(); - imgEl.style.transform = `rotate(${newRotation}deg)`; + // Update UI + angleInput.value = newRotation.toString(); + card.dataset.rotation = newRotation.toString(); + imgEl.style.transform = `rotate(${newRotation}deg)`; - // Update State - updateRotationState(pageIndex, newRotation); - }; + // Update State + updateRotationState(pageIndex, newRotation); + }; - // Event Listeners - decrementBtn.addEventListener('click', (e) => { - e.stopPropagation(); - let current = parseInt(angleInput.value) || 0; - updateRotation(current - 1); - }); + // Event Listeners + decrementBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const current = parseInt(angleInput.value) || 0; + updateRotation(current - 1); + }); - incrementBtn.addEventListener('click', (e) => { - e.stopPropagation(); - let current = parseInt(angleInput.value) || 0; - updateRotation(current + 1); - }); + incrementBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const current = parseInt(angleInput.value) || 0; + updateRotation(current + 1); + }); - angleInput.addEventListener('change', (e) => { - e.stopPropagation(); - let val = parseInt((e.target as HTMLInputElement).value) || 0; - updateRotation(val); - }); - angleInput.addEventListener('click', (e) => e.stopPropagation()); + angleInput.addEventListener('change', (e) => { + e.stopPropagation(); + const val = parseInt((e.target as HTMLInputElement).value) || 0; + updateRotation(val); + }); + angleInput.addEventListener('click', (e) => e.stopPropagation()); - stepperContainer.append(decrementBtn, angleInput, incrementBtn); + stepperContainer.append(decrementBtn, angleInput, incrementBtn); - const rotateBtn = document.createElement('button'); - rotateBtn.className = 'rotate-btn btn bg-gray-700 hover:bg-gray-600 p-1.5 rounded-md text-gray-200 transition-colors flex-shrink-0'; - rotateBtn.title = 'Rotate +90°'; - rotateBtn.innerHTML = ''; - rotateBtn.addEventListener('click', (e) => { - e.stopPropagation(); - let current = parseInt(angleInput.value) || 0; - updateRotation(current + 90); - }); + const rotateBtn = document.createElement('button'); + rotateBtn.className = + 'rotate-btn btn bg-gray-700 hover:bg-gray-600 p-1.5 rounded-md text-gray-200 transition-colors flex-shrink-0'; + rotateBtn.title = 'Rotate +90°'; + rotateBtn.innerHTML = ''; + rotateBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const current = parseInt(angleInput.value) || 0; + updateRotation(current + 90); + }); - controlsDiv.append(stepperContainer, rotateBtn); - wrapper.appendChild(controlsDiv); - } else if (toolId === 'delete-pages') { - wrapper.className = 'page-thumbnail relative group cursor-pointer transition-all duration-200'; - wrapper.dataset.pageNumber = pageNumber.toString(); + controlsDiv.append(stepperContainer, rotateBtn); + wrapper.appendChild(controlsDiv); + } else if (toolId === 'delete-pages') { + wrapper.className = + 'page-thumbnail relative cursor-pointer flex flex-col items-center gap-1 p-2 border-2 border-gray-600 hover:border-indigo-500 rounded-lg bg-gray-700 transition-colors group'; + wrapper.dataset.pageNumber = pageNumber.toString(); - const innerContainer = document.createElement('div'); - innerContainer.className = 'relative w-full h-36 bg-gray-900 rounded-lg flex items-center justify-center overflow-hidden border-2 border-gray-600 transition-colors duration-200'; - innerContainer.appendChild(img); - wrapper.appendChild(innerContainer); + imgContainer.appendChild(pageNumSpan); + wrapper.appendChild(imgContainer); - const pageNumSpan = document.createElement('span'); - pageNumSpan.className = - 'absolute top-2 left-2 bg-gray-900 bg-opacity-75 text-white text-xs font-medium rounded-md px-2 py-1 shadow-sm z-10 pointer-events-none'; - pageNumSpan.textContent = pageNumber.toString(); - wrapper.appendChild(pageNumSpan); + wrapper.addEventListener('click', () => { + const input = document.getElementById( + 'pages-to-delete' + ) as HTMLInputElement; + if (!input) return; - wrapper.addEventListener('click', () => { - const input = document.getElementById('pages-to-delete') as HTMLInputElement; - if (!input) return; + const currentVal = input.value; + let pages = currentVal + .split(',') + .map((s) => s.trim()) + .filter((s) => s); + const pageStr = pageNumber.toString(); - const currentVal = input.value; - let pages = currentVal.split(',').map(s => s.trim()).filter(s => s); - const pageStr = pageNumber.toString(); - - if (pages.includes(pageStr)) { - pages = pages.filter(p => p !== pageStr); - } else { - pages.push(pageStr); - } - - pages.sort((a, b) => { - const numA = parseInt(a.split('-')[0]); - const numB = parseInt(b.split('-')[0]); - return numA - numB; - }); - - input.value = pages.join(', '); - - input.dispatchEvent(new Event('input')); - }); + if (pages.includes(pageStr)) { + pages = pages.filter((p) => p !== pageStr); + } else { + pages.push(pageStr); } - return wrapper; - }; + pages.sort((a, b) => { + const numA = parseInt(a.split('-')[0]); + const numB = parseInt(b.split('-')[0]); + return numA - numB; + }); - try { - // Render pages progressively with lazy loading - await renderPagesProgressively( - pdf, - container, - createWrapper, - { - batchSize: 8, - useLazyLoading: true, - lazyLoadMargin: '300px', - onProgress: (current, total) => { - showLoader(`Rendering page previews: ${current}/${total}`); - }, - onBatchComplete: () => { - createIcons({ icons }); - }, - shouldCancel: () => { - return container.dataset.renderId !== currentRenderId.toString(); - } - } - ); + input.value = pages.join(', '); - if (toolId === 'organize') { - initializeOrganizeSortable(containerId); - } else if (toolId === 'delete-pages') { - // No sortable needed for delete pages - } - - // Reinitialize lucide icons for dynamically added elements - createIcons({ icons }); - } catch (error) { - console.error('Error rendering page thumbnails:', error); - showAlert(t('multiTool.error'), t('multiTool.errorRendering')); - } finally { - hideLoader(); + input.dispatchEvent(new Event('input')); + }); } + + return wrapper; + }; + + try { + // Render pages progressively with lazy loading + await renderPagesProgressively(pdf, container, createWrapper, { + batchSize: 8, + useLazyLoading: true, + lazyLoadMargin: '300px', + onProgress: (current, total) => { + showLoader(`Rendering page previews: ${current}/${total}`); + }, + onBatchComplete: () => { + createIcons({ icons }); + }, + shouldCancel: () => { + return container.dataset.renderId !== currentRenderId.toString(); + }, + }); + + if (toolId === 'organize') { + initializeOrganizeSortable(containerId); + } else if (toolId === 'delete-pages') { + // No sortable needed for delete pages + } + + // Reinitialize lucide icons for dynamically added elements + createIcons({ icons }); + + // Attach Quick Look page preview + initPagePreview(container, pdf); + } catch (error) { + console.error('Error rendering page thumbnails:', error); + showAlert(t('multiTool.error'), t('multiTool.errorRendering')); + } finally { + hideLoader(); + } }; /** @@ -386,36 +453,36 @@ export const renderPageThumbnails = async (toolId: any, pdfDoc: any) => { * @param {File[]} files The array of file objects. */ export const renderFileDisplay = (container: any, files: any) => { - container.textContent = ''; - if (files.length > 0) { - files.forEach((file: any) => { - const fileDiv = document.createElement('div'); - fileDiv.className = - 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + container.textContent = ''; + if (files.length > 0) { + files.forEach((file: any) => { + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; - const nameSpan = document.createElement('span'); - nameSpan.className = 'truncate font-medium text-gray-200'; - nameSpan.textContent = file.name; + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; - const sizeSpan = document.createElement('span'); - sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400'; - sizeSpan.textContent = formatBytes(file.size); + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400'; + sizeSpan.textContent = formatBytes(file.size); - fileDiv.append(nameSpan, sizeSpan); - container.appendChild(fileDiv); - }); - } + fileDiv.append(nameSpan, sizeSpan); + container.appendChild(fileDiv); + }); + } }; const createFileInputHTML = (options = {}) => { - // @ts-expect-error TS(2339) FIXME: Property 'multiple' does not exist on type '{}'. - const multiple = options.multiple ? 'multiple' : ''; - // @ts-expect-error TS(2339) FIXME: Property 'accept' does not exist on type '{}'. - const acceptedFiles = options.accept || 'application/pdf'; - // @ts-expect-error TS(2339) FIXME: Property 'showControls' does not exist on type '{}... Remove this comment to see the full error message - const showControls = options.showControls || false; // NEW: Add this parameter + // @ts-expect-error TS(2339) FIXME: Property 'multiple' does not exist on type '{}'. + const multiple = options.multiple ? 'multiple' : ''; + // @ts-expect-error TS(2339) FIXME: Property 'accept' does not exist on type '{}'. + const acceptedFiles = options.accept || 'application/pdf'; + // @ts-expect-error TS(2339) FIXME: Property 'showControls' does not exist on type '{}... Remove this comment to see the full error message + const showControls = options.showControls || false; // NEW: Add this parameter - return ` + return `
@@ -426,15 +493,16 @@ const createFileInputHTML = (options = {}) => {
- ${showControls + ${ + showControls ? ` ` diff --git a/src/js/utils/compress.ts b/src/js/utils/compress.ts new file mode 100644 index 0000000..ff4dbae --- /dev/null +++ b/src/js/utils/compress.ts @@ -0,0 +1,153 @@ +import { PDFDocument } from 'pdf-lib'; +import { getPDFDocument } from './helpers.js'; +import { loadPyMuPDF } from './pymupdf-loader.js'; + +export const CONDENSE_PRESETS = { + light: { + images: { quality: 90, dpiTarget: 150, dpiThreshold: 200 }, + scrub: { metadata: false, thumbnails: true }, + subsetFonts: true, + }, + balanced: { + images: { quality: 75, dpiTarget: 96, dpiThreshold: 150 }, + scrub: { metadata: true, thumbnails: true }, + subsetFonts: true, + }, + aggressive: { + images: { quality: 50, dpiTarget: 72, dpiThreshold: 100 }, + scrub: { metadata: true, thumbnails: true, xmlMetadata: true }, + subsetFonts: true, + }, + extreme: { + images: { quality: 30, dpiTarget: 60, dpiThreshold: 96 }, + scrub: { metadata: true, thumbnails: true, xmlMetadata: true }, + subsetFonts: true, + }, +}; + +export const PHOTON_PRESETS = { + light: { scale: 2.0, quality: 0.85 }, + balanced: { scale: 1.5, quality: 0.65 }, + aggressive: { scale: 1.2, quality: 0.45 }, + extreme: { scale: 1.0, quality: 0.25 }, +}; + +export interface CondenseCustomSettings { + imageQuality?: number; + dpiTarget?: number; + dpiThreshold?: number; + removeMetadata?: boolean; + subsetFonts?: boolean; + convertToGrayscale?: boolean; + removeThumbnails?: boolean; +} + +export async function performCondenseCompression( + fileBlob: Blob, + level: string, + customSettings?: CondenseCustomSettings +) { + const pymupdf = await loadPyMuPDF(); + + const preset = + CONDENSE_PRESETS[level as keyof typeof CONDENSE_PRESETS] || + CONDENSE_PRESETS.balanced; + + const dpiTarget = customSettings?.dpiTarget ?? preset.images.dpiTarget; + const userThreshold = + customSettings?.dpiThreshold ?? preset.images.dpiThreshold; + const dpiThreshold = Math.max(userThreshold, dpiTarget + 10); + + const options = { + images: { + enabled: true, + quality: customSettings?.imageQuality ?? preset.images.quality, + dpiTarget, + dpiThreshold, + convertToGray: customSettings?.convertToGrayscale ?? false, + }, + scrub: { + metadata: customSettings?.removeMetadata ?? preset.scrub.metadata, + thumbnails: customSettings?.removeThumbnails ?? preset.scrub.thumbnails, + xmlMetadata: (preset.scrub as any).xmlMetadata ?? false, + }, + subsetFonts: customSettings?.subsetFonts ?? preset.subsetFonts, + save: { + garbage: 4 as const, + deflate: true, + clean: true, + useObjstms: true, + }, + }; + + try { + const result = await pymupdf.compressPdf(fileBlob, options); + return result; + } catch { + const fallbackOptions = { + ...options, + images: { + ...options.images, + enabled: false, + }, + }; + + try { + const result = await pymupdf.compressPdf(fileBlob, fallbackOptions); + return { ...result, usedFallback: true }; + } catch (fallbackError: any) { + const msg = fallbackError?.message || String(fallbackError); + throw new Error(`PDF compression failed: ${msg}`); + } + } +} + +export async function performPhotonCompression( + arrayBuffer: ArrayBuffer, + level: string +): Promise { + const pdfJsDoc = await getPDFDocument({ data: arrayBuffer }).promise; + const newPdfDoc = await PDFDocument.create(); + const settings = + PHOTON_PRESETS[level as keyof typeof PHOTON_PRESETS] || + PHOTON_PRESETS.balanced; + + for (let i = 1; i <= pdfJsDoc.numPages; i++) { + const page = await pdfJsDoc.getPage(i); + const viewport = page.getViewport({ scale: settings.scale }); + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + if (!context) throw new Error('Failed to create canvas context'); + canvas.height = viewport.height; + canvas.width = viewport.width; + + await page.render({ canvasContext: context, viewport, canvas: canvas }) + .promise; + + const jpegBlob = await new Promise((resolve, reject) => + canvas.toBlob( + (blob) => { + if (blob) resolve(blob); + else reject(new Error('Failed to create JPEG blob')); + }, + 'image/jpeg', + settings.quality + ) + ); + + // Release canvas memory + canvas.width = 0; + canvas.height = 0; + + const jpegBytes = await jpegBlob.arrayBuffer(); + const jpegImage = await newPdfDoc.embedJpg(jpegBytes); + const newPage = newPdfDoc.addPage([viewport.width, viewport.height]); + newPage.drawImage(jpegImage, { + x: 0, + y: 0, + width: viewport.width, + height: viewport.height, + }); + } + return await newPdfDoc.save(); +} diff --git a/src/js/utils/cpdf-helper.ts b/src/js/utils/cpdf-helper.ts index 4987480..a1a004b 100644 --- a/src/js/utils/cpdf-helper.ts +++ b/src/js/utils/cpdf-helper.ts @@ -1,15 +1,35 @@ +import { WasmProvider } from './wasm-provider'; + let cpdfLoaded = false; let cpdfLoadPromise: Promise | null = null; -//TODO: @ALAM,is it better to use a worker to load the cpdf library? -// or just use the browser version? -export async function ensureCpdfLoaded(): Promise { +function getCpdfUrl(): string | undefined { + const userUrl = WasmProvider.getUrl('cpdf'); + if (userUrl) { + const baseUrl = userUrl.endsWith('/') ? userUrl : `${userUrl}/`; + return `${baseUrl}coherentpdf.browser.min.js`; + } + return undefined; +} + +export function isCpdfAvailable(): boolean { + return WasmProvider.isConfigured('cpdf'); +} + +export async function isCpdfLoaded(): Promise { if (cpdfLoaded) return; if (cpdfLoadPromise) { return cpdfLoadPromise; } + const cpdfUrl = getCpdfUrl(); + if (!cpdfUrl) { + throw new Error( + 'CoherentPDF is not configured. Please configure it in WASM Settings.' + ); + } + cpdfLoadPromise = new Promise((resolve, reject) => { if (typeof (window as any).coherentpdf !== 'undefined') { cpdfLoaded = true; @@ -18,13 +38,14 @@ export async function ensureCpdfLoaded(): Promise { } const script = document.createElement('script'); - script.src = import.meta.env.BASE_URL + 'coherentpdf.browser.min.js'; + script.src = cpdfUrl; script.onload = () => { cpdfLoaded = true; + console.log('[CPDF] Loaded from:', script.src); resolve(); }; script.onerror = () => { - reject(new Error('Failed to load CoherentPDF library')); + reject(new Error('Failed to load CoherentPDF library from: ' + cpdfUrl)); }; document.head.appendChild(script); }); @@ -32,11 +53,7 @@ export async function ensureCpdfLoaded(): Promise { return cpdfLoadPromise; } -/** - * Gets the cpdf instance, ensuring it's loaded first - */ export async function getCpdf(): Promise { - await ensureCpdfLoaded(); + await isCpdfLoaded(); return (window as any).coherentpdf; } - diff --git a/src/js/utils/csv-to-pdf.ts b/src/js/utils/csv-to-pdf.ts new file mode 100644 index 0000000..589ff59 --- /dev/null +++ b/src/js/utils/csv-to-pdf.ts @@ -0,0 +1,90 @@ +import { jsPDF } from 'jspdf'; +import autoTable from 'jspdf-autotable'; +import Papa from 'papaparse'; + +export interface CsvToPdfOptions { + onProgress?: (percent: number, message: string) => void; +} + +/** + * Convert a CSV file to PDF using jsPDF and autotable + */ +export async function convertCsvToPdf( + file: File, + options?: CsvToPdfOptions +): Promise { + const { onProgress } = options || {}; + + return new Promise((resolve, reject) => { + onProgress?.(10, 'Reading CSV file...'); + + Papa.parse(file, { + complete: (results) => { + try { + onProgress?.(50, 'Generating PDF...'); + + const data = results.data as string[][]; + + // Filter out empty rows + const filteredData = data.filter(row => + row.some(cell => cell && cell.trim() !== '') + ); + + if (filteredData.length === 0) { + reject(new Error('CSV file is empty')); + return; + } + + // Create PDF document + const doc = new jsPDF({ + orientation: 'landscape', // Better for wide tables + unit: 'mm', + format: 'a4' + }); + + // Extract headers (first row) and data + const headers = filteredData[0]; + const rows = filteredData.slice(1); + + onProgress?.(70, 'Creating table...'); + + // Generate table + autoTable(doc, { + head: [headers], + body: rows, + startY: 20, + styles: { + fontSize: 9, + cellPadding: 3, + overflow: 'linebreak', + cellWidth: 'wrap', + }, + headStyles: { + fillColor: [41, 128, 185], // Nice blue header + textColor: 255, + fontStyle: 'bold', + }, + alternateRowStyles: { + fillColor: [245, 245, 245], // Light gray for alternate rows + }, + margin: { top: 20, left: 10, right: 10 }, + theme: 'striped', + }); + + onProgress?.(90, 'Finalizing PDF...'); + + // Get PDF as blob + const pdfBlob = doc.output('blob'); + + onProgress?.(100, 'Complete!'); + resolve(pdfBlob); + } catch (error) { + reject(error); + } + }, + error: (error) => { + reject(new Error(`Failed to parse CSV: ${error.message}`)); + }, + }); + }); +} diff --git a/src/js/utils/full-width.ts b/src/js/utils/full-width.ts index efc5c89..4a07d68 100644 --- a/src/js/utils/full-width.ts +++ b/src/js/utils/full-width.ts @@ -2,7 +2,7 @@ // This script applies the full-width preference from localStorage to page uploaders export function initFullWidthMode() { - const savedFullWidth = localStorage.getItem('fullWidthMode') === 'true'; + const savedFullWidth = localStorage.getItem('fullWidthMode') !== 'false'; if (savedFullWidth) { applyFullWidthMode(true); diff --git a/src/js/utils/ghostscript-dynamic-loader.ts b/src/js/utils/ghostscript-dynamic-loader.ts new file mode 100644 index 0000000..761b1bb --- /dev/null +++ b/src/js/utils/ghostscript-dynamic-loader.ts @@ -0,0 +1,89 @@ +import { WasmProvider } from './wasm-provider.js'; + +let cachedGS: any = null; +let loadPromise: Promise | null = null; + +export interface GhostscriptInterface { + convertToPDFA(pdfBuffer: ArrayBuffer, profile: string): Promise; + fontToOutline(pdfBuffer: ArrayBuffer): Promise; +} + +export async function loadGhostscript(): Promise { + if (cachedGS) { + return cachedGS; + } + + if (loadPromise) { + return loadPromise; + } + + loadPromise = (async () => { + const baseUrl = WasmProvider.getUrl('ghostscript'); + if (!baseUrl) { + throw new Error( + 'Ghostscript is not configured. Please configure it in Advanced Settings.' + ); + } + + const normalizedUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`; + + try { + const wrapperUrl = `${normalizedUrl}gs.js`; + + await loadScript(wrapperUrl); + + const globalScope = + typeof globalThis !== 'undefined' ? globalThis : window; + + if (typeof (globalScope as any).loadGS === 'function') { + cachedGS = await (globalScope as any).loadGS({ + baseUrl: normalizedUrl, + }); + } else if (typeof (globalScope as any).GhostscriptWASM === 'function') { + cachedGS = new (globalScope as any).GhostscriptWASM(normalizedUrl); + await cachedGS.init?.(); + } else { + throw new Error( + 'Ghostscript wrapper did not expose expected interface. Expected loadGS() or GhostscriptWASM class.' + ); + } + + return cachedGS; + } catch (error: any) { + loadPromise = null; + throw new Error( + `Failed to load Ghostscript from ${normalizedUrl}: ${error.message}` + ); + } + })(); + + return loadPromise; +} + +function loadScript(url: string): Promise { + return new Promise((resolve, reject) => { + if (document.querySelector(`script[src="${url}"]`)) { + resolve(); + return; + } + + const script = document.createElement('script'); + script.src = url; + script.type = 'text/javascript'; + script.async = true; + + script.onload = () => resolve(); + script.onerror = () => reject(new Error(`Failed to load script: ${url}`)); + + document.head.appendChild(script); + }); +} + +export function isGhostscriptAvailable(): boolean { + return WasmProvider.isConfigured('ghostscript'); +} + +export function clearGhostscriptCache(): void { + cachedGS = null; + loadPromise = null; +} diff --git a/src/js/utils/ghostscript-loader.ts b/src/js/utils/ghostscript-loader.ts new file mode 100644 index 0000000..3ed86a0 --- /dev/null +++ b/src/js/utils/ghostscript-loader.ts @@ -0,0 +1,481 @@ +/** + * PDF/A Conversion using Ghostscript WASM + * Converts PDFs to PDF/A-1b, PDF/A-2b, or PDF/A-3b format. + * Requires user to configure Ghostscript URL in WASM Settings. + */ + +import { + getWasmBaseUrl, + fetchWasmFile, + isWasmAvailable, +} from '../config/wasm-cdn-config.js'; +import { PDFDocument, PDFDict, PDFName, PDFArray } from 'pdf-lib'; + +interface GhostscriptModule { + FS: { + writeFile(path: string, data: Uint8Array | string): void; + readFile(path: string, opts?: { encoding?: string }): Uint8Array; + unlink(path: string): void; + stat(path: string): { size: number }; + }; + callMain(args: string[]): number; +} + +export type PdfALevel = 'PDF/A-1b' | 'PDF/A-2b' | 'PDF/A-3b'; + +let cachedGsModule: GhostscriptModule | null = null; + +export function setCachedGsModule(module: GhostscriptModule): void { + cachedGsModule = module; +} + +export function getCachedGsModule(): GhostscriptModule | null { + return cachedGsModule; +} + +export async function loadGsModule(): Promise { + const gsBaseUrl = getWasmBaseUrl('ghostscript')!; + const normalizedUrl = gsBaseUrl.endsWith('/') ? gsBaseUrl : `${gsBaseUrl}/`; + + const gsJsUrl = `${normalizedUrl}gs.js`; + const response = await fetch(gsJsUrl); + if (!response.ok) { + throw new Error(`Failed to fetch gs.js: HTTP ${response.status}`); + } + const jsText = await response.text(); + const blob = new Blob([jsText], { type: 'application/javascript' }); + const blobUrl = URL.createObjectURL(blob); + + try { + const gsModule = await import(/* @vite-ignore */ blobUrl); + const ModuleFactory = gsModule.default; + + return (await ModuleFactory({ + locateFile: (path: string) => { + if (path.endsWith('.wasm')) { + return `${normalizedUrl}gs.wasm`; + } + return `${normalizedUrl}${path}`; + }, + print: (text: string) => console.log('[GS]', text), + printErr: (text: string) => console.error('[GS Error]', text), + })) as GhostscriptModule; + } finally { + URL.revokeObjectURL(blobUrl); + } +} + +export async function convertToPdfA( + pdfData: Uint8Array, + level: PdfALevel = 'PDF/A-2b', + onProgress?: (msg: string) => void +): Promise { + if (!isWasmAvailable('ghostscript')) { + throw new Error( + 'Ghostscript is not configured. Please configure it in WASM Settings.' + ); + } + + onProgress?.('Loading Ghostscript...'); + + let gs: GhostscriptModule; + + if (cachedGsModule) { + gs = cachedGsModule; + } else { + gs = await loadGsModule(); + cachedGsModule = gs; + } + + const pdfaMap: Record = { + 'PDF/A-1b': '1', + 'PDF/A-2b': '2', + 'PDF/A-3b': '3', + }; + + const inputPath = '/tmp/input.pdf'; + const outputPath = '/tmp/output.pdf'; + const iccPath = '/tmp/pdfa.icc'; + const pdfaDefPath = '/tmp/pdfa.ps'; + + gs.FS.writeFile(inputPath, pdfData); + console.log('[Ghostscript] Input file size:', pdfData.length); + + onProgress?.(`Converting to ${level}...`); + + try { + const iccFileName = 'sRGB_IEC61966-2-1_no_black_scaling.icc'; + const iccUrl = `${import.meta.env.BASE_URL}${iccFileName}`; + const response = await fetch(iccUrl); + + if (!response.ok) { + throw new Error( + `Failed to fetch ICC profile from ${iccUrl}: HTTP ${response.status}` + ); + } + + const iccData = new Uint8Array(await response.arrayBuffer()); + console.log( + '[Ghostscript] sRGB v2 ICC profile loaded:', + iccData.length, + 'bytes' + ); + + gs.FS.writeFile(iccPath, iccData); + console.log('[Ghostscript] sRGB ICC profile written to FS:', iccPath); + + const iccHex = Array.from(iccData) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); + console.log('[Ghostscript] ICC profile hex length:', iccHex.length); + + const pdfaSubtype = level === 'PDF/A-1b' ? '/GTS_PDFA1' : '/GTS_PDFA'; + + const pdfaPS = `%! +% PDF/A definition file for ${level} + +% Define the ICC profile stream object with embedded hex data +[/_objdef {icc_PDFA} /type /stream /OBJ pdfmark +[{icc_PDFA} << /N 3 >> /PUT pdfmark +[{icc_PDFA} <${iccHex}> /PUT pdfmark + +% Define the OutputIntent dictionary +[/_objdef {OutputIntent_PDFA} /type /dict /OBJ pdfmark +[{OutputIntent_PDFA} << + /Type /OutputIntent + /S ${pdfaSubtype} + /DestOutputProfile {icc_PDFA} + /OutputConditionIdentifier (sRGB IEC61966-2.1) + /Info (sRGB IEC61966-2.1) + /RegistryName (http://www.color.org) +>> /PUT pdfmark + +% Attach OutputIntent to the document Catalog +[{Catalog} << /OutputIntents [ {OutputIntent_PDFA} ] >> /PUT pdfmark +`; + + gs.FS.writeFile(pdfaDefPath, pdfaPS); + console.log( + '[Ghostscript] PDFA PostScript created with embedded ICC hex data' + ); + } catch (e) { + console.error('[Ghostscript] Failed to setup PDF/A assets:', e); + throw new Error('Conversion failed: could not create PDF/A definition'); + } + + const args = [ + '-dNOSAFER', + '-dBATCH', + '-dNOPAUSE', + '-sDEVICE=pdfwrite', + `-dPDFA=${pdfaMap[level]}`, + '-dPDFACompatibilityPolicy=1', + `-dCompatibilityLevel=${level === 'PDF/A-1b' ? '1.4' : '1.7'}`, + '-sColorConversionStrategy=UseDeviceIndependentColor', + '-sICCProfilesDir=/tmp/', + `-sOutputICCProfile=${iccPath}`, + `-sDefaultRGBProfile=${iccPath}`, + `-sBlendColorProfile=${iccPath}`, + '-dCompressPages=true', + '-dWriteObjStms=false', + '-dWriteXRefStm=false', + '-dEmbedAllFonts=true', + '-dSubsetFonts=true', + '-dAutoRotatePages=/None', + `-sOutputFile=${outputPath}`, + pdfaDefPath, + inputPath, + ]; + + console.log('[Ghostscript] Running PDF/A conversion...'); + try { + console.log('[Ghostscript] Checking version:'); + gs.callMain(['--version']); + } catch (e) { + console.warn('[Ghostscript] Could not check version:', e); + } + + let exitCode: number; + try { + exitCode = gs.callMain(args); + } catch (e) { + console.error('[Ghostscript] Exception:', e); + throw new Error(`Ghostscript threw an exception: ${e}`); + } + + console.log('[Ghostscript] Exit code:', exitCode); + + if (exitCode !== 0) { + try { + gs.FS.unlink(inputPath); + } catch { + /* ignore */ + } + try { + gs.FS.unlink(outputPath); + } catch { + /* ignore */ + } + try { + gs.FS.unlink(iccPath); + } catch { + /* ignore */ + } + try { + gs.FS.unlink(pdfaDefPath); + } catch { + /* ignore */ + } + throw new Error(`Ghostscript conversion failed with exit code ${exitCode}`); + } + + // Read output + let output: Uint8Array; + try { + const stat = gs.FS.stat(outputPath); + console.log('[Ghostscript] Output file size:', stat.size); + output = gs.FS.readFile(outputPath); + } catch (e) { + console.error('[Ghostscript] Failed to read output:', e); + throw new Error('Ghostscript did not produce output file'); + } + + // Cleanup + try { + gs.FS.unlink(inputPath); + } catch { + /* ignore */ + } + try { + gs.FS.unlink(outputPath); + } catch { + /* ignore */ + } + try { + gs.FS.unlink(iccPath); + } catch { + /* ignore */ + } + try { + gs.FS.unlink(pdfaDefPath); + } catch { + /* ignore */ + } + + if (level !== 'PDF/A-1b') { + onProgress?.('Post-processing for transparency compliance...'); + console.log( + '[Ghostscript] Adding Group dictionaries to pages for transparency compliance...' + ); + + try { + output = await addPageGroupDictionaries(output); + console.log('[Ghostscript] Page Group dictionaries added successfully'); + } catch (e) { + console.error('[Ghostscript] Failed to add Group dictionaries:', e); + } + } + + return output; +} + +async function addPageGroupDictionaries( + pdfData: Uint8Array +): Promise { + const pdfDoc = await PDFDocument.load(pdfData, { + ignoreEncryption: true, + updateMetadata: false, + }); + + const catalog = pdfDoc.catalog; + const outputIntentsArray = catalog.lookup(PDFName.of('OutputIntents')); + + let iccProfileRef: ReturnType = undefined; + + if (outputIntentsArray instanceof PDFArray) { + const firstIntent = outputIntentsArray.lookup(0); + if (firstIntent instanceof PDFDict) { + iccProfileRef = firstIntent.get(PDFName.of('DestOutputProfile')); + } + } + + const updateGroupCS = (groupDict: PDFDict) => { + if (!iccProfileRef) return; + + const currentCS = groupDict.get(PDFName.of('CS')); + + if (currentCS instanceof PDFName) { + const csName = currentCS.decodeText(); + if ( + csName === 'DeviceRGB' || + csName === 'DeviceGray' || + csName === 'DeviceCMYK' + ) { + const iccColorSpace = pdfDoc.context.obj([ + PDFName.of('ICCBased'), + iccProfileRef, + ]); + groupDict.set(PDFName.of('CS'), iccColorSpace); + } + } else if (!currentCS) { + const iccColorSpace = pdfDoc.context.obj([ + PDFName.of('ICCBased'), + iccProfileRef, + ]); + groupDict.set(PDFName.of('CS'), iccColorSpace); + } + }; + + const pages = pdfDoc.getPages(); + for (const page of pages) { + const pageDict = page.node; + + const existingGroup = pageDict.lookup(PDFName.of('Group')); + if (existingGroup) { + if (existingGroup instanceof PDFDict) { + updateGroupCS(existingGroup); + } + } else if (iccProfileRef) { + const colorSpace = pdfDoc.context.obj([ + PDFName.of('ICCBased'), + iccProfileRef, + ]); + const groupDict = pdfDoc.context.obj({ + Type: 'Group', + S: 'Transparency', + I: false, + K: false, + }); + (groupDict as PDFDict).set(PDFName.of('CS'), colorSpace); + pageDict.set(PDFName.of('Group'), groupDict); + } + } + + if (iccProfileRef) { + pdfDoc.context.enumerateIndirectObjects().forEach(([ref, obj]) => { + if ( + obj instanceof PDFDict || + (obj && typeof obj === 'object' && 'dict' in obj) + ) { + const dict = + 'dict' in obj ? (obj as { dict: PDFDict }).dict : (obj as PDFDict); + + const subtype = dict.get(PDFName.of('Subtype')); + if (subtype instanceof PDFName && subtype.decodeText() === 'Form') { + const group = dict.lookup(PDFName.of('Group')); + if (group instanceof PDFDict) { + updateGroupCS(group); + } + } + } + }); + } + + return await pdfDoc.save({ + useObjectStreams: false, + addDefaultPage: false, + updateFieldAppearances: false, + }); +} + +export async function convertFileToPdfA( + file: File, + level: PdfALevel = 'PDF/A-2b', + onProgress?: (msg: string) => void +): Promise { + const arrayBuffer = await file.arrayBuffer(); + const pdfData = new Uint8Array(arrayBuffer); + const result = await convertToPdfA(pdfData, level, onProgress); + const copy = new Uint8Array(result.length); + copy.set(result); + return new Blob([copy], { type: 'application/pdf' }); +} + +export async function convertFontsToOutlines( + pdfData: Uint8Array, + onProgress?: (msg: string) => void +): Promise { + if (!isWasmAvailable('ghostscript')) { + throw new Error( + 'Ghostscript is not configured. Please configure it in WASM Settings.' + ); + } + + onProgress?.('Loading Ghostscript...'); + + let gs: GhostscriptModule; + + if (cachedGsModule) { + gs = cachedGsModule; + } else { + gs = await loadGsModule(); + cachedGsModule = gs; + } + + const inputPath = '/tmp/input.pdf'; + const outputPath = '/tmp/output.pdf'; + + gs.FS.writeFile(inputPath, pdfData); + + onProgress?.('Converting fonts to outlines...'); + + const args = [ + '-dNOSAFER', + '-dBATCH', + '-dNOPAUSE', + '-sDEVICE=pdfwrite', + '-dNoOutputFonts', + '-dCompressPages=true', + '-dAutoRotatePages=/None', + `-sOutputFile=${outputPath}`, + inputPath, + ]; + + let exitCode: number; + try { + exitCode = gs.callMain(args); + } catch (e) { + try { + gs.FS.unlink(inputPath); + } catch {} + throw new Error(`Ghostscript threw an exception: ${e}`); + } + + if (exitCode !== 0) { + try { + gs.FS.unlink(inputPath); + } catch {} + try { + gs.FS.unlink(outputPath); + } catch {} + throw new Error(`Ghostscript conversion failed with exit code ${exitCode}`); + } + + let output: Uint8Array; + try { + output = gs.FS.readFile(outputPath); + } catch (e) { + throw new Error('Ghostscript did not produce output file'); + } + + try { + gs.FS.unlink(inputPath); + } catch {} + try { + gs.FS.unlink(outputPath); + } catch {} + + return output; +} + +export async function convertFileToOutlines( + file: File, + onProgress?: (msg: string) => void +): Promise { + const arrayBuffer = await file.arrayBuffer(); + const pdfData = new Uint8Array(arrayBuffer); + const result = await convertFontsToOutlines(pdfData, onProgress); + const copy = new Uint8Array(result.length); + copy.set(result); + return new Blob([copy], { type: 'application/pdf' }); +} diff --git a/src/js/utils/helpers.ts b/src/js/utils/helpers.ts index 93b299f..b5afd5f 100644 --- a/src/js/utils/helpers.ts +++ b/src/js/utils/helpers.ts @@ -2,8 +2,7 @@ import createModule from '@neslinesli93/qpdf-wasm'; import { showLoader, hideLoader, showAlert } from '../ui.js'; import { createIcons } from 'lucide'; import { state, resetState } from '../state.js'; -import * as pdfjsLib from 'pdfjs-dist' - +import * as pdfjsLib from 'pdfjs-dist'; const STANDARD_SIZES = { A4: { width: 595.28, height: 841.89 }, @@ -50,14 +49,14 @@ export function convertPoints(points: any, unit: any) { // Convert hex color to RGB export function hexToRgb(hex: string): { r: number; g: number; b: number } { - const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { - r: parseInt(result[1], 16) / 255, - g: parseInt(result[2], 16) / 255, - b: parseInt(result[3], 16) / 255, - } - : { r: 0, g: 0, b: 0 } + r: parseInt(result[1], 16) / 255, + g: parseInt(result[2], 16) / 255, + b: parseInt(result[3], 16) / 255, + } + : { r: 0, g: 0, b: 0 }; } export const formatBytes = (bytes: any, decimals = 1) => { @@ -89,7 +88,10 @@ export const readFileAsArrayBuffer = (file: any) => { }); }; -export function parsePageRanges(rangeString: string, totalPages: number): number[] { +export function parsePageRanges( + rangeString: string, + totalPages: number +): number[] { if (!rangeString || rangeString.trim() === '') { return Array.from({ length: totalPages }, (_, i) => i); } @@ -128,11 +130,9 @@ export function parsePageRanges(rangeString: string, totalPages: number): number } } - return Array.from(indices).sort((a, b) => a - b); } - /** * Formats an ISO 8601 date string (e.g., "2008-02-21T17:15:56-08:00") * into a localized, human-readable string. @@ -198,7 +198,7 @@ export function formatStars(num: number) { return (num / 1000).toFixed(1) + 'K'; } return num.toLocaleString(); -}; +} /** * Truncates a filename to a maximum length, adding ellipsis if needed. @@ -207,14 +207,18 @@ export function formatStars(num: number) { * @param maxLength - Maximum length (default: 30) * @returns Truncated filename with ellipsis if needed */ -export function truncateFilename(filename: string, maxLength: number = 25): string { +export function truncateFilename( + filename: string, + maxLength: number = 25 +): string { if (filename.length <= maxLength) { return filename; } const lastDotIndex = filename.lastIndexOf('.'); const extension = lastDotIndex !== -1 ? filename.substring(lastDotIndex) : ''; - const nameWithoutExt = lastDotIndex !== -1 ? filename.substring(0, lastDotIndex) : filename; + const nameWithoutExt = + lastDotIndex !== -1 ? filename.substring(0, lastDotIndex) : filename; const availableLength = maxLength - extension.length - 3; // 3 for '...' @@ -225,7 +229,10 @@ export function truncateFilename(filename: string, maxLength: number = 25): stri return nameWithoutExt.substring(0, availableLength) + '...' + extension; } -export function formatShortcutDisplay(shortcut: string, isMac: boolean): string { +export function formatShortcutDisplay( + shortcut: string, + isMac: boolean +): string { if (!shortcut) return ''; return shortcut .replace('mod', isMac ? '⌘' : 'Ctrl') @@ -233,7 +240,7 @@ export function formatShortcutDisplay(shortcut: string, isMac: boolean): string .replace('alt', isMac ? '⌥' : 'Alt') .replace('shift', 'Shift') .split('+') - .map(k => k.charAt(0).toUpperCase() + k.slice(1)) + .map((k) => k.charAt(0).toUpperCase() + k.slice(1)) .join(isMac ? '' : '+'); } @@ -263,7 +270,7 @@ export function resetAndReloadTool(preResetCallback?: () => void) { export function getPDFDocument(src: any) { let params = src; - // Handle different input types similar to how getDocument handles them, + // Handle different input types similar to how getDocument handles them, // but we ensure we have an object to attach wasmUrl to. if (typeof src === 'string') { params = { url: src }; @@ -285,20 +292,171 @@ export function getPDFDocument(src: any) { } /** - * Returns a sanitized PDF filename. - * - * The provided filename is processed as follows: - * - Removes a trailing `.pdf` file extension (case-insensitive) - * - Trims leading and trailing whitespace - * - Truncates the name to a maximum of 80 characters - * - * @param filename The original filename (including extension) - * @returns The sanitized filename without the `.pdf` extension, limited to 80 characters + * Escape HTML special characters to prevent XSS + * @param text - The text to escape + * @returns The escaped text */ -export function getCleanPdfFilename(filename: string): string { - let clean = filename.replace(/\.pdf$/i, '').trim(); - if (clean.length > 80) { - clean = clean.slice(0, 80); - } - return clean; +export function escapeHtml(text: string): string { + const map: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + return text.replace(/[&<>"']/g, (m) => map[m]); +} + +export function uint8ArrayToBase64(bytes: Uint8Array): string { + const CHUNK_SIZE = 0x8000; + const chunks: string[] = []; + for (let i = 0; i < bytes.length; i += CHUNK_SIZE) { + const chunk = bytes.subarray(i, Math.min(i + CHUNK_SIZE, bytes.length)); + chunks.push(String.fromCharCode(...chunk)); + } + return btoa(chunks.join('')); +} + +export function sanitizeEmailHtml(html: string): string { + if (!html) return html; + + let sanitized = html; + + sanitized = sanitized.replace(/]*>[\s\S]*?<\/head>/gi, ''); + sanitized = sanitized.replace(/]*>[\s\S]*?<\/style>/gi, ''); + sanitized = sanitized.replace(/]*>[\s\S]*?<\/script>/gi, ''); + sanitized = sanitized.replace(/]*>/gi, ''); + sanitized = sanitized.replace(/\s+style=["'][^"']*["']/gi, ''); + sanitized = sanitized.replace(/\s+class=["'][^"']*["']/gi, ''); + sanitized = sanitized.replace(/\s+data-[a-z-]+=["'][^"']*["']/gi, ''); + sanitized = sanitized.replace( + /]*(?:width=["']1["'][^>]*height=["']1["']|height=["']1["'][^>]*width=["']1["'])[^>]*\/?>/gi, + '' + ); + sanitized = sanitized.replace( + /href=["']https?:\/\/[^"']*safelinks\.protection\.outlook\.com[^"']*url=([^&"']+)[^"']*["']/gi, + (match, encodedUrl) => { + try { + const decodedUrl = decodeURIComponent(encodedUrl); + return `href="${decodedUrl}"`; + } catch { + return match; + } + } + ); + sanitized = sanitized.replace(/\s+originalsrc=["'][^"']*["']/gi, ''); + sanitized = sanitized.replace( + /href=["']([^"']{500,})["']/gi, + (match, url) => { + const baseUrl = url.split('?')[0]; + if (baseUrl && baseUrl.length < 200) { + return `href="${baseUrl}"`; + } + return `href="${url.substring(0, 200)}"`; + } + ); + + sanitized = sanitized.replace( + /\s+(cellpadding|cellspacing|bgcolor|border|valign|align|width|height|role|dir|id)=["'][^"']*["']/gi, + '' + ); + sanitized = sanitized.replace(/<\/?table[^>]*>/gi, '
'); + sanitized = sanitized.replace(/<\/?tbody[^>]*>/gi, ''); + sanitized = sanitized.replace(/<\/?thead[^>]*>/gi, ''); + sanitized = sanitized.replace(/<\/?tfoot[^>]*>/gi, ''); + sanitized = sanitized.replace(/]*>/gi, '
'); + sanitized = sanitized.replace(/<\/tr>/gi, '
'); + sanitized = sanitized.replace(/]*>/gi, ' '); + sanitized = sanitized.replace(/<\/td>/gi, ' '); + sanitized = sanitized.replace(/]*>/gi, ' '); + sanitized = sanitized.replace(/<\/th>/gi, ' '); + sanitized = sanitized.replace(/
\s*<\/div>/gi, ''); + sanitized = sanitized.replace(/\s*<\/span>/gi, ''); + sanitized = sanitized.replace(/(
)+/gi, '
'); + sanitized = sanitized.replace(/(<\/div>)+/gi, '
'); + sanitized = sanitized.replace( + /]*href=["']\s*["'][^>]*>([^<]*)<\/a>/gi, + '$1' + ); + + const MAX_HTML_SIZE = 100000; + if (sanitized.length > MAX_HTML_SIZE) { + const truncateAt = sanitized.lastIndexOf('
', MAX_HTML_SIZE); + if (truncateAt > MAX_HTML_SIZE / 2) { + sanitized = sanitized.substring(0, truncateAt) + '
'; + } else { + sanitized = sanitized.substring(0, MAX_HTML_SIZE) + '...'; + } + } + + return sanitized; +} + +/** + * Formats a raw RFC 2822 date string into a nicer human-readable format, + * while preserving the original timezone and time. + * Example input: "Sun, 8 Jan 2017 20:37:44 +0200" + * Example output: "Sunday, January 8, 2017 at 8:37 PM (+0200)" + */ +export function formatRawDate(raw: string): string { + try { + const match = raw.match( + /([A-Za-z]{3}),\s+(\d{1,2})\s+([A-Za-z]{3})\s+(\d{4})\s+(\d{2}):(\d{2})(?::(\d{2}))?\s+([+-]\d{4})/ + ); + + if (match) { + const [ + , + dayAbbr, + dom, + monthAbbr, + year, + hoursStr, + minsStr, + secsStr, + timezone, + ] = match; + + const days: Record = { + Sun: 'Sunday', + Mon: 'Monday', + Tue: 'Tuesday', + Wed: 'Wednesday', + Thu: 'Thursday', + Fri: 'Friday', + Sat: 'Saturday', + }; + const months: Record = { + Jan: 'January', + Feb: 'February', + Mar: 'March', + Apr: 'April', + May: 'May', + Jun: 'June', + Jul: 'July', + Aug: 'August', + Sep: 'September', + Oct: 'October', + Nov: 'November', + Dec: 'December', + }; + + const fullDay = days[dayAbbr] || dayAbbr; + const fullMonth = months[monthAbbr] || monthAbbr; + + let hours = parseInt(hoursStr, 10); + const ampm = hours >= 12 ? 'PM' : 'AM'; + hours = hours % 12; + hours = hours ? hours : 12; + const tzSign = timezone.substring(0, 1); + const tzHours = timezone.substring(1, 3); + const tzMins = timezone.substring(3, 5); + const formattedTz = `UTC${tzSign}${tzHours}:${tzMins}`; + + return `${fullDay}, ${fullMonth} ${dom}, ${year} at ${hours}:${minsStr} ${ampm} (${formattedTz})`; + } + } catch (e) { + // Fallback to raw string if parsing fails + } + return raw; } diff --git a/src/js/utils/hocr-transform.ts b/src/js/utils/hocr-transform.ts new file mode 100644 index 0000000..eba2772 --- /dev/null +++ b/src/js/utils/hocr-transform.ts @@ -0,0 +1,266 @@ +import { + BBox, + OcrLine, + OcrPage, + OcrWord, + WordTransform, + Baseline, +} from '@/types'; + +const BBOX_PATTERN = /bbox\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/; +const BASELINE_PATTERN = /baseline\s+([-+]?\d*\.?\d*)\s+([-+]?\d+)/; +const TEXTANGLE_PATTERN = /textangle\s+([-+]?\d*\.?\d*)/; + +export function parseBBox(title: string): BBox | null { + const match = title.match(BBOX_PATTERN); + if (!match) return null; + + return { + x0: parseInt(match[1], 10), + y0: parseInt(match[2], 10), + x1: parseInt(match[3], 10), + y1: parseInt(match[4], 10), + }; +} + +export function parseBaseline(title: string): Baseline { + const match = title.match(BASELINE_PATTERN); + if (!match) { + return { slope: 0, intercept: 0 }; + } + + return { + slope: parseFloat(match[1]) || 0, + intercept: parseInt(match[2], 10) || 0, + }; +} + +export function parseTextangle(title: string): number { + const match = title.match(TEXTANGLE_PATTERN); + if (!match) return 0; + return parseFloat(match[1]) || 0; +} + +export function getTextDirection(element: Element): 'ltr' | 'rtl' { + const dir = element.getAttribute('dir'); + return dir === 'rtl' ? 'rtl' : 'ltr'; +} + +export function shouldInjectWordBreaks(element: Element): boolean { + const lang = element.getAttribute('lang') || ''; + const cjkLangs = ['chi_sim', 'chi_tra', 'jpn', 'kor', 'zh', 'ja', 'ko']; + return !cjkLangs.includes(lang); +} + +export function normalizeText(text: string): string { + return text.normalize('NFKC'); +} + +export function parseHocrDocument(hocrText: string): OcrPage { + const parser = new DOMParser(); + const doc = parser.parseFromString(hocrText, 'text/html'); + + let width = 0; + let height = 0; + const pageDiv = doc.querySelector('.ocr_page'); + if (pageDiv) { + const title = pageDiv.getAttribute('title') || ''; + const bbox = parseBBox(title); + if (bbox) { + width = bbox.x1 - bbox.x0; + height = bbox.y1 - bbox.y0; + } + } + + const lines: OcrLine[] = []; + + const lineClasses = [ + 'ocr_line', + 'ocr_textfloat', + 'ocr_header', + 'ocr_caption', + ]; + const lineSelectors = lineClasses.map((c) => `.${c}`).join(', '); + const lineElements = doc.querySelectorAll(lineSelectors); + + if (lineElements.length > 0) { + lineElements.forEach((lineEl) => { + const line = parseHocrLine(lineEl); + if (line && line.words.length > 0) { + lines.push(line); + } + }); + } else { + const wordElements = doc.querySelectorAll('.ocrx_word'); + if (wordElements.length > 0) { + const words = parseWordsFromElements(wordElements); + if (words.length > 0) { + const allBBox = calculateBoundingBox(words.map((w) => w.bbox)); + lines.push({ + bbox: allBBox, + baseline: { slope: 0, intercept: 0 }, + textangle: 0, + words, + direction: 'ltr', + injectWordBreaks: true, + }); + } + } + } + + return { width, height, dpi: 72, lines }; +} + +function parseHocrLine(lineElement: Element): OcrLine | null { + const title = lineElement.getAttribute('title') || ''; + const bbox = parseBBox(title); + + if (!bbox) return null; + + const baseline = parseBaseline(title); + const textangle = parseTextangle(title); + + const parent = lineElement.closest('.ocr_par') || lineElement.parentElement; + const direction = parent ? getTextDirection(parent) : 'ltr'; + const injectWordBreaks = parent ? shouldInjectWordBreaks(parent) : true; + const wordElements = lineElement.querySelectorAll('.ocrx_word'); + const words = parseWordsFromElements(wordElements); + + return { + bbox, + baseline, + textangle, + words, + direction, + injectWordBreaks, + }; +} + +function parseWordsFromElements(wordElements: NodeListOf): OcrWord[] { + const words: OcrWord[] = []; + + wordElements.forEach((wordEl) => { + const title = wordEl.getAttribute('title') || ''; + const text = normalizeText((wordEl.textContent || '').trim()); + + if (!text) return; + + const bbox = parseBBox(title); + if (!bbox) return; + + const confMatch = title.match(/x_wconf\s+(\d+)/); + const confidence = confMatch ? parseInt(confMatch[1], 10) : 0; + + words.push({ + text, + bbox, + confidence, + }); + }); + + return words; +} + +function calculateBoundingBox(bboxes: BBox[]): BBox { + if (bboxes.length === 0) { + return { x0: 0, y0: 0, x1: 0, y1: 0 }; + } + + return { + x0: Math.min(...bboxes.map((b) => b.x0)), + y0: Math.min(...bboxes.map((b) => b.y0)), + x1: Math.max(...bboxes.map((b) => b.x1)), + y1: Math.max(...bboxes.map((b) => b.y1)), + }; +} + +/** + * Calculate the transformation parameters for drawing a word + * + * pdf-lib doesn't support horizontal text scaling (Tz operator), + * we calculate a font size that makes the text width exactly match the word bbox width. + * + * @param word - The word to position + * @param line - The line containing this word + * @param pageHeight - Height of the page in pixels (for coordinate flip) + * @param fontWidthFn - Function to calculate text width at a given font size + * @returns Transform parameters for pdf-lib + */ +export function calculateWordTransform( + word: OcrWord, + line: OcrLine, + pageHeight: number, + fontWidthFn: (text: string, fontSize: number) => number +): WordTransform { + const wordBBox = word.bbox; + const wordWidth = wordBBox.x1 - wordBBox.x0; + const wordHeight = wordBBox.y1 - wordBBox.y0; + + let fontSize = wordHeight; + const maxIterations = 10; + + for (let i = 0; i < maxIterations; i++) { + const currentWidth = fontWidthFn(word.text, fontSize); + if (currentWidth <= 0) break; + + const ratio = wordWidth / currentWidth; + const newFontSize = fontSize * ratio; + + if (Math.abs(newFontSize - fontSize) / fontSize < 0.01) { + fontSize = newFontSize; + break; + } + fontSize = newFontSize; + } + + fontSize = Math.max(1, Math.min(fontSize, wordHeight * 2)); + + const fontWidth = fontWidthFn(word.text, fontSize); + const horizontalScale = fontWidth > 0 ? wordWidth / fontWidth : 1; + + const slopeAngle = Math.atan(line.baseline.slope) * (180 / Math.PI); + const rotation = -line.textangle + slopeAngle; + + const x = wordBBox.x0; + + // pdf-lib draws text from baseline, so we position at word bottom + const y = pageHeight - wordBBox.y1; + + return { + x, + y, + fontSize, + horizontalScale, + rotation, + }; +} + +export function calculateSpaceTransform( + prevWord: OcrWord, + nextWord: OcrWord, + line: OcrLine, + pageHeight: number, + spaceWidthFn: (fontSize: number) => number +): { x: number; y: number; horizontalScale: number; fontSize: number } | null { + const lineHeight = line.bbox.y1 - line.bbox.y0; + const fontSize = Math.max(lineHeight + line.baseline.intercept, 1); + + const gapStart = prevWord.bbox.x1; + const gapEnd = nextWord.bbox.x0; + const gapWidth = gapEnd - gapStart; + + if (gapWidth <= 0) return null; + + const spaceWidth = spaceWidthFn(fontSize); + if (spaceWidth <= 0) return null; + + const horizontalScale = gapWidth / spaceWidth; + const baselineY = pageHeight - line.bbox.y1 - line.baseline.intercept; + + return { + x: gapStart, + y: baselineY, + horizontalScale, + fontSize, + }; +} diff --git a/src/js/utils/image-compress.ts b/src/js/utils/image-compress.ts new file mode 100644 index 0000000..3a0aa14 --- /dev/null +++ b/src/js/utils/image-compress.ts @@ -0,0 +1,158 @@ +export type ImageQuality = 'high' | 'medium' | 'low'; + +interface QualityConfig { + jpegQuality: number; + maxDimension: number; +} + +const QUALITY_CONFIGS: Record = { + high: { jpegQuality: 0.92, maxDimension: 0 }, + medium: { jpegQuality: 0.75, maxDimension: 2500 }, + low: { jpegQuality: 0.5, maxDimension: 1500 }, +}; + +export function getSelectedQuality(): ImageQuality { + const select = document.getElementById( + 'jpg-pdf-quality' + ) as HTMLSelectElement | null; + const value = select?.value; + if (value === 'high' || value === 'medium' || value === 'low') return value; + return 'medium'; +} + +export async function compressImageFile( + file: File, + quality: ImageQuality +): Promise { + if (quality === 'high') return file; + + const config = QUALITY_CONFIGS[quality]; + + return new Promise((resolve, reject) => { + const img = new Image(); + const url = URL.createObjectURL(file); + + img.onload = () => { + let width = img.naturalWidth; + let height = img.naturalHeight; + + if ( + config.maxDimension > 0 && + (width > config.maxDimension || height > config.maxDimension) + ) { + const ratio = Math.min( + config.maxDimension / width, + config.maxDimension / height + ); + width = Math.round(width * ratio); + height = Math.round(height * ratio); + } + + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + URL.revokeObjectURL(url); + reject(new Error('Canvas context failed')); + return; + } + + ctx.drawImage(img, 0, 0, width, height); + URL.revokeObjectURL(url); + + canvas.toBlob( + (blob) => { + if (!blob) { + reject(new Error('Canvas toBlob failed')); + return; + } + const newName = file.name.replace(/\.[^.]+$/, '.jpg'); + resolve(new File([blob], newName, { type: 'image/jpeg' })); + }, + 'image/jpeg', + config.jpegQuality + ); + }; + + img.onerror = () => { + URL.revokeObjectURL(url); + resolve(file); + }; + + img.src = url; + }); +} + +export async function compressImageBytes( + bytes: Uint8Array | ArrayBuffer, + quality: ImageQuality +): Promise<{ bytes: Uint8Array; type: 'jpeg' | 'png' }> { + if (quality === 'high') { + return { + bytes: bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes), + type: 'png', + }; + } + + const config = QUALITY_CONFIGS[quality]; + + return new Promise((resolve, reject) => { + const blob = new Blob([new Uint8Array(bytes)]); + const url = URL.createObjectURL(blob); + const img = new Image(); + + img.onload = () => { + let width = img.naturalWidth; + let height = img.naturalHeight; + + if ( + config.maxDimension > 0 && + (width > config.maxDimension || height > config.maxDimension) + ) { + const ratio = Math.min( + config.maxDimension / width, + config.maxDimension / height + ); + width = Math.round(width * ratio); + height = Math.round(height * ratio); + } + + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + URL.revokeObjectURL(url); + reject(new Error('Canvas context failed')); + return; + } + + ctx.drawImage(img, 0, 0, width, height); + URL.revokeObjectURL(url); + + canvas.toBlob( + async (jpegBlob) => { + if (!jpegBlob) { + reject(new Error('Canvas toBlob failed')); + return; + } + const arrayBuffer = await jpegBlob.arrayBuffer(); + resolve({ bytes: new Uint8Array(arrayBuffer), type: 'jpeg' }); + }, + 'image/jpeg', + config.jpegQuality + ); + }; + + img.onerror = () => { + URL.revokeObjectURL(url); + resolve({ + bytes: bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes), + type: 'png', + }); + }; + + img.src = url; + }); +} diff --git a/src/js/utils/image-effects.ts b/src/js/utils/image-effects.ts new file mode 100644 index 0000000..679f9b9 --- /dev/null +++ b/src/js/utils/image-effects.ts @@ -0,0 +1,312 @@ +import type { ScanSettings } from '../types/scanner-effect-type.js'; +import type { AdjustColorsSettings } from '../types/adjust-colors-type.js'; + +export function applyGreyscale(imageData: ImageData): void { + const data = imageData.data; + for (let j = 0; j < data.length; j += 4) { + const grey = Math.round( + 0.299 * data[j] + 0.587 * data[j + 1] + 0.114 * data[j + 2] + ); + data[j] = grey; + data[j + 1] = grey; + data[j + 2] = grey; + } +} + +export function applyInvertColors(imageData: ImageData): void { + const data = imageData.data; + for (let j = 0; j < data.length; j += 4) { + data[j] = 255 - data[j]; + data[j + 1] = 255 - data[j + 1]; + data[j + 2] = 255 - data[j + 2]; + } +} + +export function rgbToHsl( + r: number, + g: number, + b: number +): [number, number, number] { + r /= 255; + g /= 255; + b /= 255; + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + const l = (max + min) / 2; + let h = 0; + let s = 0; + + if (max !== min) { + const d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6; + else if (max === g) h = ((b - r) / d + 2) / 6; + else h = ((r - g) / d + 4) / 6; + } + + return [h, s, l]; +} + +export function hslToRgb( + h: number, + s: number, + l: number +): [number, number, number] { + if (s === 0) { + const v = Math.round(l * 255); + return [v, v, v]; + } + + const hue2rgb = (p: number, q: number, t: number): number => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + return [ + Math.round(hue2rgb(p, q, h + 1 / 3) * 255), + Math.round(hue2rgb(p, q, h) * 255), + Math.round(hue2rgb(p, q, h - 1 / 3) * 255), + ]; +} + +export function applyScannerEffect( + sourceData: ImageData, + canvas: HTMLCanvasElement, + settings: ScanSettings, + rotationAngle: number, + scale: number = 1 +): void { + const ctx = canvas.getContext('2d')!; + const w = sourceData.width; + const h = sourceData.height; + + const scaledBlur = settings.blur * scale; + const scaledNoise = settings.noise * scale; + + const workCanvas = document.createElement('canvas'); + workCanvas.width = w; + workCanvas.height = h; + const workCtx = workCanvas.getContext('2d')!; + + if (scaledBlur > 0) { + workCtx.filter = `blur(${scaledBlur}px)`; + } + + workCtx.putImageData(sourceData, 0, 0); + if (scaledBlur > 0) { + const tempCanvas = document.createElement('canvas'); + tempCanvas.width = w; + tempCanvas.height = h; + const tempCtx = tempCanvas.getContext('2d')!; + tempCtx.filter = `blur(${scaledBlur}px)`; + tempCtx.drawImage(workCanvas, 0, 0); + workCtx.filter = 'none'; + workCtx.clearRect(0, 0, w, h); + workCtx.drawImage(tempCanvas, 0, 0); + } + + const imageData = workCtx.getImageData(0, 0, w, h); + const data = imageData.data; + + const contrastFactor = + settings.contrast !== 0 + ? (259 * (settings.contrast + 255)) / (255 * (259 - settings.contrast)) + : 1; + + for (let i = 0; i < data.length; i += 4) { + let r = data[i]; + let g = data[i + 1]; + let b = data[i + 2]; + + if (settings.grayscale) { + const grey = Math.round(0.299 * r + 0.587 * g + 0.114 * b); + r = grey; + g = grey; + b = grey; + } + + if (settings.brightness !== 0) { + r += settings.brightness; + g += settings.brightness; + b += settings.brightness; + } + + if (settings.contrast !== 0) { + r = contrastFactor * (r - 128) + 128; + g = contrastFactor * (g - 128) + 128; + b = contrastFactor * (b - 128) + 128; + } + + if (settings.yellowish > 0) { + const intensity = settings.yellowish / 50; + r += 20 * intensity; + g += 12 * intensity; + b -= 15 * intensity; + } + + if (scaledNoise > 0) { + const n = (Math.random() - 0.5) * scaledNoise; + r += n; + g += n; + b += n; + } + + data[i] = Math.max(0, Math.min(255, r)); + data[i + 1] = Math.max(0, Math.min(255, g)); + data[i + 2] = Math.max(0, Math.min(255, b)); + } + + workCtx.putImageData(imageData, 0, 0); + + if (settings.border) { + const borderSize = Math.max(w, h) * 0.02; + const gradient1 = workCtx.createLinearGradient(0, 0, borderSize, 0); + gradient1.addColorStop(0, 'rgba(0,0,0,0.3)'); + gradient1.addColorStop(1, 'rgba(0,0,0,0)'); + workCtx.fillStyle = gradient1; + workCtx.fillRect(0, 0, borderSize, h); + + const gradient2 = workCtx.createLinearGradient(w, 0, w - borderSize, 0); + gradient2.addColorStop(0, 'rgba(0,0,0,0.3)'); + gradient2.addColorStop(1, 'rgba(0,0,0,0)'); + workCtx.fillStyle = gradient2; + workCtx.fillRect(w - borderSize, 0, borderSize, h); + + const gradient3 = workCtx.createLinearGradient(0, 0, 0, borderSize); + gradient3.addColorStop(0, 'rgba(0,0,0,0.3)'); + gradient3.addColorStop(1, 'rgba(0,0,0,0)'); + workCtx.fillStyle = gradient3; + workCtx.fillRect(0, 0, w, borderSize); + + const gradient4 = workCtx.createLinearGradient(0, h, 0, h - borderSize); + gradient4.addColorStop(0, 'rgba(0,0,0,0.3)'); + gradient4.addColorStop(1, 'rgba(0,0,0,0)'); + workCtx.fillStyle = gradient4; + workCtx.fillRect(0, h - borderSize, w, borderSize); + } + + if (rotationAngle !== 0) { + const rad = (rotationAngle * Math.PI) / 180; + const cos = Math.abs(Math.cos(rad)); + const sin = Math.abs(Math.sin(rad)); + const newW = Math.ceil(w * cos + h * sin); + const newH = Math.ceil(w * sin + h * cos); + + canvas.width = newW; + canvas.height = newH; + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, newW, newH); + ctx.translate(newW / 2, newH / 2); + ctx.rotate(rad); + ctx.drawImage(workCanvas, -w / 2, -h / 2); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } else { + canvas.width = w; + canvas.height = h; + ctx.drawImage(workCanvas, 0, 0); + } +} + +export function applyColorAdjustments( + sourceData: ImageData, + canvas: HTMLCanvasElement, + settings: AdjustColorsSettings +): void { + const ctx = canvas.getContext('2d')!; + const w = sourceData.width; + const h = sourceData.height; + + canvas.width = w; + canvas.height = h; + + const imageData = new ImageData(new Uint8ClampedArray(sourceData.data), w, h); + const data = imageData.data; + + const contrastFactor = + settings.contrast !== 0 + ? (259 * (settings.contrast + 255)) / (255 * (259 - settings.contrast)) + : 1; + + const gammaCorrection = settings.gamma !== 1.0 ? 1 / settings.gamma : 1; + const sepiaAmount = settings.sepia / 100; + + for (let i = 0; i < data.length; i += 4) { + let r = data[i]; + let g = data[i + 1]; + let b = data[i + 2]; + + if (settings.brightness !== 0) { + const adj = settings.brightness * 2.55; + r += adj; + g += adj; + b += adj; + } + + if (settings.contrast !== 0) { + r = contrastFactor * (r - 128) + 128; + g = contrastFactor * (g - 128) + 128; + b = contrastFactor * (b - 128) + 128; + } + + if (settings.saturation !== 0 || settings.hueShift !== 0) { + const [hue, sat, lig] = rgbToHsl( + Math.max(0, Math.min(255, r)), + Math.max(0, Math.min(255, g)), + Math.max(0, Math.min(255, b)) + ); + + let newHue = hue; + if (settings.hueShift !== 0) { + newHue = (hue + settings.hueShift / 360) % 1; + if (newHue < 0) newHue += 1; + } + + let newSat = sat; + if (settings.saturation !== 0) { + const satAdj = settings.saturation / 100; + newSat = satAdj > 0 ? sat + (1 - sat) * satAdj : sat * (1 + satAdj); + newSat = Math.max(0, Math.min(1, newSat)); + } + + [r, g, b] = hslToRgb(newHue, newSat, lig); + } + + if (settings.temperature !== 0) { + const t = settings.temperature / 50; + r += 30 * t; + b -= 30 * t; + } + + if (settings.tint !== 0) { + const t = settings.tint / 50; + g += 30 * t; + } + + if (settings.gamma !== 1.0) { + r = Math.pow(Math.max(0, Math.min(255, r)) / 255, gammaCorrection) * 255; + g = Math.pow(Math.max(0, Math.min(255, g)) / 255, gammaCorrection) * 255; + b = Math.pow(Math.max(0, Math.min(255, b)) / 255, gammaCorrection) * 255; + } + + if (settings.sepia > 0) { + const sr = 0.393 * r + 0.769 * g + 0.189 * b; + const sg = 0.349 * r + 0.686 * g + 0.168 * b; + const sb = 0.272 * r + 0.534 * g + 0.131 * b; + r = r + (sr - r) * sepiaAmount; + g = g + (sg - g) * sepiaAmount; + b = b + (sb - b) * sepiaAmount; + } + + data[i] = Math.max(0, Math.min(255, r)); + data[i + 1] = Math.max(0, Math.min(255, g)); + data[i + 2] = Math.max(0, Math.min(255, b)); + } + + ctx.putImageData(imageData, 0, 0); +} diff --git a/src/js/utils/libreoffice-loader.ts b/src/js/utils/libreoffice-loader.ts new file mode 100644 index 0000000..54b5b73 --- /dev/null +++ b/src/js/utils/libreoffice-loader.ts @@ -0,0 +1,160 @@ +/** + * LibreOffice WASM Converter Wrapper + * + * Uses @matbee/libreoffice-converter package for document conversion. + * Handles progress tracking and provides simpler API. + */ + +import { WorkerBrowserConverter } from '@matbee/libreoffice-converter/browser'; + +const LIBREOFFICE_LOCAL_PATH = import.meta.env.BASE_URL + 'libreoffice-wasm/'; + +export interface LoadProgress { + phase: 'loading' | 'initializing' | 'converting' | 'complete' | 'ready'; + percent: number; + message: string; +} + +export type ProgressCallback = (progress: LoadProgress) => void; + +// Singleton for converter instance +let converterInstance: LibreOfficeConverter | null = null; + +export class LibreOfficeConverter { + private converter: WorkerBrowserConverter | null = null; + private initialized = false; + private initializing = false; + private basePath: string; + + constructor(basePath?: string) { + this.basePath = basePath || LIBREOFFICE_LOCAL_PATH; + } + + async initialize(onProgress?: ProgressCallback): Promise { + if (this.initialized) return; + + if (this.initializing) { + while (this.initializing) { + await new Promise(r => setTimeout(r, 100)); + } + return; + } + + + this.initializing = true; + let progressCallback = onProgress; // Store original callback + + try { + progressCallback?.({ phase: 'loading', percent: 0, message: 'Loading conversion engine...' }); + + this.converter = new WorkerBrowserConverter({ + sofficeJs: `${this.basePath}soffice.js`, + sofficeWasm: `${this.basePath}soffice.wasm.gz`, + sofficeData: `${this.basePath}soffice.data.gz`, + sofficeWorkerJs: `${this.basePath}soffice.worker.js`, + browserWorkerJs: `${this.basePath}browser.worker.global.js`, + verbose: false, + onProgress: (info: { phase: string; percent: number; message: string }) => { + if (progressCallback && !this.initialized) { + const simplifiedMessage = `Loading conversion engine (${Math.round(info.percent)}%)...`; + progressCallback({ + phase: info.phase as LoadProgress['phase'], + percent: info.percent, + message: simplifiedMessage + }); + } + }, + onReady: () => { + console.log('[LibreOffice] Ready!'); + }, + onError: (error: Error) => { + console.error('[LibreOffice] Error:', error); + }, + }); + + await this.converter.initialize(); + this.initialized = true; + + // Call completion message + progressCallback?.({ phase: 'ready', percent: 100, message: 'Conversion engine ready!' }); + + // Null out the callback to prevent any late-firing progress updates + progressCallback = undefined; + } finally { + this.initializing = false; + } + } + + isReady(): boolean { + return this.initialized && this.converter !== null; + } + + async convertToPdf(file: File): Promise { + if (!this.converter) { + throw new Error('Converter not initialized'); + } + + console.log(`[LibreOffice] Converting ${file.name} to PDF...`); + console.log(`[LibreOffice] File type: ${file.type}, Size: ${file.size} bytes`); + + try { + console.log(`[LibreOffice] Reading file as ArrayBuffer...`); + const arrayBuffer = await file.arrayBuffer(); + const uint8Array = new Uint8Array(arrayBuffer); + console.log(`[LibreOffice] File loaded, ${uint8Array.length} bytes`); + + console.log(`[LibreOffice] Calling converter.convert() with buffer...`); + const startTime = Date.now(); + + // Detect input format - critical for CSV to apply import filters + const ext = file.name.split('.').pop()?.toLowerCase() || ''; + console.log(`[LibreOffice] Detected format from extension: ${ext}`); + + const result = await this.converter.convert(uint8Array, { + outputFormat: 'pdf', + inputFormat: ext as any, // Explicitly specify format for CSV import filters + }, file.name); + + const duration = Date.now() - startTime; + console.log(`[LibreOffice] Conversion complete! Duration: ${duration}ms, Size: ${result.data.length} bytes`); + + // Create a copy to avoid SharedArrayBuffer type issues + const data = new Uint8Array(result.data); + return new Blob([data], { type: result.mimeType }); + } catch (error) { + console.error(`[LibreOffice] Conversion FAILED for ${file.name}:`, error); + console.error(`[LibreOffice] Error details:`, { + message: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined + }); + throw error; + } + } + + async wordToPdf(file: File): Promise { + return this.convertToPdf(file); + } + + async pptToPdf(file: File): Promise { + return this.convertToPdf(file); + } + + async excelToPdf(file: File): Promise { + return this.convertToPdf(file); + } + + async destroy(): Promise { + if (this.converter) { + await this.converter.destroy(); + } + this.converter = null; + this.initialized = false; + } +} + +export function getLibreOfficeConverter(basePath?: string): LibreOfficeConverter { + if (!converterInstance) { + converterInstance = new LibreOfficeConverter(basePath); + } + return converterInstance; +} diff --git a/src/js/utils/markdown-editor.ts b/src/js/utils/markdown-editor.ts new file mode 100644 index 0000000..a1a450f --- /dev/null +++ b/src/js/utils/markdown-editor.ts @@ -0,0 +1,970 @@ +import MarkdownIt from 'markdown-it'; +import hljs from 'highlight.js/lib/core'; +import javascript from 'highlight.js/lib/languages/javascript'; +import typescript from 'highlight.js/lib/languages/typescript'; +import python from 'highlight.js/lib/languages/python'; +import css from 'highlight.js/lib/languages/css'; +import xml from 'highlight.js/lib/languages/xml'; +import json from 'highlight.js/lib/languages/json'; +import bash from 'highlight.js/lib/languages/bash'; +import markdownLang from 'highlight.js/lib/languages/markdown'; +import sql from 'highlight.js/lib/languages/sql'; +import java from 'highlight.js/lib/languages/java'; +import csharp from 'highlight.js/lib/languages/csharp'; +import cpp from 'highlight.js/lib/languages/cpp'; +import go from 'highlight.js/lib/languages/go'; +import rust from 'highlight.js/lib/languages/rust'; +import yaml from 'highlight.js/lib/languages/yaml'; +import 'highlight.js/styles/github.css'; +import mermaid from 'mermaid'; +import sub from 'markdown-it-sub'; +import sup from 'markdown-it-sup'; +import footnote from 'markdown-it-footnote'; +import deflist from 'markdown-it-deflist'; +import abbr from 'markdown-it-abbr'; +import { full as emoji } from 'markdown-it-emoji'; +import ins from 'markdown-it-ins'; +import mark from 'markdown-it-mark'; +import taskLists from 'markdown-it-task-lists'; +import anchor from 'markdown-it-anchor'; +import tocDoneRight from 'markdown-it-toc-done-right'; +import { applyTranslations } from '../i18n/i18n'; + + + +// Register highlight.js languages +hljs.registerLanguage('javascript', javascript); +hljs.registerLanguage('js', javascript); +hljs.registerLanguage('typescript', typescript); +hljs.registerLanguage('ts', typescript); +hljs.registerLanguage('python', python); +hljs.registerLanguage('py', python); +hljs.registerLanguage('css', css); +hljs.registerLanguage('html', xml); +hljs.registerLanguage('xml', xml); +hljs.registerLanguage('json', json); +hljs.registerLanguage('bash', bash); +hljs.registerLanguage('sh', bash); +hljs.registerLanguage('shell', bash); +hljs.registerLanguage('markdown', markdownLang); +hljs.registerLanguage('md', markdownLang); +hljs.registerLanguage('sql', sql); +hljs.registerLanguage('java', java); +hljs.registerLanguage('csharp', csharp); +hljs.registerLanguage('cs', csharp); +hljs.registerLanguage('cpp', cpp); +hljs.registerLanguage('c', cpp); +hljs.registerLanguage('go', go); +hljs.registerLanguage('rust', rust); +hljs.registerLanguage('yaml', yaml); +hljs.registerLanguage('yml', yaml); + +export interface MarkdownEditorOptions { + /** Initial markdown content */ + initialContent?: string; + /** Callback when user wants to go back */ + onBack?: () => void; +} + +export interface MarkdownItOptions { + /** Enable HTML tags in source */ + html: boolean; + /** Convert '\n' in paragraphs into
*/ + breaks: boolean; + /** Autoconvert URL-like text to links */ + linkify: boolean; + /** Enable some language-neutral replacement + quotes beautification */ + typographer: boolean; + /** Highlight function for fenced code blocks */ + highlight?: (str: string, lang: string) => string; +} + +const DEFAULT_MARKDOWN = `# Welcome to BentoPDF Markdown Editor + +This is a **live preview** markdown editor with full plugin support. + +\${toc} + +## Basic Formatting + +- **Bold** and *italic* text +- ~~Strikethrough~~ text +- [Links](https://bentopdf.com) +- ==Highlighted text== using mark +- ++Inserted text++ using ins +- H~2~O for subscript +- E=mc^2^ for superscript + +## Task Lists + +- [x] Completed task +- [x] Another done item +- [ ] Pending task +- [ ] Future work + +## Emoji Support :rocket: + +Use emoji shortcodes: :smile: :heart: :thumbsup: :star: :fire: + +## Code with Syntax Highlighting + +\`\`\`javascript +function greet(name) { + console.log(\`Hello, \${name}!\`); + return { message: 'Welcome!' }; +} +\`\`\` + +\`\`\`python +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) +\`\`\` + +## Tables + +| Feature | Supported | Notes | +|---------|:---------:|-------| +| Headers | ✓ | Multiple levels | +| Lists | ✓ | Ordered & unordered | +| Code | ✓ | With highlighting | +| Tables | ✓ | With alignment | +| Emoji | ✓ | :white_check_mark: | +| Mermaid | ✓ | Diagrams! | + +## Mermaid Diagrams + +### Flowchart + +\`\`\`mermaid +graph TD + A[Start] --> B{Decision} + B -->|Yes| C[OK] + B -->|No| D[Cancel] +\`\`\` + +### Sequence Diagram + +\`\`\`mermaid +sequenceDiagram + participant User + participant BentoPDF + participant Server + User->>BentoPDF: Upload PDF + BentoPDF->>BentoPDF: Process locally + BentoPDF-->>User: Download result + Note over BentoPDF: No server needed! +\`\`\` + +### Pie Chart + +\`\`\`mermaid +pie title PDF Tools Usage + "Merge" : 35 + "Compress" : 25 + "Convert" : 20 + "Edit" : 15 + "Other" : 5 +\`\`\` + +### Class Diagram + +\`\`\`mermaid +classDiagram + class PDFDocument { + +String title + +int pageCount + +merge() + +split() + +compress() + } + class Page { + +int number + +rotate() + +crop() + } + PDFDocument "1" --> "*" Page +\`\`\` + +### Gantt Chart + +\`\`\`mermaid +gantt + title Project Timeline + dateFormat YYYY-MM-DD + section Planning + Research :a1, 2024-01-01, 7d + Design :a2, after a1, 5d + section Development + Implementation :a3, after a2, 14d + Testing :a4, after a3, 7d +\`\`\` + +### Entity Relationship + +\`\`\`mermaid +erDiagram + USER ||--o{ DOCUMENT : uploads + DOCUMENT ||--|{ PAGE : contains + DOCUMENT { + string id + string name + date created + } + PAGE { + int number + string content + } +\`\`\` + +### Mindmap + +\`\`\`mermaid +mindmap + root((BentoPDF)) + Convert + Word to PDF + Excel to PDF + Image to PDF + Edit + Merge + Split + Compress + Secure + Encrypt + Sign + Watermark +\`\`\` + +## Footnotes + +Here's a sentence with a footnote[^1]. + +## Definition Lists + +Term 1 +: Definition for term 1 + +Term 2 +: Definition for term 2 +: Another definition for term 2 + +## Abbreviations + +The HTML specification is maintained by the W3C. + +*[HTML]: Hyper Text Markup Language +*[W3C]: World Wide Web Consortium + +--- + +Start editing to see the magic happen! + +[^1]: This is the footnote content. +`; + +export class MarkdownEditor { + private container: HTMLElement; + private md: MarkdownIt; + private editor: HTMLTextAreaElement | null = null; + private preview: HTMLElement | null = null; + private onBack?: () => void; + private syncScroll: boolean = false; + private isSyncing: boolean = false; + private mermaidInitialized: boolean = false; + private mdOptions: MarkdownItOptions = { + html: true, + breaks: false, + linkify: true, + typographer: true + }; + + constructor(container: HTMLElement, options: MarkdownEditorOptions) { + this.container = container; + this.onBack = options.onBack; + + this.initMermaid(); + this.md = this.createMarkdownIt(); + this.configureLinkRenderer(); + + this.render(); + + if (options.initialContent) { + this.setContent(options.initialContent); + } else { + this.setContent(DEFAULT_MARKDOWN); + } + } + + private initMermaid(): void { + if (!this.mermaidInitialized) { + mermaid.initialize({ + startOnLoad: false, + theme: 'default', + securityLevel: 'loose', + fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif' + }); + this.mermaidInitialized = true; + } + } + + private configureLinkRenderer(): void { + // Override link renderer to add target="_blank" and rel="noopener" + const defaultRender = this.md.renderer.rules.link_open || + ((tokens: any[], idx: number, options: any, _env: any, self: any) => self.renderToken(tokens, idx, options)); + + this.md.renderer.rules.link_open = (tokens: any[], idx: number, options: any, env: any, self: any) => { + const token = tokens[idx]; + token.attrSet('target', '_blank'); + token.attrSet('rel', 'noopener noreferrer'); + return defaultRender(tokens, idx, options, env, self); + }; + } + + private render(): void { + this.container.innerHTML = ` +
+
+
+
+ + +
+ +
+ +
+ + + +
+
+ +
+
+
+ Markdown +
+ +
+
+
+ Preview +
+
+
+
+
+
+ + + + `; + + this.editor = document.getElementById('mdTextarea') as HTMLTextAreaElement; + this.preview = document.getElementById('mdPreview') as HTMLElement; + + this.setupEventListeners(); + this.applyI18n(); + + // Initialize Lucide icons + if (typeof (window as any).lucide !== 'undefined') { + (window as any).lucide.createIcons(); + } + } + + + private setupEventListeners(): void { + // Editor input + this.editor?.addEventListener('input', () => { + this.updatePreview(); + }); + + // Sync scroll + const syncScrollBtn = document.getElementById('mdSyncScroll'); + syncScrollBtn?.addEventListener('click', () => { + this.syncScroll = !this.syncScroll; + syncScrollBtn.classList.toggle('md-editor-btn-primary'); + syncScrollBtn.classList.toggle('md-editor-btn-secondary'); + }); + + // Editor scroll sync + this.editor?.addEventListener('scroll', () => { + if (this.syncScroll && !this.isSyncing && this.editor && this.preview) { + this.isSyncing = true; + const scrollPercentage = this.editor.scrollTop / (this.editor.scrollHeight - this.editor.clientHeight); + this.preview.scrollTop = scrollPercentage * (this.preview.scrollHeight - this.preview.clientHeight); + setTimeout(() => this.isSyncing = false, 10); + } + }); + + // Preview scroll sync (bidirectional) + this.preview?.addEventListener('scroll', () => { + if (this.syncScroll && !this.isSyncing && this.editor && this.preview) { + this.isSyncing = true; + const scrollPercentage = this.preview.scrollTop / (this.preview.scrollHeight - this.preview.clientHeight); + this.editor.scrollTop = scrollPercentage * (this.editor.scrollHeight - this.editor.clientHeight); + setTimeout(() => this.isSyncing = false, 10); + } + }); + + // Theme toggle + const themeToggle = document.getElementById('themeToggle'); + const editorContainer = document.querySelector('.md-editor'); + themeToggle?.addEventListener('click', () => { + editorContainer?.classList.toggle('light-mode'); + themeToggle.classList.toggle('active'); + }); + + // Settings modal open + document.getElementById('mdSettings')?.addEventListener('click', () => { + const modal = document.getElementById('mdSettingsModal'); + if (modal) { + modal.style.display = 'flex'; + } + }); + + // Settings modal close + document.getElementById('mdCloseSettings')?.addEventListener('click', () => { + const modal = document.getElementById('mdSettingsModal'); + if (modal) { + modal.style.display = 'none'; + } + }); + + // Close modal on overlay click + document.getElementById('mdSettingsModal')?.addEventListener('click', (e) => { + if ((e.target as HTMLElement).classList.contains('md-editor-modal-overlay')) { + const modal = document.getElementById('mdSettingsModal'); + if (modal) { + modal.style.display = 'none'; + } + } + }); + + // Settings checkboxes + document.getElementById('mdOptHtml')?.addEventListener('change', (e) => { + this.mdOptions.html = (e.target as HTMLInputElement).checked; + this.updateMarkdownIt(); + }); + + document.getElementById('mdOptBreaks')?.addEventListener('change', (e) => { + this.mdOptions.breaks = (e.target as HTMLInputElement).checked; + this.updateMarkdownIt(); + }); + + document.getElementById('mdOptLinkify')?.addEventListener('change', (e) => { + this.mdOptions.linkify = (e.target as HTMLInputElement).checked; + this.updateMarkdownIt(); + }); + + document.getElementById('mdOptTypographer')?.addEventListener('change', (e) => { + this.mdOptions.typographer = (e.target as HTMLInputElement).checked; + this.updateMarkdownIt(); + }); + + // Preset selector + document.getElementById('mdPreset')?.addEventListener('change', (e) => { + const preset = (e.target as HTMLSelectElement).value; + this.applyPreset(preset as 'default' | 'commonmark' | 'zero'); + }); + + // Upload button + document.getElementById('mdUpload')?.addEventListener('click', () => { + document.getElementById('mdFileInput')?.click(); + }); + + // File input change + document.getElementById('mdFileInput')?.addEventListener('change', (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + this.loadFile(file); + } + }); + + // Export PDF + document.getElementById('mdExport')?.addEventListener('click', () => { + this.exportPdf(); + }); + + // Keyboard shortcuts + this.editor?.addEventListener('keydown', (e) => { + // Ctrl/Cmd + S to export + if ((e.ctrlKey || e.metaKey) && e.key === 's') { + e.preventDefault(); + this.exportPdf(); + } + // Tab key for indentation + if (e.key === 'Tab') { + e.preventDefault(); + const start = this.editor!.selectionStart; + const end = this.editor!.selectionEnd; + const value = this.editor!.value; + this.editor!.value = value.substring(0, start) + ' ' + value.substring(end); + this.editor!.selectionStart = this.editor!.selectionEnd = start + 2; + this.updatePreview(); + } + }); + } + + private currentPreset: 'default' | 'commonmark' | 'zero' = 'default'; + + private applyPreset(preset: 'default' | 'commonmark' | 'zero'): void { + this.currentPreset = preset; + + // Update options based on preset + if (preset === 'commonmark') { + this.mdOptions = { html: false, breaks: false, linkify: false, typographer: false }; + } else if (preset === 'zero') { + this.mdOptions = { html: false, breaks: false, linkify: false, typographer: false }; + } else { + this.mdOptions = { html: true, breaks: false, linkify: true, typographer: true }; + } + + // Update UI checkboxes + (document.getElementById('mdOptHtml') as HTMLInputElement).checked = this.mdOptions.html; + (document.getElementById('mdOptBreaks') as HTMLInputElement).checked = this.mdOptions.breaks; + (document.getElementById('mdOptLinkify') as HTMLInputElement).checked = this.mdOptions.linkify; + (document.getElementById('mdOptTypographer') as HTMLInputElement).checked = this.mdOptions.typographer; + + this.updateMarkdownIt(); + } + + private async loadFile(file: File): Promise { + try { + const text = await file.text(); + this.setContent(text); + } catch (error) { + console.error('Failed to load file:', error); + } + } + + + private createMarkdownIt(): MarkdownIt { + // Use preset if commonmark or zero + let md: MarkdownIt; + if (this.currentPreset === 'commonmark') { + md = new MarkdownIt('commonmark'); + } else if (this.currentPreset === 'zero') { + md = new MarkdownIt('zero'); + // Enable basic features for zero preset + md.enable(['paragraph', 'newline', 'text']); + } else { + md = new MarkdownIt({ + ...this.mdOptions, + highlight: (str: string, lang: string) => { + if (lang && hljs.getLanguage(lang)) { + try { + return hljs.highlight(str, { language: lang, ignoreIllegals: true }).value; + } catch { + // Fall through to default + } + } + return ''; // Use external default escaping + } + }); + } + + // Apply plugins only for default preset (plugins may not work well with commonmark/zero) + if (this.currentPreset === 'default') { + md.use(sub) // Subscript: ~text~ -> text + .use(sup) // Superscript: ^text^ -> text + .use(footnote) // Footnotes: [^1] and [^1]: footnote text + .use(deflist) // Definition lists + .use(abbr) // Abbreviations: *[abbr]: full text + .use(emoji) // Emoji: :smile: -> 😄 + .use(ins) // Inserted text: ++text++ -> text + .use(mark) // Marked text: ==text== -> text + .use(taskLists, { enabled: true, label: true, labelAfter: true }) // Task lists: - [x] done + .use(anchor, { permalink: false }) // Header anchors + .use(tocDoneRight); // Table of contents: ${toc} + } + + return md; + } + + private updateMarkdownIt(): void { + this.md = this.createMarkdownIt(); + this.configureLinkRenderer(); + this.updatePreview(); + } + + private updatePreview(): void { + if (!this.editor || !this.preview) return; + + const markdown = this.editor.value; + const html = this.md.render(markdown); + this.preview.innerHTML = html; + this.renderMermaidDiagrams(); + } + + private async renderMermaidDiagrams(): Promise { + if (!this.preview) return; + + const mermaidBlocks = this.preview.querySelectorAll('pre > code.language-mermaid'); + + for (let i = 0; i < mermaidBlocks.length; i++) { + const block = mermaidBlocks[i] as HTMLElement; + const code = block.textContent || ''; + const pre = block.parentElement; + + if (pre && code.trim()) { + try { + const id = `mermaid-diagram-${i}-${Date.now()}`; + const { svg } = await mermaid.render(id, code.trim()); + + const wrapper = document.createElement('div'); + wrapper.className = 'mermaid-diagram'; + wrapper.innerHTML = svg; + + pre.replaceWith(wrapper); + } catch (error) { + console.error('Mermaid rendering error:', error); + const errorDiv = document.createElement('div'); + errorDiv.className = 'mermaid-error'; + errorDiv.textContent = `Mermaid Error: ${(error as Error).message}`; + pre.replaceWith(errorDiv); + } + } + } + } + + public setContent(content: string): void { + if (this.editor) { + this.editor.value = content; + this.updatePreview(); + } + } + + public getContent(): string { + return this.editor?.value || ''; + } + + public getHtml(): string { + return this.md.render(this.getContent()); + } + + private exportPdf(): void { + // Use browser's native print functionality + window.print(); + } + + private getStyledHtml(): string { + const content = this.getHtml(); + + return ` + + + + + + +${content} + +`; + } + + private applyI18n(): void { + // Apply translations to elements within this component + applyTranslations(); + + // Special handling for select options (data-i18n on options doesn't work with applyTranslations) + const presetSelect = document.getElementById('mdPreset') as HTMLSelectElement; + if (presetSelect) { + const options = presetSelect.querySelectorAll('option[data-i18n]'); + options.forEach((option) => { + const key = option.getAttribute('data-i18n'); + if (key) { + // Use i18next directly for option text + const translated = (window as any).i18next?.t(key); + if (translated && translated !== key) { + option.textContent = translated; + } + } + }); + } + } + + public destroy(): void { + this.container.innerHTML = ''; + } +} diff --git a/src/js/utils/ocr.ts b/src/js/utils/ocr.ts new file mode 100644 index 0000000..5a38d39 --- /dev/null +++ b/src/js/utils/ocr.ts @@ -0,0 +1,304 @@ +import Tesseract from 'tesseract.js'; +import { PDFDocument, StandardFonts, rgb, PDFFont } from 'pdf-lib'; +import fontkit from '@pdf-lib/fontkit'; +import * as pdfjsLib from 'pdfjs-dist'; +import { getFontForLanguage } from './font-loader.js'; +import { OcrPage, OcrLine } from '@/types'; +import { + parseHocrDocument, + calculateWordTransform, + calculateSpaceTransform, +} from './hocr-transform.js'; +import { getPDFDocument } from './helpers.js'; + +export interface OcrOptions { + language: string; + resolution: number; + binarize: boolean; + whitelist: string; + onProgress?: (status: string, progress: number) => void; +} + +export interface OcrResult { + pdfBytes: Uint8Array; + pdfDoc: PDFDocument; + fullText: string; +} + +function binarizeCanvas(ctx: CanvasRenderingContext2D) { + const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); + const data = imageData.data; + for (let i = 0; i < data.length; i += 4) { + const brightness = + 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; + const color = brightness > 128 ? 255 : 0; + data[i] = data[i + 1] = data[i + 2] = color; + } + ctx.putImageData(imageData, 0, 0); +} + +function drawOcrTextLayer( + page: ReturnType, + ocrPage: OcrPage, + pageHeight: number, + primaryFont: PDFFont, + latinFont: PDFFont +): void { + ocrPage.lines.forEach(function (line: OcrLine) { + const words = line.words; + + for (let i = 0; i < words.length; i++) { + const word = words[i]; + const text = word.text.replace( + /[\u0000-\u001F\u007F-\u009F\u200E\u200F\u202A-\u202E\uFEFF]/g, + '' + ); + + if (!text.trim()) continue; + + const hasNonLatin = /[^\u0000-\u007F]/.test(text); + const font = hasNonLatin ? primaryFont : latinFont; + + if (!font) { + console.warn('Font not available for text: "' + text + '"'); + continue; + } + + const transform = calculateWordTransform( + word, + line, + pageHeight, + (txt: string, size: number) => { + try { + return font.widthOfTextAtSize(txt, size); + } catch { + return 0; + } + } + ); + + if (transform.fontSize <= 0) continue; + + try { + page.drawText(text, { + x: transform.x, + y: transform.y, + font, + size: transform.fontSize, + color: rgb(0, 0, 0), + opacity: 0, + }); + } catch (error) { + console.warn(`Could not draw text "${text}":`, error); + } + + if (line.injectWordBreaks && i < words.length - 1) { + const nextWord = words[i + 1]; + const spaceTransform = calculateSpaceTransform( + word, + nextWord, + line, + pageHeight, + (size: number) => { + try { + return font.widthOfTextAtSize(' ', size); + } catch { + return 0; + } + } + ); + + if (spaceTransform && spaceTransform.horizontalScale > 0.1) { + try { + page.drawText(' ', { + x: spaceTransform.x, + y: spaceTransform.y, + font, + size: spaceTransform.fontSize, + color: rgb(0, 0, 0), + opacity: 0, + }); + } catch { + console.warn(`Could not draw space between words`); + } + } + } + } + }); +} + +export async function performOcr( + pdfBytes: Uint8Array | ArrayBuffer, + options: OcrOptions +): Promise { + const { language, resolution, binarize, whitelist, onProgress } = options; + const progress = onProgress || (() => {}); + + const worker = await Tesseract.createWorker(language, 1, { + logger: function (m: { status: string; progress: number }) { + progress(m.status, m.progress || 0); + }, + }); + + await worker.setParameters({ + tessjs_create_hocr: '1', + tessedit_pageseg_mode: Tesseract.PSM.AUTO, + }); + + if (whitelist) { + await worker.setParameters({ + tessedit_char_whitelist: whitelist, + }); + } + + const pdf = await getPDFDocument({ data: pdfBytes }).promise; + const newPdfDoc = await PDFDocument.create(); + + newPdfDoc.registerFontkit(fontkit); + + progress('Loading fonts...', 0); + + const selectedLangs = language.split('+'); + const cjkLangs = ['jpn', 'chi_sim', 'chi_tra', 'kor']; + const indicLangs = [ + 'hin', + 'ben', + 'guj', + 'kan', + 'mal', + 'ori', + 'pan', + 'tam', + 'tel', + 'sin', + ]; + const priorityLangs = [...cjkLangs, ...indicLangs, 'ara', 'rus', 'ukr']; + + const primaryLang = + selectedLangs.find((l) => priorityLangs.includes(l)) || + selectedLangs[0] || + 'eng'; + + const hasCJK = selectedLangs.some((l) => cjkLangs.includes(l)); + const hasIndic = selectedLangs.some((l) => indicLangs.includes(l)); + const hasLatin = + selectedLangs.some((l) => !priorityLangs.includes(l)) || + selectedLangs.includes('eng'); + const isIndicPlusLatin = hasIndic && hasLatin && !hasCJK; + + let primaryFont: PDFFont; + let latinFont: PDFFont; + + try { + if (isIndicPlusLatin) { + const [scriptFontBytes, latinFontBytes] = await Promise.all([ + getFontForLanguage(primaryLang), + getFontForLanguage('eng'), + ]); + primaryFont = await newPdfDoc.embedFont(scriptFontBytes, { + subset: false, + }); + latinFont = await newPdfDoc.embedFont(latinFontBytes, { + subset: false, + }); + } else { + const fontBytes = await getFontForLanguage(primaryLang); + primaryFont = await newPdfDoc.embedFont(fontBytes, { subset: false }); + latinFont = primaryFont; + } + } catch (e) { + console.error('Font loading failed, falling back to Helvetica', e); + primaryFont = await newPdfDoc.embedFont(StandardFonts.Helvetica); + latinFont = primaryFont; + } + + let fullText = ''; + + try { + for (let i = 1; i <= pdf.numPages; i++) { + progress( + `Processing page ${i} of ${pdf.numPages}`, + (i - 1) / pdf.numPages + ); + + const page = await pdf.getPage(i); + const viewport = page.getViewport({ scale: resolution }); + + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + const context = canvas.getContext('2d'); + if (!context) throw new Error('Failed to create canvas context'); + + await page.render({ canvasContext: context, viewport, canvas }).promise; + + if (binarize) { + binarizeCanvas(context); + } + + const result = await worker.recognize( + canvas, + {}, + { text: true, hocr: true } + ); + const data = result.data; + + const newPage = newPdfDoc.addPage([viewport.width, viewport.height]); + + const pngImageBytes = await new Promise(function ( + resolve, + reject + ) { + canvas.toBlob(function (blob) { + if (!blob) { + reject(new Error('Failed to create image blob')); + return; + } + const reader = new FileReader(); + reader.onload = function () { + resolve(new Uint8Array(reader.result as ArrayBuffer)); + }; + reader.onerror = function () { + reject(new Error('Failed to read image data')); + }; + reader.readAsArrayBuffer(blob); + }, 'image/png'); + }); + + // Release canvas memory + canvas.width = 0; + canvas.height = 0; + + const pngImage = await newPdfDoc.embedPng(pngImageBytes); + newPage.drawImage(pngImage, { + x: 0, + y: 0, + width: viewport.width, + height: viewport.height, + }); + + if (data.hocr) { + const ocrPage = parseHocrDocument(data.hocr); + drawOcrTextLayer( + newPage, + ocrPage, + viewport.height, + primaryFont, + latinFont + ); + } + + fullText += data.text + '\n\n'; + } + } finally { + await worker.terminate(); + } + + const savedBytes = await newPdfDoc.save(); + + return { + pdfBytes: new Uint8Array(savedBytes), + pdfDoc: newPdfDoc, + fullText, + }; +} diff --git a/src/js/utils/page-preview.ts b/src/js/utils/page-preview.ts new file mode 100644 index 0000000..a0bac06 --- /dev/null +++ b/src/js/utils/page-preview.ts @@ -0,0 +1,215 @@ +import * as pdfjsLib from 'pdfjs-dist'; +import type { PDFDocumentProxy } from 'pdfjs-dist'; +import { PreviewState } from '@/types'; + +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); + +const state: PreviewState = { + modal: null, + pdfjsDoc: null, + currentPage: 1, + totalPages: 0, + isOpen: false, + container: null, +}; + +function getOrCreateModal(): HTMLElement { + if (state.modal) return state.modal; + + const modal = document.createElement('div'); + modal.id = 'page-preview-modal'; + modal.className = + 'fixed inset-0 bg-black/80 backdrop-blur-sm z-[60] flex items-center justify-center opacity-0 pointer-events-none transition-opacity duration-200'; + modal.innerHTML = ` + + + +
+
Loading...
+
+
+ `; + + modal.addEventListener('click', (e) => { + if (e.target === modal) hidePreview(); + }); + modal.querySelector('#preview-close')!.addEventListener('click', hidePreview); + modal + .querySelector('#preview-prev')! + .addEventListener('click', () => navigatePage(-1)); + modal + .querySelector('#preview-next')! + .addEventListener('click', () => navigatePage(1)); + + document.body.appendChild(modal); + state.modal = modal; + return modal; +} + +async function renderPreviewPage(pageNumber: number): Promise { + if (!state.pdfjsDoc) return; + + const modal = getOrCreateModal(); + const container = modal.querySelector( + '#preview-canvas-container' + ) as HTMLElement; + const pageInfo = modal.querySelector('#preview-page-info') as HTMLElement; + const prevBtn = modal.querySelector('#preview-prev') as HTMLElement; + const nextBtn = modal.querySelector('#preview-next') as HTMLElement; + + container.innerHTML = '
Loading...
'; + + pageInfo.textContent = `Page ${pageNumber} of ${state.totalPages}`; + prevBtn.style.visibility = pageNumber > 1 ? 'visible' : 'hidden'; + nextBtn.style.visibility = + pageNumber < state.totalPages ? 'visible' : 'hidden'; + + try { + const page = await state.pdfjsDoc.getPage(pageNumber); + const scale = 2.0; + const viewport = page.getViewport({ scale }); + + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + canvas.className = + 'max-w-[90vw] max-h-[85vh] object-contain rounded-lg shadow-2xl'; + canvas.style.width = 'auto'; + canvas.style.height = 'auto'; + canvas.style.maxWidth = '90vw'; + canvas.style.maxHeight = '85vh'; + + const ctx = canvas.getContext('2d')!; + await page.render({ canvasContext: ctx, viewport, canvas }).promise; + + container.innerHTML = ''; + container.appendChild(canvas); + state.currentPage = pageNumber; + } catch (err) { + console.error('Preview render error:', err); + container.innerHTML = + '
Failed to render page
'; + } +} + +function navigatePage(delta: number): void { + const newPage = state.currentPage + delta; + if (newPage >= 1 && newPage <= state.totalPages) { + renderPreviewPage(newPage); + } +} + +export function showPreview( + pdfjsDoc: PDFDocumentProxy, + pageNumber: number, + totalPages: number +): void { + state.pdfjsDoc = pdfjsDoc; + state.totalPages = totalPages; + state.isOpen = true; + + const modal = getOrCreateModal(); + modal.classList.remove('opacity-0', 'pointer-events-none'); + document.body.style.overflow = 'hidden'; + + renderPreviewPage(pageNumber); +} + +export function hidePreview(): void { + if (!state.modal) return; + state.isOpen = false; + state.modal.classList.add('opacity-0', 'pointer-events-none'); + document.body.style.overflow = ''; +} + +function handleKeydown(e: KeyboardEvent): void { + if (!state.isOpen) return; + + switch (e.key) { + case 'Escape': + hidePreview(); + break; + case 'ArrowLeft': + e.preventDefault(); + navigatePage(-1); + break; + case 'ArrowRight': + e.preventDefault(); + navigatePage(1); + break; + } +} + +document.addEventListener('keydown', handleKeydown); + +export function initPagePreview( + container: HTMLElement, + pdfjsDoc: PDFDocumentProxy, + options: { pageAttr?: string } = {} +): void { + const totalPages = pdfjsDoc.numPages; + + const thumbnails = container.querySelectorAll( + '[data-page-number], [data-page-index], [data-pageIndex]' + ); + + thumbnails.forEach((thumb) => { + if (thumb.dataset.previewInit) return; + thumb.dataset.previewInit = 'true'; + + let pageNum = 1; + if (thumb.dataset.pageNumber) { + pageNum = parseInt(thumb.dataset.pageNumber, 10); + } else if (thumb.dataset.pageIndex !== undefined) { + pageNum = parseInt(thumb.dataset.pageIndex, 10) + 1; + } + + const icon = document.createElement('button'); + icon.className = + 'page-preview-btn absolute bottom-1 right-1 bg-gray-900/80 hover:bg-indigo-600 text-white/70 hover:text-white rounded-full w-7 h-7 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity z-10'; + icon.title = 'Preview'; + icon.innerHTML = + ''; + icon.addEventListener('click', (e) => { + e.stopPropagation(); + e.preventDefault(); + showPreview(pdfjsDoc, pageNum, totalPages); + }); + + if (!thumb.classList.contains('relative')) { + thumb.classList.add('relative'); + } + if (!thumb.classList.contains('group')) { + thumb.classList.add('group'); + } + + thumb.appendChild(icon); + }); + + container.addEventListener('keydown', (e) => { + if (e.key === ' ' && !state.isOpen) { + const hovered = container.querySelector( + '[data-preview-init]:hover' + ); + if (hovered) { + e.preventDefault(); + let pageNum = 1; + if (hovered.dataset.pageNumber) { + pageNum = parseInt(hovered.dataset.pageNumber, 10); + } else if (hovered.dataset.pageIndex !== undefined) { + pageNum = parseInt(hovered.dataset.pageIndex, 10) + 1; + } + showPreview(pdfjsDoc, pageNum, totalPages); + } + } + }); +} diff --git a/src/js/utils/pdf-operations.ts b/src/js/utils/pdf-operations.ts new file mode 100644 index 0000000..5b9a72b --- /dev/null +++ b/src/js/utils/pdf-operations.ts @@ -0,0 +1,524 @@ +import { PDFDocument, degrees, rgb, StandardFonts, PageSizes } from 'pdf-lib'; + +export async function mergePdfs( + pdfBytesList: Uint8Array[] +): Promise { + const mergedDoc = await PDFDocument.create(); + for (const bytes of pdfBytesList) { + const srcDoc = await PDFDocument.load(bytes); + const copiedPages = await mergedDoc.copyPages( + srcDoc, + srcDoc.getPageIndices() + ); + copiedPages.forEach((page) => mergedDoc.addPage(page)); + } + return new Uint8Array(await mergedDoc.save({ addDefaultPage: false })); +} + +export async function splitPdf( + pdfBytes: Uint8Array, + pageIndices: number[] +): Promise { + const srcDoc = await PDFDocument.load(pdfBytes); + const newPdf = await PDFDocument.create(); + const copiedPages = await newPdf.copyPages(srcDoc, pageIndices); + copiedPages.forEach((page) => newPdf.addPage(page)); + return new Uint8Array(await newPdf.save()); +} + +export async function rotatePdfUniform( + pdfBytes: Uint8Array, + angle: number +): Promise { + const srcDoc = await PDFDocument.load(pdfBytes); + const newPdfDoc = await PDFDocument.create(); + const pageCount = srcDoc.getPageCount(); + + for (let i = 0; i < pageCount; i++) { + const originalPage = srcDoc.getPage(i); + const currentRotation = originalPage.getRotation().angle; + const totalRotation = currentRotation + angle; + + if (totalRotation % 90 === 0) { + const [copiedPage] = await newPdfDoc.copyPages(srcDoc, [i]); + copiedPage.setRotation(degrees(totalRotation)); + newPdfDoc.addPage(copiedPage); + } else { + const embeddedPage = await newPdfDoc.embedPage(originalPage); + const { width, height } = embeddedPage.scale(1); + const angleRad = (totalRotation * Math.PI) / 180; + const absCos = Math.abs(Math.cos(angleRad)); + const absSin = Math.abs(Math.sin(angleRad)); + const newWidth = width * absCos + height * absSin; + const newHeight = width * absSin + height * absCos; + const newPage = newPdfDoc.addPage([newWidth, newHeight]); + const x = + newWidth / 2 - + ((width / 2) * Math.cos(angleRad) - (height / 2) * Math.sin(angleRad)); + const y = + newHeight / 2 - + ((width / 2) * Math.sin(angleRad) + (height / 2) * Math.cos(angleRad)); + newPage.drawPage(embeddedPage, { + x, + y, + width, + height, + rotate: degrees(totalRotation), + }); + } + } + + return new Uint8Array(await newPdfDoc.save()); +} + +export async function rotatePdfPages( + pdfBytes: Uint8Array, + rotations: number[] +): Promise { + const srcDoc = await PDFDocument.load(pdfBytes); + const newPdfDoc = await PDFDocument.create(); + const pageCount = srcDoc.getPageCount(); + + for (let i = 0; i < pageCount; i++) { + const rotation = rotations[i] || 0; + const originalPage = srcDoc.getPage(i); + const currentRotation = originalPage.getRotation().angle; + const totalRotation = currentRotation + rotation; + + if (totalRotation % 90 === 0) { + const [copiedPage] = await newPdfDoc.copyPages(srcDoc, [i]); + copiedPage.setRotation(degrees(totalRotation)); + newPdfDoc.addPage(copiedPage); + } else { + const embeddedPage = await newPdfDoc.embedPage(originalPage); + const { width, height } = embeddedPage.scale(1); + const angleRad = (totalRotation * Math.PI) / 180; + const absCos = Math.abs(Math.cos(angleRad)); + const absSin = Math.abs(Math.sin(angleRad)); + const newWidth = width * absCos + height * absSin; + const newHeight = width * absSin + height * absCos; + const newPage = newPdfDoc.addPage([newWidth, newHeight]); + const x = + newWidth / 2 - + ((width / 2) * Math.cos(angleRad) - (height / 2) * Math.sin(angleRad)); + const y = + newHeight / 2 - + ((width / 2) * Math.sin(angleRad) + (height / 2) * Math.cos(angleRad)); + newPage.drawPage(embeddedPage, { + x, + y, + width, + height, + rotate: degrees(totalRotation), + }); + } + } + + return new Uint8Array(await newPdfDoc.save()); +} + +export async function deletePdfPages( + pdfBytes: Uint8Array, + pagesToDelete: Set +): Promise { + const srcDoc = await PDFDocument.load(pdfBytes); + const totalPages = srcDoc.getPageCount(); + + const pagesToKeep: number[] = []; + for (let i = 0; i < totalPages; i++) { + if (!pagesToDelete.has(i + 1)) { + pagesToKeep.push(i); + } + } + + if (pagesToKeep.length === 0) throw new Error('Cannot delete all pages'); + + const newPdf = await PDFDocument.create(); + const copiedPages = await newPdf.copyPages(srcDoc, pagesToKeep); + copiedPages.forEach((page) => newPdf.addPage(page)); + return new Uint8Array(await newPdf.save()); +} + +export function parsePageRange(rangeStr: string, totalPages: number): number[] { + const indices: Set = new Set(); + const parts = rangeStr.split(',').map((s) => s.trim()); + + for (const part of parts) { + if (part.includes('-')) { + const [startStr, endStr] = part.split('-'); + const start = Math.max(1, parseInt(startStr, 10) || 1); + const end = Math.min(totalPages, parseInt(endStr, 10) || totalPages); + for (let i = start; i <= end; i++) { + indices.add(i - 1); + } + } else { + const page = parseInt(part, 10); + if (page >= 1 && page <= totalPages) { + indices.add(page - 1); + } + } + } + + return Array.from(indices).sort((a, b) => a - b); +} + +export function parseDeletePages(str: string, totalPages: number): Set { + const pages = new Set(); + const parts = str.split(',').map((s) => s.trim()); + + for (const part of parts) { + if (part.includes('-')) { + const [startStr, endStr] = part.split('-'); + const start = Math.max(1, parseInt(startStr, 10) || 1); + const end = Math.min(totalPages, parseInt(endStr, 10) || totalPages); + for (let i = start; i <= end; i++) pages.add(i); + } else { + const page = parseInt(part, 10); + if (page >= 1 && page <= totalPages) pages.add(page); + } + } + + return pages; +} + +export interface TextWatermarkOptions { + text: string; + fontSize: number; + color: { r: number; g: number; b: number }; + opacity: number; + angle: number; + x?: number; + y?: number; + pageIndices?: number[]; +} + +export async function addTextWatermark( + pdfBytes: Uint8Array, + options: TextWatermarkOptions +): Promise { + const pdfDoc = await PDFDocument.load(pdfBytes); + + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (!ctx) throw new Error('Failed to create canvas context'); + + const dpr = 2; + const colorR = Math.round(options.color.r * 255); + const colorG = Math.round(options.color.g * 255); + const colorB = Math.round(options.color.b * 255); + const fontStr = `bold ${options.fontSize * dpr}px "Noto Sans SC", "Noto Sans JP", "Noto Sans KR", "Noto Sans Arabic", Arial, sans-serif`; + + ctx.font = fontStr; + const metrics = ctx.measureText(options.text); + + canvas.width = Math.ceil(metrics.width) + 4; + canvas.height = Math.ceil(options.fontSize * dpr * 1.4); + + ctx.font = fontStr; + ctx.fillStyle = `rgb(${colorR}, ${colorG}, ${colorB})`; + ctx.textBaseline = 'middle'; + ctx.fillText(options.text, 2, canvas.height / 2); + + const blob = await new Promise((resolve, reject) => { + canvas.toBlob( + (b) => (b ? resolve(b) : reject(new Error('Canvas toBlob failed'))), + 'image/png' + ); + }); + const imageBytes = new Uint8Array(await blob.arrayBuffer()); + + const image = await pdfDoc.embedPng(imageBytes); + const pages = pdfDoc.getPages(); + const posX = options.x ?? 0.5; + const posY = options.y ?? 0.5; + const imgWidth = image.width / dpr; + const imgHeight = image.height / dpr; + + const rad = (options.angle * Math.PI) / 180; + const halfW = imgWidth / 2; + const halfH = imgHeight / 2; + + const targetIndices = options.pageIndices ?? pages.map((_, i) => i); + for (const idx of targetIndices) { + const page = pages[idx]; + if (!page) continue; + const { width, height } = page.getSize(); + const cx = posX * width; + const cy = posY * height; + + page.drawImage(image, { + x: cx - Math.cos(rad) * halfW + Math.sin(rad) * halfH, + y: cy - Math.sin(rad) * halfW - Math.cos(rad) * halfH, + width: imgWidth, + height: imgHeight, + opacity: options.opacity, + rotate: degrees(options.angle), + }); + } + + return new Uint8Array(await pdfDoc.save()); +} + +export interface ImageWatermarkOptions { + imageBytes: Uint8Array; + imageType: 'png' | 'jpg'; + opacity: number; + angle: number; + scale: number; + x?: number; + y?: number; + pageIndices?: number[]; +} + +export async function addImageWatermark( + pdfBytes: Uint8Array, + options: ImageWatermarkOptions +): Promise { + const pdfDoc = await PDFDocument.load(pdfBytes); + const image = + options.imageType === 'png' + ? await pdfDoc.embedPng(options.imageBytes) + : await pdfDoc.embedJpg(options.imageBytes); + const pages = pdfDoc.getPages(); + const posX = options.x ?? 0.5; + const posY = options.y ?? 0.5; + + const imgWidth = image.width * options.scale; + const imgHeight = image.height * options.scale; + const rad = (options.angle * Math.PI) / 180; + const halfW = imgWidth / 2; + const halfH = imgHeight / 2; + + const targetIndices = options.pageIndices ?? pages.map((_, i) => i); + for (const idx of targetIndices) { + const page = pages[idx]; + if (!page) continue; + const { width, height } = page.getSize(); + const cx = posX * width; + const cy = posY * height; + + page.drawImage(image, { + x: cx - Math.cos(rad) * halfW + Math.sin(rad) * halfH, + y: cy - Math.sin(rad) * halfW - Math.cos(rad) * halfH, + width: imgWidth, + height: imgHeight, + opacity: options.opacity, + rotate: degrees(options.angle), + }); + } + + return new Uint8Array(await pdfDoc.save()); +} + +export type PageNumberPosition = + | 'bottom-center' + | 'bottom-left' + | 'bottom-right' + | 'top-center' + | 'top-left' + | 'top-right'; +export type PageNumberFormat = 'simple' | 'page_x_of_y'; + +export interface PageNumberOptions { + position: PageNumberPosition; + fontSize: number; + format: PageNumberFormat; + color: { r: number; g: number; b: number }; +} + +export async function addPageNumbers( + pdfBytes: Uint8Array, + options: PageNumberOptions +): Promise { + const pdfDoc = await PDFDocument.load(pdfBytes); + const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica); + const pages = pdfDoc.getPages(); + const totalPages = pages.length; + + for (let i = 0; i < totalPages; i++) { + const page = pages[i]; + const mediaBox = page.getMediaBox(); + const cropBox = page.getCropBox(); + const bounds = cropBox || mediaBox; + const width = bounds.width; + const height = bounds.height; + const xOffset = bounds.x || 0; + const yOffset = bounds.y || 0; + + const pageNumText = + options.format === 'page_x_of_y' + ? `${i + 1} / ${totalPages}` + : `${i + 1}`; + + const textWidth = helveticaFont.widthOfTextAtSize( + pageNumText, + options.fontSize + ); + const textHeight = options.fontSize; + + const minMargin = 8; + const maxMargin = 40; + const marginPercentage = 0.04; + + const horizontalMargin = Math.max( + minMargin, + Math.min(maxMargin, width * marginPercentage) + ); + const verticalMargin = Math.max( + minMargin, + Math.min(maxMargin, height * marginPercentage) + ); + + const safeHorizontalMargin = Math.max(horizontalMargin, textWidth / 2 + 3); + const safeVerticalMargin = Math.max(verticalMargin, textHeight + 3); + + let x = 0, + y = 0; + + switch (options.position) { + case 'bottom-center': + x = + Math.max( + safeHorizontalMargin, + Math.min( + width - safeHorizontalMargin - textWidth, + (width - textWidth) / 2 + ) + ) + xOffset; + y = safeVerticalMargin + yOffset; + break; + case 'bottom-left': + x = safeHorizontalMargin + xOffset; + y = safeVerticalMargin + yOffset; + break; + case 'bottom-right': + x = + Math.max( + safeHorizontalMargin, + width - safeHorizontalMargin - textWidth + ) + xOffset; + y = safeVerticalMargin + yOffset; + break; + case 'top-center': + x = + Math.max( + safeHorizontalMargin, + Math.min( + width - safeHorizontalMargin - textWidth, + (width - textWidth) / 2 + ) + ) + xOffset; + y = height - safeVerticalMargin - textHeight + yOffset; + break; + case 'top-left': + x = safeHorizontalMargin + xOffset; + y = height - safeVerticalMargin - textHeight + yOffset; + break; + case 'top-right': + x = + Math.max( + safeHorizontalMargin, + width - safeHorizontalMargin - textWidth + ) + xOffset; + y = height - safeVerticalMargin - textHeight + yOffset; + break; + } + + x = Math.max(xOffset + 3, Math.min(xOffset + width - textWidth - 3, x)); + y = Math.max(yOffset + 3, Math.min(yOffset + height - textHeight - 3, y)); + + page.drawText(pageNumText, { + x, + y, + font: helveticaFont, + size: options.fontSize, + color: rgb(options.color.r, options.color.g, options.color.b), + }); + } + + return new Uint8Array(await pdfDoc.save()); +} + +export interface FixPageSizeOptions { + targetSize: string; + orientation: string; + scalingMode: string; + backgroundColor: { r: number; g: number; b: number }; + customWidth?: number; + customHeight?: number; + customUnits?: string; +} + +export async function fixPageSize( + pdfBytes: Uint8Array, + options: FixPageSizeOptions +): Promise { + let targetWidth: number; + let targetHeight: number; + + if (options.targetSize.toLowerCase() === 'custom') { + const w = options.customWidth ?? 210; + const h = options.customHeight ?? 297; + const units = (options.customUnits ?? 'mm').toLowerCase(); + if (units === 'in') { + targetWidth = w * 72; + targetHeight = h * 72; + } else { + targetWidth = w * (72 / 25.4); + targetHeight = h * (72 / 25.4); + } + } else { + const selected = + PageSizes[options.targetSize as keyof typeof PageSizes] || PageSizes.A4; + targetWidth = selected[0]; + targetHeight = selected[1]; + } + + const orientation = options.orientation.toLowerCase(); + if (orientation === 'landscape' && targetWidth < targetHeight) { + [targetWidth, targetHeight] = [targetHeight, targetWidth]; + } else if (orientation === 'portrait' && targetWidth > targetHeight) { + [targetWidth, targetHeight] = [targetHeight, targetWidth]; + } + + const sourceDoc = await PDFDocument.load(pdfBytes); + const outputDoc = await PDFDocument.create(); + + for (const sourcePage of sourceDoc.getPages()) { + const { width: sourceWidth, height: sourceHeight } = sourcePage.getSize(); + const embeddedPage = await outputDoc.embedPage(sourcePage); + + const outputPage = outputDoc.addPage([targetWidth, targetHeight]); + outputPage.drawRectangle({ + x: 0, + y: 0, + width: targetWidth, + height: targetHeight, + color: rgb( + options.backgroundColor.r, + options.backgroundColor.g, + options.backgroundColor.b + ), + }); + + const scaleX = targetWidth / sourceWidth; + const scaleY = targetHeight / sourceHeight; + const useFill = options.scalingMode.toLowerCase() === 'fill'; + const scale = useFill ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY); + + const scaledWidth = sourceWidth * scale; + const scaledHeight = sourceHeight * scale; + + const x = (targetWidth - scaledWidth) / 2; + const y = (targetHeight - scaledHeight) / 2; + + outputPage.drawPage(embeddedPage, { + x, + y, + width: scaledWidth, + height: scaledHeight, + }); + } + + return new Uint8Array(await outputDoc.save()); +} diff --git a/src/js/utils/pymupdf-loader.ts b/src/js/utils/pymupdf-loader.ts new file mode 100644 index 0000000..b6d39ab --- /dev/null +++ b/src/js/utils/pymupdf-loader.ts @@ -0,0 +1,87 @@ +import { WasmProvider } from './wasm-provider.js'; + +let cachedPyMuPDF: any = null; +let loadPromise: Promise | null = null; + +export interface PyMuPDFInterface { + load(): Promise; + compressPdf( + file: Blob, + options: any + ): Promise<{ blob: Blob; compressedSize: number }>; + convertToPdf(file: Blob, ext: string): Promise; + extractText(file: Blob, options?: any): Promise; + extractImages(file: Blob): Promise>; + extractTables(file: Blob): Promise; + toSvg(file: Blob, pageNum: number): Promise; + renderPageToImage(file: Blob, pageNum: number, scale: number): Promise; + getPageCount(file: Blob): Promise; + rasterizePdf(file: Blob | File, options: any): Promise; +} + +export async function loadPyMuPDF(): Promise { + if (cachedPyMuPDF) { + return cachedPyMuPDF; + } + + if (loadPromise) { + return loadPromise; + } + + loadPromise = (async () => { + if (!WasmProvider.isConfigured('pymupdf')) { + throw new Error( + 'PyMuPDF is not configured. Please configure it in Advanced Settings.' + ); + } + if (!WasmProvider.isConfigured('ghostscript')) { + throw new Error( + 'Ghostscript is not configured. PyMuPDF requires Ghostscript for some operations. Please configure both in Advanced Settings.' + ); + } + + const pymupdfUrl = WasmProvider.getUrl('pymupdf')!; + const gsUrl = WasmProvider.getUrl('ghostscript')!; + const normalizedPymupdf = pymupdfUrl.endsWith('/') + ? pymupdfUrl + : `${pymupdfUrl}/`; + + try { + const wrapperUrl = `${normalizedPymupdf}dist/index.js`; + const module = await import(/* @vite-ignore */ wrapperUrl); + + if (typeof module.PyMuPDF !== 'function') { + throw new Error( + 'PyMuPDF module did not export expected PyMuPDF class.' + ); + } + + cachedPyMuPDF = new module.PyMuPDF({ + assetPath: `${normalizedPymupdf}assets/`, + ghostscriptUrl: gsUrl, + }); + + await cachedPyMuPDF.load(); + + console.log('[PyMuPDF Loader] Successfully loaded from CDN'); + return cachedPyMuPDF; + } catch (error: any) { + loadPromise = null; + throw new Error(`Failed to load PyMuPDF from CDN: ${error.message}`); + } + })(); + + return loadPromise; +} + +export function isPyMuPDFAvailable(): boolean { + return ( + WasmProvider.isConfigured('pymupdf') && + WasmProvider.isConfigured('ghostscript') + ); +} + +export function clearPyMuPDFCache(): void { + cachedPyMuPDF = null; + loadPromise = null; +} diff --git a/src/js/utils/render-utils.ts b/src/js/utils/render-utils.ts index 5e4511a..72b4681 100644 --- a/src/js/utils/render-utils.ts +++ b/src/js/utils/render-utils.ts @@ -1,381 +1,466 @@ import * as pdfjsLib from 'pdfjs-dist'; -pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); /** * Configuration for progressive rendering */ export interface RenderConfig { - batchSize?: number; - useLazyLoading?: boolean; - lazyLoadMargin?: string; - eagerLoadBatches?: number; // Number of batches to load ahead eagerly (default: 2) - onProgress?: (current: number, total: number) => void; - onPageRendered?: (pageIndex: number, element: HTMLElement) => void; - onBatchComplete?: () => void; - shouldCancel?: () => boolean; + batchSize?: number; + useLazyLoading?: boolean; + lazyLoadMargin?: string; + eagerLoadBatches?: number; // Number of batches to load ahead eagerly (default: 2) + onProgress?: (current: number, total: number) => void; + onPageRendered?: (pageIndex: number, element: HTMLElement) => void; + onBatchComplete?: () => void; + shouldCancel?: () => boolean; } /** * Page rendering task */ interface PageTask { - pageNumber: number; - pdfjsDoc: any; - fileName?: string; - container: HTMLElement; - scale?: number; - createWrapper: (canvas: HTMLCanvasElement, pageNumber: number, fileName?: string) => HTMLElement; + pageNumber: number; + pdfjsDoc: pdfjsLib.PDFDocumentProxy; + fileName?: string; + container: HTMLElement; + scale?: number; + createWrapper: ( + canvas: HTMLCanvasElement, + pageNumber: number, + fileName?: string + ) => HTMLElement; + placeholderElement?: HTMLElement; } /** * Lazy loading state */ interface LazyLoadState { - observer: IntersectionObserver | null; - pendingTasks: Map; - isRendering: boolean; - eagerLoadQueue: PageTask[]; - nextEagerIndex: number; + observer: IntersectionObserver | null; + pendingTasks: Map; + pendingTasksByPageNumber: Map; + isRendering: boolean; + eagerLoadQueue: PageTask[]; + nextEagerIndex: number; } const lazyLoadState: LazyLoadState = { - observer: null, - pendingTasks: new Map(), - isRendering: false, - eagerLoadQueue: [], - nextEagerIndex: 0, + observer: null, + pendingTasks: new Map(), + pendingTasksByPageNumber: new Map(), + isRendering: false, + eagerLoadQueue: [], + nextEagerIndex: 0, }; /** * Creates a placeholder element for a page that will be lazy-loaded */ -export function createPlaceholder(pageNumber: number, fileName?: string): HTMLElement { - const placeholder = document.createElement('div'); - placeholder.className = - 'page-thumbnail relative cursor-move flex flex-col items-center gap-1 p-2 border-2 border-gray-600 rounded-lg bg-gray-800 transition-colors'; - placeholder.dataset.pageNumber = pageNumber.toString(); - if (fileName) { - placeholder.dataset.fileName = fileName; - } - placeholder.dataset.lazyLoad = 'true'; +export function createPlaceholder( + pageNumber: number, + fileName?: string +): HTMLElement { + const placeholder = document.createElement('div'); + placeholder.className = + 'page-thumbnail relative cursor-move flex flex-col items-center gap-1 p-2 border-2 border-gray-600 rounded-lg bg-gray-800 transition-colors'; + placeholder.dataset.pageNumber = pageNumber.toString(); + if (fileName) { + placeholder.dataset.fileName = fileName; + } + placeholder.dataset.lazyLoad = 'true'; - // Create skeleton loader - const skeletonContainer = document.createElement('div'); - skeletonContainer.className = 'relative w-full h-36 bg-gray-700 rounded-md animate-pulse flex items-center justify-center'; + // Create skeleton loader + const skeletonContainer = document.createElement('div'); + skeletonContainer.className = + 'relative w-full h-36 bg-gray-700 rounded-md animate-pulse flex items-center justify-center'; - const loadingText = document.createElement('span'); - loadingText.className = 'text-gray-500 text-xs'; - loadingText.textContent = 'Loading...'; + const loadingText = document.createElement('span'); + loadingText.className = 'text-gray-500 text-xs'; + loadingText.textContent = 'Loading...'; - skeletonContainer.appendChild(loadingText); - placeholder.appendChild(skeletonContainer); + skeletonContainer.appendChild(loadingText); + placeholder.appendChild(skeletonContainer); - return placeholder; + return placeholder; } /** * Renders a single page to canvas */ export async function renderPageToCanvas( - pdfjsDoc: any, - pageNumber: number, - scale: number = 0.5 + pdfjsDoc: pdfjsLib.PDFDocumentProxy, + pageNumber: number, + scale: number = 1 ): Promise { - const page = await pdfjsDoc.getPage(pageNumber); - const viewport = page.getViewport({ scale }); + const page = await pdfjsDoc.getPage(pageNumber); + const viewport = page.getViewport({ scale }); - const canvas = document.createElement('canvas'); - canvas.height = viewport.height; - canvas.width = viewport.width; + const canvas = document.createElement('canvas'); + canvas.height = viewport.height; + canvas.width = viewport.width; - const context = canvas.getContext('2d')!; + const context = canvas.getContext('2d'); + if (!context) { + throw new Error(`Failed to get 2D context for page ${pageNumber}`); + } - await page.render({ - canvasContext: context, - canvas: canvas, - viewport, - }).promise; + await page.render({ + canvasContext: context, + canvas: canvas, + viewport, + }).promise; - return canvas; + return canvas; } /** - * Renders a batch of pages in parallel + * Renders a batch of pages */ -async function renderPageBatch( - tasks: PageTask[], - onProgress?: (current: number, total: number) => void -): Promise { - const renderPromises = tasks.map(async (task) => { - try { - const canvas = await renderPageToCanvas( - task.pdfjsDoc, - task.pageNumber, - task.scale || 0.5 - ); +async function renderPageBatch(tasks: PageTask[]): Promise { + for (const task of tasks) { + try { + const canvas = await renderPageToCanvas( + task.pdfjsDoc, + task.pageNumber, + task.scale || 0.5 + ); - const wrapper = task.createWrapper(canvas, task.pageNumber, task.fileName); + const wrapper = task.createWrapper( + canvas, + task.pageNumber, + task.fileName + ); - // Find and replace the placeholder for this specific page number - const placeholder = task.container.querySelector( - `[data-page-number="${task.pageNumber}"][data-lazy-load="true"]` - ); + let placeholder: Element | null = task.placeholderElement || null; + if (!placeholder) { + placeholder = task.container.querySelector( + `[data-page-number="${task.pageNumber}"][data-lazy-load="true"]` + ); + } - if (placeholder) { - // Replace placeholder with rendered page - task.container.replaceChild(wrapper, placeholder); - } else { - // Fallback: shouldn't happen with new approach, but just in case - console.warn(`No placeholder found for page ${task.pageNumber}, appending instead`); - task.container.appendChild(wrapper); - } - - return wrapper; - } catch (error) { - console.error(`Error rendering page ${task.pageNumber}:`, error); - return null; + if (placeholder && placeholder.parentNode) { + const parent = placeholder.parentNode; + parent.insertBefore(wrapper, placeholder); + parent.removeChild(placeholder); + } else { + const existingRendered = task.container.querySelector( + `[data-page-number="${task.pageNumber}"]:not([data-lazy-load="true"])` + ); + if (existingRendered) { + continue; } - }); - await Promise.all(renderPromises); + const allChildren = Array.from( + task.container.children + ) as HTMLElement[]; + let insertBefore: Element | null = null; + + for (const child of allChildren) { + const childPageNum = parseInt(child.dataset.pageNumber || '0', 10); + if (childPageNum > task.pageNumber) { + insertBefore = child; + break; + } + } + + if (insertBefore) { + task.container.insertBefore(wrapper, insertBefore); + } else { + task.container.appendChild(wrapper); + } + console.warn( + `Placeholder not found for page ${task.pageNumber}, inserted at calculated position` + ); + } + } catch (error) { + console.error(`Error rendering page ${task.pageNumber}:`, error); + } + } } /** * Sets up Intersection Observer for lazy loading */ function setupLazyRendering( - container: HTMLElement, - config: RenderConfig + container: HTMLElement, + config: RenderConfig ): IntersectionObserver { - const options = { - root: container.closest('.overflow-auto') || null, - rootMargin: config.lazyLoadMargin || '200px', - threshold: 0.01, - }; + const options = { + root: container.closest('.overflow-auto') || null, + rootMargin: config.lazyLoadMargin || '200px', + threshold: 0.01, + }; - const observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - const placeholder = entry.target as HTMLElement; - const task = lazyLoadState.pendingTasks.get(placeholder); + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const placeholder = entry.target as HTMLElement; + const pageNumberStr = placeholder.dataset.pageNumber; + if (!pageNumberStr) return; - if (task) { - // Immediately unobserve to prevent multiple triggers - observer.unobserve(placeholder); - lazyLoadState.pendingTasks.delete(placeholder); + const pageNumber = parseInt(pageNumberStr, 10); + const task = lazyLoadState.pendingTasksByPageNumber.get(pageNumber); - // Render this page immediately (not waiting for isRendering flag) - renderPageBatch([task], config.onProgress) - .then(() => { - // Trigger callback after lazy load batch - if (config.onBatchComplete) { - config.onBatchComplete(); - } + if (task) { + // Immediately unobserve to prevent multiple triggers + observer.unobserve(placeholder); + lazyLoadState.pendingTasks.delete(placeholder); + lazyLoadState.pendingTasksByPageNumber.delete(pageNumber); - // Check if all pages are rendered - if (lazyLoadState.pendingTasks.size === 0 && lazyLoadState.observer) { - lazyLoadState.observer.disconnect(); - lazyLoadState.observer = null; - } - }) - .catch((error) => { - console.error(`Error lazy loading page ${task.pageNumber}:`, error); - }); - } - } - }); - }, options); + task.placeholderElement = placeholder; - lazyLoadState.observer = observer; - return observer; + // Render this page immediately (not waiting for isRendering flag) + renderPageBatch([task]) + .then(() => { + // Trigger callback after lazy load batch + if (config.onBatchComplete) { + config.onBatchComplete(); + } + + // Check if all pages are rendered + if ( + lazyLoadState.pendingTasks.size === 0 && + lazyLoadState.observer + ) { + lazyLoadState.observer.disconnect(); + lazyLoadState.observer = null; + } + }) + .catch((error) => { + console.error( + `Error lazy loading page ${task.pageNumber}:`, + error + ); + }); + } + } + }); + }, options); + + lazyLoadState.observer = observer; + return observer; } /** * Request idle callback with fallback */ function requestIdleCallbackPolyfill(callback: () => void): void { - if ('requestIdleCallback' in window) { - requestIdleCallback(callback); - } else { - setTimeout(callback, 16); // ~60fps - } + if ('requestIdleCallback' in window) { + requestIdleCallback(callback); + } else { + setTimeout(callback, 16); // ~60fps + } } /** * Main function to render pages progressively with optional lazy loading */ export async function renderPagesProgressively( - pdfjsDoc: any, - container: HTMLElement, - createWrapper: (canvas: HTMLCanvasElement, pageNumber: number, fileName?: string) => HTMLElement, - config: RenderConfig = {} + pdfjsDoc: pdfjsLib.PDFDocumentProxy, + container: HTMLElement, + createWrapper: ( + canvas: HTMLCanvasElement, + pageNumber: number, + fileName?: string + ) => HTMLElement, + config: RenderConfig = {} ): Promise { - const { - batchSize = 8, // Increased from 5 to 8 for faster initial render - useLazyLoading = true, - eagerLoadBatches = 2, // Eagerly load 1 batch ahead by default - onProgress, - onBatchComplete, - } = config; + const { + batchSize = 8, + useLazyLoading = true, + eagerLoadBatches = 2, + onProgress, + onBatchComplete, + } = config; - const totalPages = pdfjsDoc.numPages; + const totalPages = pdfjsDoc.numPages; - // Render more pages initially to reduce lazy loading issues - const initialRenderCount = useLazyLoading - ? Math.min(20, totalPages) // Increased from 12 to 20 pages - : totalPages; + const initialRenderCount = useLazyLoading + ? Math.min(20, totalPages) + : totalPages; - // CRITICAL FIX: Create placeholders for ALL pages first to maintain order - const placeholders: HTMLElement[] = []; - for (let i = 1; i <= totalPages; i++) { - const placeholder = createPlaceholder(i); - container.appendChild(placeholder); - placeholders.push(placeholder); + const placeholders: HTMLElement[] = []; + for (let i = 1; i <= totalPages; i++) { + const placeholder = createPlaceholder(i); + container.appendChild(placeholder); + placeholders.push(placeholder); + } + + const tasks: PageTask[] = []; + + // Create tasks for all pages with direct placeholder references + for (let i = 1; i <= totalPages; i++) { + tasks.push({ + pageNumber: i, + pdfjsDoc, + container, + scale: useLazyLoading ? 0.5 : 1, + createWrapper, + placeholderElement: placeholders[i - 1], + }); + } + + // If lazy loading is enabled, set up observer for pages beyond initial render + if (useLazyLoading && totalPages > initialRenderCount) { + const observer = setupLazyRendering(container, config); + + for (let i = initialRenderCount + 1; i <= totalPages; i++) { + const placeholder = placeholders[i - 1]; + const task = tasks[i - 1]; + // Store the task for lazy rendering + lazyLoadState.pendingTasks.set(placeholder, task); + lazyLoadState.pendingTasksByPageNumber.set(task.pageNumber, task); + observer.observe(placeholder); } - const tasks: PageTask[] = []; + // Prepare eager load queue + const eagerStartIndex = initialRenderCount; + const eagerEndIndex = Math.min( + eagerStartIndex + eagerLoadBatches * batchSize, + totalPages + ); + lazyLoadState.eagerLoadQueue = tasks.slice(eagerStartIndex, eagerEndIndex); + lazyLoadState.nextEagerIndex = 0; + } - // Create tasks for all pages - for (let i = 1; i <= totalPages; i++) { - tasks.push({ - pageNumber: i, - pdfjsDoc, - container, - scale: config.useLazyLoading ? 0.3 : 0.5, - createWrapper, - }); - } + // Render initial pages in batches + const initialTasks = tasks.slice(0, initialRenderCount); - // If lazy loading is enabled, set up observer for pages beyond initial render - if (useLazyLoading && totalPages > initialRenderCount) { - const observer = setupLazyRendering(container, config); + for (let i = 0; i < initialTasks.length; i += batchSize) { + if (config.shouldCancel?.()) return; - for (let i = initialRenderCount + 1; i <= totalPages; i++) { - const placeholder = placeholders[i - 1]; - // Store the task for lazy rendering - lazyLoadState.pendingTasks.set(placeholder, tasks[i - 1]); - observer.observe(placeholder); - } + const batch = initialTasks.slice(i, i + batchSize); - // Prepare eager load queue - const eagerStartIndex = initialRenderCount; - const eagerEndIndex = Math.min( - eagerStartIndex + (eagerLoadBatches * batchSize), - totalPages - ); - lazyLoadState.eagerLoadQueue = tasks.slice(eagerStartIndex, eagerEndIndex); - lazyLoadState.nextEagerIndex = 0; - } + await new Promise((resolve, reject) => { + requestIdleCallbackPolyfill(() => { + renderPageBatch(batch) + .then(() => { + if (onProgress) { + onProgress( + Math.min(i + batchSize, initialRenderCount), + totalPages + ); + } - // Render initial pages in batches - const initialTasks = tasks.slice(0, initialRenderCount); + if (onBatchComplete) { + onBatchComplete(); + } - for (let i = 0; i < initialTasks.length; i += batchSize) { - if (config.shouldCancel?.()) return; + resolve(); + }) + .catch(reject); + }); + }); + } - const batch = initialTasks.slice(i, i + batchSize); - - await new Promise((resolve) => { - requestIdleCallbackPolyfill(async () => { - await renderPageBatch(batch, onProgress); - - if (onProgress) { - onProgress(Math.min(i + batchSize, initialRenderCount), totalPages); - } - - if (onBatchComplete) { - onBatchComplete(); - } - - resolve(); - }); - }); - } - - // Start eager loading AFTER initial batch is complete - if (useLazyLoading && eagerLoadBatches > 0 && totalPages > initialRenderCount) { - renderEagerBatch(config); - } + // Start eager loading AFTER initial batch is complete + if ( + useLazyLoading && + eagerLoadBatches > 0 && + totalPages > initialRenderCount + ) { + renderEagerBatch(config); + } } /** * Manually observe a placeholder element (useful for dynamically created placeholders) */ export function observePlaceholder( - placeholder: HTMLElement, - task: PageTask + placeholder: HTMLElement, + task: PageTask ): void { - if (!lazyLoadState.observer) { - console.warn('No active observer to register placeholder'); - return; - } - lazyLoadState.pendingTasks.set(placeholder, task); - lazyLoadState.observer.observe(placeholder); + if (!lazyLoadState.observer) { + console.warn('No active observer to register placeholder'); + return; + } + lazyLoadState.pendingTasks.set(placeholder, task); + lazyLoadState.pendingTasksByPageNumber.set(task.pageNumber, task); + lazyLoadState.observer.observe(placeholder); } /** * Eagerly renders the next batch in the background */ function renderEagerBatch(config: RenderConfig): void { - const { eagerLoadBatches = 2, batchSize = 8 } = config; + const { eagerLoadBatches = 2, batchSize = 8 } = config; - if (eagerLoadBatches <= 0 || lazyLoadState.eagerLoadQueue.length === 0) { - return; - } + if (eagerLoadBatches <= 0 || lazyLoadState.eagerLoadQueue.length === 0) { + return; + } + if (config.shouldCancel?.()) return; + + const { nextEagerIndex, eagerLoadQueue } = lazyLoadState; + + if (nextEagerIndex >= eagerLoadQueue.length) { + return; // All eager batches rendered + } + + const batchEnd = Math.min(nextEagerIndex + batchSize, eagerLoadQueue.length); + const batch = eagerLoadQueue.slice(nextEagerIndex, batchEnd); + + requestIdleCallbackPolyfill(async () => { if (config.shouldCancel?.()) return; - const { nextEagerIndex, eagerLoadQueue } = lazyLoadState; + const tasksToRender = batch.filter((task) => + lazyLoadState.pendingTasksByPageNumber.has(task.pageNumber) + ); - if (nextEagerIndex >= eagerLoadQueue.length) { - return; // All eager batches rendered + tasksToRender.forEach((task) => { + const placeholder = task.placeholderElement; + if (placeholder && lazyLoadState.observer) { + lazyLoadState.observer.unobserve(placeholder); + lazyLoadState.pendingTasks.delete(placeholder); + lazyLoadState.pendingTasksByPageNumber.delete(task.pageNumber); + } + }); + + if (tasksToRender.length === 0) { + lazyLoadState.nextEagerIndex = batchEnd; + const remainingBatches = Math.ceil( + (eagerLoadQueue.length - batchEnd) / batchSize + ); + if (remainingBatches > 0 && remainingBatches < eagerLoadBatches) { + renderEagerBatch(config); + } + return; } - const batchEnd = Math.min(nextEagerIndex + batchSize, eagerLoadQueue.length); - const batch = eagerLoadQueue.slice(nextEagerIndex, batchEnd); + await renderPageBatch(tasksToRender); - requestIdleCallbackPolyfill(async () => { - if (config.shouldCancel?.()) return; + if (config.onBatchComplete) { + config.onBatchComplete(); + } - // Remove these tasks from pending since we're rendering them eagerly - batch.forEach(task => { - const placeholder = Array.from(lazyLoadState.pendingTasks.entries()) - .find(([_, t]) => t.pageNumber === task.pageNumber)?.[0]; - if (placeholder && lazyLoadState.observer) { - lazyLoadState.observer.unobserve(placeholder); - lazyLoadState.pendingTasks.delete(placeholder); - } - }); + // Update next eager index + lazyLoadState.nextEagerIndex = batchEnd; - await renderPageBatch(batch, config.onProgress); - - if (config.onBatchComplete) { - config.onBatchComplete(); - } - - // Update next eager index - lazyLoadState.nextEagerIndex = batchEnd; - - // Queue next eager batch - const remainingBatches = Math.ceil((eagerLoadQueue.length - batchEnd) / batchSize); - if (remainingBatches > 0 && remainingBatches < eagerLoadBatches) { - // Continue eager loading if we have more batches within the eager threshold - renderEagerBatch(config); - } - }); + // Queue next eager batch + const remainingBatches = Math.ceil( + (eagerLoadQueue.length - batchEnd) / batchSize + ); + if (remainingBatches > 0 && remainingBatches < eagerLoadBatches) { + renderEagerBatch(config); + } + }); } /** * Cleanup function to disconnect observers */ export function cleanupLazyRendering(): void { - if (lazyLoadState.observer) { - lazyLoadState.observer.disconnect(); - lazyLoadState.observer = null; - } - lazyLoadState.pendingTasks.clear(); - lazyLoadState.isRendering = false; - lazyLoadState.eagerLoadQueue = []; - lazyLoadState.nextEagerIndex = 0; + if (lazyLoadState.observer) { + lazyLoadState.observer.disconnect(); + lazyLoadState.observer = null; + } + lazyLoadState.pendingTasks.clear(); + lazyLoadState.pendingTasksByPageNumber.clear(); + lazyLoadState.isRendering = false; + lazyLoadState.eagerLoadQueue = []; + lazyLoadState.nextEagerIndex = 0; } diff --git a/src/js/utils/sanitize.ts b/src/js/utils/sanitize.ts new file mode 100644 index 0000000..4da78fa --- /dev/null +++ b/src/js/utils/sanitize.ts @@ -0,0 +1,590 @@ +import { PDFDocument, PDFName } from 'pdf-lib'; + +export interface SanitizeOptions { + flattenForms: boolean; + removeMetadata: boolean; + removeAnnotations: boolean; + removeJavascript: boolean; + removeEmbeddedFiles: boolean; + removeLayers: boolean; + removeLinks: boolean; + removeStructureTree: boolean; + removeMarkInfo: boolean; + removeFonts: boolean; +} + +export const defaultSanitizeOptions: SanitizeOptions = { + flattenForms: true, + removeMetadata: true, + removeAnnotations: true, + removeJavascript: true, + removeEmbeddedFiles: true, + removeLayers: true, + removeLinks: true, + removeStructureTree: true, + removeMarkInfo: true, + removeFonts: false, +}; + +function removeMetadataFromDoc(pdfDoc: PDFDocument) { + const infoDict = (pdfDoc as any).getInfoDict(); + const allKeys = infoDict.keys(); + allKeys.forEach((key: any) => { + infoDict.delete(key); + }); + + pdfDoc.setTitle(''); + pdfDoc.setAuthor(''); + pdfDoc.setSubject(''); + pdfDoc.setKeywords([]); + pdfDoc.setCreator(''); + pdfDoc.setProducer(''); + + try { + const catalogDict = (pdfDoc.catalog as any).dict; + if (catalogDict.has(PDFName.of('Metadata'))) { + catalogDict.delete(PDFName.of('Metadata')); + } + } catch (e: any) { + console.warn('Could not remove XMP metadata:', e.message); + } + + try { + const context = pdfDoc.context; + if ((context as any).trailerInfo) { + delete (context as any).trailerInfo.ID; + } + } catch (e: any) { + console.warn('Could not remove document IDs:', e.message); + } + + try { + const catalogDict = (pdfDoc.catalog as any).dict; + if (catalogDict.has(PDFName.of('PieceInfo'))) { + catalogDict.delete(PDFName.of('PieceInfo')); + } + } catch (e: any) { + console.warn('Could not remove PieceInfo:', e.message); + } +} + +function removeAnnotationsFromDoc(pdfDoc: PDFDocument) { + const pages = pdfDoc.getPages(); + for (const page of pages) { + try { + page.node.delete(PDFName.of('Annots')); + } catch (e: any) { + console.warn('Could not remove annotations from page:', e.message); + } + } +} + +function flattenFormsInDoc(pdfDoc: PDFDocument) { + const form = pdfDoc.getForm(); + form.flatten(); +} + +function removeJavascriptFromDoc(pdfDoc: PDFDocument) { + if ((pdfDoc as any).javaScripts && (pdfDoc as any).javaScripts.length > 0) { + (pdfDoc as any).javaScripts = []; + } + + const catalogDict = (pdfDoc.catalog as any).dict; + + const namesRef = catalogDict.get(PDFName.of('Names')); + if (namesRef) { + try { + const namesDict = pdfDoc.context.lookup(namesRef) as any; + if (namesDict.has(PDFName.of('JavaScript'))) { + namesDict.delete(PDFName.of('JavaScript')); + } + } catch (e: any) { + console.warn('Could not access Names/JavaScript:', e.message); + } + } + + if (catalogDict.has(PDFName.of('OpenAction'))) { + catalogDict.delete(PDFName.of('OpenAction')); + } + + if (catalogDict.has(PDFName.of('AA'))) { + catalogDict.delete(PDFName.of('AA')); + } + + const pages = pdfDoc.getPages(); + for (const page of pages) { + try { + const pageDict = page.node; + + if (pageDict.has(PDFName.of('AA'))) { + pageDict.delete(PDFName.of('AA')); + } + + const annotRefs = pageDict.Annots()?.asArray() || []; + for (const annotRef of annotRefs) { + try { + const annot = pdfDoc.context.lookup(annotRef) as any; + + if (annot.has(PDFName.of('A'))) { + const actionRef = annot.get(PDFName.of('A')); + try { + const actionDict = pdfDoc.context.lookup(actionRef) as any; + const actionType = actionDict + .get(PDFName.of('S')) + ?.toString() + .substring(1); + + if (actionType === 'JavaScript') { + annot.delete(PDFName.of('A')); + } + } catch (e: any) { + console.warn('Could not read action:', e.message); + } + } + + if (annot.has(PDFName.of('AA'))) { + annot.delete(PDFName.of('AA')); + } + } catch (e: any) { + console.warn('Could not process annotation for JS:', e.message); + } + } + } catch (e: any) { + console.warn('Could not remove page actions:', e.message); + } + } + + try { + const acroFormRef = catalogDict.get(PDFName.of('AcroForm')); + if (acroFormRef) { + const acroFormDict = pdfDoc.context.lookup(acroFormRef) as any; + const fieldsRef = acroFormDict.get(PDFName.of('Fields')); + + if (fieldsRef) { + const fieldsArray = pdfDoc.context.lookup(fieldsRef) as any; + const fields = fieldsArray.asArray(); + + for (const fieldRef of fields) { + try { + const field = pdfDoc.context.lookup(fieldRef) as any; + + if (field.has(PDFName.of('A'))) { + field.delete(PDFName.of('A')); + } + + if (field.has(PDFName.of('AA'))) { + field.delete(PDFName.of('AA')); + } + } catch (e: any) { + console.warn('Could not process field for JS:', e.message); + } + } + } + } + } catch (e: any) { + console.warn('Could not process form fields for JS:', e.message); + } +} + +function removeEmbeddedFilesFromDoc(pdfDoc: PDFDocument) { + const catalogDict = (pdfDoc.catalog as any).dict; + + const namesRef = catalogDict.get(PDFName.of('Names')); + if (namesRef) { + try { + const namesDict = pdfDoc.context.lookup(namesRef) as any; + if (namesDict.has(PDFName.of('EmbeddedFiles'))) { + namesDict.delete(PDFName.of('EmbeddedFiles')); + } + } catch (e: any) { + console.warn('Could not access Names/EmbeddedFiles:', e.message); + } + } + + if (catalogDict.has(PDFName.of('EmbeddedFiles'))) { + catalogDict.delete(PDFName.of('EmbeddedFiles')); + } + + const pages = pdfDoc.getPages(); + for (const page of pages) { + try { + const annotRefs = page.node.Annots()?.asArray() || []; + const annotsToKeep = []; + + for (const ref of annotRefs) { + try { + const annot = pdfDoc.context.lookup(ref) as any; + const subtype = annot + .get(PDFName.of('Subtype')) + ?.toString() + .substring(1); + + if (subtype !== 'FileAttachment') { + annotsToKeep.push(ref); + } + } catch (e) { + annotsToKeep.push(ref); + } + } + + if (annotsToKeep.length !== annotRefs.length) { + if (annotsToKeep.length > 0) { + const newAnnotsArray = pdfDoc.context.obj(annotsToKeep); + page.node.set(PDFName.of('Annots'), newAnnotsArray); + } else { + page.node.delete(PDFName.of('Annots')); + } + } + } catch (pageError: any) { + console.warn( + `Could not process page for attachments: ${pageError.message}` + ); + } + } + + if ( + (pdfDoc as any).embeddedFiles && + (pdfDoc as any).embeddedFiles.length > 0 + ) { + (pdfDoc as any).embeddedFiles = []; + } + + if (catalogDict.has(PDFName.of('Collection'))) { + catalogDict.delete(PDFName.of('Collection')); + } +} + +function removeLayersFromDoc(pdfDoc: PDFDocument) { + const catalogDict = (pdfDoc.catalog as any).dict; + + if (catalogDict.has(PDFName.of('OCProperties'))) { + catalogDict.delete(PDFName.of('OCProperties')); + } + + const pages = pdfDoc.getPages(); + for (const page of pages) { + try { + const pageDict = page.node; + + if (pageDict.has(PDFName.of('OCProperties'))) { + pageDict.delete(PDFName.of('OCProperties')); + } + + const resourcesRef = pageDict.get(PDFName.of('Resources')); + if (resourcesRef) { + try { + const resourcesDict = pdfDoc.context.lookup(resourcesRef) as any; + if (resourcesDict.has(PDFName.of('Properties'))) { + resourcesDict.delete(PDFName.of('Properties')); + } + } catch (e: any) { + console.warn('Could not access Resources:', e.message); + } + } + } catch (e: any) { + console.warn('Could not remove page layers:', e.message); + } + } +} + +function removeLinksFromDoc(pdfDoc: PDFDocument) { + const pages = pdfDoc.getPages(); + + for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) { + try { + const page = pages[pageIndex]; + const pageDict = page.node; + + const annotsRef = pageDict.get(PDFName.of('Annots')); + if (!annotsRef) continue; + + const annotsArray = pdfDoc.context.lookup(annotsRef) as any; + const annotRefs = annotsArray.asArray(); + + if (annotRefs.length === 0) continue; + + const annotsToKeep = []; + let linksRemoved = 0; + + for (const ref of annotRefs) { + try { + const annot = pdfDoc.context.lookup(ref) as any; + const subtype = annot + .get(PDFName.of('Subtype')) + ?.toString() + .substring(1); + + let isLink = false; + + if (subtype === 'Link') { + isLink = true; + linksRemoved++; + } else { + const actionRef = annot.get(PDFName.of('A')); + if (actionRef) { + try { + const actionDict = pdfDoc.context.lookup(actionRef) as any; + const actionType = actionDict + .get(PDFName.of('S')) + ?.toString() + .substring(1); + + if ( + actionType === 'URI' || + actionType === 'Launch' || + actionType === 'GoTo' || + actionType === 'GoToR' + ) { + isLink = true; + linksRemoved++; + } + } catch (e: any) { + console.warn('Could not read action:', e.message); + } + } + + const dest = annot.get(PDFName.of('Dest')); + if (dest && !isLink) { + isLink = true; + linksRemoved++; + } + } + + if (!isLink) { + annotsToKeep.push(ref); + } + } catch (e: any) { + console.warn('Could not process annotation:', e.message); + annotsToKeep.push(ref); + } + } + + if (linksRemoved > 0) { + if (annotsToKeep.length > 0) { + const newAnnotsArray = pdfDoc.context.obj(annotsToKeep); + pageDict.set(PDFName.of('Annots'), newAnnotsArray); + } else { + pageDict.delete(PDFName.of('Annots')); + } + } + } catch (pageError: any) { + console.warn( + `Could not process page ${pageIndex + 1} for links: ${pageError.message}` + ); + } + } + + try { + const catalogDict = (pdfDoc.catalog as any).dict; + const namesRef = catalogDict.get(PDFName.of('Names')); + if (namesRef) { + try { + const namesDict = pdfDoc.context.lookup(namesRef) as any; + if (namesDict.has(PDFName.of('Dests'))) { + namesDict.delete(PDFName.of('Dests')); + } + } catch (e: any) { + console.warn('Could not access Names/Dests:', e.message); + } + } + + if (catalogDict.has(PDFName.of('Dests'))) { + catalogDict.delete(PDFName.of('Dests')); + } + } catch (e: any) { + console.warn('Could not remove named destinations:', e.message); + } +} + +function removeStructureTreeFromDoc(pdfDoc: PDFDocument) { + const catalogDict = (pdfDoc.catalog as any).dict; + + if (catalogDict.has(PDFName.of('StructTreeRoot'))) { + catalogDict.delete(PDFName.of('StructTreeRoot')); + } + + const pages = pdfDoc.getPages(); + for (const page of pages) { + try { + const pageDict = page.node; + if (pageDict.has(PDFName.of('StructParents'))) { + pageDict.delete(PDFName.of('StructParents')); + } + } catch (e: any) { + console.warn('Could not remove page StructParents:', e.message); + } + } + + if (catalogDict.has(PDFName.of('ParentTree'))) { + catalogDict.delete(PDFName.of('ParentTree')); + } +} + +function removeMarkInfoFromDoc(pdfDoc: PDFDocument) { + const catalogDict = (pdfDoc.catalog as any).dict; + + if (catalogDict.has(PDFName.of('MarkInfo'))) { + catalogDict.delete(PDFName.of('MarkInfo')); + } + + if (catalogDict.has(PDFName.of('Marked'))) { + catalogDict.delete(PDFName.of('Marked')); + } +} + +function removeFontsFromDoc(pdfDoc: PDFDocument) { + const pages = pdfDoc.getPages(); + + for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) { + try { + const page = pages[pageIndex]; + const pageDict = page.node; + const resourcesRef = pageDict.get(PDFName.of('Resources')); + + if (resourcesRef) { + try { + const resourcesDict = pdfDoc.context.lookup(resourcesRef) as any; + + if (resourcesDict.has(PDFName.of('Font'))) { + const fontRef = resourcesDict.get(PDFName.of('Font')); + + try { + const fontDict = pdfDoc.context.lookup(fontRef) as any; + const fontKeys = fontDict.keys(); + + for (const fontKey of fontKeys) { + try { + const specificFontRef = fontDict.get(fontKey); + const specificFont = pdfDoc.context.lookup( + specificFontRef + ) as any; + + if (specificFont.has(PDFName.of('FontDescriptor'))) { + const descriptorRef = specificFont.get( + PDFName.of('FontDescriptor') + ); + const descriptor = pdfDoc.context.lookup( + descriptorRef + ) as any; + + const fontFileKeys = ['FontFile', 'FontFile2', 'FontFile3']; + for (const key of fontFileKeys) { + if (descriptor.has(PDFName.of(key))) { + descriptor.delete(PDFName.of(key)); + } + } + } + } catch (e: any) { + console.warn(`Could not process font ${fontKey}:`, e.message); + } + } + } catch (e: any) { + console.warn('Could not access font dictionary:', e.message); + } + } + } catch (e: any) { + console.warn('Could not access Resources for fonts:', e.message); + } + } + } catch (e: any) { + console.warn( + `Could not remove fonts from page ${pageIndex + 1}:`, + e.message + ); + } + } + + if ((pdfDoc as any).fonts && (pdfDoc as any).fonts.length > 0) { + (pdfDoc as any).fonts = []; + } +} + +export async function sanitizePdf( + pdfBytes: Uint8Array, + options: SanitizeOptions +): Promise<{ pdfDoc: PDFDocument; bytes: Uint8Array }> { + const pdfDoc = await PDFDocument.load(pdfBytes); + + if (options.flattenForms) { + try { + flattenFormsInDoc(pdfDoc); + } catch (e: any) { + console.warn(`Could not flatten forms: ${e.message}`); + try { + const catalogDict = (pdfDoc.catalog as any).dict; + if (catalogDict.has(PDFName.of('AcroForm'))) { + catalogDict.delete(PDFName.of('AcroForm')); + } + } catch (removeError: any) { + console.warn('Could not remove AcroForm:', removeError.message); + } + } + } + + if (options.removeMetadata) { + removeMetadataFromDoc(pdfDoc); + } + + if (options.removeAnnotations) { + removeAnnotationsFromDoc(pdfDoc); + } + + if (options.removeJavascript) { + try { + removeJavascriptFromDoc(pdfDoc); + } catch (e: any) { + console.warn(`Could not remove JavaScript: ${e.message}`); + } + } + + if (options.removeEmbeddedFiles) { + try { + removeEmbeddedFilesFromDoc(pdfDoc); + } catch (e: any) { + console.warn(`Could not remove embedded files: ${e.message}`); + } + } + + if (options.removeLayers) { + try { + removeLayersFromDoc(pdfDoc); + } catch (e: any) { + console.warn(`Could not remove layers: ${e.message}`); + } + } + + if (options.removeLinks) { + try { + removeLinksFromDoc(pdfDoc); + } catch (e: any) { + console.warn(`Could not remove links: ${e.message}`); + } + } + + if (options.removeStructureTree) { + try { + removeStructureTreeFromDoc(pdfDoc); + } catch (e: any) { + console.warn(`Could not remove structure tree: ${e.message}`); + } + } + + if (options.removeMarkInfo) { + try { + removeMarkInfoFromDoc(pdfDoc); + } catch (e: any) { + console.warn(`Could not remove MarkInfo: ${e.message}`); + } + } + + if (options.removeFonts) { + try { + removeFontsFromDoc(pdfDoc); + } catch (e: any) { + console.warn(`Could not remove fonts: ${e.message}`); + } + } + + const savedBytes = await pdfDoc.save(); + return { pdfDoc, bytes: new Uint8Array(savedBytes) }; +} diff --git a/src/js/utils/simple-mode-footer.ts b/src/js/utils/simple-mode-footer.ts index f2a1658..7d9ba50 100644 --- a/src/js/utils/simple-mode-footer.ts +++ b/src/js/utils/simple-mode-footer.ts @@ -1,45 +1,38 @@ import { APP_VERSION } from '../../version.js'; import { createLanguageSwitcher } from '../i18n/language-switcher.js'; -// Handle simple mode footer replacement for tool pages +// Handle simple mode adjustments for tool pages if (__SIMPLE_MODE__) { - const footer = document.querySelector('footer'); - if (footer && !document.querySelector('[data-simple-footer]')) { - footer.style.display = 'none'; + const sectionsToHide = [ + 'How It Works', + 'Related PDF Tools', + 'Related Tools', + 'Frequently Asked Questions', + ]; - const simpleFooter = document.createElement('footer'); - simpleFooter.className = 'mt-16 border-t-2 border-gray-700 py-8'; - simpleFooter.setAttribute('data-simple-footer', 'true'); - simpleFooter.innerHTML = ` -
-
-
-
- Bento PDF Logo - BentoPDF -
-

- © 2025 BentoPDF. All rights reserved. -

-

- Version ${APP_VERSION} -

-
-
-
-
- `; - document.body.appendChild(simpleFooter); - - const langContainer = simpleFooter.querySelector('#simple-mode-lang-switcher'); - if (langContainer) { - const switcher = createLanguageSwitcher(); - const dropdown = switcher.querySelector('div[role="menu"]'); - if (dropdown) { - dropdown.classList.remove('mt-2'); - dropdown.classList.add('bottom-full', 'mb-2'); + document.querySelectorAll('section').forEach((section) => { + const h2 = section.querySelector('h2'); + if (h2) { + const heading = h2.textContent?.trim() || ''; + if (sectionsToHide.some((text) => heading.includes(text))) { + (section as HTMLElement).style.display = 'none'; } - langContainer.appendChild(switcher); } + }); + + const versionElement = document.getElementById('app-version-simple'); + if (versionElement) { + versionElement.textContent = APP_VERSION; + } + + const langContainer = document.getElementById('simple-mode-lang-switcher'); + if (langContainer) { + const switcher = createLanguageSwitcher(); + const dropdown = switcher.querySelector('div[role="menu"]'); + if (dropdown) { + dropdown.classList.remove('mt-2'); + dropdown.classList.add('bottom-full', 'mb-2'); + } + langContainer.appendChild(switcher); } } diff --git a/src/js/utils/wasm-preloader.ts b/src/js/utils/wasm-preloader.ts new file mode 100644 index 0000000..818e281 --- /dev/null +++ b/src/js/utils/wasm-preloader.ts @@ -0,0 +1,135 @@ +import { getLibreOfficeConverter } from './libreoffice-loader.js'; +import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; + +export enum PreloadStatus { + IDLE = 'idle', + LOADING = 'loading', + READY = 'ready', + ERROR = 'error', + UNAVAILABLE = 'unavailable', +} + +interface PreloadState { + libreoffice: PreloadStatus; + pymupdf: PreloadStatus; + ghostscript: PreloadStatus; +} + +const preloadState: PreloadState = { + libreoffice: PreloadStatus.IDLE, + pymupdf: PreloadStatus.IDLE, + ghostscript: PreloadStatus.IDLE, +}; + +export function getPreloadStatus(): Readonly { + return { ...preloadState }; +} + +async function preloadPyMuPDF(): Promise { + if (preloadState.pymupdf !== PreloadStatus.IDLE) return; + + if (!isWasmAvailable('pymupdf')) { + preloadState.pymupdf = PreloadStatus.UNAVAILABLE; + console.log('[Preloader] PyMuPDF not configured, skipping preload'); + return; + } + + preloadState.pymupdf = PreloadStatus.LOADING; + console.log('[Preloader] Starting PyMuPDF preload...'); + + try { + const pymupdfBaseUrl = getWasmBaseUrl('pymupdf')!; + const gsBaseUrl = getWasmBaseUrl('ghostscript'); + const normalizedUrl = pymupdfBaseUrl.endsWith('/') + ? pymupdfBaseUrl + : `${pymupdfBaseUrl}/`; + + const wrapperUrl = `${normalizedUrl}dist/index.js`; + const module = await import(/* @vite-ignore */ wrapperUrl); + + const pymupdfInstance = new module.PyMuPDF({ + assetPath: `${normalizedUrl}assets/`, + ghostscriptUrl: gsBaseUrl || '', + }); + await pymupdfInstance.load(); + preloadState.pymupdf = PreloadStatus.READY; + console.log('[Preloader] PyMuPDF ready'); + } catch (e) { + preloadState.pymupdf = PreloadStatus.ERROR; + console.warn('[Preloader] PyMuPDF preload failed:', e); + } +} + +async function preloadGhostscript(): Promise { + if (preloadState.ghostscript !== PreloadStatus.IDLE) return; + + if (!isWasmAvailable('ghostscript')) { + preloadState.ghostscript = PreloadStatus.UNAVAILABLE; + console.log('[Preloader] Ghostscript not configured, skipping preload'); + return; + } + + preloadState.ghostscript = PreloadStatus.LOADING; + console.log('[Preloader] Starting Ghostscript WASM preload...'); + + try { + const { loadGsModule, setCachedGsModule } = + await import('./ghostscript-loader.js'); + + const gsModule = await loadGsModule(); + setCachedGsModule(gsModule as any); + preloadState.ghostscript = PreloadStatus.READY; + console.log('[Preloader] Ghostscript WASM ready'); + } catch (e) { + preloadState.ghostscript = PreloadStatus.ERROR; + console.warn('[Preloader] Ghostscript preload failed:', e); + } +} + +function scheduleIdleTask(task: () => Promise): void { + if ('requestIdleCallback' in window) { + requestIdleCallback(() => task(), { timeout: 5000 }); + } else { + setTimeout(() => task(), 1000); + } +} + +export function startBackgroundPreload(): void { + console.log('[Preloader] Scheduling background WASM preloads...'); + + const libreOfficePages = [ + 'word-to-pdf', + 'excel-to-pdf', + 'ppt-to-pdf', + 'powerpoint-to-pdf', + 'docx-to-pdf', + 'xlsx-to-pdf', + 'pptx-to-pdf', + 'csv-to-pdf', + 'rtf-to-pdf', + 'odt-to-pdf', + 'ods-to-pdf', + 'odp-to-pdf', + ]; + + const currentPath = window.location.pathname; + const isLibreOfficePage = libreOfficePages.some((page) => + currentPath.includes(page) + ); + + if (isLibreOfficePage) { + console.log( + '[Preloader] Skipping preloads on LibreOffice page to save memory' + ); + return; + } + + scheduleIdleTask(async () => { + console.log('[Preloader] Starting sequential WASM preloads...'); + + await preloadPyMuPDF(); + await preloadGhostscript(); + + console.log('[Preloader] Sequential preloads complete'); + }); +} diff --git a/src/js/utils/wasm-provider.ts b/src/js/utils/wasm-provider.ts new file mode 100644 index 0000000..a6bb1c5 --- /dev/null +++ b/src/js/utils/wasm-provider.ts @@ -0,0 +1,377 @@ +export type WasmPackage = 'pymupdf' | 'ghostscript' | 'cpdf'; + +interface WasmProviderConfig { + pymupdf?: string; + ghostscript?: string; + cpdf?: string; +} + +const STORAGE_KEY = 'bentopdf:wasm-providers'; + +const CDN_DEFAULTS: Record = { + pymupdf: 'https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/', + ghostscript: 'https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/', + cpdf: 'https://cdn.jsdelivr.net/npm/coherentpdf/dist/', +}; + +function envOrDefault(envVar: string | undefined, fallback: string): string { + return envVar || fallback; +} + +const ENV_DEFAULTS: Record = { + pymupdf: envOrDefault( + import.meta.env.VITE_WASM_PYMUPDF_URL, + CDN_DEFAULTS.pymupdf + ), + ghostscript: envOrDefault( + import.meta.env.VITE_WASM_GS_URL, + CDN_DEFAULTS.ghostscript + ), + cpdf: envOrDefault(import.meta.env.VITE_WASM_CPDF_URL, CDN_DEFAULTS.cpdf), +}; + +class WasmProviderManager { + private config: WasmProviderConfig; + private validationCache: Map = new Map(); + + constructor() { + this.config = this.loadConfig(); + } + + private loadConfig(): WasmProviderConfig { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + return JSON.parse(stored); + } + } catch (e) { + console.warn( + '[WasmProvider] Failed to load config from localStorage:', + e + ); + } + return {}; + } + + private getEnvDefault(packageName: WasmPackage): string | undefined { + return ENV_DEFAULTS[packageName]; + } + + private saveConfig(): void { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(this.config)); + } catch (e) { + console.error('[WasmProvider] Failed to save config to localStorage:', e); + } + } + + getUrl(packageName: WasmPackage): string | undefined { + return this.config[packageName] || this.getEnvDefault(packageName); + } + + setUrl(packageName: WasmPackage, url: string): void { + const normalizedUrl = url.endsWith('/') ? url : `${url}/`; + this.config[packageName] = normalizedUrl; + this.validationCache.delete(packageName); + this.saveConfig(); + } + + removeUrl(packageName: WasmPackage): void { + delete this.config[packageName]; + this.validationCache.delete(packageName); + this.saveConfig(); + } + + isConfigured(packageName: WasmPackage): boolean { + return !!(this.config[packageName] || this.getEnvDefault(packageName)); + } + + isUserConfigured(packageName: WasmPackage): boolean { + return !!this.config[packageName]; + } + + hasEnvDefault(packageName: WasmPackage): boolean { + return !!this.getEnvDefault(packageName); + } + + hasAnyProvider(): boolean { + return ( + Object.keys(this.config).length > 0 || + Object.values(ENV_DEFAULTS).some(Boolean) + ); + } + + async validateUrl( + packageName: WasmPackage, + url?: string + ): Promise<{ valid: boolean; error?: string }> { + const testUrl = url || this.config[packageName]; + if (!testUrl) { + return { valid: false, error: 'No URL configured' }; + } + + try { + const parsedUrl = new URL(testUrl); + if (!['http:', 'https:'].includes(parsedUrl.protocol)) { + return { + valid: false, + error: 'URL must start with http:// or https://', + }; + } + } catch { + return { + valid: false, + error: + 'Invalid URL format. Please enter a valid URL (e.g., https://example.com/wasm/)', + }; + } + + const normalizedUrl = testUrl.endsWith('/') ? testUrl : `${testUrl}/`; + + try { + const testFiles: Record = { + pymupdf: 'dist/index.js', + ghostscript: 'gs.js', + cpdf: 'coherentpdf.browser.min.js', + }; + + const testFile = testFiles[packageName]; + const fullUrl = `${normalizedUrl}${testFile}`; + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s + + const response = await fetch(fullUrl, { + method: 'GET', + mode: 'cors', + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + return { + valid: false, + error: `Could not find ${testFile} at the specified URL (HTTP ${response.status}). Make sure the file exists.`, + }; + } + + const reader = response.body?.getReader(); + if (reader) { + try { + await reader.read(); + reader.cancel(); + } catch { + return { + valid: false, + error: `File exists but could not be read. Check CORS configuration.`, + }; + } + } + + const contentType = response.headers.get('content-type'); + if ( + contentType && + !contentType.includes('javascript') && + !contentType.includes('application/octet-stream') && + !contentType.includes('text/') + ) { + return { + valid: false, + error: `The URL returned unexpected content type: ${contentType}. Expected a JavaScript file.`, + }; + } + + if (!url || url === this.config[packageName]) { + this.validationCache.set(packageName, true); + } + + return { valid: true }; + } catch (e: unknown) { + const errorMessage = e instanceof Error ? e.message : 'Unknown error'; + + if ( + errorMessage.includes('Failed to fetch') || + errorMessage.includes('NetworkError') + ) { + return { + valid: false, + error: + 'Network error: Could not connect to the URL. Check that the URL is correct and the server allows CORS requests.', + }; + } + + return { + valid: false, + error: `Network error: ${errorMessage}`, + }; + } + } + + getAllProviders(): WasmProviderConfig { + return { + pymupdf: this.config.pymupdf || ENV_DEFAULTS.pymupdf, + ghostscript: this.config.ghostscript || ENV_DEFAULTS.ghostscript, + cpdf: this.config.cpdf || ENV_DEFAULTS.cpdf, + }; + } + + clearAll(): void { + this.config = {}; + this.validationCache.clear(); + try { + localStorage.removeItem(STORAGE_KEY); + } catch (e) { + console.error('[WasmProvider] Failed to clear localStorage:', e); + } + } + + resetToDefaults(): void { + this.clearAll(); + } + + getPackageDisplayName(packageName: WasmPackage): string { + const names: Record = { + pymupdf: 'PyMuPDF (Document Processing)', + ghostscript: 'Ghostscript (PDF/A Conversion)', + cpdf: 'CoherentPDF (Bookmarks & Metadata)', + }; + return names[packageName]; + } + + getPackageFeatures(packageName: WasmPackage): string[] { + const features: Record = { + pymupdf: [ + 'PDF to Text', + 'PDF to Markdown', + 'PDF to SVG', + 'PDF to Images (High Quality)', + 'PDF to DOCX', + 'PDF to Excel/CSV', + 'Extract Images', + 'Extract Tables', + 'EPUB/MOBI/FB2/XPS/CBZ to PDF', + 'Image Compression', + 'Deskew PDF', + 'PDF Layers', + ], + ghostscript: ['PDF/A Conversion', 'Font to Outline'], + cpdf: [ + 'Merge PDF', + 'Alternate Merge', + 'Split by Bookmarks', + 'Table of Contents', + 'PDF to JSON', + 'JSON to PDF', + 'Add/Edit/Extract Attachments', + 'Edit Bookmarks', + 'PDF Metadata', + ], + }; + return features[packageName]; + } +} + +export const WasmProvider = new WasmProviderManager(); + +export function showWasmRequiredDialog( + packageName: WasmPackage, + onConfigure?: () => void +): void { + const displayName = WasmProvider.getPackageDisplayName(packageName); + const features = WasmProvider.getPackageFeatures(packageName); + + // Create modal + const overlay = document.createElement('div'); + overlay.className = + 'fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4'; + overlay.id = 'wasm-required-modal'; + + const modal = document.createElement('div'); + modal.className = + 'bg-gray-800 rounded-2xl max-w-md w-full shadow-2xl border border-gray-700'; + + modal.innerHTML = ` +
+
+
+ + + +
+
+

Advanced Feature Required

+

External processing module needed

+
+
+ +

+ This feature requires ${displayName} to be configured. +

+ +
+

Features enabled by this module:

+
    + ${features + .slice(0, 4) + .map( + (f) => + `
  • ${f}
  • ` + ) + .join('')} + ${features.length > 4 ? `
  • + ${features.length - 4} more...
  • ` : ''} +
+
+ +

+ This module is licensed under AGPL-3.0. By configuring it, you agree to its license terms. +

+
+ +
+ + +
+ `; + + overlay.appendChild(modal); + document.body.appendChild(overlay); + + const cancelBtn = modal.querySelector('#wasm-modal-cancel'); + const configureBtn = modal.querySelector('#wasm-modal-configure'); + + const closeModal = () => { + overlay.remove(); + }; + + cancelBtn?.addEventListener('click', closeModal); + overlay.addEventListener('click', (e) => { + if (e.target === overlay) closeModal(); + }); + + configureBtn?.addEventListener('click', () => { + closeModal(); + if (onConfigure) { + onConfigure(); + } else { + window.location.href = `${import.meta.env.BASE_URL}wasm-settings.html`; + } + }); +} + +export function requireWasm( + packageName: WasmPackage, + onAvailable?: () => void +): boolean { + if (WasmProvider.isConfigured(packageName)) { + onAvailable?.(); + return true; + } + + showWasmRequiredDialog(packageName); + return false; +} diff --git a/src/js/utils/xml-to-pdf.ts b/src/js/utils/xml-to-pdf.ts new file mode 100644 index 0000000..506b443 --- /dev/null +++ b/src/js/utils/xml-to-pdf.ts @@ -0,0 +1,196 @@ +import { jsPDF } from 'jspdf'; +import autoTable from 'jspdf-autotable'; + +export interface XmlToPdfOptions { + onProgress?: (percent: number, message: string) => void; +} + +interface jsPDFWithAutoTable extends jsPDF { + lastAutoTable?: { finalY: number }; +} + +export async function convertXmlToPdf( + file: File, + options?: XmlToPdfOptions +): Promise { + const { onProgress } = options || {}; + + onProgress?.(10, 'Reading XML file...'); + const xmlText = await file.text(); + + onProgress?.(30, 'Parsing XML structure...'); + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(xmlText, 'text/xml'); + + const parseError = xmlDoc.querySelector('parsererror'); + if (parseError) { + throw new Error('Invalid XML: ' + parseError.textContent); + } + + onProgress?.(50, 'Analyzing data structure...'); + + const doc: jsPDFWithAutoTable = new jsPDF({ + orientation: 'landscape', + unit: 'mm', + format: 'a4' + }); + + const pageWidth = doc.internal.pageSize.getWidth(); + let yPosition = 20; + + const root = xmlDoc.documentElement; + const rootName = formatTitle(root.tagName); + + doc.setFontSize(18); + doc.setFont('helvetica', 'bold'); + doc.text(rootName, pageWidth / 2, yPosition, { align: 'center' }); + yPosition += 15; + + onProgress?.(60, 'Generating formatted content...'); + + const children = Array.from(root.children); + + if (children.length > 0) { + const groups = groupByTagName(children); + + for (const [groupName, elements] of Object.entries(groups)) { + const { headers, rows } = extractTableData(elements); + + if (headers.length > 0 && rows.length > 0) { + if (Object.keys(groups).length > 1) { + doc.setFontSize(14); + doc.setFont('helvetica', 'bold'); + doc.text(formatTitle(groupName), 14, yPosition); + yPosition += 8; + } + + autoTable(doc, { + head: [headers.map(h => formatTitle(h))], + body: rows, + startY: yPosition, + styles: { + fontSize: 9, + cellPadding: 4, + overflow: 'linebreak', + }, + headStyles: { + fillColor: [79, 70, 229], + textColor: 255, + fontStyle: 'bold', + }, + alternateRowStyles: { + fillColor: [243, 244, 246], + }, + margin: { top: 20, left: 14, right: 14 }, + theme: 'striped', + didDrawPage: (data) => { + yPosition = (data.cursor?.y || yPosition) + 10; + } + }); + + yPosition = (doc.lastAutoTable?.finalY || yPosition) + 15; + } + } + } else { + const kvPairs = extractKeyValuePairs(root); + if (kvPairs.length > 0) { + autoTable(doc, { + head: [['Property', 'Value']], + body: kvPairs, + startY: yPosition, + styles: { + fontSize: 10, + cellPadding: 5, + }, + headStyles: { + fillColor: [79, 70, 229], + textColor: 255, + fontStyle: 'bold', + }, + columnStyles: { + 0: { fontStyle: 'bold', cellWidth: 60 }, + 1: { cellWidth: 'auto' }, + }, + margin: { left: 14, right: 14 }, + theme: 'striped', + }); + } + } + + onProgress?.(90, 'Finalizing PDF...'); + + const pdfBlob = doc.output('blob'); + + onProgress?.(100, 'Complete!'); + return pdfBlob; +} + + +function groupByTagName(elements: Element[]): Record { + const groups: Record = {}; + + for (const element of elements) { + const tagName = element.tagName; + if (!groups[tagName]) { + groups[tagName] = []; + } + groups[tagName].push(element); + } + + return groups; +} + +function extractTableData(elements: Element[]): { headers: string[], rows: string[][] } { + if (elements.length === 0) { + return { headers: [], rows: [] }; + } + + const headerSet = new Set(); + for (const element of elements) { + for (const child of Array.from(element.children)) { + headerSet.add(child.tagName); + } + } + const headers = Array.from(headerSet); + + const rows: string[][] = []; + for (const element of elements) { + const row: string[] = []; + for (const header of headers) { + const child = element.querySelector(header); + row.push(child?.textContent?.trim() || ''); + } + rows.push(row); + } + + return { headers, rows }; +} + + +function extractKeyValuePairs(element: Element): string[][] { + const pairs: string[][] = []; + + for (const child of Array.from(element.children)) { + const key = child.tagName; + const value = child.textContent?.trim() || ''; + if (value) { + pairs.push([formatTitle(key), value]); + } + } + + for (const attr of Array.from(element.attributes)) { + pairs.push([formatTitle(attr.name), attr.value]); + } + + return pairs; +} + + +function formatTitle(tagName: string): string { + return tagName + .replace(/[_-]/g, ' ') + .replace(/([a-z])([A-Z])/g, '$1 $2') + .split(' ') + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' '); +} diff --git a/src/js/workflow/editor.ts b/src/js/workflow/editor.ts new file mode 100644 index 0000000..0725660 --- /dev/null +++ b/src/js/workflow/editor.ts @@ -0,0 +1,408 @@ +import { NodeEditor } from 'rete'; +import { AreaPlugin, AreaExtensions } from 'rete-area-plugin'; +import { + ConnectionPlugin, + Presets as ConnectionPresets, +} from 'rete-connection-plugin'; +import { LitPlugin, Presets as LitPresets } from '@retejs/lit-plugin'; +import type { ClassicScheme, LitArea2D } from '@retejs/lit-plugin'; +import { DataflowEngine } from 'rete-engine'; +import type { DataflowEngineScheme } from 'rete-engine'; +import { LitElement, html } from 'lit'; +import type { BaseWorkflowNode } from './nodes/base-node'; +// @ts-ignore -- Vite ?inline import for injecting into Shadow DOM +import phosphorCSS from '@phosphor-icons/web/regular?inline'; + +// Shared stylesheet for Phosphor icons (font-face already loaded globally, strip it) +const phosphorSheet = new CSSStyleSheet(); +phosphorSheet.replaceSync(phosphorCSS.replace(/@font-face[^}]*\}/g, '')); + +type AreaExtra = LitArea2D; + +export interface WorkflowEditor { + editor: NodeEditor; + area: AreaPlugin; + engine: DataflowEngine; + destroy: () => void; +} + +const categoryColors: Record = { + Input: '#60a5fa', + 'Edit & Annotate': '#a5b4fc', + 'Organize & Manage': '#c4b5fd', + 'Optimize & Repair': '#fcd34d', + 'Secure PDF': '#fda4af', + Output: '#5eead4', +}; + +function getStatusInfo(status: string, connected: boolean) { + if (status === 'running') + return { color: '#eab308', label: 'Running...', animate: true }; + if (status === 'completed') + return { color: '#22c55e', label: 'Complete', animate: false }; + if (status === 'error') + return { color: '#ef4444', label: 'Failed', animate: false }; + return { + color: connected ? '#22c55e' : '#6b7280', + label: connected ? 'Connected' : 'Not connected', + animate: false, + }; +} + +class WorkflowNodeElement extends LitElement { + static properties = { + data: { attribute: false }, + emit: { attribute: false }, + }; + + declare data: BaseWorkflowNode | undefined; + declare emit: ((data: unknown) => void) | undefined; + + createRenderRoot(): HTMLElement | ShadowRoot { + return this; + } + + render() { + if (!this.data) return html``; + const node = this.data; + const inputs = Object.entries(node.inputs || {}); + const outputs = Object.entries(node.outputs || {}); + const color = categoryColors[node.category] || '#6b7280'; + const emitFn = this.emit; + + return html` +
+ ${inputs.length > 0 + ? html` +
+ ${inputs.map(([key, input]) => + input + ? html` +
+ +
+ ` + : null + )} +
+ ` + : null} +
+
+
+
+
+ + Not connected + + + + + + +
+
+
+ +
+
+ ${node.label} +
+
+ ${node.description} +
+
+
+
+ ${outputs.length > 0 + ? html` +
+ ${outputs.map(([key, output]) => + output + ? html` +
+ +
+ ` + : null + )} +
+ ` + : null} +
+ `; + } +} + +if (!customElements.get('wf-node')) { + customElements.define('wf-node', WorkflowNodeElement); +} + +export function updateNodeDisplay( + nodeId: string, + editor: NodeEditor, + area: AreaPlugin +) { + const view = area.nodeViews.get(nodeId); + if (!view) return; + const el = view.element; + const node = editor.getNode(nodeId) as BaseWorkflowNode; + if (!node) return; + + const conns = editor.getConnections(); + const connected = conns.some( + (c) => c.target === nodeId || c.source === nodeId + ); + const status = node.execStatus || 'idle'; + const st = getStatusInfo(status, connected); + + const bar = el.querySelector('[data-wf="bar"]'); + const dot = el.querySelector('[data-wf="dot"]'); + const label = el.querySelector('[data-wf="label"]'); + + if (bar) { + bar.className = st.animate ? 'wf-bar-slide' : ''; + bar.style.background = st.animate + ? `linear-gradient(90deg, #1f2937 0%, ${st.color} 50%, #1f2937 100%)` + : st.color; + bar.style.opacity = + status === 'idle' && !connected + ? '0.25' + : status === 'idle' + ? '0.5' + : '1'; + bar.style.backgroundSize = st.animate ? '200% 100%' : ''; + } + + if (dot) { + dot.className = st.animate ? 'wf-dot-pulse' : ''; + dot.style.background = st.color; + dot.style.boxShadow = 'none'; + } + + if (label) { + label.style.color = st.color; + label.textContent = st.label; + } +} + +export async function createWorkflowEditor( + container: HTMLElement +): Promise { + const editor = new NodeEditor(); + const area = new AreaPlugin(container); + const connection = new ConnectionPlugin(); + const litPlugin = new LitPlugin(); + const engine = new DataflowEngine(); + + litPlugin.addPreset( + LitPresets.classic.setup({ + customize: { + node(data) { + return ({ emit }: { emit: (data: unknown) => void }) => { + return html``; + }; + }, + socket() { + return () => { + return html`
`; + }; + }, + }, + }) + ); + + connection.addPreset(ConnectionPresets.classic.setup()); + + // Override connection path to use vertical bezier curves (top-to-bottom flow) + litPlugin.addPipe((context) => { + if ((context as any).type === 'connectionpath') { + const { points } = (context as any).data; + const [start, end] = points as [ + { x: number; y: number }, + { x: number; y: number }, + ]; + const curvature = 0.3; + const horizontal = Math.abs(start.x - end.x); + const dy = + Math.max(horizontal / 2, Math.abs(end.y - start.y)) * curvature; + const path = `M ${start.x} ${start.y} C ${start.x} ${start.y + dy} ${end.x} ${end.y - dy} ${end.x} ${end.y}`; + return { + ...context, + data: { ...(context as any).data, path }, + } as typeof context; + } + return context; + }); + + editor.use(area); + area.use(connection); + area.use(litPlugin); + (editor as NodeEditor).use(engine); + + AreaExtensions.selectableNodes(area, AreaExtensions.selector(), { + accumulating: AreaExtensions.accumulateOnCtrl(), + }); + + AreaExtensions.simpleNodesOrder(area); + + // Inject Phosphor icon styles into Shadow DOM roots created by the Lit plugin + let phosphorTimer: ReturnType | null = null; + const injectPhosphor = () => { + if (phosphorTimer) return; + phosphorTimer = setTimeout(() => { + phosphorTimer = null; + for (const el of container.querySelectorAll('*')) { + const sr = (el as HTMLElement).shadowRoot; + if (sr && !sr.adoptedStyleSheets.includes(phosphorSheet)) { + sr.adoptedStyleSheets = [...sr.adoptedStyleSheets, phosphorSheet]; + } + } + }, 50); + }; + const observer = new MutationObserver(injectPhosphor); + observer.observe(container, { childList: true, subtree: true }); + + const onPointerDown = (e: Event) => { + const target = (e.target as HTMLElement).closest( + '[data-wf-delete]' + ); + if (!target) return; + e.stopPropagation(); + e.preventDefault(); + const nodeId = target.getAttribute('data-wf-delete'); + if (nodeId) { + document.dispatchEvent( + new CustomEvent('wf-delete-node', { detail: { nodeId } }) + ); + } + }; + + const onMouseEnter = (e: Event) => { + const target = (e.target as HTMLElement).closest( + '[data-wf-delete]' + ); + if (!target) return; + target.style.color = '#f87171'; + target.style.background = 'rgba(248,113,113,0.1)'; + }; + + const onMouseLeave = (e: Event) => { + const target = (e.target as HTMLElement).closest( + '[data-wf-delete]' + ); + if (!target) return; + target.style.color = '#6b7280'; + target.style.background = 'transparent'; + }; + + container.addEventListener('pointerdown', onPointerDown, true); + container.addEventListener('mouseenter', onMouseEnter, true); + container.addEventListener('mouseleave', onMouseLeave, true); + + return { + editor, + area, + engine, + destroy: () => { + observer.disconnect(); + if (phosphorTimer) { + clearTimeout(phosphorTimer); + phosphorTimer = null; + } + container.removeEventListener('pointerdown', onPointerDown, true); + container.removeEventListener('mouseenter', onMouseEnter, true); + container.removeEventListener('mouseleave', onMouseLeave, true); + area.destroy(); + }, + }; +} diff --git a/src/js/workflow/engine.ts b/src/js/workflow/engine.ts new file mode 100644 index 0000000..d7a6ca5 --- /dev/null +++ b/src/js/workflow/engine.ts @@ -0,0 +1,188 @@ +import type { ClassicScheme, LitArea2D } from '@retejs/lit-plugin'; +import { NodeEditor } from 'rete'; +import { AreaPlugin } from 'rete-area-plugin'; +import { DataflowEngine } from 'rete-engine'; +import type { DataflowEngineScheme } from 'rete-engine'; +import type { BaseWorkflowNode } from './nodes/base-node'; +import { WorkflowError } from './types'; +import type { ExecutionProgress } from './types'; +import { updateNodeDisplay } from './editor'; + +type AreaExtra = LitArea2D; + +function getUpstreamNodes( + nodeId: string, + editor: NodeEditor +): Set { + const visited = new Set(); + const queue = [nodeId]; + while (queue.length > 0) { + const current = queue.shift()!; + if (visited.has(current)) continue; + visited.add(current); + const incoming = editor + .getConnections() + .filter((c) => c.target === current); + for (const conn of incoming) { + queue.push(conn.source); + } + } + return visited; +} + +function topologicalSort( + nodeIds: Set, + editor: NodeEditor +): string[] { + const inDegree = new Map(); + const adj = new Map(); + for (const id of nodeIds) { + inDegree.set(id, 0); + adj.set(id, []); + } + for (const conn of editor.getConnections()) { + if (nodeIds.has(conn.source) && nodeIds.has(conn.target)) { + adj.get(conn.source)!.push(conn.target); + inDegree.set(conn.target, (inDegree.get(conn.target) || 0) + 1); + } + } + const queue: string[] = []; + for (const [id, deg] of inDegree) { + if (deg === 0) queue.push(id); + } + const sorted: string[] = []; + while (queue.length > 0) { + const current = queue.shift()!; + sorted.push(current); + for (const next of adj.get(current) || []) { + const newDeg = (inDegree.get(next) ?? 0) - 1; + inDegree.set(next, newDeg); + if (newDeg === 0) queue.push(next); + } + } + if (sorted.length < nodeIds.size) { + throw new WorkflowError( + 'Circular dependency detected in workflow. Please remove any loops between nodes.' + ); + } + + return sorted; +} + +function tick(): Promise { + return new Promise((r) => setTimeout(r, 0)); +} + +function validateEncryptOrdering( + editor: NodeEditor, + pipelineNodes: string[] +): string | null { + for (const nodeId of pipelineNodes) { + const node = editor.getNode(nodeId) as BaseWorkflowNode; + if (node?.label !== 'Encrypt') continue; + + const outConns = editor.getConnections().filter((c) => c.source === nodeId); + for (const conn of outConns) { + const target = editor.getNode(conn.target) as BaseWorkflowNode; + if (target && target.category !== 'Output') { + return `The Encrypt node feeds into "${target.label}", which may fail on encrypted data. Move Encrypt to just before the output node.`; + } + } + } + return null; +} + +export async function executeWorkflow( + editor: NodeEditor, + engine: DataflowEngine, + area: AreaPlugin, + onProgress: (progress: ExecutionProgress) => void +): Promise { + const nodes = editor.getNodes() as BaseWorkflowNode[]; + const connections = editor.getConnections(); + + const nodesWithOutputConnections = new Set(connections.map((c) => c.source)); + const terminalNodes = nodes.filter( + (n) => !nodesWithOutputConnections.has(n.id) + ); + + if (terminalNodes.length === 0) { + throw new Error( + 'No output nodes found. Add a Download node to complete your workflow.' + ); + } + + const pipelineNodes = new Set(); + for (const terminal of terminalNodes) { + const upstream = getUpstreamNodes(terminal.id, editor); + for (const id of upstream) pipelineNodes.add(id); + } + + for (const node of nodes) { + node.execStatus = 'idle'; + updateNodeDisplay(node.id, editor, area); + } + await tick(); + + const sorted = topologicalSort(pipelineNodes, editor); + + const encryptWarning = validateEncryptOrdering(editor, sorted); + if (encryptWarning) { + throw new WorkflowError(encryptWarning, 'Encrypt'); + } + + engine.reset(); + + for (const nodeId of sorted) { + const node = editor.getNode(nodeId) as BaseWorkflowNode; + if (!node) continue; + + node.execStatus = 'running'; + updateNodeDisplay(node.id, editor, area); + onProgress({ + nodeId: node.id, + nodeName: node.label, + status: 'running', + message: `Processing ${node.label}...`, + }); + await tick(); + + try { + await engine.fetch(nodeId); + + node.execStatus = 'completed'; + updateNodeDisplay(node.id, editor, area); + onProgress({ + nodeId: node.id, + nodeName: node.label, + status: 'completed', + }); + await tick(); + } catch (error) { + node.execStatus = 'error'; + updateNodeDisplay(node.id, editor, area); + + for (const remainingId of sorted) { + const remaining = editor.getNode(remainingId) as BaseWorkflowNode; + if (remaining && remaining.execStatus === 'running') { + remaining.execStatus = 'idle'; + updateNodeDisplay(remaining.id, editor, area); + } + } + + const message = error instanceof Error ? error.message : String(error); + const wrapped = + error instanceof WorkflowError + ? error + : new WorkflowError(message, node.label); + + onProgress({ + nodeId: node.id, + nodeName: node.label, + status: 'error', + message: wrapped.message, + }); + throw wrapped; + } + } +} diff --git a/src/js/workflow/nodes/add-blank-page-node.ts b/src/js/workflow/nodes/add-blank-page-node.ts new file mode 100644 index 0000000..d648688 --- /dev/null +++ b/src/js/workflow/nodes/add-blank-page-node.ts @@ -0,0 +1,76 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; + +export class AddBlankPageNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-file-plus'; + readonly description = 'Insert blank pages'; + + constructor() { + super('Add Blank Page'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + this.addControl( + 'blankPosition', + new ClassicPreset.InputControl('text', { initial: 'end' }) + ); + this.addControl( + 'afterPage', + new ClassicPreset.InputControl('number', { initial: 1 }) + ); + this.addControl( + 'count', + new ClassicPreset.InputControl('number', { initial: 1 }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Add Blank Page'); + const posCtrl = this.controls['blankPosition'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const position = posCtrl?.value || 'end'; + const afterPageCtrl = this.controls['afterPage'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const afterPage = afterPageCtrl?.value ?? 1; + const countCtrl = this.controls['count'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const count = Math.max(1, Math.min(100, countCtrl?.value ?? 1)); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pdfDoc = await PDFDocument.load(input.bytes); + const firstPage = pdfDoc.getPages()[0]; + const { width, height } = firstPage + ? firstPage.getSize() + : { width: 595.28, height: 841.89 }; + for (let i = 0; i < count; i++) { + if (position === 'start') { + pdfDoc.insertPage(0, [width, height]); + } else if (position === 'after') { + const insertAt = + Math.min(Math.max(1, afterPage), pdfDoc.getPageCount()) + i; + pdfDoc.insertPage(insertAt, [width, height]); + } else { + pdfDoc.addPage([width, height]); + } + } + const pdfBytes = await pdfDoc.save(); + return { + type: 'pdf', + document: pdfDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_blank.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/adjust-colors-node.ts b/src/js/workflow/nodes/adjust-colors-node.ts new file mode 100644 index 0000000..e1d7ba2 --- /dev/null +++ b/src/js/workflow/nodes/adjust-colors-node.ts @@ -0,0 +1,141 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { applyColorAdjustments } from '../../utils/image-effects'; +import { PDFDocument } from 'pdf-lib'; +import * as pdfjsLib from 'pdfjs-dist'; +import type { AdjustColorsSettings } from '../../types/adjust-colors-type'; + +export class AdjustColorsNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-sliders-horizontal'; + readonly description = 'Adjust brightness, contrast, and colors'; + + constructor() { + super('Adjust Colors'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Adjusted PDF')); + this.addControl( + 'brightness', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'contrast', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'saturation', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'hueShift', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'temperature', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'tint', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'gamma', + new ClassicPreset.InputControl('number', { initial: 1.0 }) + ); + this.addControl( + 'sepia', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Adjust Colors'); + + const getNum = (key: string, fallback: number) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'number'> + | undefined; + return ctrl?.value ?? fallback; + }; + + const settings: AdjustColorsSettings = { + brightness: getNum('brightness', 0), + contrast: getNum('contrast', 0), + saturation: getNum('saturation', 0), + hueShift: getNum('hueShift', 0), + temperature: getNum('temperature', 0), + tint: getNum('tint', 0), + gamma: getNum('gamma', 1.0), + sepia: getNum('sepia', 0), + }; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const newPdfDoc = await PDFDocument.create(); + const pdfjsDoc = await pdfjsLib.getDocument({ data: input.bytes }) + .promise; + + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + const page = await pdfjsDoc.getPage(i); + const viewport = page.getViewport({ scale: 2.0 }); + + const renderCanvas = document.createElement('canvas'); + renderCanvas.width = viewport.width; + renderCanvas.height = viewport.height; + const renderCtx = renderCanvas.getContext('2d'); + if (!renderCtx) + throw new Error(`Failed to get canvas context for page ${i}`); + await page.render({ + canvasContext: renderCtx, + viewport, + canvas: renderCanvas, + }).promise; + + const baseData = renderCtx.getImageData( + 0, + 0, + renderCanvas.width, + renderCanvas.height + ); + const outputCanvas = document.createElement('canvas'); + applyColorAdjustments(baseData, outputCanvas, settings); + + const pngBlob = await new Promise((resolve) => + outputCanvas.toBlob(resolve, 'image/png') + ); + + if (!pngBlob) throw new Error(`Failed to render page ${i} to image`); + + const pngBytes = await pngBlob.arrayBuffer(); + const pngImage = await newPdfDoc.embedPng(pngBytes); + const origViewport = page.getViewport({ scale: 1.0 }); + const newPage = newPdfDoc.addPage([ + origViewport.width, + origViewport.height, + ]); + newPage.drawImage(pngImage, { + x: 0, + y: 0, + width: origViewport.width, + height: origViewport.height, + }); + } + + if (newPdfDoc.getPageCount() === 0) + throw new Error('No pages were processed'); + const pdfBytes = await newPdfDoc.save(); + return { + type: 'pdf', + document: newPdfDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_adjusted.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/background-color-node.ts b/src/js/workflow/nodes/background-color-node.ts new file mode 100644 index 0000000..2d44c3e --- /dev/null +++ b/src/js/workflow/nodes/background-color-node.ts @@ -0,0 +1,65 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument, rgb } from 'pdf-lib'; +import { hexToRgb } from '../../utils/helpers.js'; + +export class BackgroundColorNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-palette'; + readonly description = 'Change background color'; + + constructor() { + super('Background Color'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + this.addControl( + 'color', + new ClassicPreset.InputControl('text', { initial: '#ffffff' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Background Color'); + + const colorCtrl = this.controls['color'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const hex = colorCtrl?.value || '#ffffff'; + const c = hexToRgb(hex); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const srcDoc = await PDFDocument.load(input.bytes); + const newDoc = await PDFDocument.create(); + + for (let i = 0; i < srcDoc.getPageCount(); i++) { + const [originalPage] = await newDoc.copyPages(srcDoc, [i]); + const { width, height } = originalPage.getSize(); + const newPage = newDoc.addPage([width, height]); + newPage.drawRectangle({ + x: 0, + y: 0, + width, + height, + color: rgb(c.r, c.g, c.b), + }); + const embedded = await newDoc.embedPage(originalPage); + newPage.drawPage(embedded, { x: 0, y: 0, width, height }); + } + + const pdfBytes = await newDoc.save(); + return { + type: 'pdf', + document: newDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_bg.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/base-node.ts b/src/js/workflow/nodes/base-node.ts new file mode 100644 index 0000000..563d750 --- /dev/null +++ b/src/js/workflow/nodes/base-node.ts @@ -0,0 +1,31 @@ +import { ClassicPreset } from 'rete'; +import type { NodeCategory, NodeMeta, SocketData } from '../types'; + +export abstract class BaseWorkflowNode extends ClassicPreset.Node { + abstract readonly category: NodeCategory; + abstract readonly icon: string; + abstract readonly description: string; + + width = 280; + height = 140; + execStatus: 'idle' | 'running' | 'completed' | 'error' = 'idle'; + nodeType: string = ''; + + constructor(label: string) { + super(label); + } + + abstract data( + inputs: Record + ): Promise>; + + getMeta(): NodeMeta { + return { + id: this.id, + label: this.label, + category: this.category, + icon: this.icon, + description: this.description, + }; + } +} diff --git a/src/js/workflow/nodes/booklet-node.ts b/src/js/workflow/nodes/booklet-node.ts new file mode 100644 index 0000000..440fc3f --- /dev/null +++ b/src/js/workflow/nodes/booklet-node.ts @@ -0,0 +1,177 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument, PageSizes } from 'pdf-lib'; + +const paperSizeLookup: Record = { + Letter: PageSizes.Letter, + A4: PageSizes.A4, + A3: PageSizes.A3, + Tabloid: PageSizes.Tabloid, + Legal: PageSizes.Legal, +}; + +export class BookletNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-book-open'; + readonly description = 'Arrange pages for booklet printing'; + + constructor() { + super('Booklet'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Booklet PDF')); + this.addControl( + 'gridMode', + new ClassicPreset.InputControl('text', { initial: '1x2' }) + ); + this.addControl( + 'paperSize', + new ClassicPreset.InputControl('text', { initial: 'Letter' }) + ); + this.addControl( + 'orientation', + new ClassicPreset.InputControl('text', { initial: 'auto' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Booklet'); + + const getText = (key: string, fallback: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || fallback; + }; + + const gridMode = getText('gridMode', '1x2'); + const paperSizeKey = getText('paperSize', 'Letter'); + const orientationVal = getText('orientation', 'auto'); + + let rows: number, cols: number; + switch (gridMode) { + case '2x2': + rows = 2; + cols = 2; + break; + case '2x4': + rows = 2; + cols = 4; + break; + case '4x4': + rows = 4; + cols = 4; + break; + default: + rows = 1; + cols = 2; + break; + } + + const isBookletMode = rows === 1 && cols === 2; + const pageDims = paperSizeLookup[paperSizeKey] || PageSizes.Letter; + const orientation = + orientationVal === 'portrait' + ? 'portrait' + : orientationVal === 'landscape' + ? 'landscape' + : isBookletMode + ? 'landscape' + : 'portrait'; + + const sheetWidth = orientation === 'landscape' ? pageDims[1] : pageDims[0]; + const sheetHeight = orientation === 'landscape' ? pageDims[0] : pageDims[1]; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const sourceDoc = await PDFDocument.load(input.bytes); + const totalPages = sourceDoc.getPageCount(); + const pagesPerSheet = rows * cols; + const outputDoc = await PDFDocument.create(); + + let numSheets: number; + let totalRounded: number; + if (isBookletMode) { + totalRounded = Math.ceil(totalPages / 4) * 4; + numSheets = Math.ceil(totalPages / 4) * 2; + } else { + totalRounded = totalPages; + numSheets = Math.ceil(totalPages / pagesPerSheet); + } + + const cellWidth = sheetWidth / cols; + const cellHeight = sheetHeight / rows; + const padding = 10; + + for (let sheetIndex = 0; sheetIndex < numSheets; sheetIndex++) { + const outputPage = outputDoc.addPage([sheetWidth, sheetHeight]); + + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + const slotIndex = r * cols + c; + let pageNumber: number; + + if (isBookletMode) { + const physicalSheet = Math.floor(sheetIndex / 2); + const isFrontSide = sheetIndex % 2 === 0; + if (isFrontSide) { + pageNumber = + c === 0 + ? totalRounded - 2 * physicalSheet + : 2 * physicalSheet + 1; + } else { + pageNumber = + c === 0 + ? 2 * physicalSheet + 2 + : totalRounded - 2 * physicalSheet - 1; + } + } else { + pageNumber = sheetIndex * pagesPerSheet + slotIndex + 1; + } + + if (pageNumber >= 1 && pageNumber <= totalPages) { + const [embeddedPage] = await outputDoc.embedPdf(sourceDoc, [ + pageNumber - 1, + ]); + const { width: srcW, height: srcH } = embeddedPage; + const availableWidth = cellWidth - padding * 2; + const availableHeight = cellHeight - padding * 2; + const scale = Math.min( + availableWidth / srcW, + availableHeight / srcH + ); + const scaledWidth = srcW * scale; + const scaledHeight = srcH * scale; + const x = + c * cellWidth + padding + (availableWidth - scaledWidth) / 2; + const y = + sheetHeight - + (r + 1) * cellHeight + + padding + + (availableHeight - scaledHeight) / 2; + outputPage.drawPage(embeddedPage, { + x, + y, + width: scaledWidth, + height: scaledHeight, + }); + } + } + } + } + + const pdfBytes = new Uint8Array(await outputDoc.save()); + return { + type: 'pdf', + document: outputDoc, + bytes: pdfBytes, + filename: input.filename.replace(/\.pdf$/i, '_booklet.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/cbz-to-pdf-node.ts b/src/js/workflow/nodes/cbz-to-pdf-node.ts new file mode 100644 index 0000000..ac1b641 --- /dev/null +++ b/src/js/workflow/nodes/cbz-to-pdf-node.ts @@ -0,0 +1,70 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class CbzToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-book-open'; + readonly description = 'Upload comic book archives and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('CBZ Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + const name = file.name.toLowerCase(); + if (name.endsWith('.cbz') || name.endsWith('.cbr')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} comic archives`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No comic archives uploaded in CBZ Input node'); + + const pymupdf = await loadPyMuPDF(); + const results: PDFData[] = []; + for (const file of this.files) { + const blob = await pymupdf.convertToPdf(file, { filetype: 'cbz' }); + const bytes = new Uint8Array(await blob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.(cbz|cbr)$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/combine-single-page-node.ts b/src/js/workflow/nodes/combine-single-page-node.ts new file mode 100644 index 0000000..a6b9b1f --- /dev/null +++ b/src/js/workflow/nodes/combine-single-page-node.ts @@ -0,0 +1,174 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument, rgb } from 'pdf-lib'; +import * as pdfjsLib from 'pdfjs-dist'; +import { hexToRgb } from '../../utils/helpers.js'; + +export class CombineSinglePageNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-arrows-out-line-vertical'; + readonly description = 'Stitch all pages into one continuous page'; + + constructor() { + super('Combine to Single Page'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Combined PDF')); + this.addControl( + 'orientation', + new ClassicPreset.InputControl('text', { initial: 'vertical' }) + ); + this.addControl( + 'spacing', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'backgroundColor', + new ClassicPreset.InputControl('text', { initial: '#ffffff' }) + ); + this.addControl( + 'separator', + new ClassicPreset.InputControl('text', { initial: 'false' }) + ); + this.addControl( + 'separatorThickness', + new ClassicPreset.InputControl('number', { initial: 0.5 }) + ); + this.addControl( + 'separatorColor', + new ClassicPreset.InputControl('text', { initial: '#000000' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Combine to Single Page'); + + const getText = (key: string, fallback: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || fallback; + }; + const getNum = (key: string, fallback: number) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'number'> + | undefined; + return ctrl?.value ?? fallback; + }; + + const isVertical = getText('orientation', 'vertical') === 'vertical'; + const spacing = Math.max(0, getNum('spacing', 0)); + const bgC = hexToRgb(getText('backgroundColor', '#ffffff')); + const bgColor = rgb(bgC.r, bgC.g, bgC.b); + const addSeparator = getText('separator', 'false') === 'true'; + const sepThickness = getNum('separatorThickness', 0.5); + const sepC = hexToRgb(getText('separatorColor', '#000000')); + const sepColor = rgb(sepC.r, sepC.g, sepC.b); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pdfjsDoc = await pdfjsLib.getDocument({ data: input.bytes }) + .promise; + const newDoc = await PDFDocument.create(); + + const pageDims: { width: number; height: number }[] = []; + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + const page = await pdfjsDoc.getPage(i); + const vp = page.getViewport({ scale: 1.0 }); + pageDims.push({ width: vp.width, height: vp.height }); + } + + const maxWidth = Math.max(...pageDims.map((d) => d.width)); + const maxHeight = Math.max(...pageDims.map((d) => d.height)); + const totalSpacing = spacing * (pdfjsDoc.numPages - 1); + + const finalWidth = isVertical + ? maxWidth + : pageDims.reduce((sum, d) => sum + d.width, 0) + totalSpacing; + const finalHeight = isVertical + ? pageDims.reduce((sum, d) => sum + d.height, 0) + totalSpacing + : maxHeight; + + const combinedPage = newDoc.addPage([finalWidth, finalHeight]); + combinedPage.drawRectangle({ + x: 0, + y: 0, + width: finalWidth, + height: finalHeight, + color: bgColor, + }); + + let offset = isVertical ? finalHeight : 0; + + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + const page = await pdfjsDoc.getPage(i); + const viewport = page.getViewport({ scale: 2.0 }); + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + const ctx = canvas.getContext('2d'); + if (!ctx) + throw new Error(`Failed to get canvas context for page ${i}`); + await page.render({ canvasContext: ctx, viewport, canvas }).promise; + + const pngBlob = await new Promise((resolve) => + canvas.toBlob(resolve, 'image/png') + ); + if (!pngBlob) throw new Error(`Failed to render page ${i} to image`); + + const pngBytes = await pngBlob.arrayBuffer(); + const pngImage = await newDoc.embedPng(pngBytes); + const dim = pageDims[i - 1]; + + if (isVertical) { + offset -= dim.height; + combinedPage.drawImage(pngImage, { + x: 0, + y: offset, + width: dim.width, + height: dim.height, + }); + if (addSeparator && i < pdfjsDoc.numPages) { + combinedPage.drawLine({ + start: { x: 0, y: offset - spacing / 2 }, + end: { x: finalWidth, y: offset - spacing / 2 }, + thickness: sepThickness, + color: sepColor, + }); + } + offset -= spacing; + } else { + combinedPage.drawImage(pngImage, { + x: offset, + y: 0, + width: dim.width, + height: dim.height, + }); + offset += dim.width; + if (addSeparator && i < pdfjsDoc.numPages) { + combinedPage.drawLine({ + start: { x: offset + spacing / 2, y: 0 }, + end: { x: offset + spacing / 2, y: finalHeight }, + thickness: sepThickness, + color: sepColor, + }); + } + offset += spacing; + } + } + + const pdfBytes = await newDoc.save(); + return { + type: 'pdf', + document: newDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_combined.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/compress-node.ts b/src/js/workflow/nodes/compress-node.ts new file mode 100644 index 0000000..d2e66ce --- /dev/null +++ b/src/js/workflow/nodes/compress-node.ts @@ -0,0 +1,129 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { + performCondenseCompression, + performPhotonCompression, +} from '../../utils/compress.js'; +import type { CondenseCustomSettings } from '../../utils/compress.js'; +import { isPyMuPDFAvailable } from '../../utils/pymupdf-loader.js'; + +export class CompressNode extends BaseWorkflowNode { + readonly category = 'Optimize & Repair' as const; + readonly icon = 'ph-lightning'; + readonly description = 'Reduce PDF file size'; + + constructor() { + super('Compress'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput( + 'pdf', + new ClassicPreset.Output(pdfSocket, 'Compressed PDF') + ); + this.addControl( + 'algorithm', + new ClassicPreset.InputControl('text', { initial: 'condense' }) + ); + this.addControl( + 'compressionLevel', + new ClassicPreset.InputControl('text', { initial: 'balanced' }) + ); + this.addControl( + 'imageQuality', + new ClassicPreset.InputControl('number', { initial: 75 }) + ); + this.addControl( + 'dpiTarget', + new ClassicPreset.InputControl('number', { initial: 96 }) + ); + this.addControl( + 'dpiThreshold', + new ClassicPreset.InputControl('number', { initial: 150 }) + ); + this.addControl( + 'removeMetadata', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'subsetFonts', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'convertToGrayscale', + new ClassicPreset.InputControl('text', { initial: 'false' }) + ); + this.addControl( + 'removeThumbnails', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + } + + private getTextControl(key: string, fallback: string): string { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value ?? fallback; + } + + private getNumberControl(key: string, fallback: number): number { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'number'> + | undefined; + return ctrl?.value ?? fallback; + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Compress'); + + const algorithm = this.getTextControl('algorithm', 'condense'); + const level = this.getTextControl('compressionLevel', 'balanced'); + const customSettings: CondenseCustomSettings = { + imageQuality: this.getNumberControl('imageQuality', 75), + dpiTarget: this.getNumberControl('dpiTarget', 96), + dpiThreshold: this.getNumberControl('dpiThreshold', 150), + removeMetadata: this.getTextControl('removeMetadata', 'true') === 'true', + subsetFonts: this.getTextControl('subsetFonts', 'true') === 'true', + convertToGrayscale: + this.getTextControl('convertToGrayscale', 'false') === 'true', + removeThumbnails: + this.getTextControl('removeThumbnails', 'true') === 'true', + }; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const arrayBuffer = input.bytes.buffer.slice( + input.bytes.byteOffset, + input.bytes.byteOffset + input.bytes.byteLength + ) as ArrayBuffer; + + let pdfBytes: Uint8Array; + + if (algorithm === 'condense' && isPyMuPDFAvailable()) { + const blob = new Blob([arrayBuffer], { type: 'application/pdf' }); + const result = await performCondenseCompression( + blob, + level, + customSettings + ); + pdfBytes = new Uint8Array(await result.blob.arrayBuffer()); + } else { + pdfBytes = await performPhotonCompression(arrayBuffer, level); + } + + const document = await PDFDocument.load(pdfBytes); + + return { + type: 'pdf', + document, + bytes: pdfBytes, + filename: input.filename.replace(/\.pdf$/i, '_compressed.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/crop-node.ts b/src/js/workflow/nodes/crop-node.ts new file mode 100644 index 0000000..e82a18d --- /dev/null +++ b/src/js/workflow/nodes/crop-node.ts @@ -0,0 +1,79 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; + +export class CropNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-crop'; + readonly description = 'Trim margins from all pages'; + + constructor() { + super('Crop'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Cropped PDF')); + this.addControl( + 'top', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'bottom', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'left', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'right', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Crop'); + + const getNum = (key: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'number'> + | undefined; + return Math.max(0, ctrl?.value ?? 0); + }; + + const top = getNum('top'); + const bottom = getNum('bottom'); + const left = getNum('left'); + const right = getNum('right'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pdfDoc = await PDFDocument.load(input.bytes); + const pages = pdfDoc.getPages(); + + for (const page of pages) { + const { width, height } = page.getSize(); + const cropWidth = width - left - right; + const cropHeight = height - top - bottom; + if (cropWidth <= 0 || cropHeight <= 0) { + throw new Error( + 'Crop margins exceed page dimensions. Reduce crop values.' + ); + } + page.setCropBox(left, bottom, cropWidth, cropHeight); + } + + const pdfBytes = await pdfDoc.save(); + return { + type: 'pdf', + document: pdfDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_cropped.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/decrypt-node.ts b/src/js/workflow/nodes/decrypt-node.ts new file mode 100644 index 0000000..6954151 --- /dev/null +++ b/src/js/workflow/nodes/decrypt-node.ts @@ -0,0 +1,75 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { initializeQpdf } from '../../utils/helpers.js'; + +export class DecryptNode extends BaseWorkflowNode { + readonly category = 'Secure PDF' as const; + readonly icon = 'ph-lock-open'; + readonly description = 'Remove PDF password protection'; + + constructor() { + super('Decrypt'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Decrypted PDF')); + this.addControl( + 'password', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Decrypt'); + + const passCtrl = this.controls['password'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const password = passCtrl?.value || ''; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const qpdf = await initializeQpdf(); + const uid = `${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; + const inputPath = `/tmp/input_decrypt_${uid}.pdf`; + const outputPath = `/tmp/output_decrypt_${uid}.pdf`; + + let decryptedData: Uint8Array; + try { + qpdf.FS.writeFile(inputPath, input.bytes); + const args = password + ? [inputPath, '--password=' + password, '--decrypt', outputPath] + : [inputPath, '--decrypt', outputPath]; + qpdf.callMain(args); + + decryptedData = qpdf.FS.readFile(outputPath, { encoding: 'binary' }); + } finally { + try { + qpdf.FS.unlink(inputPath); + } catch { + /* cleanup */ + } + try { + qpdf.FS.unlink(outputPath); + } catch { + /* cleanup */ + } + } + + const resultBytes = new Uint8Array(decryptedData); + const document = await PDFDocument.load(resultBytes); + + return { + type: 'pdf', + document, + bytes: resultBytes, + filename: input.filename.replace(/\.pdf$/i, '_decrypted.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/delete-pages-node.ts b/src/js/workflow/nodes/delete-pages-node.ts new file mode 100644 index 0000000..91b0fd7 --- /dev/null +++ b/src/js/workflow/nodes/delete-pages-node.ts @@ -0,0 +1,49 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { deletePdfPages, parseDeletePages } from '../../utils/pdf-operations'; +import { PDFDocument } from 'pdf-lib'; + +export class DeletePagesNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-trash'; + readonly description = 'Remove specific pages'; + + constructor() { + super('Delete Pages'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + this.addControl( + 'pages', + new ClassicPreset.InputControl('text', { initial: '1' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Delete Pages'); + const pagesControl = this.controls['pages'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const deleteStr = pagesControl?.value || ''; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const srcDoc = await PDFDocument.load(input.bytes); + const totalPages = srcDoc.getPageCount(); + const pagesToDelete = parseDeletePages(deleteStr, totalPages); + const resultBytes = await deletePdfPages(input.bytes, pagesToDelete); + const resultDoc = await PDFDocument.load(resultBytes); + return { + type: 'pdf', + document: resultDoc, + bytes: resultBytes, + filename: input.filename.replace(/\.pdf$/i, '_trimmed.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/deskew-node.ts b/src/js/workflow/nodes/deskew-node.ts new file mode 100644 index 0000000..16251d5 --- /dev/null +++ b/src/js/workflow/nodes/deskew-node.ts @@ -0,0 +1,65 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class DeskewNode extends BaseWorkflowNode { + readonly category = 'Optimize & Repair' as const; + readonly icon = 'ph-perspective'; + readonly description = 'Straighten skewed PDF pages'; + + constructor() { + super('Deskew'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Deskewed PDF')); + this.addControl( + 'skewThreshold', + new ClassicPreset.InputControl('text', { initial: '0.5' }) + ); + this.addControl( + 'processingDpi', + new ClassicPreset.InputControl('text', { initial: '150' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Deskew'); + + const getText = (key: string, fallback: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || fallback; + }; + const threshold = parseFloat(getText('skewThreshold', '0.5')); + const dpi = parseInt(getText('processingDpi', '150')) || 150; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pymupdf = await loadPyMuPDF(); + const blob = new Blob([new Uint8Array(input.bytes)], { + type: 'application/pdf', + }); + const { pdf: resultPdf } = await pymupdf.deskewPdf(blob, { + threshold, + dpi, + }); + + const bytes = new Uint8Array(await resultPdf.arrayBuffer()); + const document = await PDFDocument.load(bytes); + + return { + type: 'pdf', + document, + bytes, + filename: input.filename.replace(/\.pdf$/i, '_deskewed.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/digital-sign-node.ts b/src/js/workflow/nodes/digital-sign-node.ts new file mode 100644 index 0000000..c82fe55 --- /dev/null +++ b/src/js/workflow/nodes/digital-sign-node.ts @@ -0,0 +1,131 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { + signPdf, + parsePfxFile, + parseCombinedPem, +} from '../../logic/digital-sign-pdf.js'; +import type { CertificateData } from '@/types'; + +export class DigitalSignNode extends BaseWorkflowNode { + readonly category = 'Secure PDF' as const; + readonly icon = 'ph-certificate'; + readonly description = 'Apply a digital signature to PDF'; + + private certFile: File | null = null; + private certData: CertificateData | null = null; + private certPassword: string = ''; + + constructor() { + super('Digital Sign'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Signed PDF')); + this.addControl( + 'reason', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'location', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'contactInfo', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + } + + setCertFile(file: File): void { + this.certFile = file; + this.certData = null; + } + + getCertFilename(): string { + return this.certFile?.name ?? ''; + } + + hasCert(): boolean { + return this.certData !== null; + } + + hasCertFile(): boolean { + return this.certFile !== null; + } + + removeCert(): void { + this.certFile = null; + this.certData = null; + this.certPassword = ''; + } + + async unlockCert(password: string): Promise { + if (!this.certFile) return false; + this.certPassword = password; + + try { + const isPem = this.certFile.name.toLowerCase().endsWith('.pem'); + if (isPem) { + const pemContent = await this.certFile.text(); + this.certData = parseCombinedPem(pemContent, password || undefined); + } else { + const certBytes = await this.certFile.arrayBuffer(); + this.certData = parsePfxFile(certBytes, password); + } + return true; + } catch { + this.certData = null; + return false; + } + } + + needsPassword(): boolean { + return this.certFile !== null && this.certData === null; + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Digital Sign'); + if (!this.certData) + throw new Error('No certificate loaded in Digital Sign node'); + + const reasonCtrl = this.controls['reason'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const locationCtrl = this.controls['location'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const contactCtrl = this.controls['contactInfo'] as + | ClassicPreset.InputControl<'text'> + | undefined; + + const reason = reasonCtrl?.value ?? ''; + const location = locationCtrl?.value ?? ''; + const contactInfo = contactCtrl?.value ?? ''; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const signedBytes = await signPdf(input.bytes, this.certData!, { + signatureInfo: { + ...(reason ? { reason } : {}), + ...(location ? { location } : {}), + ...(contactInfo ? { contactInfo } : {}), + }, + }); + + const bytes = new Uint8Array(signedBytes); + const document = await PDFDocument.load(bytes); + + return { + type: 'pdf', + document, + bytes, + filename: input.filename.replace(/\.pdf$/i, '_signed.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/divide-pages-node.ts b/src/js/workflow/nodes/divide-pages-node.ts new file mode 100644 index 0000000..1334697 --- /dev/null +++ b/src/js/workflow/nodes/divide-pages-node.ts @@ -0,0 +1,82 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { parsePageRange } from '../../utils/pdf-operations'; + +export class DividePagesNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-columns'; + readonly description = 'Split pages vertically or horizontally'; + + constructor() { + super('Divide Pages'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Divided PDF')); + this.addControl( + 'direction', + new ClassicPreset.InputControl('text', { initial: 'vertical' }) + ); + this.addControl( + 'pages', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Divide Pages'); + const dirCtrl = this.controls['direction'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const direction = + dirCtrl?.value === 'horizontal' ? 'horizontal' : 'vertical'; + + const pagesCtrl = this.controls['pages'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const rangeStr = (pagesCtrl?.value || '').trim(); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const srcDoc = await PDFDocument.load(input.bytes); + const newDoc = await PDFDocument.create(); + const totalPages = srcDoc.getPageCount(); + + const pagesToDivide: Set = rangeStr + ? new Set(parsePageRange(rangeStr, totalPages)) + : new Set(Array.from({ length: totalPages }, (_, i) => i)); + + for (let i = 0; i < totalPages; i++) { + if (pagesToDivide.has(i)) { + const [page1] = await newDoc.copyPages(srcDoc, [i]); + const [page2] = await newDoc.copyPages(srcDoc, [i]); + const { width, height } = page1.getSize(); + if (direction === 'vertical') { + page1.setCropBox(0, 0, width / 2, height); + page2.setCropBox(width / 2, 0, width / 2, height); + } else { + page1.setCropBox(0, height / 2, width, height / 2); + page2.setCropBox(0, 0, width, height / 2); + } + newDoc.addPage(page1); + newDoc.addPage(page2); + } else { + const [copiedPage] = await newDoc.copyPages(srcDoc, [i]); + newDoc.addPage(copiedPage); + } + } + const pdfBytes = await newDoc.save(); + return { + type: 'pdf', + document: newDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_divided.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/download-node.ts b/src/js/workflow/nodes/download-node.ts new file mode 100644 index 0000000..de0b3ea --- /dev/null +++ b/src/js/workflow/nodes/download-node.ts @@ -0,0 +1,70 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; + +export class DownloadNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-download-simple'; + readonly description = 'Download as PDF or ZIP automatically'; + + constructor() { + super('Download'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF', true)); + this.addControl( + 'filename', + new ClassicPreset.InputControl('text', { + initial: 'output', + }) + ); + } + + async data( + inputs: Record + ): Promise> { + const allInputs = Object.values(inputs).flat(); + const allPdfs = extractAllPdfs(allInputs); + if (allPdfs.length === 0) + throw new Error('No PDFs connected to Download node'); + + const filenameControl = this.controls['filename'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const baseName = filenameControl?.value || 'output'; + + if (allPdfs.length === 1) { + const filename = baseName.toLowerCase().endsWith('.pdf') + ? baseName + : `${baseName}.pdf`; + const blob = new Blob([new Uint8Array(allPdfs[0].bytes)], { + type: 'application/pdf', + }); + downloadFile(blob, filename); + } else { + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + const usedNames = new Set(); + for (const pdf of allPdfs) { + let name = pdf.filename || 'document.pdf'; + if (!name.toLowerCase().endsWith('.pdf')) name += '.pdf'; + let uniqueName = name; + let counter = 1; + while (usedNames.has(uniqueName)) { + uniqueName = name.replace(/\.pdf$/i, `_${counter}.pdf`); + counter++; + } + usedNames.add(uniqueName); + zip.file(uniqueName, pdf.bytes); + } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + const zipFilename = baseName.toLowerCase().endsWith('.zip') + ? baseName + : `${baseName}.zip`; + downloadFile(zipBlob, zipFilename); + } + + return {}; + } +} diff --git a/src/js/workflow/nodes/download-pdf-node.ts b/src/js/workflow/nodes/download-pdf-node.ts new file mode 100644 index 0000000..f6c5eaa --- /dev/null +++ b/src/js/workflow/nodes/download-pdf-node.ts @@ -0,0 +1,62 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; + +export class DownloadPDFNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-download-simple'; + readonly description = 'Download the resulting PDF'; + + constructor() { + super('Download PDF'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addControl( + 'filename', + new ClassicPreset.InputControl('text', { + initial: 'output.pdf', + }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Download PDF'); + const allPdfs = extractAllPdfs(pdfInputs); + + const filenameControl = this.controls['filename'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const filename = filenameControl?.value || 'output.pdf'; + + if (allPdfs.length === 1) { + const blob = new Blob([new Uint8Array(allPdfs[0].bytes)], { + type: 'application/pdf', + }); + downloadFile(blob, filename); + } else { + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + const usedNames = new Set(); + for (const pdf of allPdfs) { + let name = pdf.filename || 'document.pdf'; + if (!name.toLowerCase().endsWith('.pdf')) name += '.pdf'; + let uniqueName = name; + let counter = 1; + while (usedNames.has(uniqueName)) { + uniqueName = name.replace(/\.pdf$/i, `_${counter}.pdf`); + counter++; + } + usedNames.add(uniqueName); + zip.file(uniqueName, pdf.bytes); + } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, filename.replace(/\.pdf$/i, '.zip')); + } + + return {}; + } +} diff --git a/src/js/workflow/nodes/download-zip-node.ts b/src/js/workflow/nodes/download-zip-node.ts new file mode 100644 index 0000000..a40eb3f --- /dev/null +++ b/src/js/workflow/nodes/download-zip-node.ts @@ -0,0 +1,57 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; + +export class DownloadZipNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-archive'; + readonly description = 'Download multiple PDFs as ZIP'; + + constructor() { + super('Download ZIP'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDFs', true)); + this.addControl( + 'filename', + new ClassicPreset.InputControl('text', { initial: 'output.zip' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const allInputs = Object.values(inputs).flat(); + const allPdfs = extractAllPdfs(allInputs); + if (allPdfs.length === 0) + throw new Error('No PDFs connected to Download ZIP node'); + + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + const usedNames = new Set(); + for (const pdf of allPdfs) { + let name = pdf.filename || 'document.pdf'; + if (!name.toLowerCase().endsWith('.pdf')) name += '.pdf'; + let uniqueName = name; + let counter = 1; + while (usedNames.has(uniqueName)) { + uniqueName = name.replace(/\.pdf$/i, `_${counter}.pdf`); + counter++; + } + usedNames.add(uniqueName); + zip.file(uniqueName, pdf.bytes); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + + const filenameCtrl = this.controls['filename'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const filename = filenameCtrl?.value || 'output.zip'; + downloadFile(zipBlob, filename); + + return {}; + } +} diff --git a/src/js/workflow/nodes/edit-metadata-node.ts b/src/js/workflow/nodes/edit-metadata-node.ts new file mode 100644 index 0000000..36696a6 --- /dev/null +++ b/src/js/workflow/nodes/edit-metadata-node.ts @@ -0,0 +1,90 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; + +export class EditMetadataNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-file-code'; + readonly description = 'Edit PDF metadata'; + + constructor() { + super('Edit Metadata'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + this.addControl( + 'title', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'author', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'subject', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'keywords', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'creator', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'producer', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Edit Metadata'); + + const getText = (key: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || ''; + }; + + const title = getText('title'); + const author = getText('author'); + const subject = getText('subject'); + const keywords = getText('keywords'); + const creator = getText('creator'); + const producer = getText('producer'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pdfDoc = await PDFDocument.load(input.bytes); + + if (title) pdfDoc.setTitle(title); + if (author) pdfDoc.setAuthor(author); + if (subject) pdfDoc.setSubject(subject); + if (keywords) + pdfDoc.setKeywords( + keywords + .split(',') + .map((k) => k.trim()) + .filter(Boolean) + ); + if (creator) pdfDoc.setCreator(creator); + if (producer) pdfDoc.setProducer(producer); + pdfDoc.setModificationDate(new Date()); + + const pdfBytes = await pdfDoc.save(); + return { + type: 'pdf', + document: pdfDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_metadata.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/email-to-pdf-node.ts b/src/js/workflow/nodes/email-to-pdf-node.ts new file mode 100644 index 0000000..08635ae --- /dev/null +++ b/src/js/workflow/nodes/email-to-pdf-node.ts @@ -0,0 +1,114 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { parseEmailFile, renderEmailToHtml } from '../../logic/email-to-pdf.js'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class EmailToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-envelope'; + readonly description = 'Upload email files (.eml, .msg) and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('Email Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + this.addControl( + 'pageSize', + new ClassicPreset.InputControl('text', { initial: 'a4' }) + ); + this.addControl( + 'includeCcBcc', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'includeAttachments', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + const name = file.name.toLowerCase(); + if (name.endsWith('.eml') || name.endsWith('.msg')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} email files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No email files uploaded in Email Input node'); + + const pageSizeCtrl = this.controls['pageSize'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const ccBccCtrl = this.controls['includeCcBcc'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const attachCtrl = this.controls['includeAttachments'] as + | ClassicPreset.InputControl<'text'> + | undefined; + + const pageSize = (pageSizeCtrl?.value ?? 'a4') as 'a4' | 'letter' | 'legal'; + const includeCcBcc = (ccBccCtrl?.value ?? 'true') === 'true'; + const includeAttachments = (attachCtrl?.value ?? 'true') === 'true'; + + const pymupdf = await loadPyMuPDF(); + const results: PDFData[] = []; + for (const file of this.files) { + const email = await parseEmailFile(file); + const htmlContent = renderEmailToHtml(email, { + includeCcBcc, + includeAttachments, + pageSize, + }); + + const pdfBlob = await (pymupdf as any).htmlToPdf(htmlContent, { + pageSize, + margins: { top: 50, right: 50, bottom: 50, left: 50 }, + attachments: email.attachments + .filter((a) => a.content) + .map((a) => ({ + filename: a.filename, + content: a.content!, + })), + }); + + const bytes = new Uint8Array(await pdfBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.[^.]+$/, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/encrypt-node.ts b/src/js/workflow/nodes/encrypt-node.ts new file mode 100644 index 0000000..8f81fec --- /dev/null +++ b/src/js/workflow/nodes/encrypt-node.ts @@ -0,0 +1,106 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { initializeQpdf } from '../../utils/helpers.js'; + +export class EncryptNode extends BaseWorkflowNode { + readonly category = 'Secure PDF' as const; + readonly icon = 'ph-lock'; + readonly description = 'Encrypt PDF with password'; + + constructor() { + super('Encrypt'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Encrypted PDF')); + this.addControl( + 'userPassword', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'ownerPassword', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Encrypt'); + + const getText = (key: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || ''; + }; + + const userPassword = getText('userPassword'); + const ownerPassword = getText('ownerPassword') || userPassword; + if (!userPassword) + throw new Error('User password is required for encryption'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const qpdf = await initializeQpdf(); + const uid = `${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; + const inputPath = `/tmp/input_encrypt_${uid}.pdf`; + const outputPath = `/tmp/output_encrypt_${uid}.pdf`; + + let encryptedData: Uint8Array; + try { + qpdf.FS.writeFile(inputPath, input.bytes); + + const args = [ + inputPath, + '--encrypt', + userPassword, + ownerPassword, + '256', + ]; + if (ownerPassword !== userPassword) { + args.push( + '--modify=none', + '--extract=n', + '--print=none', + '--accessibility=n', + '--annotate=n', + '--assemble=n', + '--form=n', + '--modify-other=n' + ); + } + args.push('--', outputPath); + qpdf.callMain(args); + + encryptedData = qpdf.FS.readFile(outputPath, { encoding: 'binary' }); + } finally { + try { + qpdf.FS.unlink(inputPath); + } catch { + /* cleanup */ + } + try { + qpdf.FS.unlink(outputPath); + } catch { + /* cleanup */ + } + } + + const resultBytes = new Uint8Array(encryptedData); + const document = await PDFDocument.load(resultBytes, { + ignoreEncryption: true, + }); + + return { + type: 'pdf', + document, + bytes: resultBytes, + filename: input.filename.replace(/\.pdf$/i, '_encrypted.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/epub-to-pdf-node.ts b/src/js/workflow/nodes/epub-to-pdf-node.ts new file mode 100644 index 0000000..18f7335 --- /dev/null +++ b/src/js/workflow/nodes/epub-to-pdf-node.ts @@ -0,0 +1,69 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class EpubToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-book-open-text'; + readonly description = 'Upload EPUB and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('EPUB Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.endsWith('.epub')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} EPUB files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No EPUB files uploaded in EPUB Input node'); + + const pymupdf = await loadPyMuPDF(); + const results: PDFData[] = []; + for (const file of this.files) { + const blob = await pymupdf.convertToPdf(file, { filetype: 'epub' }); + const bytes = new Uint8Array(await blob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.epub$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/excel-to-pdf-node.ts b/src/js/workflow/nodes/excel-to-pdf-node.ts new file mode 100644 index 0000000..8fb2479 --- /dev/null +++ b/src/js/workflow/nodes/excel-to-pdf-node.ts @@ -0,0 +1,77 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js'; + +export class ExcelToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-microsoft-excel-logo'; + readonly description = 'Upload Excel spreadsheet and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('Excel Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + const ext = file.name.toLowerCase(); + if ( + ext.endsWith('.xlsx') || + ext.endsWith('.xls') || + ext.endsWith('.ods') || + ext.endsWith('.csv') + ) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} spreadsheets`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No spreadsheets uploaded in Excel Input node'); + + const converter = getLibreOfficeConverter(); + await converter.initialize(); + + const results: PDFData[] = []; + for (const file of this.files) { + const resultBlob = await converter.convertToPdf(file); + const bytes = new Uint8Array(await resultBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.[^.]+$/, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/extract-images-node.ts b/src/js/workflow/nodes/extract-images-node.ts new file mode 100644 index 0000000..d2cabe9 --- /dev/null +++ b/src/js/workflow/nodes/extract-images-node.ts @@ -0,0 +1,70 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class ExtractImagesNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-download-simple'; + readonly description = 'Extract all images from PDF'; + + constructor() { + super('Extract Images'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Extract Images'); + const allPdfs = extractAllPdfs(pdfInputs); + const pymupdf = await loadPyMuPDF(); + + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + let totalImages = 0; + + for (const pdf of allPdfs) { + const blob = new Blob([new Uint8Array(pdf.bytes)], { + type: 'application/pdf', + }); + const doc = await pymupdf.open(blob); + const pageCount = doc.pageCount; + const prefix = + allPdfs.length > 1 ? pdf.filename.replace(/\.pdf$/i, '') + '/' : ''; + + for (let pageIdx = 0; pageIdx < pageCount; pageIdx++) { + const page = doc.getPage(pageIdx); + const images = page.getImages(); + + for (const imgInfo of images) { + try { + const imgData = page.extractImage(imgInfo.xref); + if (imgData && imgData.data) { + totalImages++; + zip.file( + `${prefix}image_${totalImages}.${imgData.ext || 'png'}`, + imgData.data + ); + } + } catch { + continue; + } + } + } + doc.close(); + } + + if (totalImages === 0) { + throw new Error('No images found in any of the connected PDFs'); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'extracted_images.zip'); + + return {}; + } +} diff --git a/src/js/workflow/nodes/extract-pages-node.ts b/src/js/workflow/nodes/extract-pages-node.ts new file mode 100644 index 0000000..9c38697 --- /dev/null +++ b/src/js/workflow/nodes/extract-pages-node.ts @@ -0,0 +1,68 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData, MultiPDFData } from '../types'; +import { requirePdfInput, extractAllPdfs } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { parsePageRange } from '../../utils/pdf-operations'; + +export class ExtractPagesNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-squares-four'; + readonly description = 'Extract pages as separate PDFs'; + + constructor() { + super('Extract Pages'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput( + 'pdf', + new ClassicPreset.Output(pdfSocket, 'Extracted PDFs') + ); + this.addControl( + 'pages', + new ClassicPreset.InputControl('text', { initial: '1,2,3' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Extract Pages'); + const allPdfs = extractAllPdfs(pdfInputs); + + const pagesCtrl = this.controls['pages'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const rangeStr = pagesCtrl?.value || '1'; + + const allItems = []; + for (const input of allPdfs) { + const srcDoc = await PDFDocument.load(input.bytes); + const totalPages = srcDoc.getPageCount(); + const indices = parsePageRange(rangeStr, totalPages); + + for (const idx of indices) { + const newPdf = await PDFDocument.create(); + const [copiedPage] = await newPdf.copyPages(srcDoc, [idx]); + newPdf.addPage(copiedPage); + const pdfBytes = await newPdf.save(); + allItems.push({ + type: 'pdf' as const, + document: newPdf, + bytes: new Uint8Array(pdfBytes), + filename: `page_${idx + 1}.pdf`, + }); + } + } + + if (allItems.length === 1) { + return { pdf: allItems[0] }; + } + + const result: MultiPDFData = { + type: 'multi-pdf', + items: allItems, + }; + return { pdf: result }; + } +} diff --git a/src/js/workflow/nodes/fb2-to-pdf-node.ts b/src/js/workflow/nodes/fb2-to-pdf-node.ts new file mode 100644 index 0000000..23e7099 --- /dev/null +++ b/src/js/workflow/nodes/fb2-to-pdf-node.ts @@ -0,0 +1,69 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class Fb2ToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-book-bookmark'; + readonly description = 'Upload FB2 e-books and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('FB2 Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.toLowerCase().endsWith('.fb2')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} FB2 files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No FB2 files uploaded in FB2 Input node'); + + const pymupdf = await loadPyMuPDF(); + const results: PDFData[] = []; + for (const file of this.files) { + const blob = await pymupdf.convertToPdf(file, { filetype: 'fb2' }); + const bytes = new Uint8Array(await blob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.fb2$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/fix-page-size-node.ts b/src/js/workflow/nodes/fix-page-size-node.ts new file mode 100644 index 0000000..7ae8581 --- /dev/null +++ b/src/js/workflow/nodes/fix-page-size-node.ts @@ -0,0 +1,104 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { fixPageSize } from '../../utils/pdf-operations'; +import { PDFDocument } from 'pdf-lib'; +import { hexToRgb } from '../../utils/helpers.js'; + +export class FixPageSizeNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-frame-corners'; + readonly description = 'Standardize all pages to a target size'; + + constructor() { + super('Fix Page Size'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput( + 'pdf', + new ClassicPreset.Output(pdfSocket, 'Standardized PDF') + ); + this.addControl( + 'targetSize', + new ClassicPreset.InputControl('text', { initial: 'A4' }) + ); + this.addControl( + 'orientation', + new ClassicPreset.InputControl('text', { initial: 'auto' }) + ); + this.addControl( + 'scalingMode', + new ClassicPreset.InputControl('text', { initial: 'fit' }) + ); + this.addControl( + 'backgroundColor', + new ClassicPreset.InputControl('text', { initial: '#ffffff' }) + ); + this.addControl( + 'customWidth', + new ClassicPreset.InputControl('number', { initial: 210 }) + ); + this.addControl( + 'customHeight', + new ClassicPreset.InputControl('number', { initial: 297 }) + ); + this.addControl( + 'customUnits', + new ClassicPreset.InputControl('text', { initial: 'mm' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Fix Page Size'); + + const getText = (key: string, fallback: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return (ctrl?.value || fallback).trim(); + }; + + const getNum = (key: string, fallback: number) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'number'> + | undefined; + const value = ctrl?.value; + return Number.isFinite(value) ? (value as number) : fallback; + }; + + const targetSize = getText('targetSize', 'A4'); + const orientation = getText('orientation', 'auto'); + const scalingMode = getText('scalingMode', 'fit'); + const backgroundHex = getText('backgroundColor', '#ffffff'); + const customWidth = Math.max(1, getNum('customWidth', 210)); + const customHeight = Math.max(1, getNum('customHeight', 297)); + const customUnits = getText('customUnits', 'mm'); + const backgroundColor = hexToRgb(backgroundHex); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const resultBytes = await fixPageSize(input.bytes, { + targetSize, + orientation, + scalingMode, + backgroundColor, + customWidth, + customHeight, + customUnits, + }); + + const resultDoc = await PDFDocument.load(resultBytes); + + return { + type: 'pdf', + document: resultDoc, + bytes: resultBytes, + filename: input.filename.replace(/\.pdf$/i, '_standardized.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/flatten-node.ts b/src/js/workflow/nodes/flatten-node.ts new file mode 100644 index 0000000..1554626 --- /dev/null +++ b/src/js/workflow/nodes/flatten-node.ts @@ -0,0 +1,45 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; + +export class FlattenNode extends BaseWorkflowNode { + readonly category = 'Secure PDF' as const; + readonly icon = 'ph-stack'; + readonly description = 'Flatten forms and annotations'; + + constructor() { + super('Flatten'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Flattened PDF')); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Flatten'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pdfDoc = await PDFDocument.load(input.bytes); + + try { + const form = pdfDoc.getForm(); + form.flatten(); + } catch (err) { + console.error('Flatten form error (may have no forms):', err); + } + + const pdfBytes = await pdfDoc.save(); + return { + type: 'pdf', + document: pdfDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_flattened.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/font-to-outline-node.ts b/src/js/workflow/nodes/font-to-outline-node.ts new file mode 100644 index 0000000..026fa86 --- /dev/null +++ b/src/js/workflow/nodes/font-to-outline-node.ts @@ -0,0 +1,42 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { convertFontsToOutlines } from '../../utils/ghostscript-loader.js'; + +export class FontToOutlineNode extends BaseWorkflowNode { + readonly category = 'Optimize & Repair' as const; + readonly icon = 'ph-text-outdent'; + readonly description = 'Convert fonts to vector outlines'; + + constructor() { + super('Font to Outline'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Outlined PDF')); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Font to Outline'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const resultBytes = await convertFontsToOutlines( + new Uint8Array(input.bytes) + ); + const bytes = new Uint8Array(resultBytes); + const document = await PDFDocument.load(bytes); + + return { + type: 'pdf', + document, + bytes, + filename: input.filename.replace(/\.pdf$/i, '_outline.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/greyscale-node.ts b/src/js/workflow/nodes/greyscale-node.ts new file mode 100644 index 0000000..72a2d97 --- /dev/null +++ b/src/js/workflow/nodes/greyscale-node.ts @@ -0,0 +1,76 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { applyGreyscale } from '../../utils/image-effects'; +import { PDFDocument } from 'pdf-lib'; +import * as pdfjsLib from 'pdfjs-dist'; + +export class GreyscaleNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-palette'; + readonly description = 'Convert to greyscale'; + + constructor() { + super('Greyscale'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Greyscale PDF')); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Greyscale'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const newPdfDoc = await PDFDocument.create(); + const pdfjsDoc = await pdfjsLib.getDocument({ data: input.bytes }) + .promise; + + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + const page = await pdfjsDoc.getPage(i); + const viewport = page.getViewport({ scale: 2.0 }); + + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + const ctx = canvas.getContext('2d'); + if (!ctx) + throw new Error(`Failed to get canvas context for page ${i}`); + await page.render({ canvasContext: ctx, viewport, canvas }).promise; + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + applyGreyscale(imageData); + ctx.putImageData(imageData, 0, 0); + + const jpegBlob = await new Promise((resolve) => + canvas.toBlob(resolve, 'image/jpeg', 0.9) + ); + + if (!jpegBlob) throw new Error(`Failed to render page ${i} to image`); + const jpegBytes = await jpegBlob.arrayBuffer(); + const jpegImage = await newPdfDoc.embedJpg(jpegBytes); + const newPage = newPdfDoc.addPage([viewport.width, viewport.height]); + newPage.drawImage(jpegImage, { + x: 0, + y: 0, + width: viewport.width, + height: viewport.height, + }); + } + + if (newPdfDoc.getPageCount() === 0) + throw new Error('No pages were processed'); + const pdfBytes = await newPdfDoc.save(); + return { + type: 'pdf', + document: newPdfDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_greyscale.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/header-footer-node.ts b/src/js/workflow/nodes/header-footer-node.ts new file mode 100644 index 0000000..19a7654 --- /dev/null +++ b/src/js/workflow/nodes/header-footer-node.ts @@ -0,0 +1,172 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'; +import { hexToRgb } from '../../utils/helpers.js'; + +export class HeaderFooterNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-paragraph'; + readonly description = 'Add header and footer text'; + + constructor() { + super('Header & Footer'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput( + 'pdf', + new ClassicPreset.Output(pdfSocket, 'PDF with Header/Footer') + ); + this.addControl( + 'headerLeft', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'headerCenter', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'headerRight', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'footerLeft', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'footerCenter', + new ClassicPreset.InputControl('text', { + initial: 'Page {page} of {total}', + }) + ); + this.addControl( + 'footerRight', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'fontSize', + new ClassicPreset.InputControl('number', { initial: 10 }) + ); + this.addControl( + 'color', + new ClassicPreset.InputControl('text', { initial: '#000000' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Header & Footer'); + + const getText = (key: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || ''; + }; + const sizeCtrl = this.controls['fontSize'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const fontSize = Math.max(4, Math.min(72, sizeCtrl?.value ?? 10)); + + const colorHex = getText('color') || '#000000'; + const c = hexToRgb(colorHex); + const color = rgb(c.r, c.g, c.b); + + const fields = { + headerLeft: getText('headerLeft'), + headerCenter: getText('headerCenter'), + headerRight: getText('headerRight'), + footerLeft: getText('footerLeft'), + footerCenter: getText('footerCenter'), + footerRight: getText('footerRight'), + }; + + const hasAny = Object.values(fields).some((v) => v.length > 0); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + if (!hasAny) return input; + + const pdfDoc = await PDFDocument.load(input.bytes); + const font = await pdfDoc.embedFont(StandardFonts.Helvetica); + const pages = pdfDoc.getPages(); + const totalPages = pages.length; + const margin = 36; + + for (let i = 0; i < pages.length; i++) { + const page = pages[i]; + const { width, height } = page.getSize(); + const pageNum = i + 1; + + const processText = (tmpl: string) => + tmpl + .replace(/{page}/g, String(pageNum)) + .replace(/{total}/g, String(totalPages)); + + const drawOpts = { size: fontSize, font, color }; + + if (fields.headerLeft) { + page.drawText(processText(fields.headerLeft), { + ...drawOpts, + x: margin, + y: height - margin, + }); + } + if (fields.headerCenter) { + const text = processText(fields.headerCenter); + const tw = font.widthOfTextAtSize(text, fontSize); + page.drawText(text, { + ...drawOpts, + x: (width - tw) / 2, + y: height - margin, + }); + } + if (fields.headerRight) { + const text = processText(fields.headerRight); + const tw = font.widthOfTextAtSize(text, fontSize); + page.drawText(text, { + ...drawOpts, + x: width - margin - tw, + y: height - margin, + }); + } + if (fields.footerLeft) { + page.drawText(processText(fields.footerLeft), { + ...drawOpts, + x: margin, + y: margin - fontSize, + }); + } + if (fields.footerCenter) { + const text = processText(fields.footerCenter); + const tw = font.widthOfTextAtSize(text, fontSize); + page.drawText(text, { + ...drawOpts, + x: (width - tw) / 2, + y: margin - fontSize, + }); + } + if (fields.footerRight) { + const text = processText(fields.footerRight); + const tw = font.widthOfTextAtSize(text, fontSize); + page.drawText(text, { + ...drawOpts, + x: width - margin - tw, + y: margin - fontSize, + }); + } + } + + const pdfBytes = await pdfDoc.save(); + return { + type: 'pdf', + document: pdfDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_hf.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/image-input-node.ts b/src/js/workflow/nodes/image-input-node.ts new file mode 100644 index 0000000..5a72b4a --- /dev/null +++ b/src/js/workflow/nodes/image-input-node.ts @@ -0,0 +1,71 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class ImageInputNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-image'; + readonly description = 'Upload images and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('Image Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.type.startsWith('image/')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + + hasFile(): boolean { + return this.files.length > 0; + } + + getFileCount(): number { + return this.files.length; + } + + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} images`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) { + throw new Error('No images uploaded in Image Input node'); + } + + const pymupdf = await loadPyMuPDF(); + const pdfBlob = await pymupdf.imagesToPdf(this.files); + const bytes = new Uint8Array(await pdfBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + + const result: PDFData = { + type: 'pdf', + document, + bytes, + filename: 'images.pdf', + }; + + return { pdf: result }; + } +} diff --git a/src/js/workflow/nodes/invert-colors-node.ts b/src/js/workflow/nodes/invert-colors-node.ts new file mode 100644 index 0000000..e5a169b --- /dev/null +++ b/src/js/workflow/nodes/invert-colors-node.ts @@ -0,0 +1,83 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { applyInvertColors } from '../../utils/image-effects'; +import { PDFDocument } from 'pdf-lib'; +import * as pdfjsLib from 'pdfjs-dist'; + +export class InvertColorsNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-circle-half'; + readonly description = 'Invert all colors'; + + constructor() { + super('Invert Colors'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Inverted PDF')); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Invert Colors'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const newPdfDoc = await PDFDocument.create(); + const pdfjsDoc = await pdfjsLib.getDocument({ data: input.bytes }) + .promise; + + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + const page = await pdfjsDoc.getPage(i); + const viewport = page.getViewport({ scale: 1.5 }); + + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + const ctx = canvas.getContext('2d'); + if (!ctx) + throw new Error(`Failed to get canvas context for page ${i}`); + await page.render({ canvasContext: ctx, viewport, canvas }).promise; + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + applyInvertColors(imageData); + ctx.putImageData(imageData, 0, 0); + + const pngBytes = await new Promise((resolve, reject) => + canvas.toBlob((blob) => { + if (!blob) { + reject(new Error(`Failed to render page ${i}`)); + return; + } + const reader = new FileReader(); + reader.onload = () => + resolve(new Uint8Array(reader.result as ArrayBuffer)); + reader.onerror = () => + reject(new Error('Failed to read image data')); + reader.readAsArrayBuffer(blob); + }, 'image/png') + ); + + const image = await newPdfDoc.embedPng(pngBytes); + const newPage = newPdfDoc.addPage([image.width, image.height]); + newPage.drawImage(image, { + x: 0, + y: 0, + width: image.width, + height: image.height, + }); + } + + const pdfBytes = await newPdfDoc.save(); + return { + type: 'pdf', + document: newPdfDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_inverted.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/json-to-pdf-node.ts b/src/js/workflow/nodes/json-to-pdf-node.ts new file mode 100644 index 0000000..d59b0b7 --- /dev/null +++ b/src/js/workflow/nodes/json-to-pdf-node.ts @@ -0,0 +1,80 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class JsonToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-file-code'; + readonly description = 'Upload JSON files and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('JSON Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.toLowerCase().endsWith('.json')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} JSON files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No JSON files uploaded in JSON Input node'); + + const pymupdf = await loadPyMuPDF(); + const results: PDFData[] = []; + for (const file of this.files) { + const rawText = await file.text(); + let formatted: string; + try { + formatted = JSON.stringify(JSON.parse(rawText), null, 2); + } catch { + formatted = rawText; + } + const pdfBlob = await pymupdf.textToPdf(formatted, { + fontSize: 10, + fontName: 'cour', + pageSize: 'a4', + }); + const bytes = new Uint8Array(await pdfBlob.arrayBuffer()); + const pdfDoc = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document: pdfDoc, + bytes, + filename: file.name.replace(/\.json$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/linearize-node.ts b/src/js/workflow/nodes/linearize-node.ts new file mode 100644 index 0000000..c274622 --- /dev/null +++ b/src/js/workflow/nodes/linearize-node.ts @@ -0,0 +1,64 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { initializeQpdf } from '../../utils/helpers.js'; + +export class LinearizeNode extends BaseWorkflowNode { + readonly category = 'Optimize & Repair' as const; + readonly icon = 'ph-gauge'; + readonly description = 'Linearize PDF for fast web viewing'; + + constructor() { + super('Linearize'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput( + 'pdf', + new ClassicPreset.Output(pdfSocket, 'Linearized PDF') + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Linearize'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const qpdf = await initializeQpdf(); + const uid = `${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; + const inputPath = `/tmp/input_linearize_${uid}.pdf`; + const outputPath = `/tmp/output_linearize_${uid}.pdf`; + + let resultBytes: Uint8Array; + try { + qpdf.FS.writeFile(inputPath, input.bytes); + qpdf.callMain([inputPath, '--linearize', outputPath]); + resultBytes = new Uint8Array(qpdf.FS.readFile(outputPath)); + } finally { + try { + qpdf.FS.unlink(inputPath); + } catch { + /* cleanup */ + } + try { + qpdf.FS.unlink(outputPath); + } catch { + /* cleanup */ + } + } + + const document = await PDFDocument.load(resultBytes); + + return { + type: 'pdf', + document, + bytes: resultBytes, + filename: input.filename.replace(/\.pdf$/i, '_linearized.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/markdown-to-pdf-node.ts b/src/js/workflow/nodes/markdown-to-pdf-node.ts new file mode 100644 index 0000000..8be87c8 --- /dev/null +++ b/src/js/workflow/nodes/markdown-to-pdf-node.ts @@ -0,0 +1,84 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; +import MarkdownIt from 'markdown-it'; + +const md = new MarkdownIt({ html: true, linkify: true, typographer: true }); + +export class MarkdownToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-markdown-logo'; + readonly description = 'Upload Markdown files and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('Markdown Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + const name = file.name.toLowerCase(); + if (name.endsWith('.md') || name.endsWith('.markdown')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} Markdown files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No Markdown files uploaded in Markdown Input node'); + + const pymupdf = await loadPyMuPDF(); + const results: PDFData[] = []; + for (const file of this.files) { + const textContent = await file.text(); + const htmlContent = `${md.render(textContent)}`; + const pdfBlob = await (pymupdf as any).htmlToPdf(htmlContent, { + pageSize: 'a4', + }); + const bytes = new Uint8Array(await pdfBlob.arrayBuffer()); + const pdfDoc = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document: pdfDoc, + bytes, + filename: file.name.replace(/\.(md|markdown)$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/merge-node.ts b/src/js/workflow/nodes/merge-node.ts new file mode 100644 index 0000000..948d78b --- /dev/null +++ b/src/js/workflow/nodes/merge-node.ts @@ -0,0 +1,40 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { extractAllPdfs } from '../types'; +import { mergePdfs } from '../../utils/pdf-operations'; +import { PDFDocument } from 'pdf-lib'; + +export class MergeNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-browsers'; + readonly description = 'Combine multiple PDFs into one'; + + constructor() { + super('Merge PDFs'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDFs', true)); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Merged PDF')); + } + + async data( + inputs: Record + ): Promise> { + const allInputs = Object.values(inputs).flat(); + const allPdfs = extractAllPdfs(allInputs); + if (allPdfs.length === 0) + throw new Error('No PDFs connected to Merge node'); + + const mergedBytes = await mergePdfs(allPdfs.map((p) => p.bytes)); + const mergedDoc = await PDFDocument.load(mergedBytes); + + return { + pdf: { + type: 'pdf', + document: mergedDoc, + bytes: mergedBytes, + filename: 'merged.pdf', + }, + }; + } +} diff --git a/src/js/workflow/nodes/mobi-to-pdf-node.ts b/src/js/workflow/nodes/mobi-to-pdf-node.ts new file mode 100644 index 0000000..332989d --- /dev/null +++ b/src/js/workflow/nodes/mobi-to-pdf-node.ts @@ -0,0 +1,69 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class MobiToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-book-open-text'; + readonly description = 'Upload MOBI e-books and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('MOBI Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.toLowerCase().endsWith('.mobi')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} MOBI files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No MOBI files uploaded in MOBI Input node'); + + const pymupdf = await loadPyMuPDF(); + const results: PDFData[] = []; + for (const file of this.files) { + const blob = await pymupdf.convertToPdf(file, { filetype: 'mobi' }); + const bytes = new Uint8Array(await blob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.mobi$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/n-up-node.ts b/src/js/workflow/nodes/n-up-node.ts new file mode 100644 index 0000000..f1358fa --- /dev/null +++ b/src/js/workflow/nodes/n-up-node.ts @@ -0,0 +1,138 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument, rgb } from 'pdf-lib'; +import { hexToRgb } from '../../utils/helpers.js'; + +export class NUpNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-squares-four'; + readonly description = 'Arrange multiple pages per sheet'; + + constructor() { + super('N-Up'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'N-Up PDF')); + this.addControl( + 'pagesPerSheet', + new ClassicPreset.InputControl('number', { initial: 4 }) + ); + this.addControl( + 'orientation', + new ClassicPreset.InputControl('text', { initial: 'auto' }) + ); + this.addControl( + 'margins', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'border', + new ClassicPreset.InputControl('text', { initial: 'false' }) + ); + this.addControl( + 'borderColor', + new ClassicPreset.InputControl('text', { initial: '#000000' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'N-Up'); + + const nCtrl = this.controls['pagesPerSheet'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const n = [2, 4, 9, 16].includes(nCtrl?.value ?? 4) + ? (nCtrl?.value ?? 4) + : 4; + + const getText = (key: string, fallback: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || fallback; + }; + + const orientation = getText('orientation', 'auto'); + const useMargins = getText('margins', 'true') === 'true'; + const addBorder = getText('border', 'false') === 'true'; + const borderHex = getText('borderColor', '#000000'); + const bc = hexToRgb(borderHex); + const borderColor = rgb(bc.r, bc.g, bc.b); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const gridDims: Record = { + 2: [2, 1], + 4: [2, 2], + 9: [3, 3], + 16: [4, 4], + }; + const [cols, rows] = gridDims[n]; + + const srcDoc = await PDFDocument.load(input.bytes); + const newDoc = await PDFDocument.create(); + const pageCount = srcDoc.getPageCount(); + const firstPage = srcDoc.getPages()[0]; + let { width: pageWidth, height: pageHeight } = firstPage.getSize(); + + if (orientation === 'landscape' && pageWidth < pageHeight) { + [pageWidth, pageHeight] = [pageHeight, pageWidth]; + } else if (orientation === 'portrait' && pageWidth > pageHeight) { + [pageWidth, pageHeight] = [pageHeight, pageWidth]; + } + + const margin = useMargins ? 36 : 0; + const gutter = useMargins ? 5 : 0; + const usableWidth = pageWidth - margin * 2; + const usableHeight = pageHeight - margin * 2; + const cellWidth = (usableWidth - gutter * (cols - 1)) / cols; + const cellHeight = (usableHeight - gutter * (rows - 1)) / rows; + + for (let start = 0; start < pageCount; start += n) { + const outputPage = newDoc.addPage([pageWidth, pageHeight]); + const chunk = Math.min(n, pageCount - start); + + for (let j = 0; j < chunk; j++) { + const srcPage = srcDoc.getPages()[start + j]; + const embedded = await newDoc.embedPage(srcPage); + const col = j % cols; + const row = Math.floor(j / cols); + const x = margin + col * (cellWidth + gutter); + const y = + pageHeight - margin - (row + 1) * cellHeight - row * gutter; + + outputPage.drawPage(embedded, { + x, + y, + width: cellWidth, + height: cellHeight, + }); + + if (addBorder) { + outputPage.drawRectangle({ + x, + y, + width: cellWidth, + height: cellHeight, + borderColor, + borderWidth: 1, + }); + } + } + } + + const pdfBytes = await newDoc.save(); + return { + type: 'pdf', + document: newDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_nup.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/ocr-node.ts b/src/js/workflow/nodes/ocr-node.ts new file mode 100644 index 0000000..03bec08 --- /dev/null +++ b/src/js/workflow/nodes/ocr-node.ts @@ -0,0 +1,84 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { performOcr } from '../../utils/ocr'; + +export class OCRNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-barcode'; + readonly description = 'Add searchable text layer via OCR'; + + constructor() { + super('OCR'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput( + 'pdf', + new ClassicPreset.Output(pdfSocket, 'Searchable PDF') + ); + this.addControl( + 'language', + new ClassicPreset.InputControl('text', { initial: 'eng' }) + ); + this.addControl( + 'resolution', + new ClassicPreset.InputControl('text', { initial: '3.0' }) + ); + this.addControl( + 'binarize', + new ClassicPreset.InputControl('text', { initial: 'false' }) + ); + this.addControl( + 'whitelist', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'OCR'); + + const langCtrl = this.controls['language'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const language = langCtrl?.value || 'eng'; + + const resCtrl = this.controls['resolution'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const resolution = Math.max( + 1.0, + Math.min(4.0, parseFloat(resCtrl?.value ?? '3.0')) + ); + + const binarizeCtrl = this.controls['binarize'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const binarize = (binarizeCtrl?.value ?? 'false') === 'true'; + + const whitelistCtrl = this.controls['whitelist'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const whitelist = whitelistCtrl?.value || ''; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const result = await performOcr(input.bytes, { + language, + resolution, + binarize, + whitelist, + }); + + return { + type: 'pdf', + document: result.pdfDoc, + bytes: result.pdfBytes, + filename: input.filename.replace(/\.pdf$/i, '_ocr.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/odg-to-pdf-node.ts b/src/js/workflow/nodes/odg-to-pdf-node.ts new file mode 100644 index 0000000..50d4df3 --- /dev/null +++ b/src/js/workflow/nodes/odg-to-pdf-node.ts @@ -0,0 +1,71 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js'; + +export class OdgToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-image'; + readonly description = 'Upload OpenDocument Graphics and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('ODG Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.toLowerCase().endsWith('.odg')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} ODG files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No ODG files uploaded in ODG Input node'); + + const converter = getLibreOfficeConverter(); + await converter.initialize(); + + const results: PDFData[] = []; + for (const file of this.files) { + const resultBlob = await converter.convertToPdf(file); + const bytes = new Uint8Array(await resultBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.odg$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/page-numbers-node.ts b/src/js/workflow/nodes/page-numbers-node.ts new file mode 100644 index 0000000..280478c --- /dev/null +++ b/src/js/workflow/nodes/page-numbers-node.ts @@ -0,0 +1,82 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { + addPageNumbers, + type PageNumberPosition, + type PageNumberFormat, +} from '../../utils/pdf-operations'; +import { PDFDocument } from 'pdf-lib'; +import { hexToRgb } from '../../utils/helpers.js'; + +export class PageNumbersNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-list-numbers'; + readonly description = 'Add page numbers'; + + constructor() { + super('Page Numbers'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Numbered PDF')); + this.addControl( + 'position', + new ClassicPreset.InputControl('text', { initial: 'bottom-center' }) + ); + this.addControl( + 'fontSize', + new ClassicPreset.InputControl('number', { initial: 12 }) + ); + this.addControl( + 'numberFormat', + new ClassicPreset.InputControl('text', { initial: 'simple' }) + ); + this.addControl( + 'color', + new ClassicPreset.InputControl('text', { initial: '#000000' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Page Numbers'); + + const getText = (key: string, fallback: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || fallback; + }; + const sizeCtrl = this.controls['fontSize'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const fontSize = Math.max(4, Math.min(72, sizeCtrl?.value ?? 12)); + + const position = getText('position', 'bottom-center') as PageNumberPosition; + const format = getText('numberFormat', 'simple') as PageNumberFormat; + const colorHex = getText('color', '#000000'); + const c = hexToRgb(colorHex); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const resultBytes = await addPageNumbers(input.bytes, { + position, + fontSize, + format, + color: { r: c.r, g: c.g, b: c.b }, + }); + + const resultDoc = await PDFDocument.load(resultBytes); + + return { + type: 'pdf', + document: resultDoc, + bytes: new Uint8Array(resultBytes), + filename: input.filename.replace(/\.pdf$/i, '_numbered.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/pages-to-pdf-node.ts b/src/js/workflow/nodes/pages-to-pdf-node.ts new file mode 100644 index 0000000..eff335e --- /dev/null +++ b/src/js/workflow/nodes/pages-to-pdf-node.ts @@ -0,0 +1,71 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js'; + +export class PagesToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-file-text'; + readonly description = 'Upload Apple Pages documents and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('Pages Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.toLowerCase().endsWith('.pages')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} Pages files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No Pages files uploaded in Pages Input node'); + + const converter = getLibreOfficeConverter(); + await converter.initialize(); + + const results: PDFData[] = []; + for (const file of this.files) { + const resultBlob = await converter.convertToPdf(file); + const bytes = new Uint8Array(await resultBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.pages$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/pdf-input-node.ts b/src/js/workflow/nodes/pdf-input-node.ts new file mode 100644 index 0000000..d041479 --- /dev/null +++ b/src/js/workflow/nodes/pdf-input-node.ts @@ -0,0 +1,157 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { readFileAsArrayBuffer, initializeQpdf } from '../../utils/helpers.js'; + +export class EncryptedPDFError extends Error { + constructor(public readonly filename: string) { + super(`PDF "${filename}" is password-protected`); + this.name = 'EncryptedPDFError'; + } +} + +export class PDFInputNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-file-pdf'; + readonly description = 'Upload one or more PDF files'; + + private files: PDFData[] = []; + + constructor() { + super('PDF Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFile(file: File): Promise { + const arrayBuffer = await readFileAsArrayBuffer(file); + const bytes = new Uint8Array(arrayBuffer as ArrayBuffer); + + let isEncrypted = false; + try { + await PDFDocument.load(bytes, { throwOnInvalidObject: false }); + } catch { + isEncrypted = true; + } + + if (isEncrypted) { + try { + await PDFDocument.load(bytes, { + ignoreEncryption: true, + throwOnInvalidObject: false, + }); + } catch { + throw new Error( + `Failed to load "${file.name}" - file may be corrupted` + ); + } + throw new EncryptedPDFError(file.name); + } + + const document = await PDFDocument.load(bytes, { + throwOnInvalidObject: false, + }); + this.files.push({ + type: 'pdf', + document, + bytes, + filename: file.name, + }); + } + + async addDecryptedFile(file: File, password: string): Promise { + const arrayBuffer = await readFileAsArrayBuffer(file); + const bytes = new Uint8Array(arrayBuffer as ArrayBuffer); + const qpdf = await initializeQpdf(); + const uid = `${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; + const inputPath = `/tmp/input_decrypt_${uid}.pdf`; + const outputPath = `/tmp/output_decrypt_${uid}.pdf`; + + try { + qpdf.FS.writeFile(inputPath, bytes); + qpdf.callMain([ + inputPath, + '--password=' + password, + '--decrypt', + outputPath, + ]); + const decryptedData = qpdf.FS.readFile(outputPath, { + encoding: 'binary', + }); + const decryptedBytes = new Uint8Array(decryptedData); + const document = await PDFDocument.load(decryptedBytes, { + throwOnInvalidObject: false, + }); + this.files.push({ + type: 'pdf', + document, + bytes: decryptedBytes, + filename: file.name, + }); + } finally { + try { + qpdf.FS.unlink(inputPath); + } catch { + /* cleanup */ + } + try { + qpdf.FS.unlink(outputPath); + } catch { + /* cleanup */ + } + } + } + + async setFile(file: File): Promise { + this.files = []; + await this.addFile(file); + } + + async setFiles(fileList: File[]): Promise { + this.files = []; + for (const file of fileList) { + await this.addFile(file); + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + + hasFile(): boolean { + return this.files.length > 0; + } + + getFileCount(): number { + return this.files.length; + } + + getFilenames(): string[] { + return this.files.map((f) => f.filename); + } + + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].filename; + return `${this.files.length} files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) { + throw new Error('No PDF files uploaded in PDF Input node'); + } + + if (this.files.length === 1) { + return { pdf: this.files[0] }; + } + + const multiData: MultiPDFData = { + type: 'multi-pdf', + items: this.files, + }; + return { pdf: multiData }; + } +} diff --git a/src/js/workflow/nodes/pdf-to-csv-node.ts b/src/js/workflow/nodes/pdf-to-csv-node.ts new file mode 100644 index 0000000..7c9f00c --- /dev/null +++ b/src/js/workflow/nodes/pdf-to-csv-node.ts @@ -0,0 +1,88 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +function tableToCsv(rows: (string | null)[][]): string { + return rows + .map((row) => + row + .map((cell) => { + const str = String(cell ?? ''); + if (str.includes(',') || str.includes('"') || str.includes('\n')) { + return `"${str.replace(/"/g, '""')}"`; + } + return str; + }) + .join(',') + ) + .join('\n'); +} + +export class PdfToCsvNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-file-csv'; + readonly description = 'Extract tables from PDF to CSV'; + + constructor() { + super('PDF to CSV'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + } + + private async extractTables(bytes: Uint8Array): Promise<(string | null)[][]> { + const pymupdf = await loadPyMuPDF(); + const blob = new Blob([new Uint8Array(bytes)], { type: 'application/pdf' }); + const doc = await pymupdf.open(blob); + const allRows: (string | null)[][] = []; + + try { + const pageCount = doc.pageCount; + for (let i = 0; i < pageCount; i++) { + const page = doc.getPage(i); + const tables = page.findTables(); + tables.forEach((table: any) => { + allRows.push(...table.rows); + }); + } + } finally { + doc.close(); + } + + return allRows; + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'PDF to CSV'); + const allPdfs = extractAllPdfs(pdfInputs); + + if (allPdfs.length === 1) { + const allRows = await this.extractTables(allPdfs[0].bytes); + if (allRows.length === 0) { + throw new Error('No tables found in PDF'); + } + const csv = tableToCsv(allRows); + const csvBlob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + const name = allPdfs[0].filename.replace(/\.pdf$/i, '') + '.csv'; + downloadFile(csvBlob, name); + } else { + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + for (const pdf of allPdfs) { + const allRows = await this.extractTables(pdf.bytes); + if (allRows.length === 0) continue; + const csv = tableToCsv(allRows); + const name = pdf.filename.replace(/\.pdf$/i, '') + '.csv'; + zip.file(name, csv); + } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'csv_files.zip'); + } + + return {}; + } +} diff --git a/src/js/workflow/nodes/pdf-to-docx-node.ts b/src/js/workflow/nodes/pdf-to-docx-node.ts new file mode 100644 index 0000000..501aa75 --- /dev/null +++ b/src/js/workflow/nodes/pdf-to-docx-node.ts @@ -0,0 +1,51 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class PdfToDocxNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-microsoft-word-logo'; + readonly description = 'Convert PDF to Word document'; + + constructor() { + super('PDF to Word'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'PDF to Word'); + const allPdfs = extractAllPdfs(pdfInputs); + const pymupdf = await loadPyMuPDF(); + + if (allPdfs.length === 1) { + const blob = new Blob([new Uint8Array(allPdfs[0].bytes)], { + type: 'application/pdf', + }); + const docxBlob = await pymupdf.pdfToDocx(blob); + const name = allPdfs[0].filename.replace(/\.pdf$/i, '') + '.docx'; + downloadFile(docxBlob, name); + } else { + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + for (const pdf of allPdfs) { + const blob = new Blob([new Uint8Array(pdf.bytes)], { + type: 'application/pdf', + }); + const docxBlob = await pymupdf.pdfToDocx(blob); + const name = pdf.filename.replace(/\.pdf$/i, '') + '.docx'; + const arrayBuffer = await docxBlob.arrayBuffer(); + zip.file(name, arrayBuffer); + } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'docx_files.zip'); + } + + return {}; + } +} diff --git a/src/js/workflow/nodes/pdf-to-images-node.ts b/src/js/workflow/nodes/pdf-to-images-node.ts new file mode 100644 index 0000000..184c39a --- /dev/null +++ b/src/js/workflow/nodes/pdf-to-images-node.ts @@ -0,0 +1,150 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData, PDFData } from '../types'; +import { requirePdfInput, extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; +import * as pdfjsLib from 'pdfjs-dist'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class PdfToImagesNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-file-image'; + readonly description = 'Convert PDF pages to images (ZIP)'; + + constructor() { + super('PDF to Images'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addControl( + 'format', + new ClassicPreset.InputControl('text', { initial: 'jpg' }) + ); + this.addControl( + 'quality', + new ClassicPreset.InputControl('number', { initial: 90 }) + ); + this.addControl( + 'dpi', + new ClassicPreset.InputControl('number', { initial: 150 }) + ); + } + + private async addPdfPages( + pdf: PDFData, + zip: any, + format: string, + mimeType: string, + quality: number, + scale: number, + prefix: string + ): Promise { + const pdfjsDoc = await pdfjsLib.getDocument({ data: pdf.bytes }).promise; + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + const page = await pdfjsDoc.getPage(i); + const viewport = page.getViewport({ scale }); + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + const ctx = canvas.getContext('2d')!; + await page.render({ canvasContext: ctx, viewport, canvas }).promise; + const blob = await new Promise((resolve) => + canvas.toBlob(resolve, mimeType, quality) + ); + // Release canvas memory + canvas.width = 0; + canvas.height = 0; + if (blob) { + zip.file(`${prefix}page_${i}.${format}`, blob); + } + } + } + + private async addPdfPagesAsSvg(allPdfs: PDFData[], zip: any): Promise { + const pymupdf = await loadPyMuPDF(); + for (const pdf of allPdfs) { + const blob = new Blob([new Uint8Array(pdf.bytes)], { + type: 'application/pdf', + }); + const doc = await pymupdf.open(blob); + try { + const pageCount = doc.pageCount; + const prefix = + allPdfs.length > 1 ? pdf.filename.replace(/\.pdf$/i, '') + '/' : ''; + for (let i = 0; i < pageCount; i++) { + const page = doc.getPage(i); + const svg = page.toSvg(); + zip.file(`${prefix}page_${i + 1}.svg`, svg); + } + } finally { + doc.close(); + } + } + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'PDF to Images'); + const allPdfs = extractAllPdfs(pdfInputs); + + const fmtCtrl = this.controls['format'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const format = ['jpg', 'png', 'webp', 'svg'].includes(fmtCtrl?.value ?? '') + ? (fmtCtrl?.value ?? 'jpg') + : 'jpg'; + + const qualCtrl = this.controls['quality'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const quality = Math.max(10, Math.min(100, qualCtrl?.value ?? 90)) / 100; + + const dpiCtrl = this.controls['dpi'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const dpi = Math.max(72, Math.min(600, dpiCtrl?.value ?? 150)); + const scale = dpi / 72; + + const mimeMap: Record = { + jpg: 'image/jpeg', + png: 'image/png', + webp: 'image/webp', + }; + const mimeType = mimeMap[format] || 'image/jpeg'; + + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + if (format === 'svg') { + await this.addPdfPagesAsSvg(allPdfs, zip); + } else if (allPdfs.length === 1) { + await this.addPdfPages( + allPdfs[0], + zip, + format, + mimeType, + quality, + scale, + '' + ); + } else { + for (const pdf of allPdfs) { + const prefix = pdf.filename.replace(/\.pdf$/i, '') + '/'; + await this.addPdfPages( + pdf, + zip, + format, + mimeType, + quality, + scale, + prefix + ); + } + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'images.zip'); + + return {}; + } +} diff --git a/src/js/workflow/nodes/pdf-to-markdown-node.ts b/src/js/workflow/nodes/pdf-to-markdown-node.ts new file mode 100644 index 0000000..d590a7e --- /dev/null +++ b/src/js/workflow/nodes/pdf-to-markdown-node.ts @@ -0,0 +1,50 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class PdfToMarkdownNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-markdown-logo'; + readonly description = 'Extract text from PDF as Markdown'; + + constructor() { + super('PDF to Markdown'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + } + + private async extractMarkdown(bytes: Uint8Array): Promise { + const pymupdf = await loadPyMuPDF(); + const blob = new Blob([new Uint8Array(bytes)], { type: 'application/pdf' }); + return pymupdf.pdfToMarkdown(blob, { includeImages: false }); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'PDF to Markdown'); + const allPdfs = extractAllPdfs(pdfInputs); + + if (allPdfs.length === 1) { + const markdown = await this.extractMarkdown(allPdfs[0].bytes); + const blob = new Blob([markdown], { type: 'text/markdown' }); + const name = allPdfs[0].filename.replace(/\.pdf$/i, '') + '.md'; + downloadFile(blob, name); + } else { + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + for (const pdf of allPdfs) { + const markdown = await this.extractMarkdown(pdf.bytes); + const name = pdf.filename.replace(/\.pdf$/i, '') + '.md'; + zip.file(name, markdown); + } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'markdown_files.zip'); + } + + return {}; + } +} diff --git a/src/js/workflow/nodes/pdf-to-pdfa-node.ts b/src/js/workflow/nodes/pdf-to-pdfa-node.ts new file mode 100644 index 0000000..842e137 --- /dev/null +++ b/src/js/workflow/nodes/pdf-to-pdfa-node.ts @@ -0,0 +1,82 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadGhostscript } from '../../utils/ghostscript-dynamic-loader.js'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class PdfToPdfANode extends BaseWorkflowNode { + readonly category = 'Optimize & Repair' as const; + readonly icon = 'ph-archive'; + readonly description = 'Convert PDF to PDF/A for archiving'; + + constructor() { + super('PDF to PDF/A'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF/A')); + this.addControl( + 'level', + new ClassicPreset.InputControl('text', { initial: 'PDF/A-2b' }) + ); + this.addControl( + 'preFlatten', + new ClassicPreset.InputControl('text', { initial: 'false' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'PDF to PDF/A'); + + const getText = (key: string, fallback: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || fallback; + }; + + const level = getText('level', 'PDF/A-2b'); + const preFlatten = getText('preFlatten', 'false') === 'true'; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + let pdfBytes = input.bytes; + + if (preFlatten) { + const pymupdf = await loadPyMuPDF(); + const blob = new Blob([new Uint8Array(pdfBytes)], { + type: 'application/pdf', + }); + const flattenedBlob = await pymupdf.rasterizePdf(blob, { + dpi: 300, + format: 'png', + }); + pdfBytes = new Uint8Array(await flattenedBlob.arrayBuffer()); + } + + const gs = await loadGhostscript(); + const pdfBuffer = pdfBytes.buffer.slice( + pdfBytes.byteOffset, + pdfBytes.byteOffset + pdfBytes.byteLength + ); + const resultBuffer = await gs.convertToPDFA( + pdfBuffer as ArrayBuffer, + level + ); + + const bytes = new Uint8Array(resultBuffer); + const document = await PDFDocument.load(bytes); + + return { + type: 'pdf', + document, + bytes, + filename: input.filename.replace(/\.pdf$/i, '_pdfa.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/pdf-to-svg-node.ts b/src/js/workflow/nodes/pdf-to-svg-node.ts new file mode 100644 index 0000000..f71a215 --- /dev/null +++ b/src/js/workflow/nodes/pdf-to-svg-node.ts @@ -0,0 +1,53 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class PdfToSvgNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-file-code'; + readonly description = 'Convert PDF pages to SVG'; + + constructor() { + super('PDF to SVG'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'PDF to SVG'); + const allPdfs = extractAllPdfs(pdfInputs); + const pymupdf = await loadPyMuPDF(); + + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + + for (const pdf of allPdfs) { + const blob = new Blob([new Uint8Array(pdf.bytes)], { + type: 'application/pdf', + }); + const doc = await pymupdf.open(blob); + try { + const pageCount = doc.pageCount; + const prefix = + allPdfs.length > 1 ? pdf.filename.replace(/\.pdf$/i, '') + '/' : ''; + for (let i = 0; i < pageCount; i++) { + const page = doc.getPage(i); + const svg = page.toSvg(); + zip.file(`${prefix}page_${i + 1}.svg`, svg); + } + } finally { + doc.close(); + } + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'svg_pages.zip'); + + return {}; + } +} diff --git a/src/js/workflow/nodes/pdf-to-text-node.ts b/src/js/workflow/nodes/pdf-to-text-node.ts new file mode 100644 index 0000000..4ba0f43 --- /dev/null +++ b/src/js/workflow/nodes/pdf-to-text-node.ts @@ -0,0 +1,50 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class PdfToTextNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-text-aa'; + readonly description = 'Extract text from PDF'; + + constructor() { + super('PDF to Text'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + } + + private async extractText(bytes: Uint8Array): Promise { + const pymupdf = await loadPyMuPDF(); + const blob = new Blob([new Uint8Array(bytes)], { type: 'application/pdf' }); + return pymupdf.pdfToText(blob); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'PDF to Text'); + const allPdfs = extractAllPdfs(pdfInputs); + + if (allPdfs.length === 1) { + const text = await this.extractText(allPdfs[0].bytes); + const blob = new Blob([text], { type: 'text/plain' }); + const name = allPdfs[0].filename.replace(/\.pdf$/i, '') + '.txt'; + downloadFile(blob, name); + } else { + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + for (const pdf of allPdfs) { + const text = await this.extractText(pdf.bytes); + const name = pdf.filename.replace(/\.pdf$/i, '') + '.txt'; + zip.file(name, text); + } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'text_files.zip'); + } + + return {}; + } +} diff --git a/src/js/workflow/nodes/pdf-to-xlsx-node.ts b/src/js/workflow/nodes/pdf-to-xlsx-node.ts new file mode 100644 index 0000000..234c948 --- /dev/null +++ b/src/js/workflow/nodes/pdf-to-xlsx-node.ts @@ -0,0 +1,104 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, extractAllPdfs } from '../types'; +import { downloadFile } from '../../utils/helpers.js'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class PdfToXlsxNode extends BaseWorkflowNode { + readonly category = 'Output' as const; + readonly icon = 'ph-microsoft-excel-logo'; + readonly description = 'Extract tables from PDF to Excel'; + + constructor() { + super('PDF to Excel'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + } + + private async convertToXlsx( + bytes: Uint8Array, + filename: string + ): Promise<{ blob: Blob; name: string }> { + const pymupdf = await loadPyMuPDF(); + const blob = new Blob([new Uint8Array(bytes)], { type: 'application/pdf' }); + const doc = await pymupdf.open(blob); + + interface TableData { + page: number; + rows: (string | null)[][]; + } + + const allTables: TableData[] = []; + + try { + const pageCount = doc.pageCount; + for (let i = 0; i < pageCount; i++) { + const page = doc.getPage(i); + const tables = page.findTables(); + tables.forEach((table: any) => { + allTables.push({ page: i + 1, rows: table.rows }); + }); + } + } finally { + doc.close(); + } + + if (allTables.length === 0) { + throw new Error(`No tables found in ${filename}`); + } + + const XLSX = await import('xlsx'); + const wb = XLSX.utils.book_new(); + + if (allTables.length === 1) { + const ws = XLSX.utils.aoa_to_sheet(allTables[0].rows); + XLSX.utils.book_append_sheet(wb, ws, 'Table'); + } else { + allTables.forEach((table, idx) => { + const sheetName = `Table ${idx + 1} (Page ${table.page})`.substring( + 0, + 31 + ); + const ws = XLSX.utils.aoa_to_sheet(table.rows); + XLSX.utils.book_append_sheet(wb, ws, sheetName); + }); + } + + const xlsxBytes = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); + const xlsxBlob = new Blob([xlsxBytes], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }); + const name = filename.replace(/\.pdf$/i, '') + '.xlsx'; + return { blob: xlsxBlob, name }; + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'PDF to Excel'); + const allPdfs = extractAllPdfs(pdfInputs); + + if (allPdfs.length === 1) { + const { blob, name } = await this.convertToXlsx( + allPdfs[0].bytes, + allPdfs[0].filename + ); + downloadFile(blob, name); + } else { + const JSZip = (await import('jszip')).default; + const zip = new JSZip(); + for (const pdf of allPdfs) { + const { blob, name } = await this.convertToXlsx( + pdf.bytes, + pdf.filename + ); + zip.file(name, blob); + } + const zipBlob = await zip.generateAsync({ type: 'blob' }); + downloadFile(zipBlob, 'xlsx_files.zip'); + } + + return {}; + } +} diff --git a/src/js/workflow/nodes/posterize-node.ts b/src/js/workflow/nodes/posterize-node.ts new file mode 100644 index 0000000..90c2f07 --- /dev/null +++ b/src/js/workflow/nodes/posterize-node.ts @@ -0,0 +1,116 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import * as pdfjsLib from 'pdfjs-dist'; + +export class PosterizeNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-notepad'; + readonly description = 'Split pages into tile grid for poster printing'; + + constructor() { + super('Posterize'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput( + 'pdf', + new ClassicPreset.Output(pdfSocket, 'Posterized PDF') + ); + this.addControl( + 'rows', + new ClassicPreset.InputControl('number', { initial: 2 }) + ); + this.addControl( + 'cols', + new ClassicPreset.InputControl('number', { initial: 2 }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Posterize'); + + const rowsCtrl = this.controls['rows'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const colsCtrl = this.controls['cols'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const rows = Math.max(1, Math.min(8, rowsCtrl?.value ?? 2)); + const cols = Math.max(1, Math.min(8, colsCtrl?.value ?? 2)); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pdfjsDoc = await pdfjsLib.getDocument({ data: input.bytes }) + .promise; + const newDoc = await PDFDocument.create(); + + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + const page = await pdfjsDoc.getPage(i); + const viewport = page.getViewport({ scale: 2.0 }); + + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + const ctx = canvas.getContext('2d'); + if (!ctx) + throw new Error(`Failed to get canvas context for page ${i}`); + await page.render({ canvasContext: ctx, viewport, canvas }).promise; + + const tileW = viewport.width / cols; + const tileH = viewport.height / rows; + + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + const tileCanvas = document.createElement('canvas'); + tileCanvas.width = tileW; + tileCanvas.height = tileH; + const tileCtx = tileCanvas.getContext('2d'); + if (!tileCtx) + throw new Error('Failed to get tile canvas context'); + tileCtx.drawImage( + canvas, + c * tileW, + r * tileH, + tileW, + tileH, + 0, + 0, + tileW, + tileH + ); + + const pngBlob = await new Promise((resolve) => + tileCanvas.toBlob(resolve, 'image/png') + ); + if (!pngBlob) + throw new Error( + `Failed to render tile (row ${r}, col ${c}) of page ${i}` + ); + const pngBytes = new Uint8Array(await pngBlob.arrayBuffer()); + const pngImage = await newDoc.embedPng(pngBytes); + const newPage = newDoc.addPage([tileW / 2, tileH / 2]); + newPage.drawImage(pngImage, { + x: 0, + y: 0, + width: tileW / 2, + height: tileH / 2, + }); + } + } + } + + const pdfBytes = new Uint8Array(await newDoc.save()); + return { + type: 'pdf', + document: newDoc, + bytes: pdfBytes, + filename: input.filename.replace(/\.pdf$/i, '_posterized.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/powerpoint-to-pdf-node.ts b/src/js/workflow/nodes/powerpoint-to-pdf-node.ts new file mode 100644 index 0000000..7fb1f42 --- /dev/null +++ b/src/js/workflow/nodes/powerpoint-to-pdf-node.ts @@ -0,0 +1,76 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js'; + +export class PowerPointToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-microsoft-powerpoint-logo'; + readonly description = 'Upload PowerPoint and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('PowerPoint Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + const ext = file.name.toLowerCase(); + if ( + ext.endsWith('.ppt') || + ext.endsWith('.pptx') || + ext.endsWith('.odp') + ) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} presentations`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No presentations uploaded in PowerPoint Input node'); + + const converter = getLibreOfficeConverter(); + await converter.initialize(); + + const results: PDFData[] = []; + for (const file of this.files) { + const resultBlob = await converter.convertToPdf(file); + const bytes = new Uint8Array(await resultBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.[^.]+$/, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/pub-to-pdf-node.ts b/src/js/workflow/nodes/pub-to-pdf-node.ts new file mode 100644 index 0000000..8af5d7e --- /dev/null +++ b/src/js/workflow/nodes/pub-to-pdf-node.ts @@ -0,0 +1,71 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js'; + +export class PubToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-book-open'; + readonly description = 'Upload Microsoft Publisher files and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('PUB Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.toLowerCase().endsWith('.pub')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} PUB files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No PUB files uploaded in PUB Input node'); + + const converter = getLibreOfficeConverter(); + await converter.initialize(); + + const results: PDFData[] = []; + for (const file of this.files) { + const resultBlob = await converter.convertToPdf(file); + const bytes = new Uint8Array(await resultBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.pub$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/rasterize-node.ts b/src/js/workflow/nodes/rasterize-node.ts new file mode 100644 index 0000000..30e40bc --- /dev/null +++ b/src/js/workflow/nodes/rasterize-node.ts @@ -0,0 +1,79 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class RasterizeNode extends BaseWorkflowNode { + readonly category = 'Optimize & Repair' as const; + readonly icon = 'ph-image'; + readonly description = 'Convert to image-based PDF'; + + constructor() { + super('Rasterize'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput( + 'pdf', + new ClassicPreset.Output(pdfSocket, 'Rasterized PDF') + ); + this.addControl( + 'rasterizeDpi', + new ClassicPreset.InputControl('text', { initial: '150' }) + ); + this.addControl( + 'imageFormat', + new ClassicPreset.InputControl('text', { initial: 'png' }) + ); + this.addControl( + 'grayscale', + new ClassicPreset.InputControl('text', { initial: 'false' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Rasterize'); + + const getText = (key: string, fallback: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || fallback; + }; + + const dpi = Math.max( + 72, + Math.min(600, parseInt(getText('rasterizeDpi', '150')) || 150) + ); + const format = getText('imageFormat', 'png') === 'jpeg' ? 'jpeg' : 'png'; + const grayscale = getText('grayscale', 'false') === 'true'; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pymupdf = await loadPyMuPDF(); + const blob = new Blob([new Uint8Array(input.bytes)], { + type: 'application/pdf', + }); + const rasterizedBlob = await pymupdf.rasterizePdf(blob, { + dpi, + format, + grayscale, + quality: 95, + }); + + const bytes = new Uint8Array(await rasterizedBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + + return { + type: 'pdf', + document, + bytes, + filename: input.filename.replace(/\.pdf$/i, '_rasterized.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/redact-node.ts b/src/js/workflow/nodes/redact-node.ts new file mode 100644 index 0000000..3997d17 --- /dev/null +++ b/src/js/workflow/nodes/redact-node.ts @@ -0,0 +1,120 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; +import { hexToRgb } from '../../utils/helpers.js'; + +export class RedactNode extends BaseWorkflowNode { + readonly category = 'Secure PDF' as const; + readonly icon = 'ph-eye-slash'; + readonly description = 'Redact text from PDF'; + + constructor() { + super('Redact'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Redacted PDF')); + this.addControl( + 'redactMode', + new ClassicPreset.InputControl('text', { initial: 'text' }) + ); + this.addControl( + 'text', + new ClassicPreset.InputControl('text', { initial: '' }) + ); + this.addControl( + 'x0', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'y0', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'x1', + new ClassicPreset.InputControl('number', { initial: 200 }) + ); + this.addControl( + 'y1', + new ClassicPreset.InputControl('number', { initial: 50 }) + ); + this.addControl( + 'fillColor', + new ClassicPreset.InputControl('text', { initial: '#000000' }) + ); + } + + private getText(key: string, fallback: string): string { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || fallback; + } + + private getNum(key: string, fallback: number): number { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'number'> + | undefined; + return ctrl?.value ?? fallback; + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Redact'); + + const mode = this.getText('redactMode', 'text'); + const searchText = this.getText('text', ''); + const fill = hexToRgb(this.getText('fillColor', '#000000')); + + if (mode === 'text' && !searchText) { + throw new Error('Redact: No text specified to redact'); + } + + const areaRect = { + x0: this.getNum('x0', 0), + y0: this.getNum('y0', 0), + x1: this.getNum('x1', 200), + y1: this.getNum('y1', 50), + }; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pymupdf = await loadPyMuPDF(); + const blob = new Blob([new Uint8Array(input.bytes)], { + type: 'application/pdf', + }); + const doc = await pymupdf.open(blob); + + for (const page of doc.pages()) { + if (mode === 'text') { + const rects = page.searchFor(searchText); + for (const rect of rects) { + page.addRedaction(rect, '', fill); + } + if (rects.length > 0) { + page.applyRedactions(); + } + } else { + page.addRedaction(areaRect, '', fill); + page.applyRedactions(); + } + } + + const resultBytes = new Uint8Array(doc.save()); + doc.close(); + + const resultDoc = await PDFDocument.load(resultBytes); + + return { + type: 'pdf', + document: resultDoc, + bytes: resultBytes, + filename: input.filename.replace(/\.pdf$/i, '_redacted.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/registry.ts b/src/js/workflow/nodes/registry.ts new file mode 100644 index 0000000..2520b16 --- /dev/null +++ b/src/js/workflow/nodes/registry.ts @@ -0,0 +1,628 @@ +import type { BaseWorkflowNode } from './base-node'; +import type { NodeCategory } from '../types'; +import { PDFInputNode } from './pdf-input-node'; +import { ImageInputNode } from './image-input-node'; +import { DownloadNode } from './download-node'; +import { PdfToImagesNode } from './pdf-to-images-node'; +import { MergeNode } from './merge-node'; +import { SplitNode } from './split-node'; +import { ExtractPagesNode } from './extract-pages-node'; +import { RotateNode } from './rotate-node'; +import { DeletePagesNode } from './delete-pages-node'; +import { ReversePagesNode } from './reverse-pages-node'; +import { AddBlankPageNode } from './add-blank-page-node'; +import { DividePagesNode } from './divide-pages-node'; +import { NUpNode } from './n-up-node'; +import { FixPageSizeNode } from './fix-page-size-node'; +import { CombineSinglePageNode } from './combine-single-page-node'; +import { CropNode } from './crop-node'; +import { GreyscaleNode } from './greyscale-node'; +import { InvertColorsNode } from './invert-colors-node'; +import { ScannerEffectNode } from './scanner-effect-node'; +import { AdjustColorsNode } from './adjust-colors-node'; +import { BackgroundColorNode } from './background-color-node'; +import { WatermarkNode } from './watermark-node'; +import { PageNumbersNode } from './page-numbers-node'; +import { HeaderFooterNode } from './header-footer-node'; +import { CompressNode } from './compress-node'; +import { RasterizeNode } from './rasterize-node'; +import { OCRNode } from './ocr-node'; +import { RemoveBlankPagesNode } from './remove-blank-pages-node'; +import { RemoveAnnotationsNode } from './remove-annotations-node'; +import { FlattenNode } from './flatten-node'; +import { EditMetadataNode } from './edit-metadata-node'; +import { SanitizeNode } from './sanitize-node'; +import { EncryptNode } from './encrypt-node'; +import { DecryptNode } from './decrypt-node'; +import { DigitalSignNode } from './digital-sign-node'; +import { RedactNode } from './redact-node'; +import { RepairNode } from './repair-node'; +import { PdfToTextNode } from './pdf-to-text-node'; +import { PdfToDocxNode } from './pdf-to-docx-node'; +import { PdfToXlsxNode } from './pdf-to-xlsx-node'; +import { PdfToCsvNode } from './pdf-to-csv-node'; +import { PdfToSvgNode } from './pdf-to-svg-node'; +import { PdfToMarkdownNode } from './pdf-to-markdown-node'; +import { ExtractImagesNode } from './extract-images-node'; +import { WordToPdfNode } from './word-to-pdf-node'; +import { ExcelToPdfNode } from './excel-to-pdf-node'; +import { PowerPointToPdfNode } from './powerpoint-to-pdf-node'; +import { TextToPdfNode } from './text-to-pdf-node'; +import { SvgToPdfNode } from './svg-to-pdf-node'; +import { EpubToPdfNode } from './epub-to-pdf-node'; +import { LinearizeNode } from './linearize-node'; +import { DeskewNode } from './deskew-node'; +import { PdfToPdfANode } from './pdf-to-pdfa-node'; +import { PosterizeNode } from './posterize-node'; +import { BookletNode } from './booklet-node'; +import { FontToOutlineNode } from './font-to-outline-node'; +import { TableOfContentsNode } from './table-of-contents-node'; +import { EmailToPdfNode } from './email-to-pdf-node'; +import { XpsToPdfNode } from './xps-to-pdf-node'; +import { MobiToPdfNode } from './mobi-to-pdf-node'; +import { Fb2ToPdfNode } from './fb2-to-pdf-node'; +import { CbzToPdfNode } from './cbz-to-pdf-node'; +import { MarkdownToPdfNode } from './markdown-to-pdf-node'; +import { JsonToPdfNode } from './json-to-pdf-node'; +import { XmlToPdfNode } from './xml-to-pdf-node'; +import { WpdToPdfNode } from './wpd-to-pdf-node'; +import { WpsToPdfNode } from './wps-to-pdf-node'; +import { PagesToPdfNode } from './pages-to-pdf-node'; +import { OdgToPdfNode } from './odg-to-pdf-node'; +import { PubToPdfNode } from './pub-to-pdf-node'; +import { VsdToPdfNode } from './vsd-to-pdf-node'; + +export interface NodeRegistryEntry { + label: string; + category: NodeCategory; + icon: string; + description: string; + factory: () => BaseWorkflowNode; + hidden?: boolean; +} + +export const nodeRegistry: Record = { + PDFInputNode: { + label: 'PDF Input', + category: 'Input', + icon: 'ph-file-pdf', + description: 'Upload one or more PDF files', + factory: () => new PDFInputNode(), + }, + ImageInputNode: { + label: 'Image Input', + category: 'Input', + icon: 'ph-image', + description: 'Upload images and convert to PDF', + factory: () => new ImageInputNode(), + }, + WordToPdfNode: { + label: 'Word to PDF', + category: 'Input', + icon: 'ph-microsoft-word-logo', + description: 'Convert Word documents to PDF', + factory: () => new WordToPdfNode(), + }, + ExcelToPdfNode: { + label: 'Excel to PDF', + category: 'Input', + icon: 'ph-microsoft-excel-logo', + description: 'Convert Excel spreadsheets to PDF', + factory: () => new ExcelToPdfNode(), + }, + PowerPointToPdfNode: { + label: 'PowerPoint to PDF', + category: 'Input', + icon: 'ph-microsoft-powerpoint-logo', + description: 'Convert PowerPoint presentations to PDF', + factory: () => new PowerPointToPdfNode(), + }, + TextToPdfNode: { + label: 'Text to PDF', + category: 'Input', + icon: 'ph-text-t', + description: 'Convert plain text to PDF', + factory: () => new TextToPdfNode(), + }, + SvgToPdfNode: { + label: 'SVG to PDF', + category: 'Input', + icon: 'ph-file-svg', + description: 'Convert SVG files to PDF', + factory: () => new SvgToPdfNode(), + }, + EpubToPdfNode: { + label: 'EPUB to PDF', + category: 'Input', + icon: 'ph-book-open-text', + description: 'Convert EPUB ebooks to PDF', + factory: () => new EpubToPdfNode(), + }, + EmailToPdfNode: { + label: 'Email to PDF', + category: 'Input', + icon: 'ph-envelope', + description: 'Convert email files (.eml, .msg) to PDF', + factory: () => new EmailToPdfNode(), + }, + XpsToPdfNode: { + label: 'XPS to PDF', + category: 'Input', + icon: 'ph-scan', + description: 'Convert XPS/OXPS documents to PDF', + factory: () => new XpsToPdfNode(), + }, + MobiToPdfNode: { + label: 'MOBI to PDF', + category: 'Input', + icon: 'ph-book-open-text', + description: 'Convert MOBI e-books to PDF', + factory: () => new MobiToPdfNode(), + }, + Fb2ToPdfNode: { + label: 'FB2 to PDF', + category: 'Input', + icon: 'ph-book-bookmark', + description: 'Convert FB2 e-books to PDF', + factory: () => new Fb2ToPdfNode(), + }, + CbzToPdfNode: { + label: 'CBZ to PDF', + category: 'Input', + icon: 'ph-book-open', + description: 'Convert comic book archives (CBZ/CBR) to PDF', + factory: () => new CbzToPdfNode(), + }, + MarkdownToPdfNode: { + label: 'Markdown to PDF', + category: 'Input', + icon: 'ph-markdown-logo', + description: 'Convert Markdown files to PDF', + factory: () => new MarkdownToPdfNode(), + }, + JsonToPdfNode: { + label: 'JSON to PDF', + category: 'Input', + icon: 'ph-file-code', + description: 'Convert JSON files to PDF', + factory: () => new JsonToPdfNode(), + }, + XmlToPdfNode: { + label: 'XML to PDF', + category: 'Input', + icon: 'ph-file-code', + description: 'Convert XML documents to PDF', + factory: () => new XmlToPdfNode(), + }, + WpdToPdfNode: { + label: 'WPD to PDF', + category: 'Input', + icon: 'ph-file-text', + description: 'Convert WordPerfect documents to PDF', + factory: () => new WpdToPdfNode(), + }, + WpsToPdfNode: { + label: 'WPS to PDF', + category: 'Input', + icon: 'ph-file-text', + description: 'Convert WPS Office documents to PDF', + factory: () => new WpsToPdfNode(), + }, + PagesToPdfNode: { + label: 'Pages to PDF', + category: 'Input', + icon: 'ph-file-text', + description: 'Convert Apple Pages documents to PDF', + factory: () => new PagesToPdfNode(), + }, + OdgToPdfNode: { + label: 'ODG to PDF', + category: 'Input', + icon: 'ph-image', + description: 'Convert OpenDocument Graphics to PDF', + factory: () => new OdgToPdfNode(), + }, + PubToPdfNode: { + label: 'PUB to PDF', + category: 'Input', + icon: 'ph-book-open', + description: 'Convert Microsoft Publisher to PDF', + factory: () => new PubToPdfNode(), + }, + VsdToPdfNode: { + label: 'VSD to PDF', + category: 'Input', + icon: 'ph-git-branch', + description: 'Convert Visio diagrams (VSD/VSDX) to PDF', + factory: () => new VsdToPdfNode(), + }, + MergeNode: { + label: 'Merge PDFs', + category: 'Organize & Manage', + icon: 'ph-browsers', + description: 'Combine multiple PDFs into one', + factory: () => new MergeNode(), + }, + SplitNode: { + label: 'Split PDF', + category: 'Organize & Manage', + icon: 'ph-scissors', + description: 'Extract a range of pages', + factory: () => new SplitNode(), + }, + ExtractPagesNode: { + label: 'Extract Pages', + category: 'Organize & Manage', + icon: 'ph-squares-four', + description: 'Extract pages as separate PDFs', + factory: () => new ExtractPagesNode(), + }, + RotateNode: { + label: 'Rotate', + category: 'Organize & Manage', + icon: 'ph-arrow-clockwise', + description: 'Rotate all pages', + factory: () => new RotateNode(), + }, + DeletePagesNode: { + label: 'Delete Pages', + category: 'Organize & Manage', + icon: 'ph-trash', + description: 'Remove specific pages', + factory: () => new DeletePagesNode(), + }, + ReversePagesNode: { + label: 'Reverse Pages', + category: 'Organize & Manage', + icon: 'ph-sort-descending', + description: 'Reverse page order', + factory: () => new ReversePagesNode(), + }, + AddBlankPageNode: { + label: 'Add Blank Page', + category: 'Organize & Manage', + icon: 'ph-file-plus', + description: 'Insert blank pages', + factory: () => new AddBlankPageNode(), + }, + DividePagesNode: { + label: 'Divide Pages', + category: 'Organize & Manage', + icon: 'ph-columns', + description: 'Split pages vertically or horizontally', + factory: () => new DividePagesNode(), + }, + NUpNode: { + label: 'N-Up', + category: 'Organize & Manage', + icon: 'ph-squares-four', + description: 'Arrange multiple pages per sheet', + factory: () => new NUpNode(), + }, + FixPageSizeNode: { + label: 'Fix Page Size', + category: 'Organize & Manage', + icon: 'ph-frame-corners', + description: 'Standardize all pages to a target size', + factory: () => new FixPageSizeNode(), + }, + CombineSinglePageNode: { + label: 'Combine to Single Page', + category: 'Organize & Manage', + icon: 'ph-arrows-out-line-vertical', + description: 'Stitch all pages into one continuous page', + factory: () => new CombineSinglePageNode(), + }, + BookletNode: { + label: 'Booklet', + category: 'Organize & Manage', + icon: 'ph-book-open', + description: 'Arrange pages for booklet printing', + factory: () => new BookletNode(), + }, + PosterizeNode: { + label: 'Posterize', + category: 'Organize & Manage', + icon: 'ph-notepad', + description: 'Split pages into tile grid for poster printing', + factory: () => new PosterizeNode(), + }, + EditMetadataNode: { + label: 'Edit Metadata', + category: 'Organize & Manage', + icon: 'ph-file-code', + description: 'Edit PDF metadata', + factory: () => new EditMetadataNode(), + }, + TableOfContentsNode: { + label: 'Table of Contents', + category: 'Organize & Manage', + icon: 'ph-list', + description: 'Generate table of contents from bookmarks', + factory: () => new TableOfContentsNode(), + }, + OCRNode: { + label: 'OCR', + category: 'Organize & Manage', + icon: 'ph-barcode', + description: 'Add searchable text layer via OCR', + factory: () => new OCRNode(), + }, + CropNode: { + label: 'Crop', + category: 'Edit & Annotate', + icon: 'ph-crop', + description: 'Trim margins from all pages', + factory: () => new CropNode(), + }, + GreyscaleNode: { + label: 'Greyscale', + category: 'Edit & Annotate', + icon: 'ph-palette', + description: 'Convert to greyscale', + factory: () => new GreyscaleNode(), + }, + InvertColorsNode: { + label: 'Invert Colors', + category: 'Edit & Annotate', + icon: 'ph-circle-half', + description: 'Invert all colors', + factory: () => new InvertColorsNode(), + }, + ScannerEffectNode: { + label: 'Scanner Effect', + category: 'Edit & Annotate', + icon: 'ph-scan', + description: 'Apply scanner simulation effect', + factory: () => new ScannerEffectNode(), + }, + AdjustColorsNode: { + label: 'Adjust Colors', + category: 'Edit & Annotate', + icon: 'ph-sliders-horizontal', + description: 'Adjust brightness, contrast, and colors', + factory: () => new AdjustColorsNode(), + }, + BackgroundColorNode: { + label: 'Background Color', + category: 'Edit & Annotate', + icon: 'ph-palette', + description: 'Change background color', + factory: () => new BackgroundColorNode(), + }, + WatermarkNode: { + label: 'Watermark', + category: 'Edit & Annotate', + icon: 'ph-drop', + description: 'Add text watermark', + factory: () => new WatermarkNode(), + }, + PageNumbersNode: { + label: 'Page Numbers', + category: 'Edit & Annotate', + icon: 'ph-list-numbers', + description: 'Add page numbers', + factory: () => new PageNumbersNode(), + }, + HeaderFooterNode: { + label: 'Header & Footer', + category: 'Edit & Annotate', + icon: 'ph-paragraph', + description: 'Add header and footer text', + factory: () => new HeaderFooterNode(), + }, + RemoveBlankPagesNode: { + label: 'Remove Blank Pages', + category: 'Edit & Annotate', + icon: 'ph-file-minus', + description: 'Remove blank pages automatically', + factory: () => new RemoveBlankPagesNode(), + }, + RemoveAnnotationsNode: { + label: 'Remove Annotations', + category: 'Edit & Annotate', + icon: 'ph-eraser', + description: 'Strip all annotations', + factory: () => new RemoveAnnotationsNode(), + }, + CompressNode: { + label: 'Compress', + category: 'Optimize & Repair', + icon: 'ph-lightning', + description: 'Reduce PDF file size', + factory: () => new CompressNode(), + }, + RasterizeNode: { + label: 'Rasterize', + category: 'Optimize & Repair', + icon: 'ph-image', + description: 'Convert to image-based PDF', + factory: () => new RasterizeNode(), + }, + LinearizeNode: { + label: 'Linearize', + category: 'Optimize & Repair', + icon: 'ph-gauge', + description: 'Optimize PDF for fast web viewing', + factory: () => new LinearizeNode(), + }, + DeskewNode: { + label: 'Deskew', + category: 'Optimize & Repair', + icon: 'ph-perspective', + description: 'Straighten skewed PDF pages', + factory: () => new DeskewNode(), + }, + PdfToPdfANode: { + label: 'PDF to PDF/A', + category: 'Optimize & Repair', + icon: 'ph-archive', + description: 'Convert PDF to PDF/A for archiving', + factory: () => new PdfToPdfANode(), + }, + FontToOutlineNode: { + label: 'Font to Outline', + category: 'Optimize & Repair', + icon: 'ph-text-outdent', + description: 'Convert fonts to vector outlines', + factory: () => new FontToOutlineNode(), + }, + RepairNode: { + label: 'Repair', + category: 'Optimize & Repair', + icon: 'ph-wrench', + description: 'Repair corrupted PDF', + factory: () => new RepairNode(), + }, + EncryptNode: { + label: 'Encrypt', + category: 'Secure PDF', + icon: 'ph-lock', + description: 'Encrypt PDF with password', + factory: () => new EncryptNode(), + }, + DecryptNode: { + label: 'Decrypt', + category: 'Secure PDF', + icon: 'ph-lock-open', + description: 'Remove PDF password protection', + factory: () => new DecryptNode(), + }, + SanitizeNode: { + label: 'Sanitize', + category: 'Secure PDF', + icon: 'ph-broom', + description: 'Remove metadata, scripts, and hidden data', + factory: () => new SanitizeNode(), + }, + FlattenNode: { + label: 'Flatten', + category: 'Secure PDF', + icon: 'ph-stack', + description: 'Flatten forms and annotations', + factory: () => new FlattenNode(), + }, + DigitalSignNode: { + label: 'Digital Sign', + category: 'Secure PDF', + icon: 'ph-certificate', + description: 'Apply a digital signature to PDF', + factory: () => new DigitalSignNode(), + }, + RedactNode: { + label: 'Redact', + category: 'Secure PDF', + icon: 'ph-eye-slash', + description: 'Redact text from PDF', + factory: () => new RedactNode(), + }, + DownloadNode: { + label: 'Download', + category: 'Output', + icon: 'ph-download-simple', + description: 'Download as PDF or ZIP automatically', + factory: () => new DownloadNode(), + }, + // Backward compat for saved workflows + DownloadPDFNode: { + label: 'Download', + category: 'Output', + icon: 'ph-download-simple', + description: 'Download as PDF or ZIP automatically', + factory: () => new DownloadNode(), + hidden: true, + }, + DownloadZipNode: { + label: 'Download', + category: 'Output', + icon: 'ph-download-simple', + description: 'Download as PDF or ZIP automatically', + factory: () => new DownloadNode(), + hidden: true, + }, + PdfToImagesNode: { + label: 'PDF to Images', + category: 'Output', + icon: 'ph-file-image', + description: 'Convert PDF pages to images (ZIP)', + factory: () => new PdfToImagesNode(), + }, + PdfToTextNode: { + label: 'PDF to Text', + category: 'Output', + icon: 'ph-text-aa', + description: 'Extract text from PDF', + factory: () => new PdfToTextNode(), + }, + PdfToDocxNode: { + label: 'PDF to DOCX', + category: 'Output', + icon: 'ph-microsoft-word-logo', + description: 'Convert PDF to Word document', + factory: () => new PdfToDocxNode(), + }, + PdfToXlsxNode: { + label: 'PDF to XLSX', + category: 'Output', + icon: 'ph-microsoft-excel-logo', + description: 'Convert PDF tables to Excel', + factory: () => new PdfToXlsxNode(), + }, + PdfToCsvNode: { + label: 'PDF to CSV', + category: 'Output', + icon: 'ph-file-csv', + description: 'Convert PDF tables to CSV', + factory: () => new PdfToCsvNode(), + }, + PdfToSvgNode: { + label: 'PDF to SVG', + category: 'Output', + icon: 'ph-file-code', + description: 'Convert PDF pages to SVG', + factory: () => new PdfToSvgNode(), + }, + PdfToMarkdownNode: { + label: 'PDF to Markdown', + category: 'Output', + icon: 'ph-markdown-logo', + description: 'Convert PDF to Markdown text', + factory: () => new PdfToMarkdownNode(), + }, + ExtractImagesNode: { + label: 'Extract Images', + category: 'Output', + icon: 'ph-download-simple', + description: 'Extract all images from PDF', + factory: () => new ExtractImagesNode(), + }, +}; + +export function createNodeByType(type: string): BaseWorkflowNode | null { + const entry = nodeRegistry[type]; + if (!entry) return null; + const node = entry.factory(); + node.nodeType = type; + return node; +} + +export function getNodesByCategory(): Record< + NodeCategory, + NodeRegistryEntry[] +> { + const result: Record = { + Input: [], + 'Edit & Annotate': [], + 'Organize & Manage': [], + 'Optimize & Repair': [], + 'Secure PDF': [], + Output: [], + }; + + for (const entry of Object.values(nodeRegistry)) { + if (entry.hidden) continue; + result[entry.category].push(entry); + } + + return result; +} diff --git a/src/js/workflow/nodes/remove-annotations-node.ts b/src/js/workflow/nodes/remove-annotations-node.ts new file mode 100644 index 0000000..b857fe3 --- /dev/null +++ b/src/js/workflow/nodes/remove-annotations-node.ts @@ -0,0 +1,46 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument, PDFName } from 'pdf-lib'; + +export class RemoveAnnotationsNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-eraser'; + readonly description = 'Strip all annotations'; + + constructor() { + super('Remove Annotations'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Clean PDF')); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Remove Annotations'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pdfDoc = await PDFDocument.load(input.bytes); + const pages = pdfDoc.getPages(); + + for (const page of pages) { + const annots = page.node.Annots(); + if (annots) { + page.node.delete(PDFName.of('Annots')); + } + } + + const pdfBytes = await pdfDoc.save(); + return { + type: 'pdf', + document: pdfDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_clean.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/remove-blank-pages-node.ts b/src/js/workflow/nodes/remove-blank-pages-node.ts new file mode 100644 index 0000000..692c9a9 --- /dev/null +++ b/src/js/workflow/nodes/remove-blank-pages-node.ts @@ -0,0 +1,95 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import * as pdfjsLib from 'pdfjs-dist'; + +export class RemoveBlankPagesNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-file-minus'; + readonly description = 'Remove blank pages automatically'; + + constructor() { + super('Remove Blank Pages'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + this.addControl( + 'threshold', + new ClassicPreset.InputControl('number', { initial: 250 }) + ); + } + + private async isPageBlank( + page: pdfjsLib.PDFPageProxy, + maxNonWhitePercent: number + ): Promise { + const viewport = page.getViewport({ scale: 0.5 }); + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + const ctx = canvas.getContext('2d')!; + await page.render({ canvasContext: ctx, viewport, canvas }).promise; + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + const totalPixels = data.length / 4; + let nonWhitePixels = 0; + for (let i = 0; i < data.length; i += 4) { + const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3; + if (brightness < 240) nonWhitePixels++; + } + const nonWhitePercent = (nonWhitePixels / totalPixels) * 100; + return nonWhitePercent <= maxNonWhitePercent; + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Remove Blank Pages'); + + const threshCtrl = this.controls['threshold'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const maxNonWhitePercent = Math.max( + 0.1, + Math.min(5, threshCtrl?.value ?? 0.5) + ); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const pdfjsDoc = await pdfjsLib.getDocument({ data: input.bytes }) + .promise; + const srcDoc = await PDFDocument.load(input.bytes); + const nonBlankIndices: number[] = []; + + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + const page = await pdfjsDoc.getPage(i); + const blank = await this.isPageBlank(page, maxNonWhitePercent); + if (!blank) { + nonBlankIndices.push(i - 1); + } else { + console.log(`Page ${i} detected as blank, removing`); + } + } + + if (nonBlankIndices.length === 0) { + throw new Error('All pages are blank'); + } + + const newDoc = await PDFDocument.create(); + const copiedPages = await newDoc.copyPages(srcDoc, nonBlankIndices); + copiedPages.forEach((page) => newDoc.addPage(page)); + + const pdfBytes = await newDoc.save(); + return { + type: 'pdf', + document: newDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_cleaned.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/repair-node.ts b/src/js/workflow/nodes/repair-node.ts new file mode 100644 index 0000000..0794316 --- /dev/null +++ b/src/js/workflow/nodes/repair-node.ts @@ -0,0 +1,66 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { initializeQpdf } from '../../utils/helpers.js'; + +export class RepairNode extends BaseWorkflowNode { + readonly category = 'Optimize & Repair' as const; + readonly icon = 'ph-wrench'; + readonly description = 'Repair corrupted PDF'; + + constructor() { + super('Repair'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Repaired PDF')); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Repair'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const qpdf = await initializeQpdf(); + const uid = `${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; + const inputPath = `/tmp/input_repair_${uid}.pdf`; + const outputPath = `/tmp/output_repair_${uid}.pdf`; + + let repairedData: Uint8Array; + try { + qpdf.FS.writeFile(inputPath, input.bytes); + qpdf.callMain([inputPath, '--decrypt', outputPath]); + + repairedData = qpdf.FS.readFile(outputPath, { encoding: 'binary' }); + } finally { + try { + qpdf.FS.unlink(inputPath); + } catch { + /* cleanup */ + } + try { + qpdf.FS.unlink(outputPath); + } catch { + /* cleanup */ + } + } + + const resultBytes = new Uint8Array(repairedData); + const resultDoc = await PDFDocument.load(resultBytes, { + ignoreEncryption: true, + throwOnInvalidObject: false, + }); + + return { + type: 'pdf', + document: resultDoc, + bytes: resultBytes, + filename: input.filename.replace(/\.pdf$/i, '_repaired.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/reverse-pages-node.ts b/src/js/workflow/nodes/reverse-pages-node.ts new file mode 100644 index 0000000..8bcbe0a --- /dev/null +++ b/src/js/workflow/nodes/reverse-pages-node.ts @@ -0,0 +1,45 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; + +export class ReversePagesNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-sort-descending'; + readonly description = 'Reverse page order'; + + constructor() { + super('Reverse Pages'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Reversed PDF')); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Reverse Pages'); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const srcDoc = await PDFDocument.load(input.bytes); + const pageCount = srcDoc.getPageCount(); + const newDoc = await PDFDocument.create(); + const reversedIndices = Array.from( + { length: pageCount }, + (_, i) => pageCount - 1 - i + ); + const copiedPages = await newDoc.copyPages(srcDoc, reversedIndices); + copiedPages.forEach((page) => newDoc.addPage(page)); + const pdfBytes = await newDoc.save(); + return { + type: 'pdf', + document: newDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_reversed.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/rotate-node.ts b/src/js/workflow/nodes/rotate-node.ts new file mode 100644 index 0000000..be5ce88 --- /dev/null +++ b/src/js/workflow/nodes/rotate-node.ts @@ -0,0 +1,46 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { rotatePdfUniform } from '../../utils/pdf-operations'; +import { PDFDocument } from 'pdf-lib'; + +export class RotateNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-arrow-clockwise'; + readonly description = 'Rotate all pages'; + + constructor() { + super('Rotate'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Rotated PDF')); + this.addControl( + 'angle', + new ClassicPreset.InputControl('text', { initial: '90' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Rotate'); + const angleControl = this.controls['angle'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const angle = parseInt(angleControl?.value ?? '90', 10) || 90; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const resultBytes = await rotatePdfUniform(input.bytes, angle); + const resultDoc = await PDFDocument.load(resultBytes); + return { + type: 'pdf', + document: resultDoc, + bytes: resultBytes, + filename: input.filename.replace(/\.pdf$/i, '_rotated.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/sanitize-node.ts b/src/js/workflow/nodes/sanitize-node.ts new file mode 100644 index 0000000..b0827e2 --- /dev/null +++ b/src/js/workflow/nodes/sanitize-node.ts @@ -0,0 +1,96 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { sanitizePdf } from '../../utils/sanitize'; + +export class SanitizeNode extends BaseWorkflowNode { + readonly category = 'Secure PDF' as const; + readonly icon = 'ph-broom'; + readonly description = 'Remove metadata, scripts, and hidden data'; + + constructor() { + super('Sanitize'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Sanitized PDF')); + this.addControl( + 'flattenForms', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'removeMetadata', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'removeAnnotations', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'removeJavascript', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'removeEmbeddedFiles', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'removeLayers', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'removeLinks', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'removeStructureTree', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'removeMarkInfo', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + this.addControl( + 'removeFonts', + new ClassicPreset.InputControl('text', { initial: 'false' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Sanitize'); + + const getBool = (key: string, fallback: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return (ctrl?.value ?? fallback) === 'true'; + }; + + const options = { + flattenForms: getBool('flattenForms', 'true'), + removeMetadata: getBool('removeMetadata', 'true'), + removeAnnotations: getBool('removeAnnotations', 'true'), + removeJavascript: getBool('removeJavascript', 'true'), + removeEmbeddedFiles: getBool('removeEmbeddedFiles', 'true'), + removeLayers: getBool('removeLayers', 'true'), + removeLinks: getBool('removeLinks', 'true'), + removeStructureTree: getBool('removeStructureTree', 'true'), + removeMarkInfo: getBool('removeMarkInfo', 'true'), + removeFonts: getBool('removeFonts', 'false'), + }; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const result = await sanitizePdf(input.bytes, options); + return { + type: 'pdf', + document: result.pdfDoc, + bytes: result.bytes, + filename: input.filename.replace(/\.pdf$/i, '_sanitized.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/scanner-effect-node.ts b/src/js/workflow/nodes/scanner-effect-node.ts new file mode 100644 index 0000000..018b727 --- /dev/null +++ b/src/js/workflow/nodes/scanner-effect-node.ts @@ -0,0 +1,164 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { applyScannerEffect } from '../../utils/image-effects'; +import { PDFDocument } from 'pdf-lib'; +import * as pdfjsLib from 'pdfjs-dist'; +import type { ScanSettings } from '../../types/scanner-effect-type'; + +export class ScannerEffectNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-scan'; + readonly description = 'Apply scanner simulation effect'; + + constructor() { + super('Scanner Effect'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Scanned PDF')); + this.addControl( + 'grayscale', + new ClassicPreset.InputControl('text', { initial: 'false' }) + ); + this.addControl( + 'border', + new ClassicPreset.InputControl('text', { initial: 'false' }) + ); + this.addControl( + 'rotation', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'rotationVariance', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'brightness', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'contrast', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'blur', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'noise', + new ClassicPreset.InputControl('number', { initial: 10 }) + ); + this.addControl( + 'yellowish', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'resolution', + new ClassicPreset.InputControl('number', { initial: 150 }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Scanner Effect'); + + const getNum = (key: string, fallback: number) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'number'> + | undefined; + return ctrl?.value ?? fallback; + }; + + const getBool = (key: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value === 'true'; + }; + + const settings: ScanSettings = { + grayscale: getBool('grayscale'), + border: getBool('border'), + rotate: getNum('rotation', 0), + rotateVariance: getNum('rotationVariance', 0), + brightness: getNum('brightness', 0), + contrast: getNum('contrast', 0), + blur: getNum('blur', 0), + noise: getNum('noise', 10), + yellowish: getNum('yellowish', 0), + resolution: getNum('resolution', 150), + }; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const newPdfDoc = await PDFDocument.create(); + const pdfjsDoc = await pdfjsLib.getDocument({ data: input.bytes }) + .promise; + const dpiScale = settings.resolution / 72; + + for (let i = 1; i <= pdfjsDoc.numPages; i++) { + const page = await pdfjsDoc.getPage(i); + const viewport = page.getViewport({ scale: dpiScale }); + + const renderCanvas = document.createElement('canvas'); + renderCanvas.width = viewport.width; + renderCanvas.height = viewport.height; + const renderCtx = renderCanvas.getContext('2d'); + if (!renderCtx) + throw new Error(`Failed to get canvas context for page ${i}`); + await page.render({ + canvasContext: renderCtx, + viewport, + canvas: renderCanvas, + }).promise; + + const baseData = renderCtx.getImageData( + 0, + 0, + renderCanvas.width, + renderCanvas.height + ); + const baselineCopy = new ImageData( + new Uint8ClampedArray(baseData.data), + baseData.width, + baseData.height + ); + + const outputCanvas = document.createElement('canvas'); + applyScannerEffect(baselineCopy, outputCanvas, settings, 0, dpiScale); + + const jpegBlob = await new Promise((resolve) => + outputCanvas.toBlob(resolve, 'image/jpeg', 0.85) + ); + + if (!jpegBlob) throw new Error(`Failed to render page ${i} to image`); + + const jpegBytes = await jpegBlob.arrayBuffer(); + const jpegImage = await newPdfDoc.embedJpg(jpegBytes); + const newPage = newPdfDoc.addPage([ + outputCanvas.width, + outputCanvas.height, + ]); + newPage.drawImage(jpegImage, { + x: 0, + y: 0, + width: outputCanvas.width, + height: outputCanvas.height, + }); + } + + if (newPdfDoc.getPageCount() === 0) + throw new Error('No pages were processed'); + const pdfBytes = await newPdfDoc.save(); + return { + type: 'pdf', + document: newPdfDoc, + bytes: new Uint8Array(pdfBytes), + filename: input.filename.replace(/\.pdf$/i, '_scanned.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/split-node.ts b/src/js/workflow/nodes/split-node.ts new file mode 100644 index 0000000..225c7f3 --- /dev/null +++ b/src/js/workflow/nodes/split-node.ts @@ -0,0 +1,49 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { splitPdf, parsePageRange } from '../../utils/pdf-operations'; +import { PDFDocument } from 'pdf-lib'; + +export class SplitNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-scissors'; + readonly description = 'Extract a range of pages'; + + constructor() { + super('Split PDF'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'Split PDF')); + this.addControl( + 'pages', + new ClassicPreset.InputControl('text', { initial: '1-3' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Split PDF'); + const pagesControl = this.controls['pages'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const rangeStr = pagesControl?.value || '1'; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const srcDoc = await PDFDocument.load(input.bytes); + const totalPages = srcDoc.getPageCount(); + const indices = parsePageRange(rangeStr, totalPages); + const resultBytes = await splitPdf(input.bytes, indices); + const resultDoc = await PDFDocument.load(resultBytes); + return { + type: 'pdf', + document: resultDoc, + bytes: resultBytes, + filename: input.filename.replace(/\.pdf$/i, '_split.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/svg-to-pdf-node.ts b/src/js/workflow/nodes/svg-to-pdf-node.ts new file mode 100644 index 0000000..b4e2b97 --- /dev/null +++ b/src/js/workflow/nodes/svg-to-pdf-node.ts @@ -0,0 +1,104 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData } from '../types'; +import { PDFDocument } from 'pdf-lib'; + +export class SvgToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-file-svg'; + readonly description = 'Upload SVG files and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('SVG Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.endsWith('.svg') || file.type === 'image/svg+xml') { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} SVG files`; + } + + private async svgToPng(svgText: string): Promise { + return new Promise((resolve, reject) => { + const img = new Image(); + const svgBlob = new Blob([svgText], { type: 'image/svg+xml' }); + const url = URL.createObjectURL(svgBlob); + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.naturalWidth || 800; + canvas.height = img.naturalHeight || 600; + const ctx = canvas.getContext('2d')!; + ctx.drawImage(img, 0, 0); + URL.revokeObjectURL(url); + canvas.toBlob(async (blob) => { + if (!blob) { + reject(new Error('Failed to convert SVG')); + return; + } + resolve(new Uint8Array(await blob.arrayBuffer())); + }, 'image/png'); + }; + img.onerror = () => { + URL.revokeObjectURL(url); + reject(new Error('Failed to load SVG')); + }; + img.src = url; + }); + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No SVG files uploaded in SVG Input node'); + + const doc = await PDFDocument.create(); + + for (const file of this.files) { + const svgText = await file.text(); + const pngBytes = await this.svgToPng(svgText); + const pngImage = await doc.embedPng(pngBytes); + const page = doc.addPage([pngImage.width, pngImage.height]); + page.drawImage(pngImage, { + x: 0, + y: 0, + width: pngImage.width, + height: pngImage.height, + }); + } + + const pdfBytes = new Uint8Array(await doc.save()); + const result: PDFData = { + type: 'pdf', + document: doc, + bytes: pdfBytes, + filename: 'svg_converted.pdf', + }; + + return { pdf: result }; + } +} diff --git a/src/js/workflow/nodes/table-of-contents-node.ts b/src/js/workflow/nodes/table-of-contents-node.ts new file mode 100644 index 0000000..ab9b21c --- /dev/null +++ b/src/js/workflow/nodes/table-of-contents-node.ts @@ -0,0 +1,121 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { WasmProvider } from '../../utils/wasm-provider.js'; + +export class TableOfContentsNode extends BaseWorkflowNode { + readonly category = 'Organize & Manage' as const; + readonly icon = 'ph-list'; + readonly description = 'Generate table of contents from bookmarks'; + + constructor() { + super('Table of Contents'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF with TOC')); + this.addControl( + 'title', + new ClassicPreset.InputControl('text', { initial: 'Table of Contents' }) + ); + this.addControl( + 'fontSize', + new ClassicPreset.InputControl('number', { initial: 12 }) + ); + this.addControl( + 'fontFamily', + new ClassicPreset.InputControl('number', { initial: 0 }) + ); + this.addControl( + 'addBookmark', + new ClassicPreset.InputControl('text', { initial: 'true' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Table of Contents'); + + const titleCtrl = this.controls['title'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const fontSizeCtrl = this.controls['fontSize'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const fontFamilyCtrl = this.controls['fontFamily'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const addBookmarkCtrl = this.controls['addBookmark'] as + | ClassicPreset.InputControl<'text'> + | undefined; + + const title = titleCtrl?.value ?? 'Table of Contents'; + const fontSize = fontSizeCtrl?.value ?? 12; + const fontFamily = fontFamilyCtrl?.value ?? 0; + const addBookmark = (addBookmarkCtrl?.value ?? 'true') === 'true'; + + const cpdfUrl = WasmProvider.getUrl('cpdf'); + if (!cpdfUrl) + throw new Error( + 'CoherentPDF is not configured. Please configure it in Advanced Settings.' + ); + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const resultBytes = await new Promise((resolve, reject) => { + const worker = new Worker( + import.meta.env.BASE_URL + 'workers/table-of-contents.worker.js' + ); + + worker.onmessage = (e: MessageEvent) => { + worker.terminate(); + if (e.data.status === 'success') { + resolve(new Uint8Array(e.data.pdfBytes)); + } else { + reject( + new Error( + e.data.message || 'Failed to generate table of contents' + ) + ); + } + }; + + worker.onerror = (err) => { + worker.terminate(); + reject(new Error('Worker error: ' + err.message)); + }; + + const arrayBuffer = input.bytes.buffer.slice( + input.bytes.byteOffset, + input.bytes.byteOffset + input.bytes.byteLength + ); + + worker.postMessage( + { + command: 'generate-toc', + pdfData: arrayBuffer, + title, + fontSize, + fontFamily, + addBookmark, + cpdfUrl: cpdfUrl + 'coherentpdf.browser.min.js', + }, + [arrayBuffer] + ); + }); + + const bytes = new Uint8Array(resultBytes); + const document = await PDFDocument.load(bytes); + + return { + type: 'pdf', + document, + bytes, + filename: input.filename.replace(/\.pdf$/i, '_toc.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/text-to-pdf-node.ts b/src/js/workflow/nodes/text-to-pdf-node.ts new file mode 100644 index 0000000..f82bdd5 --- /dev/null +++ b/src/js/workflow/nodes/text-to-pdf-node.ts @@ -0,0 +1,104 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class TextToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-text-t'; + readonly description = 'Upload text file and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('Text Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + this.addControl( + 'fontSize', + new ClassicPreset.InputControl('number', { initial: 12 }) + ); + this.addControl( + 'fontFamily', + new ClassicPreset.InputControl('text', { initial: 'helv' }) + ); + this.addControl( + 'fontColor', + new ClassicPreset.InputControl('text', { initial: '#000000' }) + ); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.endsWith('.txt') || file.type === 'text/plain') { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} text files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No text files uploaded in Text Input node'); + + const fontSizeCtrl = this.controls['fontSize'] as + | ClassicPreset.InputControl<'number'> + | undefined; + const fontSize = fontSizeCtrl?.value ?? 12; + const fontFamilyCtrl = this.controls['fontFamily'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const fontName = (fontFamilyCtrl?.value ?? 'helv') as + | 'helv' + | 'tiro' + | 'cour' + | 'times'; + const fontColorCtrl = this.controls['fontColor'] as + | ClassicPreset.InputControl<'text'> + | undefined; + const textColor = fontColorCtrl?.value ?? '#000000'; + + const pymupdf = await loadPyMuPDF(); + const results: PDFData[] = []; + for (const file of this.files) { + const textContent = await file.text(); + const pdfBlob = await pymupdf.textToPdf(textContent, { + fontSize, + fontName, + textColor, + pageSize: 'a4', + }); + const bytes = new Uint8Array(await pdfBlob.arrayBuffer()); + const pdfDoc = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document: pdfDoc, + bytes, + filename: file.name.replace(/\.[^.]+$/, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/vsd-to-pdf-node.ts b/src/js/workflow/nodes/vsd-to-pdf-node.ts new file mode 100644 index 0000000..fa0f86f --- /dev/null +++ b/src/js/workflow/nodes/vsd-to-pdf-node.ts @@ -0,0 +1,72 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js'; + +export class VsdToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-git-branch'; + readonly description = 'Upload Visio diagrams and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('VSD Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + const name = file.name.toLowerCase(); + if (name.endsWith('.vsd') || name.endsWith('.vsdx')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} Visio files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No Visio files uploaded in VSD Input node'); + + const converter = getLibreOfficeConverter(); + await converter.initialize(); + + const results: PDFData[] = []; + for (const file of this.files) { + const resultBlob = await converter.convertToPdf(file); + const bytes = new Uint8Array(await resultBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.(vsd|vsdx)$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/watermark-node.ts b/src/js/workflow/nodes/watermark-node.ts new file mode 100644 index 0000000..d16eed1 --- /dev/null +++ b/src/js/workflow/nodes/watermark-node.ts @@ -0,0 +1,177 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { SocketData } from '../types'; +import { requirePdfInput, processBatch } from '../types'; +import { addTextWatermark, parsePageRange } from '../../utils/pdf-operations'; +import { PDFDocument } from 'pdf-lib'; +import { hexToRgb } from '../../utils/helpers.js'; +import * as pdfjsLib from 'pdfjs-dist'; + +export class WatermarkNode extends BaseWorkflowNode { + readonly category = 'Edit & Annotate' as const; + readonly icon = 'ph-drop'; + readonly description = 'Add text watermark'; + + constructor() { + super('Watermark'); + this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF')); + this.addOutput( + 'pdf', + new ClassicPreset.Output(pdfSocket, 'Watermarked PDF') + ); + this.addControl( + 'text', + new ClassicPreset.InputControl('text', { initial: 'DRAFT' }) + ); + this.addControl( + 'fontSize', + new ClassicPreset.InputControl('number', { initial: 72 }) + ); + this.addControl( + 'color', + new ClassicPreset.InputControl('text', { initial: '#808080' }) + ); + this.addControl( + 'opacity', + new ClassicPreset.InputControl('number', { initial: 30 }) + ); + this.addControl( + 'angle', + new ClassicPreset.InputControl('number', { initial: -45 }) + ); + this.addControl( + 'position', + new ClassicPreset.InputControl('text', { initial: 'center' }) + ); + this.addControl( + 'pages', + new ClassicPreset.InputControl('text', { initial: 'all' }) + ); + this.addControl( + 'flatten', + new ClassicPreset.InputControl('text', { initial: 'no' }) + ); + } + + async data( + inputs: Record + ): Promise> { + const pdfInputs = requirePdfInput(inputs, 'Watermark'); + + const getText = (key: string, fallback: string) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'text'> + | undefined; + return ctrl?.value || fallback; + }; + const getNum = (key: string, fallback: number) => { + const ctrl = this.controls[key] as + | ClassicPreset.InputControl<'number'> + | undefined; + return ctrl?.value ?? fallback; + }; + + const colorHex = getText('color', '#808080'); + const c = hexToRgb(colorHex); + + const watermarkText = getText('text', 'DRAFT'); + const fontSize = getNum('fontSize', 72); + const opacity = getNum('opacity', 30) / 100; + const angle = getNum('angle', -45); + + const positionPresets: Record = { + 'top-left': { x: 0.15, y: 0.15 }, + top: { x: 0.5, y: 0.15 }, + 'top-right': { x: 0.85, y: 0.15 }, + left: { x: 0.15, y: 0.5 }, + center: { x: 0.5, y: 0.5 }, + right: { x: 0.85, y: 0.5 }, + 'bottom-left': { x: 0.15, y: 0.85 }, + bottom: { x: 0.5, y: 0.85 }, + 'bottom-right': { x: 0.85, y: 0.85 }, + }; + const posKey = getText('position', 'center').trim().toLowerCase(); + const { x, y } = positionPresets[posKey] ?? positionPresets['center']; + + const pagesStr = getText('pages', 'all').trim().toLowerCase(); + const shouldFlatten = + getText('flatten', 'no').trim().toLowerCase() === 'yes'; + + return { + pdf: await processBatch(pdfInputs, async (input) => { + const srcDoc = await PDFDocument.load(input.bytes); + const totalPages = srcDoc.getPageCount(); + + const pageIndices = + pagesStr === 'all' ? undefined : parsePageRange(pagesStr, totalPages); + + let resultBytes = await addTextWatermark(input.bytes, { + text: watermarkText, + fontSize, + color: { r: c.r, g: c.g, b: c.b }, + opacity, + angle, + x, + y: 1 - y, + pageIndices, + }); + + if (shouldFlatten) { + const watermarkedPdf = await pdfjsLib.getDocument({ + data: resultBytes.slice(), + }).promise; + const flattenedDoc = await PDFDocument.create(); + const renderScale = 2.5; + + for (let i = 1; i <= watermarkedPdf.numPages; i++) { + const page = await watermarkedPdf.getPage(i); + const unscaledVP = page.getViewport({ scale: 1 }); + const viewport = page.getViewport({ scale: renderScale }); + + const canvas = document.createElement('canvas'); + canvas.width = viewport.width; + canvas.height = viewport.height; + const ctx = canvas.getContext('2d')!; + await page.render({ canvasContext: ctx, canvas, viewport }).promise; + + const jpegBytes = await new Promise( + (resolve, reject) => + canvas.toBlob( + (blob) => + blob + ? blob.arrayBuffer().then(resolve) + : reject(new Error(`Failed to rasterize page ${i}`)), + 'image/jpeg', + 0.92 + ) + ); + + const image = await flattenedDoc.embedJpg(jpegBytes); + const newPage = flattenedDoc.addPage([ + unscaledVP.width, + unscaledVP.height, + ]); + newPage.drawImage(image, { + x: 0, + y: 0, + width: unscaledVP.width, + height: unscaledVP.height, + }); + } + + resultBytes = new Uint8Array(await flattenedDoc.save()); + } + + const resultDoc = await PDFDocument.load(resultBytes); + + return { + type: 'pdf', + document: resultDoc, + bytes: resultBytes, + filename: input.filename.replace(/\.pdf$/i, '_watermarked.pdf'), + }; + }), + }; + } +} diff --git a/src/js/workflow/nodes/word-to-pdf-node.ts b/src/js/workflow/nodes/word-to-pdf-node.ts new file mode 100644 index 0000000..8bb8318 --- /dev/null +++ b/src/js/workflow/nodes/word-to-pdf-node.ts @@ -0,0 +1,77 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js'; + +export class WordToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-microsoft-word-logo'; + readonly description = 'Upload Word document and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('Word Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + const ext = file.name.toLowerCase(); + if ( + ext.endsWith('.doc') || + ext.endsWith('.docx') || + ext.endsWith('.odt') || + ext.endsWith('.rtf') + ) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} documents`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No documents uploaded in Word Input node'); + + const converter = getLibreOfficeConverter(); + await converter.initialize(); + + const results: PDFData[] = []; + for (const file of this.files) { + const resultBlob = await converter.convertToPdf(file); + const bytes = new Uint8Array(await resultBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.[^.]+$/, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/wpd-to-pdf-node.ts b/src/js/workflow/nodes/wpd-to-pdf-node.ts new file mode 100644 index 0000000..83fc873 --- /dev/null +++ b/src/js/workflow/nodes/wpd-to-pdf-node.ts @@ -0,0 +1,71 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js'; + +export class WpdToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-file-text'; + readonly description = 'Upload WordPerfect documents and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('WPD Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.toLowerCase().endsWith('.wpd')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} WPD files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No WPD files uploaded in WPD Input node'); + + const converter = getLibreOfficeConverter(); + await converter.initialize(); + + const results: PDFData[] = []; + for (const file of this.files) { + const resultBlob = await converter.convertToPdf(file); + const bytes = new Uint8Array(await resultBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.wpd$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/wps-to-pdf-node.ts b/src/js/workflow/nodes/wps-to-pdf-node.ts new file mode 100644 index 0000000..fbd1dc6 --- /dev/null +++ b/src/js/workflow/nodes/wps-to-pdf-node.ts @@ -0,0 +1,71 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js'; + +export class WpsToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-file-text'; + readonly description = 'Upload WPS Office documents and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('WPS Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.toLowerCase().endsWith('.wps')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} WPS files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No WPS files uploaded in WPS Input node'); + + const converter = getLibreOfficeConverter(); + await converter.initialize(); + + const results: PDFData[] = []; + for (const file of this.files) { + const resultBlob = await converter.convertToPdf(file); + const bytes = new Uint8Array(await resultBlob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.wps$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/xml-to-pdf-node.ts b/src/js/workflow/nodes/xml-to-pdf-node.ts new file mode 100644 index 0000000..69e2284 --- /dev/null +++ b/src/js/workflow/nodes/xml-to-pdf-node.ts @@ -0,0 +1,74 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class XmlToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-file-code'; + readonly description = 'Upload XML files and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('XML Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + if (file.name.toLowerCase().endsWith('.xml')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} XML files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No XML files uploaded in XML Input node'); + + const pymupdf = await loadPyMuPDF(); + const results: PDFData[] = []; + for (const file of this.files) { + const textContent = await file.text(); + const pdfBlob = await pymupdf.textToPdf(textContent, { + fontSize: 10, + fontName: 'cour', + pageSize: 'a4', + }); + const bytes = new Uint8Array(await pdfBlob.arrayBuffer()); + const pdfDoc = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document: pdfDoc, + bytes, + filename: file.name.replace(/\.xml$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/nodes/xps-to-pdf-node.ts b/src/js/workflow/nodes/xps-to-pdf-node.ts new file mode 100644 index 0000000..206527e --- /dev/null +++ b/src/js/workflow/nodes/xps-to-pdf-node.ts @@ -0,0 +1,70 @@ +import { ClassicPreset } from 'rete'; +import { BaseWorkflowNode } from './base-node'; +import { pdfSocket } from '../sockets'; +import type { PDFData, SocketData, MultiPDFData } from '../types'; +import { PDFDocument } from 'pdf-lib'; +import { loadPyMuPDF } from '../../utils/pymupdf-loader.js'; + +export class XpsToPdfNode extends BaseWorkflowNode { + readonly category = 'Input' as const; + readonly icon = 'ph-scan'; + readonly description = 'Upload XPS/OXPS documents and convert to PDF'; + + private files: File[] = []; + + constructor() { + super('XPS Input'); + this.addOutput('pdf', new ClassicPreset.Output(pdfSocket, 'PDF')); + } + + async addFiles(fileList: File[]): Promise { + for (const file of fileList) { + const name = file.name.toLowerCase(); + if (name.endsWith('.xps') || name.endsWith('.oxps')) { + this.files.push(file); + } + } + } + + removeFile(index: number): void { + this.files.splice(index, 1); + } + hasFile(): boolean { + return this.files.length > 0; + } + getFileCount(): number { + return this.files.length; + } + getFilenames(): string[] { + return this.files.map((f) => f.name); + } + getFilename(): string { + if (this.files.length === 0) return ''; + if (this.files.length === 1) return this.files[0].name; + return `${this.files.length} XPS files`; + } + + async data( + _inputs: Record + ): Promise> { + if (this.files.length === 0) + throw new Error('No XPS files uploaded in XPS Input node'); + + const pymupdf = await loadPyMuPDF(); + const results: PDFData[] = []; + for (const file of this.files) { + const blob = await pymupdf.convertToPdf(file, { filetype: 'xps' }); + const bytes = new Uint8Array(await blob.arrayBuffer()); + const document = await PDFDocument.load(bytes); + results.push({ + type: 'pdf', + document, + bytes, + filename: file.name.replace(/\.(xps|oxps)$/i, '.pdf'), + }); + } + + if (results.length === 1) return { pdf: results[0] }; + return { pdf: { type: 'multi-pdf', items: results } as MultiPDFData }; + } +} diff --git a/src/js/workflow/serialization.ts b/src/js/workflow/serialization.ts new file mode 100644 index 0000000..c0aaba9 --- /dev/null +++ b/src/js/workflow/serialization.ts @@ -0,0 +1,276 @@ +import type { NodeEditor } from 'rete'; +import type { AreaPlugin } from 'rete-area-plugin'; +import type { ClassicScheme, LitArea2D } from '@retejs/lit-plugin'; +import type { BaseWorkflowNode } from './nodes/base-node'; +import { createNodeByType } from './nodes/registry'; +import { ClassicPreset } from 'rete'; +import type { SerializedWorkflow } from './types'; +import { WORKFLOW_VERSION } from './types'; + +type AreaExtra = LitArea2D; + +interface SerializedNode { + id: string; + type: string; + position: { x: number; y: number }; + controls: Record; +} + +interface SerializedConnection { + id: string; + source: string; + sourceOutput: string; + target: string; + targetInput: string; +} + +function getNodeType(node: BaseWorkflowNode): string | null { + return node.nodeType || null; +} + +function serializeWorkflow( + editor: NodeEditor, + area: AreaPlugin +): SerializedWorkflow { + const nodes: SerializedNode[] = []; + const connections: SerializedConnection[] = []; + + for (const node of editor.getNodes()) { + const view = area.nodeViews.get(node.id); + const position = view + ? { x: view.position.x, y: view.position.y } + : { x: 0, y: 0 }; + + const controls: Record = {}; + for (const [key, control] of Object.entries(node.controls)) { + if (control && 'value' in control) { + controls[key] = (control as { value: unknown }).value; + } + } + + nodes.push({ + id: node.id, + type: getNodeType(node as BaseWorkflowNode) || 'unknown', + position, + controls, + }); + } + + for (const conn of editor.getConnections()) { + connections.push({ + id: conn.id, + source: conn.source, + sourceOutput: conn.sourceOutput as string, + target: conn.target, + targetInput: conn.targetInput as string, + }); + } + + return { + version: WORKFLOW_VERSION, + nodes, + connections, + } as SerializedWorkflow; +} + +async function deserializeWorkflow( + data: SerializedWorkflow, + editor: NodeEditor, + area: AreaPlugin +): Promise { + if ( + !data || + !Array.isArray((data as any).nodes) || + !Array.isArray((data as any).connections) + ) { + throw new Error( + 'Invalid workflow file: missing nodes or connections array.' + ); + } + + if ((data as any).version !== WORKFLOW_VERSION) { + console.warn( + `Workflow version mismatch: expected ${WORKFLOW_VERSION}, got ${(data as any).version}. Attempting load anyway.` + ); + } + + for (const conn of editor.getConnections()) { + await editor.removeConnection(conn.id); + } + for (const node of editor.getNodes()) { + await editor.removeNode(node.id); + } + + const idMap = new Map(); + const skippedTypes: string[] = []; + + for (const serializedNode of (data as any).nodes) { + const node = createNodeByType(serializedNode.type); + if (!node) { + skippedTypes.push(serializedNode.type); + continue; + } + + for (const [key, value] of Object.entries(serializedNode.controls || {})) { + const control = node.controls[key]; + if (control && 'value' in control) { + (control as any).value = value; + } + } + + await editor.addNode(node as any); + idMap.set(serializedNode.id, node.id); + + await area.translate(node.id, serializedNode.position); + } + + for (const serializedConn of (data as any).connections) { + const sourceId = idMap.get(serializedConn.source); + const targetId = idMap.get(serializedConn.target); + if (!sourceId || !targetId) continue; + + const sourceNode = editor.getNode(sourceId); + const targetNode = editor.getNode(targetId); + if (!sourceNode || !targetNode) continue; + + const conn = new ClassicPreset.Connection( + sourceNode, + serializedConn.sourceOutput, + targetNode, + serializedConn.targetInput + ); + await editor.addConnection(conn as any); + } + + if (skippedTypes.length > 0) { + console.warn('Skipped unknown node types during load:', skippedTypes); + } +} + +const TEMPLATES_KEY = 'bento-pdf-workflow-templates'; + +interface StoredTemplates { + [name: string]: SerializedWorkflow; +} + +function getStoredTemplates(): StoredTemplates { + const json = localStorage.getItem(TEMPLATES_KEY); + if (!json) return {}; + try { + return JSON.parse(json) as StoredTemplates; + } catch { + return {}; + } +} + +export function getSavedTemplateNames(): string[] { + return Object.keys(getStoredTemplates()); +} + +export function saveWorkflow( + editor: NodeEditor, + area: AreaPlugin, + name: string +): void { + const data = serializeWorkflow(editor, area); + const templates = getStoredTemplates(); + const backup = templates[name]; + templates[name] = data; + try { + localStorage.setItem(TEMPLATES_KEY, JSON.stringify(templates)); + } catch (e) { + if (backup !== undefined) { + templates[name] = backup; + } else { + delete templates[name]; + } + throw new Error( + 'Failed to save workflow: storage quota exceeded. Try deleting old templates.' + ); + } +} + +export function templateNameExists(name: string): boolean { + const templates = getStoredTemplates(); + return name in templates; +} + +export async function loadWorkflow( + editor: NodeEditor, + area: AreaPlugin, + name: string +): Promise { + const templates = getStoredTemplates(); + const data = templates[name]; + if (!data) return false; + + try { + await deserializeWorkflow(data, editor, area); + return true; + } catch (err) { + console.error(`Failed to load workflow "${name}":`, err); + return false; + } +} + +export function deleteTemplate(name: string): void { + const templates = getStoredTemplates(); + delete templates[name]; + localStorage.setItem(TEMPLATES_KEY, JSON.stringify(templates)); +} + +export function exportWorkflow( + editor: NodeEditor, + area: AreaPlugin +): void { + const data = serializeWorkflow(editor, area); + const json = JSON.stringify(data, null, 2); + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'workflow.json'; + a.click(); + setTimeout(() => URL.revokeObjectURL(url), 1000); +} + +export async function importWorkflow( + editor: NodeEditor, + area: AreaPlugin +): Promise { + return new Promise((resolve, reject) => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + + let settled = false; + + input.onchange = async () => { + settled = true; + const file = input.files?.[0]; + if (!file) { + resolve(); + return; + } + try { + const text = await file.text(); + const data = JSON.parse(text) as SerializedWorkflow; + await deserializeWorkflow(data, editor, area); + resolve(); + } catch (err) { + const message = err instanceof Error ? err.message : 'Unknown error'; + reject(new Error(`Failed to import workflow: ${message}`)); + } + }; + + const onFocus = () => { + window.removeEventListener('focus', onFocus); + setTimeout(() => { + if (!settled) resolve(); + }, 300); + }; + window.addEventListener('focus', onFocus); + + input.click(); + }); +} diff --git a/src/js/workflow/sockets.ts b/src/js/workflow/sockets.ts new file mode 100644 index 0000000..f760ed4 --- /dev/null +++ b/src/js/workflow/sockets.ts @@ -0,0 +1,29 @@ +import { ClassicPreset } from 'rete'; + +export class PDFSocket extends ClassicPreset.Socket { + constructor() { + super('PDF'); + } +} + +export class ImageSocket extends ClassicPreset.Socket { + constructor() { + super('Image'); + } +} + +export class MultiPDFSocket extends ClassicPreset.Socket { + constructor() { + super('MultiPDF'); + } +} + +export const pdfSocket = new PDFSocket(); +export const imageSocket = new ImageSocket(); +export const multiPdfSocket = new MultiPDFSocket(); + +export const socketColors: Record = { + PDF: '#6366f1', + Image: '#10b981', + MultiPDF: '#f59e0b', +}; diff --git a/src/js/workflow/types.ts b/src/js/workflow/types.ts new file mode 100644 index 0000000..55f1408 --- /dev/null +++ b/src/js/workflow/types.ts @@ -0,0 +1,124 @@ +import type { PDFDocument } from 'pdf-lib'; + +export interface PDFData { + type: 'pdf'; + document: PDFDocument; + bytes: Uint8Array; + filename: string; +} + +export interface ImageData { + type: 'image'; + blob: Blob; + filename: string; +} + +export interface MultiPDFData { + type: 'multi-pdf'; + items: PDFData[]; +} + +export type SocketData = PDFData | ImageData | MultiPDFData; + +function clonePdf(pdf: PDFData): PDFData { + return { ...pdf, bytes: pdf.bytes.slice() }; +} + +export function extractSinglePdf(input: SocketData): PDFData { + if (input.type === 'pdf') return clonePdf(input as PDFData); + if (input.type === 'multi-pdf') { + const items = (input as MultiPDFData).items; + if (items.length === 0) throw new Error('No PDFs in input'); + return clonePdf(items[0]); + } + throw new Error('Expected PDF input'); +} + +export function extractAllPdfs(inputs: SocketData[]): PDFData[] { + const result: PDFData[] = []; + for (const item of inputs) { + if (item.type === 'pdf') result.push(clonePdf(item as PDFData)); + if (item.type === 'multi-pdf') + result.push(...(item as MultiPDFData).items.map(clonePdf)); + } + return result; +} + +export type NodeCategory = + | 'Input' + | 'Edit & Annotate' + | 'Organize & Manage' + | 'Optimize & Repair' + | 'Secure PDF' + | 'Output'; + +export interface NodeMeta { + id: string; + label: string; + category: NodeCategory; + icon: string; + description: string; +} + +export interface SerializedWorkflow { + version: number; + nodes: SerializedNode[]; + connections: SerializedConnection[]; +} + +export interface SerializedNode { + id: string; + type: string; + position: { x: number; y: number }; + controls: Record; +} + +export interface SerializedConnection { + id: string; + source: string; + sourceOutput: string; + target: string; + targetInput: string; +} + +export interface ExecutionProgress { + nodeId: string; + nodeName: string; + status: 'pending' | 'running' | 'completed' | 'error'; + progress?: number; + message?: string; +} + +export const WORKFLOW_VERSION = 1; + +export class WorkflowError extends Error { + nodeName?: string; + constructor(message: string, nodeName?: string) { + super(nodeName ? `${nodeName}: ${message}` : message); + this.name = 'WorkflowError'; + this.nodeName = nodeName; + } +} + +export function requirePdfInput( + inputs: Record, + nodeName: string +): SocketData[] { + if (!inputs['pdf']?.[0]) + throw new WorkflowError('No PDF connected', nodeName); + return inputs['pdf']; +} + +export async function processBatch( + pdfInputs: SocketData[], + fn: (pdf: PDFData) => Promise +): Promise { + const allPdfs = extractAllPdfs(pdfInputs); + if (allPdfs.length === 0) throw new Error('No PDFs in input'); + if (allPdfs.length === 1) return fn(allPdfs[0]); + const results: PDFData[] = []; + for (const pdf of allPdfs) { + results.push(await fn(pdf)); + } + return { type: 'multi-pdf', items: results } as MultiPDFData; +} diff --git a/src/pages/add-attachments.html b/src/pages/add-attachments.html index 35305d2..103d56f 100644 --- a/src/pages/add-attachments.html +++ b/src/pages/add-attachments.html @@ -1,228 +1,441 @@ - - + + + + Add Attachments Online Free - Add Attachments Tool | BentoPDF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Add Attachments to PDF - BentoPDF - - - - - - - - - + + - - - + + -
-
- + + + + + + + -

Add Attachments to PDF -

-

- Upload a PDF, then add files to embed as attachments. + + {{> navbar }} + +

+
+ + +

+ Add Attachments +

+

+ Embed one or more files into your PDF. +

+ + +
+
+ +

+ Click to select PDF + or drag and drop

- - -
-
- -

Click to select PDF or drag and drop

-

Your files never leave your - device.

-
- -
- -
- - - +

+ Your files never leave your device. +

+
+
+ +
+ + + +
-