mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-05 20:02:47 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6bc8e6bfb |
@@ -86,24 +86,19 @@ jobs:
|
|||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Extract version and determine release type
|
- name: Extract version from tag
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||||
VERSION=${GITHUB_REF#refs/tags/}
|
VERSION=${GITHUB_REF#refs/tags/}
|
||||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT
|
echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT
|
||||||
echo "is_release=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
else
|
||||||
SHORT_SHA=${GITHUB_SHA::7}
|
echo "version=latest" >> $GITHUB_OUTPUT
|
||||||
echo "version=edge" >> $GITHUB_OUTPUT
|
echo "version_without_v=latest" >> $GITHUB_OUTPUT
|
||||||
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
|
||||||
echo "is_release=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build and push for releases (with 'latest' tag)
|
- name: Build and push ${{ matrix.mode.name }} image
|
||||||
- name: Build and push ${{ matrix.mode.name }} image (release)
|
|
||||||
if: steps.version.outputs.is_release == 'true'
|
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
@@ -119,20 +114,3 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
# Build and push for main branch (with 'edge' tag)
|
|
||||||
- name: Build and push ${{ matrix.mode.name }} image (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: |
|
|
||||||
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
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
)
|
)
|
||||||
steps:
|
steps:
|
||||||
- name: "CLA Assistant"
|
- name: "CLA Assistant"
|
||||||
uses: cla-assistant/github-action@v2.5.0
|
uses: cla-assistant/github-action@v2.6.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_ASSISTANT_TOKEN }}
|
PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_ASSISTANT_TOKEN }}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
name: Update Sponsors
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *' # Daily
|
|
||||||
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'
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# 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 }}/
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# Single deploy job since we're just deploying
|
|
||||||
deploy:
|
|
||||||
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: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
@@ -11,7 +11,6 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
.npm-cache
|
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|||||||
+30
-22
@@ -1,12 +1,7 @@
|
|||||||
# Global variable declaration:
|
|
||||||
# Build to serve under Subdirectory BASE_URL if provided, eg: "ARG BASE_URL=/pdf/", otherwise leave blank: "ARG BASE_URL="
|
|
||||||
ARG BASE_URL=
|
|
||||||
|
|
||||||
# Build stage
|
# Build stage
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
COPY vendor ./vendor
|
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
@@ -14,31 +9,44 @@ COPY . .
|
|||||||
# Pass SIMPLE_MODE environment variable if provided
|
# Pass SIMPLE_MODE environment variable if provided
|
||||||
ARG SIMPLE_MODE=false
|
ARG SIMPLE_MODE=false
|
||||||
ENV SIMPLE_MODE=$SIMPLE_MODE
|
ENV SIMPLE_MODE=$SIMPLE_MODE
|
||||||
ARG COMPRESSION_MODE=all
|
RUN npm run build -- --mode production
|
||||||
ENV COMPRESSION_MODE=$COMPRESSION_MODE
|
|
||||||
|
|
||||||
# global arg to local arg
|
|
||||||
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
|
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
FROM nginxinc/nginx-unprivileged:stable-alpine-slim
|
FROM nginxinc/nginx-unprivileged:stable-alpine-slim
|
||||||
|
|
||||||
LABEL org.opencontainers.image.source="https://github.com/alam00000/bentopdf"
|
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
|
COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html
|
||||||
ARG BASE_URL
|
|
||||||
|
|
||||||
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 nginx.conf /etc/nginx/nginx.conf
|
||||||
RUN mkdir -p /etc/nginx/tmp && chown -R nginx:nginx /etc/nginx/tmp
|
RUN mkdir -p /etc/nginx/tmp && chown -R nginx:nginx /etc/nginx/tmp
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
||||||
|
|
||||||
|
# Old Dockerfile for Root User
|
||||||
|
# # Build stage
|
||||||
|
# FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
# WORKDIR /app
|
||||||
|
|
||||||
|
# COPY package*.json ./
|
||||||
|
# RUN 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
|
||||||
|
# RUN npm run build -- --mode production
|
||||||
|
|
||||||
|
# # Production stage
|
||||||
|
# FROM nginx:alpine
|
||||||
|
|
||||||
|
# COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
# COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
# EXPOSE 8080
|
||||||
|
|
||||||
|
# CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
@@ -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.
|
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:** Stephan Paternotte
|
**Full Legal Name:** ___________________________
|
||||||
|
|
||||||
**GitHub Username:** Stephan-P
|
**GitHub Username:** ___________________________
|
||||||
|
|
||||||
**Email Address:** stephan@paternottes.net
|
**Email Address:** ___________________________
|
||||||
|
|
||||||
**Date:** 20-12-2025
|
**Date:** ___________________________
|
||||||
|
|
||||||
**Signature:** ___________________________
|
**Signature:** ___________________________
|
||||||
|
|
||||||
|
|||||||
@@ -3,34 +3,13 @@
|
|||||||
|
|
||||||
**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.
|
**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.
|
||||||
|
|
||||||
 [](https://ko-fi.com/alio01) 
|
 [](https://ko-fi.com/alio0) 
|
||||||
[](https://github.com/sponsors/alam00000)
|
[](https://github.com/sponsors/alam00000)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📢 Join Us on Discord
|
|
||||||
|
|
||||||
[](https://discord.gg/Bgq3Ay3f2w)
|
|
||||||
|
|
||||||
Have questions, feature requests, or want to chat with the community? Join our Discord server!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Documentation
|
|
||||||
|
|
||||||
[](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
|
## 📜 Licensing
|
||||||
|
|
||||||
BentoPDF is dual-licensed:
|
BentoPDF is dual-licensed:
|
||||||
@@ -48,18 +27,6 @@ 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!
|
|
||||||
|
|
||||||
[](https://github.com/sponsors/alam00000)
|
|
||||||
[](https://ko-fi.com/alio01)
|
|
||||||
|
|
||||||
<!-- sponsors -->
|
|
||||||
<!-- sponsors -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ Why BentoPDF?
|
## ✨ Why BentoPDF?
|
||||||
|
|
||||||
- **Privacy First**: All processing happens in your browser. Your files are never uploaded to a server, guaranteeing 100% privacy.
|
- **Privacy First**: All processing happens in your browser. Your files are never uploaded to a server, guaranteeing 100% privacy.
|
||||||
@@ -135,7 +102,6 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs.
|
|||||||
| **TIFF to PDF** | Convert TIFF images to PDF. |
|
| **TIFF to PDF** | Convert TIFF images to PDF. |
|
||||||
| **Markdown to PDF** | Convert `.md` files into professional PDF documents. |
|
| **Markdown to PDF** | Convert `.md` files into professional PDF documents. |
|
||||||
| **Text to PDF** | Convert plain text files into a PDF. |
|
| **Text to PDF** | Convert plain text files into a PDF. |
|
||||||
| **EPUB to PDF** | Convert EPUB e-books to PDF format. |
|
|
||||||
| **JSON to PDF** | Convert JSON to PDF. |
|
| **JSON to PDF** | Convert JSON to PDF. |
|
||||||
|
|
||||||
### Convert from PDF
|
### Convert from PDF
|
||||||
@@ -173,21 +139,6 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌍 Translations
|
|
||||||
|
|
||||||
BentoPDF is available in multiple languages:
|
|
||||||
|
|
||||||
| Language | Status |
|
|
||||||
|------------|--------|
|
|
||||||
| English | [](public/locales/en/common.json) |
|
|
||||||
| German | [](public/locales/de/common.json) |
|
|
||||||
| Vietnamese | [](public/locales/vi/common.json) |
|
|
||||||
| Chinese | [](public/locales/zh/common.json) |
|
|
||||||
|
|
||||||
Want to help translate BentoPDF into your language? Check out our [Translation Guide](TRANSLATION.md)!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Getting Started
|
## 🚀 Getting Started
|
||||||
|
|
||||||
You can run BentoPDF locally for development or personal use.
|
You can run BentoPDF locally for development or personal use.
|
||||||
@@ -223,11 +174,7 @@ Open your browser at: http://localhost:3000
|
|||||||
|
|
||||||
This is the fastest way to try BentoPDF without setting up a development environment.
|
This is the fastest way to try BentoPDF without setting up a development environment.
|
||||||
|
|
||||||
### Static Hosting using Netlify, Vercel, and GitHub Pages
|
### 🏠 Self-Hosting
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -240,19 +187,47 @@ The easiest way to self-host is to download the pre-built distribution file from
|
|||||||
3. Extract the zip file
|
3. Extract the zip file
|
||||||
4. Serve the extracted folder with your preferred web server
|
4. Serve the extracted folder with your preferred web server
|
||||||
|
|
||||||
**Serve the extracted folder (requires Node.js):**
|
**Navigate to the extracted Folder:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Navigate to the extracted folder
|
# Navigate to the extracted folder
|
||||||
cd dist-1.7.3 # Replace with your version
|
cd dist-1.7.3 # Replace with your version
|
||||||
|
|
||||||
# Start a local server
|
|
||||||
npx http-server -c-1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The website will be accessible at: `http://localhost:8080/`
|
**Serve with Python:**
|
||||||
|
|
||||||
> **Note:** The `-c-1` flag disables caching for development.
|
```bash
|
||||||
|
# For Python 3
|
||||||
|
python -m http.server 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
The website can be accessible at: ```http://[::1]:8000/```
|
||||||
|
|
||||||
|
**Serve with Node.js:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install a simple server
|
||||||
|
npx serve .
|
||||||
|
|
||||||
|
# Or if you have serve installed globally
|
||||||
|
npm install -g serve
|
||||||
|
serve .
|
||||||
|
```
|
||||||
|
|
||||||
|
The website can be accessible at: ```http://localhost:3000/```
|
||||||
|
|
||||||
|
**Serve with other tools:**
|
||||||
|
|
||||||
|
You can also use other static file servers like:
|
||||||
|
- Go: `go run main.go` with a simple Go server
|
||||||
|
- PHP: `php -S localhost:8000`
|
||||||
|
- Ruby: `ruby -run -e httpd . -p 8000`
|
||||||
|
|
||||||
|
The website can be accessible at:
|
||||||
|
- Go: ```http://localhost:8080/``` (default) or as specified
|
||||||
|
- PHP: ```http://localhost:8000/```
|
||||||
|
- Ruby: ```http://localhost:8000/``` (default port can be changed)
|
||||||
|
|
||||||
|
Simply serve the extracted folder using any static file server, and BentoPDF will work completely client-side without any server-side dependencies.
|
||||||
|
|
||||||
**Build from Source (Advanced):**
|
**Build from Source (Advanced):**
|
||||||
|
|
||||||
@@ -272,42 +247,13 @@ npm run build
|
|||||||
# Package the distribution for hosting (optional)
|
# Package the distribution for hosting (optional)
|
||||||
npm run package
|
npm run package
|
||||||
|
|
||||||
# Preview the build locally
|
# Serve the dist folder
|
||||||
npm run preview
|
npx serve dist
|
||||||
|
|
||||||
# The website will be accessible at: http://localhost:4173/
|
The website can be accessible at: http://localhost:3000/
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**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) |
|
|
||||||
|
|
||||||
**Subdirectory Hosting:**
|
**Subdirectory Hosting:**
|
||||||
|
|
||||||
BentoPDF can also be hosted from a subdirectory (e.g., `example.com/tools/bentopdf/`):
|
BentoPDF can also be hosted from a subdirectory (e.g., `example.com/tools/bentopdf/`):
|
||||||
@@ -333,36 +279,6 @@ 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.
|
The `npm run package` command creates a `dist-{version}.zip` file that you can use for self-hosting.
|
||||||
|
|
||||||
**Docker Subdirectory Deployment:**
|
|
||||||
|
|
||||||
BentoPDF's Docker image also supports the `BASE_URL` build argument for subdirectory deployments:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build for subdirectory deployment
|
|
||||||
docker build --build-arg BASE_URL=/bentopdf/ -t bentopdf .
|
|
||||||
|
|
||||||
# Run the container
|
|
||||||
docker run -p 3000:8080 bentopdf
|
|
||||||
|
|
||||||
# The app will be accessible at http://localhost:3000/bentopdf/
|
|
||||||
```
|
|
||||||
|
|
||||||
**Combined with Simple Mode:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build with both BASE_URL and SIMPLE_MODE
|
|
||||||
docker build \
|
|
||||||
--build-arg BASE_URL=/tools/pdf/ \
|
|
||||||
--build-arg SIMPLE_MODE=true \
|
|
||||||
-t bentopdf-simple .
|
|
||||||
|
|
||||||
docker run -p 3000:8080 bentopdf-simple
|
|
||||||
```
|
|
||||||
|
|
||||||
> **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 (Recommended)
|
||||||
|
|
||||||
For a more robust setup with auto-restart capabilities:
|
For a more robust setup with auto-restart capabilities:
|
||||||
@@ -521,7 +437,7 @@ BentoPDF was originally built using **HTML**, **CSS**, and **vanilla JavaScript*
|
|||||||
- **PDF to Office**: Converts PDF files into editable Word, Excel, and PowerPoint formats.
|
- **PDF to Office**: Converts PDF files into editable Word, Excel, and PowerPoint formats.
|
||||||
- **Office to PDF**: Converts Word, Excel, and PowerPoint documents into optimized PDFs.
|
- **Office to PDF**: Converts Word, Excel, and PowerPoint documents into optimized PDFs.
|
||||||
|
|
||||||
Contributions and discussions on the roadmap are welcome! Join the conversation via [Discord](https://discord.gg/Bgq3Ay3f2w).
|
Contributions and discussions on the roadmap are welcome! Join the conversation via [Discord](https://discord.gg/AP2Y97juZT).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -536,32 +452,6 @@ 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.
|
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
|
## Special Thanks
|
||||||
@@ -577,8 +467,5 @@ BentoPDF wouldn't be possible without the amazing open-source tools and librarie
|
|||||||
- **[Tailwind CSS](https://tailwindcss.com/)** – For rapid, flexible, and beautiful UI styling.
|
- **[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
|
- **[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.
|
- **[cpdf](https://www.coherentpdf.com/)** – For content preserving pdf operations.
|
||||||
- **[LibreOffice](https://www.libreoffice.org/)** – For powerful document conversion capabilities.
|
|
||||||
- **[PyMuPDF](https://github.com/pymupdf/PyMuPDF)** – For high-performance PDF manipulation and data extraction.
|
|
||||||
- **[Ghostscript(GhostPDL)](https://github.com/ArtifexSoftware/ghostpdl)** – Needs no Introduction.
|
|
||||||
|
|
||||||
Your work inspires and empowers developers everywhere. Thank you for making open-source amazing!
|
Your work inspires and empowers developers everywhere. Thank you for making open-source amazing!
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
# 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.
|
|
||||||
-482
@@ -1,482 +0,0 @@
|
|||||||
# 🌍 Translation Guide for BentoPDF
|
|
||||||
|
|
||||||
This guide will help you add new languages or improve existing translations for BentoPDF.
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
- [Overview](#overview)
|
|
||||||
- [Quick Start](#quick-start)
|
|
||||||
- [Adding a New Language](#adding-a-new-language)
|
|
||||||
- [Translation File Structure](#translation-file-structure)
|
|
||||||
- [Where Translations Are Used](#where-translations-are-used)
|
|
||||||
- [Testing Your Translations](#testing-your-translations)
|
|
||||||
- [Translation Guidelines](#translation-guidelines)
|
|
||||||
- [Common Issues](#common-issues)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
BentoPDF uses **i18next** for internationalization (i18n). Currently supported languages:
|
|
||||||
|
|
||||||
- **English** (`en`) - Default
|
|
||||||
- **German** (`de`)
|
|
||||||
- **Vietnamese** (`vi`)
|
|
||||||
|
|
||||||
The app automatically detects the language from the URL path:
|
|
||||||
- `/en/` → English
|
|
||||||
- `/de/` → German
|
|
||||||
- `/vi/` → Vietnamese
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
**To improve existing translations:**
|
|
||||||
|
|
||||||
1. Navigate to `public/locales/{language}/common.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):**
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Adding a New Language
|
|
||||||
|
|
||||||
Let's add **French** as an example:
|
|
||||||
|
|
||||||
### Step 1: Create Translation File
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create the directory
|
|
||||||
mkdir -p public/locales/fr
|
|
||||||
|
|
||||||
# Copy the English template
|
|
||||||
cp public/locales/en/common.json public/locales/fr/common.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Translate the JSON File
|
|
||||||
|
|
||||||
Open `public/locales/fr/common.json` and translate all the values:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nav": {
|
|
||||||
"home": "Accueil",
|
|
||||||
"about": "À propos",
|
|
||||||
"contact": "Contact",
|
|
||||||
"allTools": "Tous les outils"
|
|
||||||
},
|
|
||||||
"hero": {
|
|
||||||
"title": "Votre boîte à outils PDF gratuite et sécurisée",
|
|
||||||
"subtitle": "Fusionnez, divisez, compressez et modifiez des PDF directement dans votre navigateur."
|
|
||||||
}
|
|
||||||
// ... continue translating all keys
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
⚠️ **Important**: Only translate the **values**, NOT the keys!
|
|
||||||
|
|
||||||
✅ **Correct:**
|
|
||||||
```json
|
|
||||||
"home": "Accueil"
|
|
||||||
```
|
|
||||||
|
|
||||||
❌ **Wrong:**
|
|
||||||
```json
|
|
||||||
"accueil": "Accueil"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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 type SupportedLanguage = (typeof supportedLanguages)[number];
|
|
||||||
|
|
||||||
// Add French display name
|
|
||||||
export const languageNames: Record<SupportedLanguage, string> = {
|
|
||||||
en: 'English',
|
|
||||||
de: 'Deutsch',
|
|
||||||
fr: 'Français', // ← Add this
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Test Your Translation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start the dev server
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# Visit the French version
|
|
||||||
# http://localhost:5173/fr/
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Translation File Structure
|
|
||||||
|
|
||||||
The `common.json` file is organized into logical sections:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nav": {
|
|
||||||
// Navigation menu items
|
|
||||||
},
|
|
||||||
"hero": {
|
|
||||||
// Homepage hero section
|
|
||||||
},
|
|
||||||
"features": {
|
|
||||||
// Features section
|
|
||||||
},
|
|
||||||
"tools": {
|
|
||||||
// Tool names and descriptions
|
|
||||||
},
|
|
||||||
"upload": {
|
|
||||||
// File upload UI
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
// Settings modal and keyboard shortcuts
|
|
||||||
},
|
|
||||||
"faq": {
|
|
||||||
// FAQ section
|
|
||||||
},
|
|
||||||
"footer": {
|
|
||||||
// Footer links and text
|
|
||||||
},
|
|
||||||
"compliance": {
|
|
||||||
// Security compliance information
|
|
||||||
},
|
|
||||||
"testimonials": {
|
|
||||||
// User testimonials
|
|
||||||
},
|
|
||||||
"support": {
|
|
||||||
// Support section
|
|
||||||
},
|
|
||||||
"alert": {
|
|
||||||
// Alert and error messages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Naming Convention
|
|
||||||
|
|
||||||
- Use **camelCase** for keys: `"deletePage"` not `"delete_page"`
|
|
||||||
- Use **nested objects** for organization: `"nav.home"` is represented as:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nav": {
|
|
||||||
"home": "Home"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- Be descriptive: `"shortcutsWarning"` is better than `"warning1"`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Where Translations Are Used
|
|
||||||
|
|
||||||
### 1. HTML Templates (`data-i18n` attribute)
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- Translation key: nav.home -->
|
|
||||||
<a href="/" data-i18n="nav.home">Home</a>
|
|
||||||
```
|
|
||||||
|
|
||||||
The `data-i18n` attribute tells i18next which translation to use.
|
|
||||||
|
|
||||||
### 2. Tool Definitions
|
|
||||||
|
|
||||||
Tool names and descriptions are defined in `src/js/config/tools.ts` and use a special namespace:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
name: 'Merge PDF', // Used for shortcuts only
|
|
||||||
subtitle: 'Combine multiple PDFs into one file.',
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In translations:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tools": {
|
|
||||||
"mergePdf": {
|
|
||||||
"name": "PDF zusammenführen",
|
|
||||||
"subtitle": "Mehrere PDFs in eine Datei kombinieren."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Dynamic JavaScript (`t()` function)
|
|
||||||
|
|
||||||
For translations that need to be applied dynamically:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { t } from './i18n/i18n';
|
|
||||||
|
|
||||||
const message = t('alert.error');
|
|
||||||
console.log(message); // "Error" or "Fehler" depending on language
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Placeholders
|
|
||||||
|
|
||||||
For input placeholders:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search for a tool..."
|
|
||||||
data-i18n-placeholder="tools.searchPlaceholder"
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
In `common.json`:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tools": {
|
|
||||||
"searchPlaceholder": "Nach einem Tool suchen..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Your Translations
|
|
||||||
|
|
||||||
### Manual Testing
|
|
||||||
|
|
||||||
1. **Start development server:**
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Visit each language:**
|
|
||||||
- English: `http://localhost:5173/en/`
|
|
||||||
- German: `http://localhost:5173/de/`
|
|
||||||
- Vietnamese: `http://localhost:5173/vi/`
|
|
||||||
- Your new language: `http://localhost:5173/fr/`
|
|
||||||
|
|
||||||
3. **Check these pages:**
|
|
||||||
- Homepage (`/`)
|
|
||||||
- About page (`/about.html`)
|
|
||||||
- Contact page (`/contact.html`)
|
|
||||||
- FAQ page (`/faq.html`)
|
|
||||||
- Tool pages (e.g., `/merge-pdf.html`)
|
|
||||||
|
|
||||||
4. **Test these interactions:**
|
|
||||||
- Click the language switcher in the footer
|
|
||||||
- Navigate between pages
|
|
||||||
- Open the settings modal (click gear icon next to search)
|
|
||||||
- Try a tool to see upload messages
|
|
||||||
|
|
||||||
### Automated Checks
|
|
||||||
|
|
||||||
Check for missing translations:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# This will show any missing keys
|
|
||||||
node scripts/check-translations.js
|
|
||||||
```
|
|
||||||
|
|
||||||
*(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
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Translation Guidelines
|
|
||||||
|
|
||||||
### 1. Keep the Tone Consistent
|
|
||||||
|
|
||||||
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"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Preserve Formatting
|
|
||||||
|
|
||||||
Some strings contain HTML or special characters:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"faq.analytics.answer": "We care about your privacy. BentoPDF does not track personal information. We use <a href=\"https://simpleanalytics.com\" class=\"text-indigo-400 hover:underline\" target=\"_blank\" rel=\"noopener noreferrer\">Simple Analytics</a> solely to see anonymous visit counts."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When translating, **keep the HTML tags intact**:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"faq.analytics.answer": "Wir schätzen Ihre Privatsphäre. BentoPDF verfolgt keine persönlichen Informationen. Wir verwenden <a href=\"https://simpleanalytics.com\" class=\"text-indigo-400 hover:underline\" target=\"_blank\" rel=\"noopener noreferrer\">Simple Analytics</a> ausschließlich, um anonyme Besucherzahlen zu sehen."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Handle Plurals and Gender
|
|
||||||
|
|
||||||
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",
|
|
||||||
"pages_plural": "pages"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Don't Translate Brand Names or Legal Terms
|
|
||||||
|
|
||||||
Keep these as-is:
|
|
||||||
- BentoPDF
|
|
||||||
- PDF
|
|
||||||
- GitHub
|
|
||||||
- Discord
|
|
||||||
- Chrome, Firefox, Safari, etc.
|
|
||||||
- Terms and Conditions
|
|
||||||
- Privacy Policy
|
|
||||||
- Licensing
|
|
||||||
|
|
||||||
### 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)
|
|
||||||
|
|
||||||
If unsure, check how other PDF tools translate these terms in your language.
|
|
||||||
|
|
||||||
### 6. String Length
|
|
||||||
|
|
||||||
Some UI elements have limited space. Try to keep translations **similar in length** to the English version.
|
|
||||||
|
|
||||||
If a translation is much longer, test it visually to ensure it doesn't break the layout.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Common Issues
|
|
||||||
|
|
||||||
### 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
|
|
||||||
4. Verify the JSON file is valid (no syntax errors)
|
|
||||||
|
|
||||||
### 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
|
|
||||||
- Missing or extra quotes
|
|
||||||
- Unescaped quotes inside strings (use `\"`)
|
|
||||||
|
|
||||||
### Issue: Language Switcher Not Showing New Language
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
Make sure you added the language to both arrays in `i18n.ts`:
|
|
||||||
```typescript
|
|
||||||
export const supportedLanguages = ['en', 'de', 'fr']; // ← Add here
|
|
||||||
export const languageNames = {
|
|
||||||
en: 'English',
|
|
||||||
de: 'Deutsch',
|
|
||||||
fr: 'Français', // ← And here
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Checklist
|
|
||||||
|
|
||||||
When adding a new language, make sure these files are updated:
|
|
||||||
|
|
||||||
- [ ] `public/locales/{lang}/common.json` - Main translation file
|
|
||||||
- [ ] `src/js/i18n/i18n.ts` - Add to `supportedLanguages` and `languageNames`
|
|
||||||
- [ ] Test all pages: homepage, about, contact, FAQ, tool pages
|
|
||||||
- [ ] Test settings modal and shortcuts
|
|
||||||
- [ ] Test language switcher in footer
|
|
||||||
- [ ] Verify URL routing works (`/{lang}/`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Getting Help
|
|
||||||
|
|
||||||
If you have questions or need help:
|
|
||||||
|
|
||||||
1. Check existing translations in `public/locales/de/common.json` for reference
|
|
||||||
2. Open an issue on [GitHub](https://github.com/alam00000/bentopdf/issues)
|
|
||||||
3. Join our [Discord server](https://discord.gg/Bgq3Ay3f2w)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contributing Your Translation
|
|
||||||
|
|
||||||
Once you've completed a translation:
|
|
||||||
|
|
||||||
1. **Test thoroughly** (see [Testing Your Translations](#testing-your-translations))
|
|
||||||
2. **Fork the repository** on GitHub
|
|
||||||
3. **Create a new branch**: `git checkout -b add-french-translation`
|
|
||||||
4. **Commit your changes**: `git commit -m "Add French translation"`
|
|
||||||
5. **Push to your fork**: `git push origin add-french-translation`
|
|
||||||
6. **Open a Pull Request** with:
|
|
||||||
- Description of the language added
|
|
||||||
- Screenshots showing the translation in action
|
|
||||||
- Confirmation that you've tested all pages
|
|
||||||
|
|
||||||
Thank you for contributing to BentoPDF! 🎉
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Translation Progress
|
|
||||||
|
|
||||||
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? |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Last Updated**: December 2025
|
|
||||||
+47
-58
@@ -5,14 +5,6 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>About Bentopdf - Fast, Private, and Free PDF Tools</title>
|
<title>About Bentopdf - Fast, Private, and Free PDF Tools</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
|
||||||
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
|
||||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
||||||
<link rel="alternate" hreflang="en" href="/en/about.html" />
|
|
||||||
<link rel="alternate" hreflang="de" href="/de/about.html" />
|
|
||||||
<link rel="alternate" hreflang="vi" href="/vi/about.html" />
|
|
||||||
<link rel="alternate" hreflang="x-default" href="/en/about.html" />
|
|
||||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -21,7 +13,10 @@
|
|||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex justify-between items-center h-16">
|
<div class="flex justify-between items-center h-16">
|
||||||
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8" />
|
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||||
|
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||||
|
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
<span class="text-white font-bold text-xl ml-2">
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
<a href="index.html">BentoPDF</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
</span>
|
</span>
|
||||||
@@ -29,11 +24,11 @@
|
|||||||
|
|
||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
<div class="hidden md:flex items-center space-x-8">
|
||||||
<a href="index.html" class="nav-link" data-i18n="nav.home">Home</a>
|
<a href="index.html" class="nav-link">Home</a>
|
||||||
<a href="/about.html" class="nav-link" data-i18n="nav.about">About</a>
|
<a href="./about.html" class="nav-link">About</a>
|
||||||
<a href="/contact.html" class="nav-link" data-i18n="nav.contact">Contact</a>
|
<a href="./contact.html" class="nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="nav-link" data-i18n="nav.allTools">All Tools</a>
|
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Hamburger Button -->
|
<!-- Mobile Hamburger Button -->
|
||||||
@@ -60,11 +55,11 @@
|
|||||||
<!-- Mobile Menu Dropdown -->
|
<!-- Mobile Menu Dropdown -->
|
||||||
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
||||||
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
||||||
<a href="index.html" class="mobile-nav-link" data-i18n="nav.home">Home</a>
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about">About</a>
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
<a href="/contact.html" class="mobile-nav-link" data-i18n="nav.contact">Contact</a>
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="mobile-nav-link" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="mobile-nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="mobile-nav-link" data-i18n="nav.allTools">All Tools</a>
|
<a href="index.html#tools-header" class="mobile-nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -72,10 +67,10 @@
|
|||||||
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
||||||
<section id="about-hero" class="text-center py-16 md:py-24">
|
<section id="about-hero" class="text-center py-16 md:py-24">
|
||||||
<h1 class="text-3xl md:text-6xl font-bold text-white mb-4">
|
<h1 class="text-3xl md:text-6xl font-bold text-white mb-4">
|
||||||
<span data-i18n="about.hero.title">We believe PDF tools should be</span>
|
We believe PDF tools should be
|
||||||
<span class="marker-slanted" data-i18n="about.hero.subtitle">fast, private, and free.</span>
|
<span class="marker-slanted">fast, private, and free.</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-lg md:text-xl text-gray-400" data-i18n="about.hero.noCompromises">No compromises.</p>
|
<p class="text-lg md:text-xl text-gray-400">No compromises.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="section-divider"></div>
|
<div class="section-divider"></div>
|
||||||
@@ -83,10 +78,10 @@
|
|||||||
<section id="mission-section" class="py-16 max-w-4xl mx-auto">
|
<section id="mission-section" class="py-16 max-w-4xl mx-auto">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<i data-lucide="rocket" class="w-16 h-16 text-indigo-400 mx-auto mb-6"></i>
|
<i data-lucide="rocket" class="w-16 h-16 text-indigo-400 mx-auto mb-6"></i>
|
||||||
<h2 class="text-3xl md:text-4xl font-bold text-white mb-4" data-i18n="about.mission.title">
|
<h2 class="text-3xl md:text-4xl font-bold text-white mb-4">
|
||||||
Our Mission
|
Our Mission
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-lg text-gray-400 leading-relaxed" data-i18n="about.mission.description">
|
<p class="text-lg text-gray-400 leading-relaxed">
|
||||||
To provide the most comprehensive PDF toolbox that respects your
|
To provide the most comprehensive PDF toolbox that respects your
|
||||||
privacy and never asks for payment. We believe essential document
|
privacy and never asks for payment. We believe essential document
|
||||||
tools should be accessible to everyone, everywhere, without
|
tools should be accessible to everyone, everywhere, without
|
||||||
@@ -98,12 +93,11 @@
|
|||||||
<div class="bg-gray-800 rounded-xl p-8 md:p-12 my-16 border border-gray-700">
|
<div class="bg-gray-800 rounded-xl p-8 md:p-12 my-16 border border-gray-700">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||||
<div class="text-center md:text-left">
|
<div class="text-center md:text-left">
|
||||||
<span class="text-indigo-400 font-bold uppercase" data-i18n="about.philosophy.label">Our Core
|
<span class="text-indigo-400 font-bold uppercase">Our Core Philosophy</span>
|
||||||
Philosophy</span>
|
<h2 class="text-3xl md:text-4xl font-bold text-white mt-2 mb-4">
|
||||||
<h2 class="text-3xl md:text-4xl font-bold text-white mt-2 mb-4" data-i18n="about.philosophy.title">
|
|
||||||
Privacy First. Always.
|
Privacy First. Always.
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-400 leading-relaxed" data-i18n="about.philosophy.description">
|
<p class="text-gray-400 leading-relaxed">
|
||||||
In an era where data is a commodity, we take a different approach.
|
In an era where data is a commodity, we take a different approach.
|
||||||
All processing for Bentopdf tools happens locally in your browser.
|
All processing for Bentopdf tools happens locally in your browser.
|
||||||
This means your files never touch our servers, we never see your
|
This means your files never touch our servers, we never see your
|
||||||
@@ -124,14 +118,14 @@
|
|||||||
|
|
||||||
<section id="why-Bentopdf" class="py-16">
|
<section id="why-Bentopdf" class="py-16">
|
||||||
<h2 class="text-3xl md:text-4xl font-bold text-center text-white mb-12">
|
<h2 class="text-3xl md:text-4xl font-bold text-center text-white mb-12">
|
||||||
<span data-i18n="about.whyBentopdf.title">Why</span> <span class="marker-slanted">Bentopdf?</span>
|
Why <span class="marker-slanted">Bentopdf?</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
|
||||||
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
||||||
<i data-lucide="zap" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
<i data-lucide="zap" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl font-bold text-white" data-i18n="about.whyBentopdf.speed.title">Built for Speed</h3>
|
<h3 class="text-xl font-bold text-white">Built for Speed</h3>
|
||||||
<p class="text-gray-400 mt-2" data-i18n="about.whyBentopdf.speed.description">
|
<p class="text-gray-400 mt-2">
|
||||||
No waiting for uploads or downloads to a server. By processing
|
No waiting for uploads or downloads to a server. By processing
|
||||||
files directly in your browser using modern web technologies
|
files directly in your browser using modern web technologies
|
||||||
like WebAssembly, we offer unparalleled speed for all our tools.
|
like WebAssembly, we offer unparalleled speed for all our tools.
|
||||||
@@ -141,8 +135,8 @@
|
|||||||
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
||||||
<i data-lucide="badge-dollar-sign" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
<i data-lucide="badge-dollar-sign" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl font-bold text-white" data-i18n="about.whyBentopdf.free.title">Completely Free</h3>
|
<h3 class="text-xl font-bold text-white">Completely Free</h3>
|
||||||
<p class="text-gray-400 mt-2" data-i18n="about.whyBentopdf.free.description">
|
<p class="text-gray-400 mt-2">
|
||||||
No trials, no subscriptions, no hidden fees, and no "premium"
|
No trials, no subscriptions, no hidden fees, and no "premium"
|
||||||
features held hostage. We believe powerful PDF tools should be a
|
features held hostage. We believe powerful PDF tools should be a
|
||||||
public utility, not a profit center.
|
public utility, not a profit center.
|
||||||
@@ -152,9 +146,8 @@
|
|||||||
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
||||||
<i data-lucide="user-plus" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
<i data-lucide="user-plus" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl font-bold text-white" data-i18n="about.whyBentopdf.noAccount.title">No Account Required
|
<h3 class="text-xl font-bold text-white">No Account Required</h3>
|
||||||
</h3>
|
<p class="text-gray-400 mt-2">
|
||||||
<p class="text-gray-400 mt-2" data-i18n="about.whyBentopdf.noAccount.description">
|
|
||||||
Start using any tool immediately. We don't need your email, a
|
Start using any tool immediately. We don't need your email, a
|
||||||
password, or any personal information. Your workflow should be
|
password, or any personal information. Your workflow should be
|
||||||
frictionless and anonymous.
|
frictionless and anonymous.
|
||||||
@@ -164,9 +157,8 @@
|
|||||||
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
||||||
<i data-lucide="code-2" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
<i data-lucide="code-2" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl font-bold text-white" data-i18n="about.whyBentopdf.openSource.title">Open Source Spirit
|
<h3 class="text-xl font-bold text-white">Open Source Spirit</h3>
|
||||||
</h3>
|
<p class="text-gray-400 mt-2">
|
||||||
<p class="text-gray-400 mt-2" data-i18n="about.whyBentopdf.openSource.description">
|
|
||||||
Built with transparency in mind. We leverage incredible
|
Built with transparency in mind. We leverage incredible
|
||||||
open-source libraries like PDF-lib and PDF.js, and believe in
|
open-source libraries like PDF-lib and PDF.js, and believe in
|
||||||
the community-driven effort to make powerful tools accessible to
|
the community-driven effort to make powerful tools accessible to
|
||||||
@@ -180,17 +172,16 @@
|
|||||||
<div class="section-divider"></div>
|
<div class="section-divider"></div>
|
||||||
|
|
||||||
<section id="cta-section" class="text-center py-16">
|
<section id="cta-section" class="text-center py-16">
|
||||||
<h2 class="text-3xl font-bold text-white mb-4" data-i18n="about.cta.title">
|
<h2 class="text-3xl font-bold text-white mb-4">
|
||||||
Ready to get started?
|
Ready to get started?
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-lg text-gray-400 mb-8 max-w-2xl mx-auto" data-i18n="about.cta.description">
|
<p class="text-lg text-gray-400 mb-8 max-w-2xl mx-auto">
|
||||||
Join thousands of users who trust Bentopdf for their daily document
|
Join thousands of users who trust Bentopdf for their daily document
|
||||||
needs. Experience the difference that privacy and performance can
|
needs. Experience the difference that privacy and performance can
|
||||||
make.
|
make.
|
||||||
</p>
|
</p>
|
||||||
<a href="index.html#tools-header"
|
<a href="index.html#tools-header"
|
||||||
class="inline-block px-8 py-3 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1"
|
class="inline-block px-8 py-3 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1">
|
||||||
data-i18n="about.cta.button">
|
|
||||||
Explore All Tools
|
Explore All Tools
|
||||||
</a>
|
</a>
|
||||||
</section>
|
</section>
|
||||||
@@ -201,50 +192,49 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
||||||
<div class="mb-8 md:mb-0">
|
<div class="mb-8 md:mb-0">
|
||||||
<div class="flex items-center justify-center md:justify-start mb-4">
|
<div class="flex items-center justify-center md:justify-start mb-4">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
<img src="images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
||||||
<span class="text-xl font-bold text-white">BentoPDF</span>
|
<span class="text-xl font-bold text-white">BentoPDF</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
|
<p class="text-gray-400 text-sm">
|
||||||
© 2025 BentoPDF. All rights reserved.
|
© 2025 BentoPDF. All rights reserved.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-500 text-xs mt-2">
|
<p class="text-gray-500 text-xs mt-2">
|
||||||
<span data-i18n="footer.version">Version</span> <span id="app-version"></span>
|
Version <span id="app-version"></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">Company</h3>
|
<h3 class="font-bold text-white mb-4">Company</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/about.html" class="hover:text-indigo-400" data-i18n="footer.aboutUs">About Us</a>
|
<a href="./about.html" class="hover:text-indigo-400">About Us</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/faq.html" class="hover:text-indigo-400" data-i18n="footer.faqLink">FAQ</a>
|
<a href="./faq.html" class="hover:text-indigo-400">FAQ</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/contact.html" class="hover:text-indigo-400" data-i18n="footer.contactUs">Contact Us</a>
|
<a href="./contact.html" class="hover:text-indigo-400">Contact Us</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">Legal</h3>
|
<h3 class="font-bold text-white mb-4">Legal</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/licensing.html" class="hover:text-indigo-400" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="hover:text-indigo-400">Licensing</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/terms.html" class="hover:text-indigo-400" data-i18n="footer.termsAndConditions">Terms and
|
<a href="./terms.html" class="hover:text-indigo-400">Terms and Conditions</a>
|
||||||
Conditions</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/privacy.html" class="hover:text-indigo-400" data-i18n="footer.privacyPolicy">Privacy Policy</a>
|
<a href="./privacy.html" class="hover:text-indigo-400">Privacy Policy</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">Follow Us</h3>
|
<h3 class="font-bold text-white mb-4">Follow Us</h3>
|
||||||
<div class="flex justify-center md:justify-start space-x-4">
|
<div class="flex justify-center md:justify-start space-x-4">
|
||||||
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
||||||
class="text-gray-400 hover:text-indigo-400" title="GitHub">
|
class="text-gray-400 hover:text-indigo-400" title="GitHub">
|
||||||
@@ -254,7 +244,7 @@
|
|||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/Bgq3Ay3f2w" target="_blank" rel="noopener noreferrer"
|
<a href="https://discord.gg/AP2Y97juZT" target="_blank" rel="noopener noreferrer"
|
||||||
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
||||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<path
|
<path
|
||||||
@@ -283,7 +273,6 @@
|
|||||||
|
|
||||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||||
<script type="module" src="src/version.ts"></script>
|
<script type="module" src="src/version.ts"></script>
|
||||||
<script type="module" src="src/js/main.ts"></script>
|
|
||||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
+30
-37
@@ -5,14 +5,6 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Contact Us - BentoPDF</title>
|
<title>Contact Us - BentoPDF</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
|
||||||
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
|
||||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
||||||
<link rel="alternate" hreflang="en" href="/en/contact.html" />
|
|
||||||
<link rel="alternate" hreflang="de" href="/de/contact.html" />
|
|
||||||
<link rel="alternate" hreflang="vi" href="/vi/contact.html" />
|
|
||||||
<link rel="alternate" hreflang="x-default" href="/en/contact.html" />
|
|
||||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -21,7 +13,10 @@
|
|||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex justify-between items-center h-16">
|
<div class="flex justify-between items-center h-16">
|
||||||
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8" />
|
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||||
|
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||||
|
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
<span class="text-white font-bold text-xl ml-2">
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
<a href="index.html">BentoPDF</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
</span>
|
</span>
|
||||||
@@ -29,11 +24,11 @@
|
|||||||
|
|
||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
<div class="hidden md:flex items-center space-x-8">
|
||||||
<a href="index.html" class="nav-link" data-i18n="nav.home">Home</a>
|
<a href="index.html" class="nav-link">Home</a>
|
||||||
<a href="/about.html" class="nav-link" data-i18n="nav.about">About</a>
|
<a href="./about.html" class="nav-link">About</a>
|
||||||
<a href="/contact.html" class="nav-link" data-i18n="nav.contact">Contact</a>
|
<a href="./contact.html" class="nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="nav-link" data-i18n="nav.allTools">All Tools</a>
|
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Hamburger Button -->
|
<!-- Mobile Hamburger Button -->
|
||||||
@@ -60,21 +55,21 @@
|
|||||||
<!-- Mobile Menu Dropdown -->
|
<!-- Mobile Menu Dropdown -->
|
||||||
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
||||||
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
||||||
<a href="index.html" class="mobile-nav-link" data-i18n="nav.home">Home</a>
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about">About</a>
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
<a href="/contact.html" class="mobile-nav-link" data-i18n="nav.contact">Contact</a>
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="mobile-nav-link" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="mobile-nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="mobile-nav-link" data-i18n="nav.allTools">All Tools</a>
|
<a href="index.html#tools-header" class="mobile-nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
||||||
<section id="contact-hero" class="text-center py-16 md:py-24">
|
<section id="contact-hero" class="text-center py-16 md:py-24">
|
||||||
<h1 class="text-4xl md:text-5xl font-bold text-white mb-4" data-i18n="contact.title">
|
<h1 class="text-4xl md:text-5xl font-bold text-white mb-4">
|
||||||
Get in Touch
|
Get in Touch
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-lg md:text-xl text-gray-400 max-w-3xl mx-auto" data-i18n="contact.subtitle">
|
<p class="text-lg md:text-xl text-gray-400 max-w-3xl mx-auto">
|
||||||
We'd love to hear from you. Whether you have a question, feedback, or
|
We'd love to hear from you. Whether you have a question, feedback, or
|
||||||
a feature request, please don't hesitate to reach out.
|
a feature request, please don't hesitate to reach out.
|
||||||
</p>
|
</p>
|
||||||
@@ -82,7 +77,7 @@
|
|||||||
|
|
||||||
<div class="max-w-2xl mx-auto text-center py-8">
|
<div class="max-w-2xl mx-auto text-center py-8">
|
||||||
<p class="text-lg text-gray-400">
|
<p class="text-lg text-gray-400">
|
||||||
<span data-i18n="contact.email">You can reach us directly by email at:</span>
|
You can reach us directly by email at:
|
||||||
<a href="mailto:[email protected]"
|
<a href="mailto:[email protected]"
|
||||||
class="text-indigo-400 underline hover:text-indigo-300">[email protected]</a>
|
class="text-indigo-400 underline hover:text-indigo-300">[email protected]</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -94,50 +89,49 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
||||||
<div class="mb-8 md:mb-0">
|
<div class="mb-8 md:mb-0">
|
||||||
<div class="flex items-center justify-center md:justify-start mb-4">
|
<div class="flex items-center justify-center md:justify-start mb-4">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
<img src="images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
||||||
<span class="text-xl font-bold text-white">BentoPDF</span>
|
<span class="text-xl font-bold text-white">BentoPDF</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
|
<p class="text-gray-400 text-sm">
|
||||||
© 2025 BentoPDF. All rights reserved.
|
© 2025 BentoPDF. All rights reserved.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-500 text-xs mt-2">
|
<p class="text-gray-500 text-xs mt-2">
|
||||||
<span data-i18n="footer.version">Version</span> <span id="app-version"></span>
|
Version <span id="app-version"></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">Company</h3>
|
<h3 class="font-bold text-white mb-4">Company</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/about.html" class="hover:text-indigo-400" data-i18n="footer.aboutUs">About Us</a>
|
<a href="./about.html" class="hover:text-indigo-400">About Us</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/faq.html" class="hover:text-indigo-400" data-i18n="footer.faqLink">FAQ</a>
|
<a href="./faq.html" class="hover:text-indigo-400">FAQ</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/contact.html" class="hover:text-indigo-400" data-i18n="footer.contactUs">Contact Us</a>
|
<a href="./contact.html" class="hover:text-indigo-400">Contact Us</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">Legal</h3>
|
<h3 class="font-bold text-white mb-4">Legal</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/licensing.html" class="hover:text-indigo-400" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="hover:text-indigo-400">Licensing</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/terms.html" class="hover:text-indigo-400" data-i18n="footer.termsAndConditions">Terms and
|
<a href="./terms.html" class="hover:text-indigo-400">Terms and Conditions</a>
|
||||||
Conditions</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/privacy.html" class="hover:text-indigo-400" data-i18n="footer.privacyPolicy">Privacy Policy</a>
|
<a href="./privacy.html" class="hover:text-indigo-400">Privacy Policy</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">Follow Us</h3>
|
<h3 class="font-bold text-white mb-4">Follow Us</h3>
|
||||||
<div class="flex justify-center md:justify-start space-x-4">
|
<div class="flex justify-center md:justify-start space-x-4">
|
||||||
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
||||||
class="text-gray-400 hover:text-indigo-400" title="GitHub">
|
class="text-gray-400 hover:text-indigo-400" title="GitHub">
|
||||||
@@ -147,7 +141,7 @@
|
|||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/Bgq3Ay3f2w" target="_blank" rel="noopener noreferrer"
|
<a href="https://discord.gg/AP2Y97juZT" target="_blank" rel="noopener noreferrer"
|
||||||
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
||||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<path
|
<path
|
||||||
@@ -176,7 +170,6 @@
|
|||||||
|
|
||||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||||
<script type="module" src="src/version.ts"></script>
|
<script type="module" src="src/version.ts"></script>
|
||||||
<script type="module" src="src/js/main.ts"></script>
|
|
||||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
-275
@@ -1,275 +0,0 @@
|
|||||||
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
|
|
||||||
File diff suppressed because one or more lines are too long
-58
@@ -1,58 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-9719
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-12824
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-3
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,583 +0,0 @@
|
|||||||
import {
|
|
||||||
DefaultMagicKeysAliasMap,
|
|
||||||
StorageSerializers,
|
|
||||||
TransitionPresets,
|
|
||||||
assert,
|
|
||||||
breakpointsAntDesign,
|
|
||||||
breakpointsBootstrapV5,
|
|
||||||
breakpointsElement,
|
|
||||||
breakpointsMasterCss,
|
|
||||||
breakpointsPrimeFlex,
|
|
||||||
breakpointsQuasar,
|
|
||||||
breakpointsSematic,
|
|
||||||
breakpointsTailwind,
|
|
||||||
breakpointsVuetify,
|
|
||||||
breakpointsVuetifyV2,
|
|
||||||
breakpointsVuetifyV3,
|
|
||||||
bypassFilter,
|
|
||||||
camelize,
|
|
||||||
clamp,
|
|
||||||
cloneFnJSON,
|
|
||||||
computedAsync,
|
|
||||||
computedEager,
|
|
||||||
computedInject,
|
|
||||||
computedWithControl,
|
|
||||||
containsProp,
|
|
||||||
controlledRef,
|
|
||||||
createEventHook,
|
|
||||||
createFetch,
|
|
||||||
createFilterWrapper,
|
|
||||||
createGlobalState,
|
|
||||||
createInjectionState,
|
|
||||||
createRef,
|
|
||||||
createReusableTemplate,
|
|
||||||
createSharedComposable,
|
|
||||||
createSingletonPromise,
|
|
||||||
createTemplatePromise,
|
|
||||||
createUnrefFn,
|
|
||||||
customStorageEventName,
|
|
||||||
debounceFilter,
|
|
||||||
defaultDocument,
|
|
||||||
defaultLocation,
|
|
||||||
defaultNavigator,
|
|
||||||
defaultWindow,
|
|
||||||
executeTransition,
|
|
||||||
extendRef,
|
|
||||||
formatDate,
|
|
||||||
formatTimeAgo,
|
|
||||||
get,
|
|
||||||
getLifeCycleTarget,
|
|
||||||
getSSRHandler,
|
|
||||||
hasOwn,
|
|
||||||
hyphenate,
|
|
||||||
identity,
|
|
||||||
increaseWithUnit,
|
|
||||||
injectLocal,
|
|
||||||
invoke,
|
|
||||||
isClient,
|
|
||||||
isDef,
|
|
||||||
isDefined,
|
|
||||||
isIOS,
|
|
||||||
isObject,
|
|
||||||
isWorker,
|
|
||||||
makeDestructurable,
|
|
||||||
mapGamepadToXbox360Controller,
|
|
||||||
noop,
|
|
||||||
normalizeDate,
|
|
||||||
notNullish,
|
|
||||||
now,
|
|
||||||
objectEntries,
|
|
||||||
objectOmit,
|
|
||||||
objectPick,
|
|
||||||
onClickOutside,
|
|
||||||
onElementRemoval,
|
|
||||||
onKeyDown,
|
|
||||||
onKeyPressed,
|
|
||||||
onKeyStroke,
|
|
||||||
onKeyUp,
|
|
||||||
onLongPress,
|
|
||||||
onStartTyping,
|
|
||||||
pausableFilter,
|
|
||||||
promiseTimeout,
|
|
||||||
provideLocal,
|
|
||||||
provideSSRWidth,
|
|
||||||
pxValue,
|
|
||||||
rand,
|
|
||||||
reactify,
|
|
||||||
reactifyObject,
|
|
||||||
reactiveComputed,
|
|
||||||
reactiveOmit,
|
|
||||||
reactivePick,
|
|
||||||
refAutoReset,
|
|
||||||
refDebounced,
|
|
||||||
refDefault,
|
|
||||||
refThrottled,
|
|
||||||
refWithControl,
|
|
||||||
resolveRef,
|
|
||||||
resolveUnref,
|
|
||||||
set,
|
|
||||||
setSSRHandler,
|
|
||||||
syncRef,
|
|
||||||
syncRefs,
|
|
||||||
templateRef,
|
|
||||||
throttleFilter,
|
|
||||||
timestamp,
|
|
||||||
toArray,
|
|
||||||
toReactive,
|
|
||||||
toRef,
|
|
||||||
toRefs,
|
|
||||||
toValue,
|
|
||||||
tryOnBeforeMount,
|
|
||||||
tryOnBeforeUnmount,
|
|
||||||
tryOnMounted,
|
|
||||||
tryOnScopeDispose,
|
|
||||||
tryOnUnmounted,
|
|
||||||
unrefElement,
|
|
||||||
until,
|
|
||||||
useActiveElement,
|
|
||||||
useAnimate,
|
|
||||||
useArrayDifference,
|
|
||||||
useArrayEvery,
|
|
||||||
useArrayFilter,
|
|
||||||
useArrayFind,
|
|
||||||
useArrayFindIndex,
|
|
||||||
useArrayFindLast,
|
|
||||||
useArrayIncludes,
|
|
||||||
useArrayJoin,
|
|
||||||
useArrayMap,
|
|
||||||
useArrayReduce,
|
|
||||||
useArraySome,
|
|
||||||
useArrayUnique,
|
|
||||||
useAsyncQueue,
|
|
||||||
useAsyncState,
|
|
||||||
useBase64,
|
|
||||||
useBattery,
|
|
||||||
useBluetooth,
|
|
||||||
useBreakpoints,
|
|
||||||
useBroadcastChannel,
|
|
||||||
useBrowserLocation,
|
|
||||||
useCached,
|
|
||||||
useClipboard,
|
|
||||||
useClipboardItems,
|
|
||||||
useCloned,
|
|
||||||
useColorMode,
|
|
||||||
useConfirmDialog,
|
|
||||||
useCountdown,
|
|
||||||
useCounter,
|
|
||||||
useCssVar,
|
|
||||||
useCurrentElement,
|
|
||||||
useCycleList,
|
|
||||||
useDark,
|
|
||||||
useDateFormat,
|
|
||||||
useDebounceFn,
|
|
||||||
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,
|
|
||||||
useInterval,
|
|
||||||
useIntervalFn,
|
|
||||||
useKeyModifier,
|
|
||||||
useLastChanged,
|
|
||||||
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,
|
|
||||||
useThrottleFn,
|
|
||||||
useThrottledRefHistory,
|
|
||||||
useTimeAgo,
|
|
||||||
useTimeout,
|
|
||||||
useTimeoutFn,
|
|
||||||
useTimeoutPoll,
|
|
||||||
useTimestamp,
|
|
||||||
useTitle,
|
|
||||||
useToNumber,
|
|
||||||
useToString,
|
|
||||||
useToggle,
|
|
||||||
useTransition,
|
|
||||||
useUrlSearchParams,
|
|
||||||
useUserMedia,
|
|
||||||
useVModel,
|
|
||||||
useVModels,
|
|
||||||
useVibrate,
|
|
||||||
useVirtualList,
|
|
||||||
useWakeLock,
|
|
||||||
useWebNotification,
|
|
||||||
useWebSocket,
|
|
||||||
useWebWorker,
|
|
||||||
useWebWorkerFn,
|
|
||||||
useWindowFocus,
|
|
||||||
useWindowScroll,
|
|
||||||
useWindowSize,
|
|
||||||
watchArray,
|
|
||||||
watchAtMost,
|
|
||||||
watchDebounced,
|
|
||||||
watchDeep,
|
|
||||||
watchIgnorable,
|
|
||||||
watchImmediate,
|
|
||||||
watchOnce,
|
|
||||||
watchPausable,
|
|
||||||
watchThrottled,
|
|
||||||
watchTriggerable,
|
|
||||||
watchWithFilter,
|
|
||||||
whenever
|
|
||||||
} from "./chunk-2CLQ7TTZ.js";
|
|
||||||
import "./chunk-LE5NDSFD.js";
|
|
||||||
export {
|
|
||||||
DefaultMagicKeysAliasMap,
|
|
||||||
StorageSerializers,
|
|
||||||
TransitionPresets,
|
|
||||||
assert,
|
|
||||||
computedAsync as asyncComputed,
|
|
||||||
refAutoReset as autoResetRef,
|
|
||||||
breakpointsAntDesign,
|
|
||||||
breakpointsBootstrapV5,
|
|
||||||
breakpointsElement,
|
|
||||||
breakpointsMasterCss,
|
|
||||||
breakpointsPrimeFlex,
|
|
||||||
breakpointsQuasar,
|
|
||||||
breakpointsSematic,
|
|
||||||
breakpointsTailwind,
|
|
||||||
breakpointsVuetify,
|
|
||||||
breakpointsVuetifyV2,
|
|
||||||
breakpointsVuetifyV3,
|
|
||||||
bypassFilter,
|
|
||||||
camelize,
|
|
||||||
clamp,
|
|
||||||
cloneFnJSON,
|
|
||||||
computedAsync,
|
|
||||||
computedEager,
|
|
||||||
computedInject,
|
|
||||||
computedWithControl,
|
|
||||||
containsProp,
|
|
||||||
computedWithControl as controlledComputed,
|
|
||||||
controlledRef,
|
|
||||||
createEventHook,
|
|
||||||
createFetch,
|
|
||||||
createFilterWrapper,
|
|
||||||
createGlobalState,
|
|
||||||
createInjectionState,
|
|
||||||
reactify as createReactiveFn,
|
|
||||||
createRef,
|
|
||||||
createReusableTemplate,
|
|
||||||
createSharedComposable,
|
|
||||||
createSingletonPromise,
|
|
||||||
createTemplatePromise,
|
|
||||||
createUnrefFn,
|
|
||||||
customStorageEventName,
|
|
||||||
debounceFilter,
|
|
||||||
refDebounced as debouncedRef,
|
|
||||||
watchDebounced as debouncedWatch,
|
|
||||||
defaultDocument,
|
|
||||||
defaultLocation,
|
|
||||||
defaultNavigator,
|
|
||||||
defaultWindow,
|
|
||||||
computedEager as eagerComputed,
|
|
||||||
executeTransition,
|
|
||||||
extendRef,
|
|
||||||
formatDate,
|
|
||||||
formatTimeAgo,
|
|
||||||
get,
|
|
||||||
getLifeCycleTarget,
|
|
||||||
getSSRHandler,
|
|
||||||
hasOwn,
|
|
||||||
hyphenate,
|
|
||||||
identity,
|
|
||||||
watchIgnorable as ignorableWatch,
|
|
||||||
increaseWithUnit,
|
|
||||||
injectLocal,
|
|
||||||
invoke,
|
|
||||||
isClient,
|
|
||||||
isDef,
|
|
||||||
isDefined,
|
|
||||||
isIOS,
|
|
||||||
isObject,
|
|
||||||
isWorker,
|
|
||||||
makeDestructurable,
|
|
||||||
mapGamepadToXbox360Controller,
|
|
||||||
noop,
|
|
||||||
normalizeDate,
|
|
||||||
notNullish,
|
|
||||||
now,
|
|
||||||
objectEntries,
|
|
||||||
objectOmit,
|
|
||||||
objectPick,
|
|
||||||
onClickOutside,
|
|
||||||
onElementRemoval,
|
|
||||||
onKeyDown,
|
|
||||||
onKeyPressed,
|
|
||||||
onKeyStroke,
|
|
||||||
onKeyUp,
|
|
||||||
onLongPress,
|
|
||||||
onStartTyping,
|
|
||||||
pausableFilter,
|
|
||||||
watchPausable as pausableWatch,
|
|
||||||
promiseTimeout,
|
|
||||||
provideLocal,
|
|
||||||
provideSSRWidth,
|
|
||||||
pxValue,
|
|
||||||
rand,
|
|
||||||
reactify,
|
|
||||||
reactifyObject,
|
|
||||||
reactiveComputed,
|
|
||||||
reactiveOmit,
|
|
||||||
reactivePick,
|
|
||||||
refAutoReset,
|
|
||||||
refDebounced,
|
|
||||||
refDefault,
|
|
||||||
refThrottled,
|
|
||||||
refWithControl,
|
|
||||||
resolveRef,
|
|
||||||
resolveUnref,
|
|
||||||
set,
|
|
||||||
setSSRHandler,
|
|
||||||
syncRef,
|
|
||||||
syncRefs,
|
|
||||||
templateRef,
|
|
||||||
throttleFilter,
|
|
||||||
refThrottled as throttledRef,
|
|
||||||
watchThrottled as throttledWatch,
|
|
||||||
timestamp,
|
|
||||||
toArray,
|
|
||||||
toReactive,
|
|
||||||
toRef,
|
|
||||||
toRefs,
|
|
||||||
toValue,
|
|
||||||
tryOnBeforeMount,
|
|
||||||
tryOnBeforeUnmount,
|
|
||||||
tryOnMounted,
|
|
||||||
tryOnScopeDispose,
|
|
||||||
tryOnUnmounted,
|
|
||||||
unrefElement,
|
|
||||||
until,
|
|
||||||
useActiveElement,
|
|
||||||
useAnimate,
|
|
||||||
useArrayDifference,
|
|
||||||
useArrayEvery,
|
|
||||||
useArrayFilter,
|
|
||||||
useArrayFind,
|
|
||||||
useArrayFindIndex,
|
|
||||||
useArrayFindLast,
|
|
||||||
useArrayIncludes,
|
|
||||||
useArrayJoin,
|
|
||||||
useArrayMap,
|
|
||||||
useArrayReduce,
|
|
||||||
useArraySome,
|
|
||||||
useArrayUnique,
|
|
||||||
useAsyncQueue,
|
|
||||||
useAsyncState,
|
|
||||||
useBase64,
|
|
||||||
useBattery,
|
|
||||||
useBluetooth,
|
|
||||||
useBreakpoints,
|
|
||||||
useBroadcastChannel,
|
|
||||||
useBrowserLocation,
|
|
||||||
useCached,
|
|
||||||
useClipboard,
|
|
||||||
useClipboardItems,
|
|
||||||
useCloned,
|
|
||||||
useColorMode,
|
|
||||||
useConfirmDialog,
|
|
||||||
useCountdown,
|
|
||||||
useCounter,
|
|
||||||
useCssVar,
|
|
||||||
useCurrentElement,
|
|
||||||
useCycleList,
|
|
||||||
useDark,
|
|
||||||
useDateFormat,
|
|
||||||
refDebounced as useDebounce,
|
|
||||||
useDebounceFn,
|
|
||||||
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,
|
|
||||||
useInterval,
|
|
||||||
useIntervalFn,
|
|
||||||
useKeyModifier,
|
|
||||||
useLastChanged,
|
|
||||||
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,
|
|
||||||
refThrottled as useThrottle,
|
|
||||||
useThrottleFn,
|
|
||||||
useThrottledRefHistory,
|
|
||||||
useTimeAgo,
|
|
||||||
useTimeout,
|
|
||||||
useTimeoutFn,
|
|
||||||
useTimeoutPoll,
|
|
||||||
useTimestamp,
|
|
||||||
useTitle,
|
|
||||||
useToNumber,
|
|
||||||
useToString,
|
|
||||||
useToggle,
|
|
||||||
useTransition,
|
|
||||||
useUrlSearchParams,
|
|
||||||
useUserMedia,
|
|
||||||
useVModel,
|
|
||||||
useVModels,
|
|
||||||
useVibrate,
|
|
||||||
useVirtualList,
|
|
||||||
useWakeLock,
|
|
||||||
useWebNotification,
|
|
||||||
useWebSocket,
|
|
||||||
useWebWorker,
|
|
||||||
useWebWorkerFn,
|
|
||||||
useWindowFocus,
|
|
||||||
useWindowScroll,
|
|
||||||
useWindowSize,
|
|
||||||
watchArray,
|
|
||||||
watchAtMost,
|
|
||||||
watchDebounced,
|
|
||||||
watchDeep,
|
|
||||||
watchIgnorable,
|
|
||||||
watchImmediate,
|
|
||||||
watchOnce,
|
|
||||||
watchPausable,
|
|
||||||
watchThrottled,
|
|
||||||
watchTriggerable,
|
|
||||||
watchWithFilter,
|
|
||||||
whenever
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=vitepress___@vueuse_core.js.map
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
-1334
File diff suppressed because it is too large
Load Diff
-7
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-1813
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Vendored
-347
@@ -1,347 +0,0 @@
|
|||||||
import {
|
|
||||||
BaseTransition,
|
|
||||||
BaseTransitionPropsValidators,
|
|
||||||
Comment,
|
|
||||||
DeprecationTypes,
|
|
||||||
EffectScope,
|
|
||||||
ErrorCodes,
|
|
||||||
ErrorTypeStrings,
|
|
||||||
Fragment,
|
|
||||||
KeepAlive,
|
|
||||||
ReactiveEffect,
|
|
||||||
Static,
|
|
||||||
Suspense,
|
|
||||||
Teleport,
|
|
||||||
Text,
|
|
||||||
TrackOpTypes,
|
|
||||||
Transition,
|
|
||||||
TransitionGroup,
|
|
||||||
TriggerOpTypes,
|
|
||||||
VueElement,
|
|
||||||
assertNumber,
|
|
||||||
callWithAsyncErrorHandling,
|
|
||||||
callWithErrorHandling,
|
|
||||||
camelize,
|
|
||||||
capitalize,
|
|
||||||
cloneVNode,
|
|
||||||
compatUtils,
|
|
||||||
compile,
|
|
||||||
computed,
|
|
||||||
createApp,
|
|
||||||
createBaseVNode,
|
|
||||||
createBlock,
|
|
||||||
createCommentVNode,
|
|
||||||
createElementBlock,
|
|
||||||
createHydrationRenderer,
|
|
||||||
createPropsRestProxy,
|
|
||||||
createRenderer,
|
|
||||||
createSSRApp,
|
|
||||||
createSlots,
|
|
||||||
createStaticVNode,
|
|
||||||
createTextVNode,
|
|
||||||
createVNode,
|
|
||||||
customRef,
|
|
||||||
defineAsyncComponent,
|
|
||||||
defineComponent,
|
|
||||||
defineCustomElement,
|
|
||||||
defineEmits,
|
|
||||||
defineExpose,
|
|
||||||
defineModel,
|
|
||||||
defineOptions,
|
|
||||||
defineProps,
|
|
||||||
defineSSRCustomElement,
|
|
||||||
defineSlots,
|
|
||||||
devtools,
|
|
||||||
effect,
|
|
||||||
effectScope,
|
|
||||||
getCurrentInstance,
|
|
||||||
getCurrentScope,
|
|
||||||
getCurrentWatcher,
|
|
||||||
getTransitionRawChildren,
|
|
||||||
guardReactiveProps,
|
|
||||||
h,
|
|
||||||
handleError,
|
|
||||||
hasInjectionContext,
|
|
||||||
hydrate,
|
|
||||||
hydrateOnIdle,
|
|
||||||
hydrateOnInteraction,
|
|
||||||
hydrateOnMediaQuery,
|
|
||||||
hydrateOnVisible,
|
|
||||||
initCustomFormatter,
|
|
||||||
initDirectivesForSSR,
|
|
||||||
inject,
|
|
||||||
isMemoSame,
|
|
||||||
isProxy,
|
|
||||||
isReactive,
|
|
||||||
isReadonly,
|
|
||||||
isRef,
|
|
||||||
isRuntimeOnly,
|
|
||||||
isShallow,
|
|
||||||
isVNode,
|
|
||||||
markRaw,
|
|
||||||
mergeDefaults,
|
|
||||||
mergeModels,
|
|
||||||
mergeProps,
|
|
||||||
nextTick,
|
|
||||||
nodeOps,
|
|
||||||
normalizeClass,
|
|
||||||
normalizeProps,
|
|
||||||
normalizeStyle,
|
|
||||||
onActivated,
|
|
||||||
onBeforeMount,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onBeforeUpdate,
|
|
||||||
onDeactivated,
|
|
||||||
onErrorCaptured,
|
|
||||||
onMounted,
|
|
||||||
onRenderTracked,
|
|
||||||
onRenderTriggered,
|
|
||||||
onScopeDispose,
|
|
||||||
onServerPrefetch,
|
|
||||||
onUnmounted,
|
|
||||||
onUpdated,
|
|
||||||
onWatcherCleanup,
|
|
||||||
openBlock,
|
|
||||||
patchProp,
|
|
||||||
popScopeId,
|
|
||||||
provide,
|
|
||||||
proxyRefs,
|
|
||||||
pushScopeId,
|
|
||||||
queuePostFlushCb,
|
|
||||||
reactive,
|
|
||||||
readonly,
|
|
||||||
ref,
|
|
||||||
registerRuntimeCompiler,
|
|
||||||
render,
|
|
||||||
renderList,
|
|
||||||
renderSlot,
|
|
||||||
resolveComponent,
|
|
||||||
resolveDirective,
|
|
||||||
resolveDynamicComponent,
|
|
||||||
resolveFilter,
|
|
||||||
resolveTransitionHooks,
|
|
||||||
setBlockTracking,
|
|
||||||
setDevtoolsHook,
|
|
||||||
setTransitionHooks,
|
|
||||||
shallowReactive,
|
|
||||||
shallowReadonly,
|
|
||||||
shallowRef,
|
|
||||||
ssrContextKey,
|
|
||||||
ssrUtils,
|
|
||||||
stop,
|
|
||||||
toDisplayString,
|
|
||||||
toHandlerKey,
|
|
||||||
toHandlers,
|
|
||||||
toRaw,
|
|
||||||
toRef,
|
|
||||||
toRefs,
|
|
||||||
toValue,
|
|
||||||
transformVNodeArgs,
|
|
||||||
triggerRef,
|
|
||||||
unref,
|
|
||||||
useAttrs,
|
|
||||||
useCssModule,
|
|
||||||
useCssVars,
|
|
||||||
useHost,
|
|
||||||
useId,
|
|
||||||
useModel,
|
|
||||||
useSSRContext,
|
|
||||||
useShadowRoot,
|
|
||||||
useSlots,
|
|
||||||
useTemplateRef,
|
|
||||||
useTransitionState,
|
|
||||||
vModelCheckbox,
|
|
||||||
vModelDynamic,
|
|
||||||
vModelRadio,
|
|
||||||
vModelSelect,
|
|
||||||
vModelText,
|
|
||||||
vShow,
|
|
||||||
version,
|
|
||||||
warn,
|
|
||||||
watch,
|
|
||||||
watchEffect,
|
|
||||||
watchPostEffect,
|
|
||||||
watchSyncEffect,
|
|
||||||
withAsyncContext,
|
|
||||||
withCtx,
|
|
||||||
withDefaults,
|
|
||||||
withDirectives,
|
|
||||||
withKeys,
|
|
||||||
withMemo,
|
|
||||||
withModifiers,
|
|
||||||
withScopeId
|
|
||||||
} from "./chunk-LE5NDSFD.js";
|
|
||||||
export {
|
|
||||||
BaseTransition,
|
|
||||||
BaseTransitionPropsValidators,
|
|
||||||
Comment,
|
|
||||||
DeprecationTypes,
|
|
||||||
EffectScope,
|
|
||||||
ErrorCodes,
|
|
||||||
ErrorTypeStrings,
|
|
||||||
Fragment,
|
|
||||||
KeepAlive,
|
|
||||||
ReactiveEffect,
|
|
||||||
Static,
|
|
||||||
Suspense,
|
|
||||||
Teleport,
|
|
||||||
Text,
|
|
||||||
TrackOpTypes,
|
|
||||||
Transition,
|
|
||||||
TransitionGroup,
|
|
||||||
TriggerOpTypes,
|
|
||||||
VueElement,
|
|
||||||
assertNumber,
|
|
||||||
callWithAsyncErrorHandling,
|
|
||||||
callWithErrorHandling,
|
|
||||||
camelize,
|
|
||||||
capitalize,
|
|
||||||
cloneVNode,
|
|
||||||
compatUtils,
|
|
||||||
compile,
|
|
||||||
computed,
|
|
||||||
createApp,
|
|
||||||
createBlock,
|
|
||||||
createCommentVNode,
|
|
||||||
createElementBlock,
|
|
||||||
createBaseVNode as createElementVNode,
|
|
||||||
createHydrationRenderer,
|
|
||||||
createPropsRestProxy,
|
|
||||||
createRenderer,
|
|
||||||
createSSRApp,
|
|
||||||
createSlots,
|
|
||||||
createStaticVNode,
|
|
||||||
createTextVNode,
|
|
||||||
createVNode,
|
|
||||||
customRef,
|
|
||||||
defineAsyncComponent,
|
|
||||||
defineComponent,
|
|
||||||
defineCustomElement,
|
|
||||||
defineEmits,
|
|
||||||
defineExpose,
|
|
||||||
defineModel,
|
|
||||||
defineOptions,
|
|
||||||
defineProps,
|
|
||||||
defineSSRCustomElement,
|
|
||||||
defineSlots,
|
|
||||||
devtools,
|
|
||||||
effect,
|
|
||||||
effectScope,
|
|
||||||
getCurrentInstance,
|
|
||||||
getCurrentScope,
|
|
||||||
getCurrentWatcher,
|
|
||||||
getTransitionRawChildren,
|
|
||||||
guardReactiveProps,
|
|
||||||
h,
|
|
||||||
handleError,
|
|
||||||
hasInjectionContext,
|
|
||||||
hydrate,
|
|
||||||
hydrateOnIdle,
|
|
||||||
hydrateOnInteraction,
|
|
||||||
hydrateOnMediaQuery,
|
|
||||||
hydrateOnVisible,
|
|
||||||
initCustomFormatter,
|
|
||||||
initDirectivesForSSR,
|
|
||||||
inject,
|
|
||||||
isMemoSame,
|
|
||||||
isProxy,
|
|
||||||
isReactive,
|
|
||||||
isReadonly,
|
|
||||||
isRef,
|
|
||||||
isRuntimeOnly,
|
|
||||||
isShallow,
|
|
||||||
isVNode,
|
|
||||||
markRaw,
|
|
||||||
mergeDefaults,
|
|
||||||
mergeModels,
|
|
||||||
mergeProps,
|
|
||||||
nextTick,
|
|
||||||
nodeOps,
|
|
||||||
normalizeClass,
|
|
||||||
normalizeProps,
|
|
||||||
normalizeStyle,
|
|
||||||
onActivated,
|
|
||||||
onBeforeMount,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onBeforeUpdate,
|
|
||||||
onDeactivated,
|
|
||||||
onErrorCaptured,
|
|
||||||
onMounted,
|
|
||||||
onRenderTracked,
|
|
||||||
onRenderTriggered,
|
|
||||||
onScopeDispose,
|
|
||||||
onServerPrefetch,
|
|
||||||
onUnmounted,
|
|
||||||
onUpdated,
|
|
||||||
onWatcherCleanup,
|
|
||||||
openBlock,
|
|
||||||
patchProp,
|
|
||||||
popScopeId,
|
|
||||||
provide,
|
|
||||||
proxyRefs,
|
|
||||||
pushScopeId,
|
|
||||||
queuePostFlushCb,
|
|
||||||
reactive,
|
|
||||||
readonly,
|
|
||||||
ref,
|
|
||||||
registerRuntimeCompiler,
|
|
||||||
render,
|
|
||||||
renderList,
|
|
||||||
renderSlot,
|
|
||||||
resolveComponent,
|
|
||||||
resolveDirective,
|
|
||||||
resolveDynamicComponent,
|
|
||||||
resolveFilter,
|
|
||||||
resolveTransitionHooks,
|
|
||||||
setBlockTracking,
|
|
||||||
setDevtoolsHook,
|
|
||||||
setTransitionHooks,
|
|
||||||
shallowReactive,
|
|
||||||
shallowReadonly,
|
|
||||||
shallowRef,
|
|
||||||
ssrContextKey,
|
|
||||||
ssrUtils,
|
|
||||||
stop,
|
|
||||||
toDisplayString,
|
|
||||||
toHandlerKey,
|
|
||||||
toHandlers,
|
|
||||||
toRaw,
|
|
||||||
toRef,
|
|
||||||
toRefs,
|
|
||||||
toValue,
|
|
||||||
transformVNodeArgs,
|
|
||||||
triggerRef,
|
|
||||||
unref,
|
|
||||||
useAttrs,
|
|
||||||
useCssModule,
|
|
||||||
useCssVars,
|
|
||||||
useHost,
|
|
||||||
useId,
|
|
||||||
useModel,
|
|
||||||
useSSRContext,
|
|
||||||
useShadowRoot,
|
|
||||||
useSlots,
|
|
||||||
useTemplateRef,
|
|
||||||
useTransitionState,
|
|
||||||
vModelCheckbox,
|
|
||||||
vModelDynamic,
|
|
||||||
vModelRadio,
|
|
||||||
vModelSelect,
|
|
||||||
vModelText,
|
|
||||||
vShow,
|
|
||||||
version,
|
|
||||||
warn,
|
|
||||||
watch,
|
|
||||||
watchEffect,
|
|
||||||
watchPostEffect,
|
|
||||||
watchSyncEffect,
|
|
||||||
withAsyncContext,
|
|
||||||
withCtx,
|
|
||||||
withDefaults,
|
|
||||||
withDirectives,
|
|
||||||
withKeys,
|
|
||||||
withMemo,
|
|
||||||
withModifiers,
|
|
||||||
withScopeId
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=vue.js.map
|
|
||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import { defineConfig } from 'vitepress'
|
|
||||||
|
|
||||||
// https://vitepress.dev/reference/site-config
|
|
||||||
export default defineConfig({
|
|
||||||
title: "BentoPDF Docs",
|
|
||||||
description: "Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit",
|
|
||||||
base: '/docs/',
|
|
||||||
|
|
||||||
themeConfig: {
|
|
||||||
// https://vitepress.dev/reference/default-theme-config
|
|
||||||
logo: '/images/favicon.svg',
|
|
||||||
|
|
||||||
nav: [
|
|
||||||
{ text: 'Home', link: '/' },
|
|
||||||
{ text: 'Getting Started', link: '/getting-started' },
|
|
||||||
{ text: 'Tools', link: '/tools/' },
|
|
||||||
{ text: 'Self-Hosting', link: '/self-hosting/' },
|
|
||||||
{ text: 'Contributing', link: '/contributing' },
|
|
||||||
{ text: 'Commercial License', link: '/licensing' }
|
|
||||||
],
|
|
||||||
|
|
||||||
sidebar: {
|
|
||||||
'/tools/': [
|
|
||||||
{
|
|
||||||
text: 'Tools Reference',
|
|
||||||
items: [
|
|
||||||
{ text: 'Overview', link: '/tools/' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'/self-hosting/': [
|
|
||||||
{
|
|
||||||
text: 'Self-Hosting Guide',
|
|
||||||
items: [
|
|
||||||
{ text: 'Overview', link: '/self-hosting/' },
|
|
||||||
{ text: 'Docker', link: '/self-hosting/docker' },
|
|
||||||
{ text: 'Vercel', link: '/self-hosting/vercel' },
|
|
||||||
{ text: 'Netlify', link: '/self-hosting/netlify' },
|
|
||||||
{ text: 'Cloudflare Pages', link: '/self-hosting/cloudflare' },
|
|
||||||
{ text: 'AWS S3 + CloudFront', link: '/self-hosting/aws' },
|
|
||||||
{ text: 'Hostinger', link: '/self-hosting/hostinger' },
|
|
||||||
{ text: 'Nginx', link: '/self-hosting/nginx' },
|
|
||||||
{ text: 'Apache', link: '/self-hosting/apache' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'/': [
|
|
||||||
{
|
|
||||||
text: 'Guide',
|
|
||||||
items: [
|
|
||||||
{ text: 'Getting Started', link: '/getting-started' },
|
|
||||||
{ text: 'Tools Reference', link: '/tools/' },
|
|
||||||
{ text: 'Self-Hosting', link: '/self-hosting/' },
|
|
||||||
{ text: 'Contributing', link: '/contributing' },
|
|
||||||
{ text: 'Commercial License', link: '/licensing' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
socialLinks: [
|
|
||||||
{ icon: 'github', link: 'https://github.com/alam00000/bentopdf' },
|
|
||||||
{ icon: 'discord', link: 'https://discord.gg/Bgq3Ay3f2w' }
|
|
||||||
],
|
|
||||||
|
|
||||||
footer: {
|
|
||||||
message: 'Dual-licensed under AGPL-3.0 and Commercial License.',
|
|
||||||
copyright: 'Copyright © 2025 BentoPDF'
|
|
||||||
},
|
|
||||||
|
|
||||||
search: {
|
|
||||||
provider: 'local'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
Thank you for your interest in contributing to BentoPDF! This guide will help you get started.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Node.js 18+
|
|
||||||
- npm 9+
|
|
||||||
- Git
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Fork and clone the repository
|
|
||||||
git clone https://github.com/YOUR_USERNAME/bentopdf.git
|
|
||||||
cd bentopdf
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# Start development server
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
bentopdf/
|
|
||||||
├── src/
|
|
||||||
│ ├── js/
|
|
||||||
│ │ ├── config/ # Tool definitions
|
|
||||||
│ │ ├── logic/ # Tool page logic
|
|
||||||
│ │ ├── utils/ # Shared utilities
|
|
||||||
│ │ └── ui.ts # UI helpers
|
|
||||||
│ ├── pages/ # Tool HTML pages
|
|
||||||
│ └── css/ # Styles
|
|
||||||
├── public/
|
|
||||||
│ ├── locales/ # Translations
|
|
||||||
│ └── pymupdf-wasm/ # WASM modules
|
|
||||||
├── docs/ # Documentation (VitePress)
|
|
||||||
└── scripts/ # Build scripts
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding a New Tool
|
|
||||||
|
|
||||||
### Step 1: Create the Logic File
|
|
||||||
|
|
||||||
Create `src/js/logic/your-tool-page.ts`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
|
||||||
import { state } from '../state.js';
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
// Your tool logic here
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Create the HTML Page
|
|
||||||
|
|
||||||
Create `src/pages/your-tool.html` following the existing tool templates.
|
|
||||||
|
|
||||||
### Step 3: Register the Tool
|
|
||||||
|
|
||||||
Add to `src/js/config/tools.ts`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'your-tool.html',
|
|
||||||
name: 'Your Tool',
|
|
||||||
icon: 'ph-icon-name',
|
|
||||||
subtitle: 'Description of your tool.',
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Add to Build Config
|
|
||||||
|
|
||||||
Add the page to `vite.config.ts`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
'your-tool': resolve(__dirname, 'src/pages/your-tool.html'),
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Add Translations
|
|
||||||
|
|
||||||
Add entries to all locale files in `public/locales/`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"yourTool": {
|
|
||||||
"name": "Your Tool",
|
|
||||||
"subtitle": "Description"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
- Use TypeScript for all new code
|
|
||||||
- Follow existing patterns for consistency
|
|
||||||
- Run `npm run format` before committing
|
|
||||||
- No comments in production code (comments removed during build)
|
|
||||||
|
|
||||||
## Pull Request Process
|
|
||||||
|
|
||||||
1. Create a feature branch: `git checkout -b feature/your-feature`
|
|
||||||
2. Make your changes
|
|
||||||
3. Test thoroughly: `npm run build`
|
|
||||||
4. Submit a PR with a clear description
|
|
||||||
|
|
||||||
## Translation
|
|
||||||
|
|
||||||
See [TRANSLATION.md](https://github.com/alam00000/bentopdf/blob/main/TRANSLATION.md) for adding new languages.
|
|
||||||
|
|
||||||
## Contributor License Agreement
|
|
||||||
|
|
||||||
Before your PR can be merged, you'll need to sign our CLA:
|
|
||||||
|
|
||||||
- [Individual CLA (ICLA)](https://github.com/alam00000/bentopdf/blob/main/ICLA.md)
|
|
||||||
- [Corporate CLA (CCLA)](https://github.com/alam00000/bentopdf/blob/main/CCLA.md)
|
|
||||||
|
|
||||||
## Getting Help
|
|
||||||
|
|
||||||
- [GitHub Issues](https://github.com/alam00000/bentopdf/issues)
|
|
||||||
- [Discord Community](https://discord.gg/Bgq3Ay3f2w)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
BentoPDF is dual-licensed:
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
By contributing, you agree that your contributions will be licensed under these terms.
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# Getting Started
|
|
||||||
|
|
||||||
Welcome to BentoPDF! This guide will help you get up and running quickly.
|
|
||||||
|
|
||||||
## What is BentoPDF?
|
|
||||||
|
|
||||||
BentoPDF is a free, open-source, privacy-first PDF toolkit that runs **entirely in your browser**. Your files never leave your device—all processing happens locally using WebAssembly (WASM) technology.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Option 1: Use the Hosted Version
|
|
||||||
|
|
||||||
Visit [bentopdf.com](https://bentopdf.com) to use BentoPDF instantly—no installation required.
|
|
||||||
|
|
||||||
### Option 2: Self-Host with Docker
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Pull and run the Docker image
|
|
||||||
docker run -d -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
|
|
||||||
|
|
||||||
# Or use Docker Compose
|
|
||||||
curl -O https://raw.githubusercontent.com/alam00000/bentopdf/main/docker-compose.yml
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Then open `http://localhost:3000` in your browser.
|
|
||||||
|
|
||||||
### Option 3: Build from Source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone the repository
|
|
||||||
git clone https://github.com/alam00000/bentopdf.git
|
|
||||||
cd bentopdf
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# Start development server
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features at a Glance
|
|
||||||
|
|
||||||
| Category | Tools |
|
|
||||||
|----------|-------|
|
|
||||||
| **Convert to PDF** | Word, Excel, PowerPoint, Images, Markdown, EPUB, MOBI, and more |
|
|
||||||
| **Convert from PDF** | JPG, PNG, Text, Excel, SVG, and more |
|
|
||||||
| **Edit & Annotate** | Sign, Highlight, Redact, Fill Forms, Add Stamps |
|
|
||||||
| **Organize** | Merge, Split, Rotate, Delete Pages, Reorder |
|
|
||||||
| **Optimize** | Compress, Repair, Flatten, OCR |
|
|
||||||
| **Security** | Encrypt, Decrypt, Remove Restrictions |
|
|
||||||
|
|
||||||
## Browser Support
|
|
||||||
|
|
||||||
BentoPDF works best on modern browsers:
|
|
||||||
|
|
||||||
- ✅ Chrome/Edge 90+
|
|
||||||
- ✅ Firefox 90+
|
|
||||||
- ✅ Safari 15+
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- [Explore all tools](/tools/)
|
|
||||||
- [Self-host BentoPDF](/self-hosting/)
|
|
||||||
- [Contribute to the project](/contributing)
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
# https://vitepress.dev/reference/default-theme-home-page
|
|
||||||
layout: home
|
|
||||||
|
|
||||||
hero:
|
|
||||||
name: "BentoPDF"
|
|
||||||
text: "Free, Open-Source PDF Tools"
|
|
||||||
tagline: Process PDFs entirely in your browser. No uploads. No servers. Complete privacy.
|
|
||||||
actions:
|
|
||||||
- theme: brand
|
|
||||||
text: Get Started
|
|
||||||
link: /getting-started
|
|
||||||
- theme: alt
|
|
||||||
text: View Tools
|
|
||||||
link: /tools/
|
|
||||||
- theme: alt
|
|
||||||
text: Self-Host
|
|
||||||
link: /self-hosting/
|
|
||||||
- theme: alt
|
|
||||||
text: Commercial License
|
|
||||||
link: /licensing
|
|
||||||
|
|
||||||
features:
|
|
||||||
- icon: 🔒
|
|
||||||
title: 100% Private
|
|
||||||
details: Your files never leave your device. All processing happens locally in your browser using WebAssembly.
|
|
||||||
- icon: ⚡
|
|
||||||
title: Fast & Free
|
|
||||||
details: No sign-ups, no limits, no watermarks. Process unlimited PDFs for free, forever.
|
|
||||||
- icon: 🛠️
|
|
||||||
title: 50+ Tools
|
|
||||||
details: Convert, edit, merge, split, compress, sign, OCR, and more. Everything you need in one place.
|
|
||||||
- icon: 🌐
|
|
||||||
title: Self-Hostable
|
|
||||||
details: Deploy on your own infrastructure. Docker, Vercel, Netlify, AWS, or any static hosting.
|
|
||||||
---
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# Commercial License
|
|
||||||
|
|
||||||
BentoPDF is dual-licensed under **AGPL-3.0** (for open-source projects) and a **Commercial License** (for proprietary/closed-source applications).
|
|
||||||
|
|
||||||
::: tip Full Details
|
|
||||||
For complete licensing information, delivery details, AGPL component notices, and invoicing, visit our [Licensing Page](https://bentopdf.com/licensing.html).
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Get a Commercial License
|
|
||||||
|
|
||||||
**[Get Lifetime License for $49](https://ko-fi.com/s/f32ca4cb75)** — A one-time purchase that includes all future updates forever.
|
|
||||||
|
|
||||||
## When Do You Need a Commercial License?
|
|
||||||
|
|
||||||
| Use Case | License Required |
|
|
||||||
|----------|------------------|
|
|
||||||
| Open-source project with public source code | AGPL-3.0 (Free) |
|
|
||||||
| Internal company tool (not distributed) | AGPL-3.0 (Free) |
|
|
||||||
| Proprietary/closed-source application | **Commercial License** |
|
|
||||||
| SaaS product without source code disclosure | **Commercial License** |
|
|
||||||
| Redistributing without AGPL compliance | **Commercial License** |
|
|
||||||
|
|
||||||
## Delivery & Licensing Model
|
|
||||||
|
|
||||||
- **No license key is required or provided.** BentoPDF intentionally does not use license keys to streamline deployment and reduce operational friction for users.
|
|
||||||
- Upon purchase, users receive a ZIP archive containing the complete BentoPDF source code and binaries.
|
|
||||||
- The source code provided is identical to the code published in our public open-source repository.
|
|
||||||
|
|
||||||
**GitHub Repository:** [github.com/alam00000/bentopdf](https://github.com/alam00000/bentopdf)
|
|
||||||
|
|
||||||
## Important Notice on Third-Party Components
|
|
||||||
|
|
||||||
::: warning AGPL Components
|
|
||||||
This software includes components licensed under the **GNU AGPL v3**, such as CPDF.
|
|
||||||
|
|
||||||
- This commercial license **does not** grant rights to use AGPL components in a closed-source manner.
|
|
||||||
- Users must comply with the AGPL v3 terms for these components.
|
|
||||||
- Source code for all AGPL components is included in the distribution.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Invoicing
|
|
||||||
|
|
||||||
- Ko-fi does not automatically issue invoices.
|
|
||||||
- An official invoice will be provided immediately upon request.
|
|
||||||
|
|
||||||
**Contact us:** [[email protected]](mailto:[email protected]) with your purchase details.
|
|
||||||
|
|
||||||
## What's Included
|
|
||||||
|
|
||||||
| Feature | Included |
|
|
||||||
|---------|----------|
|
|
||||||
| Full source code | ✅ |
|
|
||||||
| All 50+ PDF tools | ✅ |
|
|
||||||
| Self-hosting rights | ✅ |
|
|
||||||
| Lifetime updates | ✅ |
|
|
||||||
| Remove branding (Simple Mode) | ✅ |
|
|
||||||
| Commercial support | ✅ (via email) |
|
|
||||||
| Priority feature requests | ✅ |
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
### Do I need a license key to use BentoPDF?
|
|
||||||
|
|
||||||
No. BentoPDF does not use license keys. The commercial license grants you legal rights to use the software in proprietary applications.
|
|
||||||
|
|
||||||
### Can I use BentoPDF in my SaaS product?
|
|
||||||
|
|
||||||
Yes, with a commercial license. Without it, you must comply with AGPL-3.0, which requires you to make your full source code available to users.
|
|
||||||
|
|
||||||
### What about the AGPL components?
|
|
||||||
|
|
||||||
Components like CPDF are licensed under AGPL v3 and remain under that license. The commercial license covers BentoPDF's own code but does not override third-party AGPL obligations.
|
|
||||||
|
|
||||||
### How do I get an invoice?
|
|
||||||
|
|
||||||
Email [[email protected]](mailto:[email protected]) with your Ko-fi purchase confirmation, and we'll send an official invoice.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<svg width="166" height="166" viewBox="0 0 166 166" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g transform="translate(40, 0)">
|
|
||||||
<path d="M56.7848 80.9116L0 25.6211L24.8434 1.43151C26.8037 -0.47717 29.9814 -0.47717 31.9414 1.43151L85.177 53.2662L56.7848 80.9116Z" fill="#A5B4FC"/>
|
|
||||||
<path d="M-1.30805e-05 80.7335L21.2939 101.467L42.5878 80.7335L21.2939 60L-1.30805e-05 80.7335Z" fill="#E0E7FF"/>
|
|
||||||
<path d="M56.7848 83L81.6279 107.19C83.5881 109.098 83.5881 112.192 81.6279 114.101L28.3925 165.936L0 138.29L56.7848 83Z" fill="#6366F1"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 550 B |
@@ -1,158 +0,0 @@
|
|||||||
# Deploy with Apache
|
|
||||||
|
|
||||||
Host BentoPDF using Apache HTTP Server.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Apache 2.4+
|
|
||||||
- mod_rewrite enabled
|
|
||||||
- SSL certificate (recommended)
|
|
||||||
|
|
||||||
## Step 1: Build the Project
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/alam00000/bentopdf.git
|
|
||||||
cd bentopdf
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 2: Copy Files
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo mkdir -p /var/www/bentopdf
|
|
||||||
sudo cp -r dist/* /var/www/bentopdf/
|
|
||||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 3: Apache Configuration
|
|
||||||
|
|
||||||
Create `/etc/apache2/sites-available/bentopdf.conf`:
|
|
||||||
|
|
||||||
```apache
|
|
||||||
<VirtualHost *:80>
|
|
||||||
ServerName your-domain.com
|
|
||||||
Redirect permanent / https://your-domain.com/
|
|
||||||
</VirtualHost>
|
|
||||||
|
|
||||||
<VirtualHost *:443>
|
|
||||||
ServerName your-domain.com
|
|
||||||
DocumentRoot /var/www/bentopdf
|
|
||||||
|
|
||||||
SSLEngine on
|
|
||||||
SSLCertificateFile /etc/letsencrypt/live/your-domain.com/fullchain.pem
|
|
||||||
SSLCertificateKeyFile /etc/letsencrypt/live/your-domain.com/privkey.pem
|
|
||||||
|
|
||||||
<Directory /var/www/bentopdf>
|
|
||||||
Options -Indexes +FollowSymLinks
|
|
||||||
AllowOverride All
|
|
||||||
Require all granted
|
|
||||||
</Directory>
|
|
||||||
|
|
||||||
# WASM MIME type
|
|
||||||
AddType application/wasm .wasm
|
|
||||||
|
|
||||||
# Compression
|
|
||||||
<IfModule mod_deflate.c>
|
|
||||||
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json application/wasm
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# Cache headers
|
|
||||||
<IfModule mod_expires.c>
|
|
||||||
ExpiresActive On
|
|
||||||
ExpiresByType application/javascript "access plus 1 year"
|
|
||||||
ExpiresByType text/css "access plus 1 year"
|
|
||||||
ExpiresByType application/wasm "access plus 1 year"
|
|
||||||
ExpiresByType image/png "access plus 1 year"
|
|
||||||
ExpiresByType image/svg+xml "access plus 1 year"
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# Security headers
|
|
||||||
Header always set X-Frame-Options "SAMEORIGIN"
|
|
||||||
Header always set X-Content-Type-Options "nosniff"
|
|
||||||
</VirtualHost>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 4: .htaccess for SPA Routing
|
|
||||||
|
|
||||||
Create `/var/www/bentopdf/.htaccess`:
|
|
||||||
|
|
||||||
```apache
|
|
||||||
<IfModule mod_rewrite.c>
|
|
||||||
RewriteEngine On
|
|
||||||
RewriteBase /
|
|
||||||
|
|
||||||
# Don't rewrite files or directories
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
|
|
||||||
# Rewrite everything else to index.html
|
|
||||||
RewriteRule ^ index.html [L]
|
|
||||||
</IfModule>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 5: Enable Required Modules
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo a2enmod rewrite
|
|
||||||
sudo a2enmod ssl
|
|
||||||
sudo a2enmod headers
|
|
||||||
sudo a2enmod expires
|
|
||||||
sudo a2enmod deflate
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 6: Enable the Site
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo a2ensite bentopdf.conf
|
|
||||||
sudo apache2ctl configtest
|
|
||||||
sudo systemctl reload apache2
|
|
||||||
```
|
|
||||||
|
|
||||||
## Subdirectory Deployment
|
|
||||||
|
|
||||||
To host at `/pdf/`:
|
|
||||||
|
|
||||||
1. Build with base URL:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
BASE_URL=/pdf/ npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Update `.htaccess`:
|
|
||||||
|
|
||||||
```apache
|
|
||||||
<IfModule mod_rewrite.c>
|
|
||||||
RewriteEngine On
|
|
||||||
RewriteBase /pdf/
|
|
||||||
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
RewriteRule ^ index.html [L]
|
|
||||||
</IfModule>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### WASM 404 Errors
|
|
||||||
|
|
||||||
Ensure MIME type is configured:
|
|
||||||
|
|
||||||
```apache
|
|
||||||
AddType application/wasm .wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rewrite Not Working
|
|
||||||
|
|
||||||
Check that mod_rewrite is enabled:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo a2enmod rewrite
|
|
||||||
```
|
|
||||||
|
|
||||||
### Permission Denied
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
|
||||||
sudo chmod -R 755 /var/www/bentopdf
|
|
||||||
```
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
# Deploy to AWS S3 + CloudFront
|
|
||||||
|
|
||||||
Host BentoPDF on AWS for maximum control and scalability.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
User → CloudFront (CDN) → S3 (Static Files)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 1: Create S3 Bucket
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create bucket
|
|
||||||
aws s3 mb s3://your-bentopdf-bucket --region us-east-1
|
|
||||||
|
|
||||||
# Enable static website hosting
|
|
||||||
aws s3 website s3://your-bentopdf-bucket \
|
|
||||||
--index-document index.html \
|
|
||||||
--error-document index.html
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 2: Build and Upload
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build the project
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Sync to S3
|
|
||||||
aws s3 sync dist/ s3://your-bentopdf-bucket \
|
|
||||||
--delete \
|
|
||||||
--cache-control "max-age=31536000"
|
|
||||||
|
|
||||||
# Set correct MIME types for WASM
|
|
||||||
aws s3 cp s3://your-bentopdf-bucket/ s3://your-bentopdf-bucket/ \
|
|
||||||
--recursive \
|
|
||||||
--exclude "*" \
|
|
||||||
--include "*.wasm" \
|
|
||||||
--content-type "application/wasm" \
|
|
||||||
--metadata-directive REPLACE
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 3: Create CloudFront Distribution
|
|
||||||
|
|
||||||
```bash
|
|
||||||
aws cloudfront create-distribution \
|
|
||||||
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
|
|
||||||
--default-root-object index.html
|
|
||||||
```
|
|
||||||
|
|
||||||
Or use the AWS Console:
|
|
||||||
|
|
||||||
1. Go to CloudFront → Create distribution
|
|
||||||
2. Origin domain: Select your S3 bucket
|
|
||||||
3. Enable "Origin Access Control"
|
|
||||||
4. Default root object: `index.html`
|
|
||||||
5. Create distribution
|
|
||||||
|
|
||||||
## Step 4: S3 Bucket Policy
|
|
||||||
|
|
||||||
Allow CloudFront to access the bucket:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Sid": "AllowCloudFrontAccess",
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Principal": {
|
|
||||||
"Service": "cloudfront.amazonaws.com"
|
|
||||||
},
|
|
||||||
"Action": "s3:GetObject",
|
|
||||||
"Resource": "arn:aws:s3:::your-bentopdf-bucket/*",
|
|
||||||
"Condition": {
|
|
||||||
"StringEquals": {
|
|
||||||
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 5: Custom Error Pages
|
|
||||||
|
|
||||||
Configure 404 to return `index.html` for SPA routing:
|
|
||||||
|
|
||||||
1. CloudFront → Error pages
|
|
||||||
2. Create custom error response:
|
|
||||||
- HTTP error code: 404
|
|
||||||
- Response page path: `/index.html`
|
|
||||||
- HTTP response code: 200
|
|
||||||
|
|
||||||
## Cost Estimation
|
|
||||||
|
|
||||||
| Resource | Estimated Cost |
|
|
||||||
|----------|----------------|
|
|
||||||
| S3 Storage (~500MB) | ~$0.01/month |
|
|
||||||
| CloudFront (1TB transfer) | ~$85/month |
|
|
||||||
| CloudFront (10GB transfer) | ~$0.85/month |
|
|
||||||
|
|
||||||
::: tip
|
|
||||||
Use S3 Intelligent Tiering for cost optimization on infrequently accessed files.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Automation with Terraform
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# main.tf
|
|
||||||
resource "aws_s3_bucket" "bentopdf" {
|
|
||||||
bucket = "your-bentopdf-bucket"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_cloudfront_distribution" "bentopdf" {
|
|
||||||
origin {
|
|
||||||
domain_name = aws_s3_bucket.bentopdf.bucket_regional_domain_name
|
|
||||||
origin_id = "S3Origin"
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled = true
|
|
||||||
default_root_object = "index.html"
|
|
||||||
|
|
||||||
default_cache_behavior {
|
|
||||||
allowed_methods = ["GET", "HEAD"]
|
|
||||||
cached_methods = ["GET", "HEAD"]
|
|
||||||
target_origin_id = "S3Origin"
|
|
||||||
|
|
||||||
viewer_protocol_policy = "redirect-to-https"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
# Deploy to Cloudflare Pages
|
|
||||||
|
|
||||||
[Cloudflare Pages](https://pages.cloudflare.com) offers fast, global static site hosting with unlimited bandwidth.
|
|
||||||
|
|
||||||
## Quick Deploy
|
|
||||||
|
|
||||||
1. Go to [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
|
|
||||||
2. Click "Create a project"
|
|
||||||
3. Connect your GitHub repository
|
|
||||||
|
|
||||||
## Build Configuration
|
|
||||||
|
|
||||||
| Setting | Value |
|
|
||||||
|---------|-------|
|
|
||||||
| Framework preset | None |
|
|
||||||
| Build command | `npm run build` |
|
|
||||||
| Build output directory | `dist` |
|
|
||||||
| Root directory | `/` |
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
Add these in Settings → Environment variables:
|
|
||||||
|
|
||||||
| Variable | Value |
|
|
||||||
|----------|-------|
|
|
||||||
| `NODE_VERSION` | `18` |
|
|
||||||
| `SIMPLE_MODE` | `false` (optional) |
|
|
||||||
|
|
||||||
## Configuration File
|
|
||||||
|
|
||||||
Create `_headers` in your `public` folder:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Cache WASM files aggressively
|
|
||||||
/*.wasm
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
Content-Type: application/wasm
|
|
||||||
|
|
||||||
# Service worker
|
|
||||||
/sw.js
|
|
||||||
Cache-Control: no-cache
|
|
||||||
```
|
|
||||||
|
|
||||||
Create `_redirects` for SPA routing:
|
|
||||||
|
|
||||||
```
|
|
||||||
/* /index.html 200
|
|
||||||
```
|
|
||||||
|
|
||||||
## Custom Domain
|
|
||||||
|
|
||||||
1. Go to your Pages project
|
|
||||||
2. Click "Custom domains"
|
|
||||||
3. Add your domain
|
|
||||||
4. Cloudflare will auto-configure DNS if the domain is on Cloudflare
|
|
||||||
|
|
||||||
## Advantages
|
|
||||||
|
|
||||||
- **Free unlimited bandwidth**
|
|
||||||
- **Global CDN** with 300+ edge locations
|
|
||||||
- **Automatic HTTPS**
|
|
||||||
- **Preview deployments** for pull requests
|
|
||||||
- **Fast builds**
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Large File Uploads
|
|
||||||
|
|
||||||
Cloudflare Pages supports files up to 25 MB. WASM modules should be fine, but if you hit limits, consider:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Split large files during build
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Worker Size Limits
|
|
||||||
|
|
||||||
If using Cloudflare Workers for advanced routing, note the 1 MB limit for free plans.
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
# Deploy with Docker
|
|
||||||
|
|
||||||
The easiest way to self-host BentoPDF in a production environment.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d \
|
|
||||||
--name bentopdf \
|
|
||||||
-p 3000:8080 \
|
|
||||||
--restart unless-stopped \
|
|
||||||
ghcr.io/alam00000/bentopdf:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Docker Compose
|
|
||||||
|
|
||||||
Create `docker-compose.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
bentopdf:
|
|
||||||
image: ghcr.io/alam00000/bentopdf:latest
|
|
||||||
container_name: bentopdf
|
|
||||||
ports:
|
|
||||||
- "3000:8080"
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8080"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
```
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build Your Own Image
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
# Dockerfile
|
|
||||||
FROM node:20-alpine AS builder
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm ci
|
|
||||||
COPY . .
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
FROM nginxinc/nginx-unprivileged:alpine
|
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
|
||||||
EXPOSE 8080
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Build and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker build -t bentopdf:custom .
|
|
||||||
docker run -d -p 3000:8080 bentopdf:custom
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
| Variable | Description | Default |
|
|
||||||
|----------|-------------|---------|
|
|
||||||
| `SIMPLE_MODE` | Build without LibreOffice tools | `false` |
|
|
||||||
| `BASE_URL` | Deploy to subdirectory | `/` |
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d \
|
|
||||||
-e SIMPLE_MODE=true \
|
|
||||||
-p 3000:8080 \
|
|
||||||
ghcr.io/alam00000/bentopdf:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## With Traefik (Reverse Proxy)
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
traefik:
|
|
||||||
image: traefik:v2.10
|
|
||||||
command:
|
|
||||||
- "--providers.docker=true"
|
|
||||||
- "--entrypoints.web.address=:80"
|
|
||||||
- "--entrypoints.websecure.address=:443"
|
|
||||||
- "[email protected]"
|
|
||||||
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
|
||||||
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- ./letsencrypt:/letsencrypt
|
|
||||||
|
|
||||||
bentopdf:
|
|
||||||
image: ghcr.io/alam00000/bentopdf:latest
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.bentopdf.rule=Host(`pdf.example.com`)"
|
|
||||||
- "traefik.http.routers.bentopdf.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.bentopdf.tls.certresolver=letsencrypt"
|
|
||||||
- "traefik.http.services.bentopdf.loadbalancer.server.port=8080"
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
## With Caddy (Reverse Proxy)
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
caddy:
|
|
||||||
image: caddy:2
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
volumes:
|
|
||||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
|
||||||
- caddy_data:/data
|
|
||||||
|
|
||||||
bentopdf:
|
|
||||||
image: ghcr.io/alam00000/bentopdf:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
caddy_data:
|
|
||||||
```
|
|
||||||
|
|
||||||
Caddyfile:
|
|
||||||
|
|
||||||
```
|
|
||||||
pdf.example.com {
|
|
||||||
reverse_proxy bentopdf:8080
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Resource Limits
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
bentopdf:
|
|
||||||
image: ghcr.io/alam00000/bentopdf:latest
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpus: '1'
|
|
||||||
memory: 512M
|
|
||||||
reservations:
|
|
||||||
cpus: '0.25'
|
|
||||||
memory: 128M
|
|
||||||
```
|
|
||||||
|
|
||||||
## Updating
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Pull latest image
|
|
||||||
docker compose pull
|
|
||||||
|
|
||||||
# Recreate container
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
# Deploy to Hostinger
|
|
||||||
|
|
||||||
[Hostinger](https://hostinger.com) is a popular shared hosting provider. This guide covers deploying BentoPDF to Hostinger's shared hosting.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Hostinger hosting plan with file manager access
|
|
||||||
- Node.js installed locally for building
|
|
||||||
|
|
||||||
## Step 1: Build the Project
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/alam00000/bentopdf.git
|
|
||||||
cd bentopdf
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
The built files will be in the `dist` folder.
|
|
||||||
|
|
||||||
## Step 2: Upload to Hostinger
|
|
||||||
|
|
||||||
### Root Domain Deployment
|
|
||||||
|
|
||||||
1. Log in to your Hostinger account
|
|
||||||
2. Go to **File Manager** → **public_html**
|
|
||||||
3. **Delete** any existing files (backup if needed)
|
|
||||||
4. **Upload** all contents of your local `dist` folder to `public_html`
|
|
||||||
|
|
||||||
### Subdirectory Deployment
|
|
||||||
|
|
||||||
If deploying to a subdirectory (e.g., `yourdomain.com/pdf-tools/`):
|
|
||||||
|
|
||||||
1. Build with the correct base URL:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
BASE_URL=/pdf-tools/ npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Create the folder in Hostinger:
|
|
||||||
- Go to **File Manager** → **public_html**
|
|
||||||
- Create a new folder: `pdf-tools`
|
|
||||||
|
|
||||||
3. Upload all contents of `dist` to `public_html/pdf-tools/`
|
|
||||||
|
|
||||||
## Step 3: Create .htaccess File
|
|
||||||
|
|
||||||
Create a `.htaccess` file in the root of your deployment folder (`public_html` or `public_html/pdf-tools/`):
|
|
||||||
|
|
||||||
::: warning Important
|
|
||||||
For subdirectory deployment, change `RewriteBase /` to `RewriteBase /pdf-tools/` (or your folder name).
|
|
||||||
:::
|
|
||||||
|
|
||||||
```apache
|
|
||||||
RewriteEngine On
|
|
||||||
RewriteBase /
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# 1. SECURITY HEADERS (CRITICAL FOR WASM)
|
|
||||||
# ============================================
|
|
||||||
<IfModule mod_headers.c>
|
|
||||||
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=()"
|
|
||||||
|
|
||||||
# REQUIRED for soffice.js (SharedArrayBuffer)
|
|
||||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
|
||||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# 2. BROWSER CACHING
|
|
||||||
# ============================================
|
|
||||||
<IfModule mod_expires.c>
|
|
||||||
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"
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# 3. COMPRESSION (STANDARD)
|
|
||||||
# ============================================
|
|
||||||
# Prevent server from double-compressing files
|
|
||||||
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
|
|
||||||
SetEnvIfNoCase Request_URI "\.br$" no-gzip
|
|
||||||
SetEnvIfNoCase Request_URI "\.wasm$" no-gzip
|
|
||||||
|
|
||||||
<IfModule mod_deflate.c>
|
|
||||||
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
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Handle soffice.wasm.gz correctly
|
|
||||||
<FilesMatch "soffice\.wasm\.gz$">
|
|
||||||
ForceType application/wasm
|
|
||||||
Header set Content-Encoding "gzip"
|
|
||||||
Header set Cross-Origin-Resource-Policy "cross-origin"
|
|
||||||
Header append Vary Accept-Encoding
|
|
||||||
</FilesMatch>
|
|
||||||
|
|
||||||
# Handle data.gz
|
|
||||||
<FilesMatch "soffice\.data\.gz$">
|
|
||||||
ForceType application/octet-stream
|
|
||||||
Header set Content-Encoding "gzip"
|
|
||||||
Header append Vary Accept-Encoding
|
|
||||||
</FilesMatch>
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# 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
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
RewriteCond %{REQUEST_URI} (.+)/$
|
|
||||||
RewriteRule ^ %1 [R=301,L]
|
|
||||||
|
|
||||||
# Existing files/dirs
|
|
||||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
|
||||||
RewriteCond %{REQUEST_FILENAME} -d
|
|
||||||
RewriteRule ^ - [L]
|
|
||||||
|
|
||||||
# Language routes
|
|
||||||
RewriteRule ^(de|en|zh|vi)/(.*)$ /$2 [L]
|
|
||||||
RewriteRule ^(de|en|zh|vi)/?$ / [L]
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# 5.5. DOCS ROUTING (VitePress)
|
|
||||||
# ============================================
|
|
||||||
RewriteCond %{REQUEST_URI} ^/docs
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
RewriteCond %{REQUEST_FILENAME}\.html -f
|
|
||||||
RewriteRule ^(.*)$ $1.html [L]
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# 6. SPA FALLBACK
|
|
||||||
# ============================================
|
|
||||||
# SPA Fallback (exclude /docs)
|
|
||||||
RewriteCond %{REQUEST_URI} !^/docs
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
RewriteRule ^ /index.html [L]
|
|
||||||
|
|
||||||
ErrorDocument 404 /index.html
|
|
||||||
```
|
|
||||||
|
|
||||||
## Subdirectory .htaccess Example
|
|
||||||
|
|
||||||
For `yourdomain.com/pdf-tools/`, update these lines:
|
|
||||||
|
|
||||||
```apache
|
|
||||||
RewriteBase /pdf-tools/
|
|
||||||
|
|
||||||
# ... (same content) ...
|
|
||||||
|
|
||||||
# SPA Fallback - update path
|
|
||||||
RewriteRule ^ /pdf-tools/index.html [L]
|
|
||||||
|
|
||||||
ErrorDocument 404 /pdf-tools/index.html
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### WASM Files Not Loading
|
|
||||||
|
|
||||||
Ensure the MIME types are correctly set in `.htaccess`:
|
|
||||||
|
|
||||||
```apache
|
|
||||||
AddType application/wasm .wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
### LibreOffice Tools Not Working
|
|
||||||
|
|
||||||
The security headers are critical for SharedArrayBuffer:
|
|
||||||
|
|
||||||
```apache
|
|
||||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
|
||||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
|
||||||
```
|
|
||||||
|
|
||||||
If headers aren't being applied, contact Hostinger support to enable `mod_headers`.
|
|
||||||
|
|
||||||
### 404 Errors on Page Refresh
|
|
||||||
|
|
||||||
Make sure the SPA fallback rule is at the end of your `.htaccess`:
|
|
||||||
|
|
||||||
```apache
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
RewriteRule ^ /index.html [L]
|
|
||||||
```
|
|
||||||
|
|
||||||
### File Upload Limits
|
|
||||||
|
|
||||||
Hostinger may have file size limits. Upload WASM files separately if bulk upload fails.
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# Self-Hosting Guide
|
|
||||||
|
|
||||||
BentoPDF can be self-hosted on your own infrastructure. This guide covers various deployment options.
|
|
||||||
|
|
||||||
## Quick Start with Docker
|
|
||||||
|
|
||||||
The fastest way to self-host BentoPDF:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
Or with Docker Compose:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# docker-compose.yml
|
|
||||||
services:
|
|
||||||
bentopdf:
|
|
||||||
image: ghcr.io/alam00000/bentopdf:latest
|
|
||||||
ports:
|
|
||||||
- "3000:8080"
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building from Source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone and build
|
|
||||||
git clone https://github.com/alam00000/bentopdf.git
|
|
||||||
cd bentopdf
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# The built files are in the `dist` folder
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### Simple Mode
|
|
||||||
|
|
||||||
Simple Mode is designed for internal organizational use where you want to hide all branding and marketing content, showing only the essential PDF tools.
|
|
||||||
|
|
||||||
**What Simple Mode hides:**
|
|
||||||
- Navigation bar
|
|
||||||
- Hero section with marketing content
|
|
||||||
- Features, FAQ, testimonials sections
|
|
||||||
- Footer
|
|
||||||
- Updates page title to "PDF Tools"
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build with Simple Mode
|
|
||||||
SIMPLE_MODE=true npm run build
|
|
||||||
|
|
||||||
# Or use the pre-built Docker image
|
|
||||||
docker run -p 3000:8080 bentopdf/bentopdf-simple:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
See [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) for full details.
|
|
||||||
|
|
||||||
### Base URL
|
|
||||||
|
|
||||||
Deploy to a subdirectory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
BASE_URL=/pdf-tools/ npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deployment Guides
|
|
||||||
|
|
||||||
Choose your platform:
|
|
||||||
|
|
||||||
- [Vercel](/self-hosting/vercel)
|
|
||||||
- [Netlify](/self-hosting/netlify)
|
|
||||||
- [Cloudflare Pages](/self-hosting/cloudflare)
|
|
||||||
- [AWS S3 + CloudFront](/self-hosting/aws)
|
|
||||||
- [Hostinger](/self-hosting/hostinger)
|
|
||||||
- [Nginx](/self-hosting/nginx)
|
|
||||||
- [Apache](/self-hosting/apache)
|
|
||||||
- [Docker](/self-hosting/docker)
|
|
||||||
|
|
||||||
## System Requirements
|
|
||||||
|
|
||||||
| Requirement | Minimum |
|
|
||||||
|-------------|---------|
|
|
||||||
| Storage | ~500 MB (with all WASM modules) |
|
|
||||||
| RAM | 512 MB |
|
|
||||||
| CPU | Any modern processor |
|
|
||||||
|
|
||||||
::: tip
|
|
||||||
BentoPDF is a static site—there's no database or backend server required!
|
|
||||||
:::
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
# Deploy to Netlify
|
|
||||||
|
|
||||||
[Netlify](https://netlify.com) provides excellent static site hosting with a generous free tier.
|
|
||||||
|
|
||||||
## One-Click Deploy
|
|
||||||
|
|
||||||
[](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
|
|
||||||
|
|
||||||
## Manual Deployment
|
|
||||||
|
|
||||||
### Step 1: Connect Repository
|
|
||||||
|
|
||||||
1. Log in to [Netlify](https://app.netlify.com)
|
|
||||||
2. Click "Add new site" → "Import an existing project"
|
|
||||||
3. Connect your GitHub account
|
|
||||||
4. Select your BentoPDF fork
|
|
||||||
|
|
||||||
### Step 2: Configure Build Settings
|
|
||||||
|
|
||||||
| Setting | Value |
|
|
||||||
|---------|-------|
|
|
||||||
| Build command | `npm run build` |
|
|
||||||
| Publish directory | `dist` |
|
|
||||||
| Node version | 18+ |
|
|
||||||
|
|
||||||
### Step 3: Deploy
|
|
||||||
|
|
||||||
Click "Deploy site" and wait for the build.
|
|
||||||
|
|
||||||
## Configuration File
|
|
||||||
|
|
||||||
Create `netlify.toml` in your project root:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[build]
|
|
||||||
command = "npm run build"
|
|
||||||
publish = "dist"
|
|
||||||
|
|
||||||
[build.environment]
|
|
||||||
NODE_VERSION = "18"
|
|
||||||
|
|
||||||
# SPA routing
|
|
||||||
[[redirects]]
|
|
||||||
from = "/*"
|
|
||||||
to = "/index.html"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
# Cache WASM files
|
|
||||||
[[headers]]
|
|
||||||
for = "*.wasm"
|
|
||||||
[headers.values]
|
|
||||||
Cache-Control = "public, max-age=31536000, immutable"
|
|
||||||
Content-Type = "application/wasm"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
Set these in Site settings → Environment variables:
|
|
||||||
|
|
||||||
| Variable | Description |
|
|
||||||
|----------|-------------|
|
|
||||||
| `SIMPLE_MODE` | Set to `true` for minimal build |
|
|
||||||
|
|
||||||
## Custom Domain
|
|
||||||
|
|
||||||
1. Go to Site settings → Domain management
|
|
||||||
2. Click "Add custom domain"
|
|
||||||
3. Follow DNS configuration instructions
|
|
||||||
|
|
||||||
## Large Media
|
|
||||||
|
|
||||||
For large WASM files, consider enabling [Netlify Large Media](https://docs.netlify.com/large-media/overview/):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
netlify lm:setup
|
|
||||||
git lfs track "*.wasm"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Build Fails
|
|
||||||
|
|
||||||
Check Node version compatibility:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[build.environment]
|
|
||||||
NODE_VERSION = "20"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Slow Initial Load
|
|
||||||
|
|
||||||
Enable asset optimization in Site settings → Build & deploy → Asset optimization.
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
# Deploy with Nginx
|
|
||||||
|
|
||||||
Host BentoPDF on your own server using Nginx.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Ubuntu/Debian server
|
|
||||||
- Nginx installed
|
|
||||||
- SSL certificate (recommended: Let's Encrypt)
|
|
||||||
|
|
||||||
## Step 1: Build the Project
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/alam00000/bentopdf.git
|
|
||||||
cd bentopdf
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 2: Copy Files
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo mkdir -p /var/www/bentopdf
|
|
||||||
sudo cp -r dist/* /var/www/bentopdf/
|
|
||||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 3: Nginx Configuration
|
|
||||||
|
|
||||||
Create `/etc/nginx/sites-available/bentopdf`:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name your-domain.com;
|
|
||||||
return 301 https://$server_name$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
server_name your-domain.com;
|
|
||||||
|
|
||||||
# SSL Configuration
|
|
||||||
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
|
||||||
|
|
||||||
root /var/www/bentopdf;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# Gzip compression
|
|
||||||
gzip on;
|
|
||||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/wasm;
|
|
||||||
gzip_min_length 1000;
|
|
||||||
|
|
||||||
# WASM MIME type
|
|
||||||
types {
|
|
||||||
application/wasm wasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Cache static assets
|
|
||||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|wasm)$ {
|
|
||||||
expires 1y;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
}
|
|
||||||
|
|
||||||
# SPA routing - serve index.html for all routes
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Security headers
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 4: Enable the Site
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
|
|
||||||
sudo nginx -t
|
|
||||||
sudo systemctl reload nginx
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 5: SSL with Let's Encrypt
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install certbot python3-certbot-nginx
|
|
||||||
sudo certbot --nginx -d your-domain.com
|
|
||||||
```
|
|
||||||
|
|
||||||
## Subdirectory Deployment
|
|
||||||
|
|
||||||
To host at `/pdf/`:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
location /pdf/ {
|
|
||||||
alias /var/www/bentopdf/;
|
|
||||||
try_files $uri $uri/ /pdf/index.html;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Build with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
BASE_URL=/pdf/ npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tuning
|
|
||||||
|
|
||||||
Add to `nginx.conf`:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
http {
|
|
||||||
# Enable sendfile
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
tcp_nodelay on;
|
|
||||||
|
|
||||||
# Increase buffer sizes
|
|
||||||
client_max_body_size 100M;
|
|
||||||
|
|
||||||
# Worker connections
|
|
||||||
worker_connections 2048;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### WASM Not Loading
|
|
||||||
|
|
||||||
Ensure MIME type is set:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
types {
|
|
||||||
application/wasm wasm;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 502 Bad Gateway
|
|
||||||
|
|
||||||
Check Nginx error logs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo tail -f /var/log/nginx/error.log
|
|
||||||
```
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# Deploy to Vercel
|
|
||||||
|
|
||||||
[Vercel](https://vercel.com) offers the easiest deployment experience for static sites.
|
|
||||||
|
|
||||||
## One-Click Deploy
|
|
||||||
|
|
||||||
[](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
|
|
||||||
|
|
||||||
## Manual Deployment
|
|
||||||
|
|
||||||
### Step 1: Fork the Repository
|
|
||||||
|
|
||||||
Fork [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) to your GitHub account.
|
|
||||||
|
|
||||||
### Step 2: Import to Vercel
|
|
||||||
|
|
||||||
1. Go to [vercel.com/new](https://vercel.com/new)
|
|
||||||
2. Select your forked repository
|
|
||||||
3. Configure the project:
|
|
||||||
|
|
||||||
| Setting | Value |
|
|
||||||
|---------|-------|
|
|
||||||
| Framework Preset | Vite |
|
|
||||||
| Build Command | `npm run build` |
|
|
||||||
| Output Directory | `dist` |
|
|
||||||
| Install Command | `npm install` |
|
|
||||||
|
|
||||||
### Step 3: Environment Variables (Optional)
|
|
||||||
|
|
||||||
Add these if needed:
|
|
||||||
|
|
||||||
```
|
|
||||||
SIMPLE_MODE=false
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Deploy
|
|
||||||
|
|
||||||
Click "Deploy" and wait for the build to complete.
|
|
||||||
|
|
||||||
## Custom Domain
|
|
||||||
|
|
||||||
1. Go to your project settings
|
|
||||||
2. Navigate to "Domains"
|
|
||||||
3. Add your custom domain
|
|
||||||
4. Configure DNS as instructed
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
::: warning Large Files
|
|
||||||
Vercel's serverless functions have a 50MB limit. Since BentoPDF is a static site, this shouldn't affect you, but WASM modules are large (~100MB total). Ensure they're served from the `/public` folder.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Build Timeout
|
|
||||||
|
|
||||||
If builds timeout, try:
|
|
||||||
|
|
||||||
```json
|
|
||||||
// vercel.json
|
|
||||||
{
|
|
||||||
"buildCommand": "npm run build",
|
|
||||||
"outputDirectory": "dist"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 404 on Refresh
|
|
||||||
|
|
||||||
Add a `vercel.json` for SPA routing:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# Tools Reference
|
|
||||||
|
|
||||||
BentoPDF offers **50+ tools** for working with PDFs. All tools run entirely in your browser—your files never leave your device.
|
|
||||||
|
|
||||||
## 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. |
|
|
||||||
|
|
||||||
## Edit & Modify PDFs
|
|
||||||
|
|
||||||
| Tool Name | Description |
|
|
||||||
| :--------------------- | :---------------------------------------------------------- |
|
|
||||||
| **PDF Editor** | A comprehensive editor to modify your PDFs. |
|
|
||||||
| **Create Fillable Forms** | Create professional fillable PDF forms with text fields, checkboxes, dropdowns, radio buttons, signatures, and more. |
|
|
||||||
| **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. |
|
|
||||||
|
|
||||||
## 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. |
|
|
||||||
| **EPUB to PDF** | Convert EPUB e-books to PDF format. |
|
|
||||||
| **MOBI to PDF** | Convert MOBI e-books to PDF format. |
|
|
||||||
| **JSON to PDF** | Convert JSON to PDF. |
|
|
||||||
|
|
||||||
## Convert from PDF
|
|
||||||
|
|
||||||
| Tool Name | Description |
|
|
||||||
| :------------------- | :----------------------------------------------------------------------------- |
|
|
||||||
| **PDF to Image** | Convert PDF pages to JPG, PNG, WebP, BMP, or TIFF formats. |
|
|
||||||
| **PDF to JPG** | Convert each PDF page into a JPG image. |
|
|
||||||
| **PDF to PNG** | Convert each PDF page into a PNG image. |
|
|
||||||
| **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 Greyscale** | Convert a color PDF into a black-and-white version. |
|
|
||||||
| **PDF to Text** | Extract text content from PDF files. |
|
|
||||||
| **PDF to Excel** | Extract tables from PDF to XLSX format. |
|
|
||||||
| **PDF to CSV** | Extract tables from PDF to CSV format. |
|
|
||||||
| **OCR PDF** | Make scanned PDFs searchable and copyable using Optical Character Recognition. |
|
|
||||||
| **PDF to JSON** | Convert PDF files to JSON format. |
|
|
||||||
| **Extract Images** | Pull all images from a PDF. |
|
|
||||||
| **Extract Tables** | Export tables as CSV, JSON, or Markdown. |
|
|
||||||
|
|
||||||
## 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 malicious 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. |
|
|
||||||
@@ -5,10 +5,6 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Frequently Asked Questions - BentoPDF</title>
|
<title>Frequently Asked Questions - BentoPDF</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
|
||||||
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
|
||||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
||||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -17,7 +13,10 @@
|
|||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex justify-between items-center h-16">
|
<div class="flex justify-between items-center h-16">
|
||||||
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8" />
|
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||||
|
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||||
|
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
<span class="text-white font-bold text-xl ml-2">
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
<a href="index.html">BentoPDF</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
</span>
|
</span>
|
||||||
@@ -26,9 +25,9 @@
|
|||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
<div class="hidden md:flex items-center space-x-8">
|
||||||
<a href="index.html" class="nav-link">Home</a>
|
<a href="index.html" class="nav-link">Home</a>
|
||||||
<a href="/about.html" class="nav-link">About</a>
|
<a href="./about.html" class="nav-link">About</a>
|
||||||
<a href="/contact.html" class="nav-link">Contact</a>
|
<a href="./contact.html" class="nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="nav-link">Licensing</a>
|
<a href="./licensing.html" class="nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -57,9 +56,9 @@
|
|||||||
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
||||||
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
||||||
<a href="index.html" class="mobile-nav-link">Home</a>
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
<a href="/about.html" class="mobile-nav-link">About</a>
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
<a href="/contact.html" class="mobile-nav-link">Contact</a>
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="mobile-nav-link">Licensing</a>
|
<a href="./licensing.html" class="mobile-nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="mobile-nav-link">All Tools</a>
|
<a href="index.html#tools-header" class="mobile-nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -237,7 +236,7 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
||||||
<div class="mb-8 md:mb-0">
|
<div class="mb-8 md:mb-0">
|
||||||
<div class="flex items-center justify-center md:justify-start mb-4">
|
<div class="flex items-center justify-center md:justify-start mb-4">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
<img src="images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
||||||
<span class="text-xl font-bold text-white">BentoPDF</span>
|
<span class="text-xl font-bold text-white">BentoPDF</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 text-sm">
|
<p class="text-gray-400 text-sm">
|
||||||
@@ -252,13 +251,13 @@
|
|||||||
<h3 class="font-bold text-white mb-4">Company</h3>
|
<h3 class="font-bold text-white mb-4">Company</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/about.html" class="hover:text-indigo-400">About Us</a>
|
<a href="./about.html" class="hover:text-indigo-400">About Us</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/faq.html" class="hover:text-indigo-400">FAQ</a>
|
<a href="./faq.html" class="hover:text-indigo-400">FAQ</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/contact.html" class="hover:text-indigo-400">Contact Us</a>
|
<a href="./contact.html" class="hover:text-indigo-400">Contact Us</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -267,13 +266,13 @@
|
|||||||
<h3 class="font-bold text-white mb-4">Legal</h3>
|
<h3 class="font-bold text-white mb-4">Legal</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/licensing.html" class="hover:text-indigo-400">Licensing</a>
|
<a href="./licensing.html" class="hover:text-indigo-400">Licensing</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/terms.html" class="hover:text-indigo-400">Terms and Conditions</a>
|
<a href="./terms.html" class="hover:text-indigo-400">Terms and Conditions</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/privacy.html" class="hover:text-indigo-400">Privacy Policy</a>
|
<a href="./privacy.html" class="hover:text-indigo-400">Privacy Policy</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -289,7 +288,7 @@
|
|||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/Bgq3Ay3f2w" target="_blank" rel="noopener noreferrer"
|
<a href="https://discord.gg/AP2Y97juZT" target="_blank" rel="noopener noreferrer"
|
||||||
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
||||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<path
|
<path
|
||||||
@@ -319,7 +318,6 @@
|
|||||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||||
<script type="module" src="src/version.ts"></script>
|
<script type="module" src="src/version.ts"></script>
|
||||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
<script type="module" src="src/js/main.ts"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
+111
-150
@@ -5,15 +5,10 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>BentoPDF - The Privacy First PDF Toolkit</title>
|
<title>BentoPDF - The Privacy First PDF Toolkit</title>
|
||||||
<!-- i18n: hreflang tags for SEO -->
|
<link rel="icon" type="image/svg+xml" href="images/favicon.svg" />
|
||||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
<link rel="icon" type="image/png" href="images/favicon.png" />
|
||||||
<link rel="alternate" hreflang="en" href="/en/" />
|
<link rel="apple-touch-icon" href="images/favicon.png" />
|
||||||
<link rel="alternate" hreflang="de" href="/de/" />
|
<link rel="icon" href="favicon.ico" sizes="any" />
|
||||||
<link rel="alternate" hreflang="vi" href="/vi/" />
|
|
||||||
<link rel="alternate" hreflang="x-default" href="/en/" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
|
||||||
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
|
||||||
<link href="src/css/styles.css" rel="stylesheet" />
|
<link href="src/css/styles.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -22,7 +17,7 @@
|
|||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex justify-between items-center h-16">
|
<div class="flex justify-between items-center h-16">
|
||||||
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8" />
|
<img src="images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8" />
|
||||||
<span class="text-white font-bold text-xl ml-2">
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
<a href="index.html">BentoPDF</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
</span>
|
</span>
|
||||||
@@ -30,11 +25,11 @@
|
|||||||
|
|
||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
<div class="hidden md:flex items-center space-x-8">
|
||||||
<a href="index.html" class="nav-link" data-i18n="nav.home">Home</a>
|
<a href="index.html" class="nav-link">Home</a>
|
||||||
<a href="/about.html" class="nav-link" data-i18n="nav.about">About</a>
|
<a href="./about.html" class="nav-link">About</a>
|
||||||
<a href="/contact.html" class="nav-link" data-i18n="nav.contact">Contact</a>
|
<a href="./contact.html" class="nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="nav-link" data-i18n="nav.allTools">All Tools</a>
|
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
||||||
<a href="https://github.com/alam00000/bentopdf/" target="_blank" rel="noopener noreferrer" class="
|
<a href="https://github.com/alam00000/bentopdf/" target="_blank" rel="noopener noreferrer" class="
|
||||||
inline-flex items-center gap-1.5 text-sm font-medium
|
inline-flex items-center gap-1.5 text-sm font-medium
|
||||||
bg-gray-800 text-gray-200 border border-gray-600
|
bg-gray-800 text-gray-200 border border-gray-600
|
||||||
@@ -70,7 +65,7 @@
|
|||||||
<button id="mobile-menu-button" type="button"
|
<button id="mobile-menu-button" type="button"
|
||||||
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
|
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
|
||||||
aria-controls="mobile-menu" aria-expanded="false">
|
aria-controls="mobile-menu" aria-expanded="false">
|
||||||
<span class="sr-only" data-i18n="nav.openMainMenu">Open main menu</span>
|
<span class="sr-only">Open main menu</span>
|
||||||
<!-- Hamburger Icon -->
|
<!-- Hamburger Icon -->
|
||||||
<svg id="menu-icon" class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
<svg id="menu-icon" class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
stroke="currentColor" aria-hidden="true">
|
stroke="currentColor" aria-hidden="true">
|
||||||
@@ -89,50 +84,23 @@
|
|||||||
<!-- Mobile Menu Dropdown -->
|
<!-- Mobile Menu Dropdown -->
|
||||||
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
||||||
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
||||||
<a href="index.html" class="mobile-nav-link" data-i18n="nav.home">Home</a>
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about">About</a>
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
<a href="/contact.html" class="mobile-nav-link" data-i18n="nav.contact">Contact</a>
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="mobile-nav-link" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="mobile-nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="mobile-nav-link" data-i18n="nav.allTools">All Tools</a>
|
<a href="index.html#tools-header" class="mobile-nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Donation Ribbon -->
|
|
||||||
<div id="donation-ribbon"
|
|
||||||
class="bg-gradient-to-r from-indigo-600 via-purple-600 to-pink-500 py-2 px-4 text-center hide-section">
|
|
||||||
<div class="container mx-auto flex items-center justify-center gap-3 flex-wrap">
|
|
||||||
<span class="text-white text-sm font-medium flex items-center gap-2">
|
|
||||||
<span data-i18n="donation.message">Love BentoPDF? Help us keep it free and open source!</span>
|
|
||||||
</span>
|
|
||||||
<a href="https://ko-fi.com/alio01" target="_blank" rel="noopener noreferrer"
|
|
||||||
class="inline-flex items-center gap-1.5 px-3 py-1 bg-white text-purple-700 rounded-full text-xs font-semibold hover:bg-gray-100 transition-colors shadow-sm">
|
|
||||||
<i data-lucide="heart" class="w-3.5 h-3.5 text-purple-700"></i>
|
|
||||||
<span data-i18n="donation.button">Donate</span>
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/sponsors/alam00000" target="_blank" rel="noopener noreferrer"
|
|
||||||
class="inline-flex items-center gap-1.5 px-3 py-1 bg-gray-900 text-white rounded-full text-xs font-semibold hover:bg-gray-800 transition-colors shadow-sm border border-gray-700">
|
|
||||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
|
||||||
clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
<span>Sponsor</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
||||||
<section id="hero-section" class="text-center py-20 relative">
|
<section id="hero-section" class="text-center py-20 relative">
|
||||||
<div class="bg-grid-pattern"></div>
|
<div class="bg-grid-pattern"></div>
|
||||||
<h1 class="text-4xl md:text-7xl font-bold text-white mb-4">
|
<h1 class="text-4xl md:text-7xl font-bold text-white mb-4">
|
||||||
<span data-i18n="hero.title">The</span> <span
|
The <span
|
||||||
class="inline-flex items-center gap-3 border-2 border-indigo-400 bg-indigo-900 text-indigo-300 px-4 py-2 rounded-full mx-2 align-middle">
|
class="inline-flex items-center gap-3 border-2 border-indigo-400 bg-indigo-900 text-indigo-300 px-4 py-2 rounded-full mx-2 align-middle">
|
||||||
<span data-i18n="hero.pdfToolkit">PDF Toolkit</span> <i data-lucide="briefcase"
|
PDF Toolkit <i data-lucide="briefcase" class="w-8 h-8 md:w-12 md:h-12 transform -rotate-4"></i> </span> built
|
||||||
class="w-8 h-8 md:w-12 md:h-12 transform -rotate-4"></i> </span> <span
|
for
|
||||||
data-i18n="hero.builtForPrivacy">built
|
privacy<span
|
||||||
for
|
|
||||||
privacy</span><span
|
|
||||||
class="text-4xl md:text-6xl text-transparent bg-clip-text bg-gradient-to-r from-indigo-500 to-purple-500">.</span>
|
class="text-4xl md:text-6xl text-transparent bg-clip-text bg-gradient-to-r from-indigo-500 to-purple-500">.</span>
|
||||||
</h1>
|
</h1>
|
||||||
<!-- <p class="text-lg text-gray-400 mb-12">Fast, Secure and Forever Free.</p> -->
|
<!-- <p class="text-lg text-gray-400 mb-12">Fast, Secure and Forever Free.</p> -->
|
||||||
@@ -140,24 +108,24 @@
|
|||||||
<span
|
<span
|
||||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-gray-800/40 border border-gray-700/50 text-gray-400 text-sm font-medium">
|
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-gray-800/40 border border-gray-700/50 text-gray-400 text-sm font-medium">
|
||||||
<i data-lucide="check-circle" class="w-4 h-4 text-gray-400"></i>
|
<i data-lucide="check-circle" class="w-4 h-4 text-gray-400"></i>
|
||||||
<span data-i18n="hero.noSignups">No Signups</span>
|
No Signups
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-gray-800/40 border border-gray-700/50 text-gray-400 text-sm font-medium">
|
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-gray-800/40 border border-gray-700/50 text-gray-400 text-sm font-medium">
|
||||||
<i data-lucide="check-circle" class="w-4 h-4 text-gray-400"></i>
|
<i data-lucide="check-circle" class="w-4 h-4 text-gray-400"></i>
|
||||||
<span data-i18n="hero.unlimitedUse">Unlimited Use</span>
|
Unlimited Use
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-gray-800/40 border border-gray-700/50 text-gray-400 text-sm font-medium">
|
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-gray-800/40 border border-gray-700/50 text-gray-400 text-sm font-medium">
|
||||||
<i data-lucide="check-circle" class="w-4 h-4 text-gray-400"></i>
|
<i data-lucide="check-circle" class="w-4 h-4 text-gray-400"></i>
|
||||||
<span data-i18n="hero.worksOffline">Works Offline</span>
|
Works Offline
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col items-center gap-4">
|
<div class="flex flex-col items-center gap-4">
|
||||||
<a href="#tools-header"
|
<a href="#tools-header"
|
||||||
class="inline-flex items-center gap-3 pl-8 pr-2 py-2 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1">
|
class="inline-flex items-center gap-3 pl-8 pr-2 py-2 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1">
|
||||||
<span data-i18n="hero.startUsing">Start Using Now</span>
|
Start Using Now
|
||||||
<span class="bg-white text-indigo-600 rounded-full p-2">
|
<span class="bg-white text-indigo-600 rounded-full p-2">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4">
|
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4">
|
||||||
@@ -173,8 +141,7 @@
|
|||||||
<!-- USED BY SECTION START -->
|
<!-- USED BY SECTION START -->
|
||||||
<section class="py-10 hide-section">
|
<section class="py-10 hide-section">
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<p class="text-center text-gray-500 text-sm font-medium mb-8" data-i18n="usedBy.title">Used by companies and
|
<p class="text-center text-gray-500 text-sm font-medium mb-8">Used by companies and people working at</p>
|
||||||
people working at</p>
|
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap justify-center items-center gap-8 md:gap-12 opacity-70 grayscale hover:grayscale-0 transition-all duration-500">
|
class="flex flex-wrap justify-center items-center gap-8 md:gap-12 opacity-70 grayscale hover:grayscale-0 transition-all duration-500">
|
||||||
|
|
||||||
@@ -233,61 +200,58 @@
|
|||||||
|
|
||||||
<section id="features-section" class="py-20">
|
<section id="features-section" class="py-20">
|
||||||
<h2 class="text-3xl md:text-4xl font-bold text-center text-white mb-12">
|
<h2 class="text-3xl md:text-4xl font-bold text-center text-white mb-12">
|
||||||
<span data-i18n="features.title"></span> <span class="marker-slanted"
|
Why <span class="marker-slanted">BentoPDF?</span>
|
||||||
data-i18n="features.bentoPdf">BentoPDF?</span>
|
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
<div class="bg-gray-800 p-6 rounded-lg">
|
<div class="bg-gray-800 p-6 rounded-lg">
|
||||||
<div class="flex items-center gap-4 mb-3">
|
<div class="flex items-center gap-4 mb-3">
|
||||||
<i data-lucide="user-plus" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
<i data-lucide="user-plus" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
||||||
<h3 class="text-xl font-bold text-white" data-i18n="features.noSignup.title">No Signup</h3>
|
<h3 class="text-xl font-bold text-white">No Signup</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 pl-14" data-i18n="features.noSignup.description">
|
<p class="text-gray-400 pl-14">
|
||||||
Start instantly, no accounts or emails.
|
Start instantly, no accounts or emails.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-800 p-6 rounded-lg">
|
<div class="bg-gray-800 p-6 rounded-lg">
|
||||||
<div class="flex items-center gap-4 mb-3">
|
<div class="flex items-center gap-4 mb-3">
|
||||||
<i data-lucide="shield" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
<i data-lucide="shield" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
||||||
<h3 class="text-xl font-bold text-white" data-i18n="features.noUploads.title">No Uploads</h3>
|
<h3 class="text-xl font-bold text-white">No Uploads</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 pl-14" data-i18n="features.noUploads.description">
|
<p class="text-gray-400 pl-14">
|
||||||
100% client-side, your files never leave your device.
|
100% client-side, your files never leave your device.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-800 p-6 rounded-lg">
|
<div class="bg-gray-800 p-6 rounded-lg">
|
||||||
<div class="flex items-center gap-4 mb-3">
|
<div class="flex items-center gap-4 mb-3">
|
||||||
<i data-lucide="badge-dollar-sign" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
<i data-lucide="badge-dollar-sign" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
||||||
<h3 class="text-xl font-bold text-white" data-i18n="features.foreverFree.title">Forever Free</h3>
|
<h3 class="text-xl font-bold text-white">Forever Free</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 pl-14" data-i18n="features.foreverFree.description">
|
<p class="text-gray-400 pl-14">
|
||||||
All tools, no trials, no paywalls.
|
All tools, no trials, no paywalls.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-800 p-6 rounded-lg">
|
<div class="bg-gray-800 p-6 rounded-lg">
|
||||||
<div class="flex items-center gap-4 mb-3">
|
<div class="flex items-center gap-4 mb-3">
|
||||||
<i data-lucide="infinity" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
<i data-lucide="infinity" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
||||||
<h3 class="text-xl font-bold text-white" data-i18n="features.noLimits.title">No Limits</h3>
|
<h3 class="text-xl font-bold text-white">No Limits</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 pl-14" data-i18n="features.noLimits.description">
|
<p class="text-gray-400 pl-14">
|
||||||
Use as much as you want, no hidden caps.
|
Use as much as you want, no hidden caps.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-800 p-6 rounded-lg">
|
<div class="bg-gray-800 p-6 rounded-lg">
|
||||||
<div class="flex items-center gap-4 mb-3">
|
<div class="flex items-center gap-4 mb-3">
|
||||||
<i data-lucide="layers" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
<i data-lucide="layers" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
||||||
<h3 class="text-xl font-bold text-white" data-i18n="features.batchProcessing.title">Batch Processing</h3>
|
<h3 class="text-xl font-bold text-white">Batch Processing</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 pl-14" data-i18n="features.batchProcessing.description">Handle unlimited PDFs in one
|
<p class="text-gray-400 pl-14">Handle unlimited PDFs in one go.</p>
|
||||||
go.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-800 p-6 rounded-lg">
|
<div class="bg-gray-800 p-6 rounded-lg">
|
||||||
<div class="flex items-center gap-4 mb-3">
|
<div class="flex items-center gap-4 mb-3">
|
||||||
<i data-lucide="zap" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
<i data-lucide="zap" class="w-10 h-10 text-indigo-400 flex-shrink-0"></i>
|
||||||
<h3 class="text-xl font-bold text-white" data-i18n="features.lightningFast.title">Lightning Fast</h3>
|
<h3 class="text-xl font-bold text-white">Lightning Fast</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 pl-14" data-i18n="features.lightningFast.description">
|
<p class="text-gray-400 pl-14">
|
||||||
Process PDFs instantly, without waiting or delays.
|
Process PDFs instantly, without waiting or delays.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -298,10 +262,9 @@
|
|||||||
|
|
||||||
<div id="tools-header" class="text-center mb-12">
|
<div id="tools-header" class="text-center mb-12">
|
||||||
<h2 class="text-3xl md:text-4xl font-bold text-white mb-3">
|
<h2 class="text-3xl md:text-4xl font-bold text-white mb-3">
|
||||||
<span data-i18n="tools.title">Get Started with</span> <span class="marker-slanted ml-2"
|
Get Started with <span class="marker-slanted ml-2"> Tools</span>
|
||||||
data-i18n="tools.toolsLabel"> Tools</span>
|
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-400" data-i18n="tools.subtitle">Click a tool to open the file uploader</p>
|
<p class="text-gray-400">Click a tool to open the file uploader</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="grid-view">
|
<div id="grid-view">
|
||||||
@@ -312,8 +275,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<input type="text" id="search-bar"
|
<input type="text" id="search-bar"
|
||||||
class="w-full pl-10 pr-4 py-3 bg-gray-700 text-white border border-gray-600 rounded-lg focus:ring-indigo-500 focus:border-indigo-500"
|
class="w-full pl-10 pr-4 py-3 bg-gray-700 text-white border border-gray-600 rounded-lg focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
placeholder="Search for a tool (e.g., 'split', 'organize'...)"
|
placeholder="Search for a tool (e.g., 'split', 'organize'...)" />
|
||||||
data-i18n-placeholder="tools.searchPlaceholder" />
|
|
||||||
<span class="absolute inset-y-0 right-0 flex items-center rounded-lg pr-2 gap-2">
|
<span class="absolute inset-y-0 right-0 flex items-center rounded-lg pr-2 gap-2">
|
||||||
<kbd id="shortcut"
|
<kbd id="shortcut"
|
||||||
class="bg-gray-800 px-2.5 py-1.5 rounded-md text-xs font-semibold text-gray-300 border border-gray-700 shadow-sm"></kbd>
|
class="bg-gray-800 px-2.5 py-1.5 rounded-md text-xs font-semibold text-gray-300 border border-gray-700 shadow-sm"></kbd>
|
||||||
@@ -334,7 +296,7 @@
|
|||||||
<button id="back-to-grid"
|
<button id="back-to-grid"
|
||||||
class="flex items-center gap-2 text-indigo-400 hover:text-indigo-300 mb-6 font-semibold">
|
class="flex items-center gap-2 text-indigo-400 hover:text-indigo-300 mb-6 font-semibold">
|
||||||
<i data-lucide="arrow-left" class="cursor-pointer"></i>
|
<i data-lucide="arrow-left" class="cursor-pointer"></i>
|
||||||
<span class="cursor-pointer" data-i18n="tools.backToTools"> Back to Tools </span>
|
<span class="cursor-pointer"> Back to Tools </span>
|
||||||
</button>
|
</button>
|
||||||
<div id="tool-content"></div>
|
<div id="tool-content"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -342,7 +304,7 @@
|
|||||||
<div id="loader-modal" class="hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50">
|
<div id="loader-modal" class="hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50">
|
||||||
<div class="bg-gray-800 p-8 rounded-lg flex flex-col items-center gap-4 border border-gray-700 shadow-xl">
|
<div class="bg-gray-800 p-8 rounded-lg flex flex-col items-center gap-4 border border-gray-700 shadow-xl">
|
||||||
<div class="solid-spinner"></div>
|
<div class="solid-spinner"></div>
|
||||||
<p id="loader-text" class="text-white text-lg font-medium" data-i18n="loader.processing">
|
<p id="loader-text" class="text-white text-lg font-medium">
|
||||||
Processing...
|
Processing...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -405,7 +367,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button id="preferences-tab-btn" class="flex-1 py-3 text-sm font-medium text-gray-300 hover:text-white">
|
<button id="preferences-tab-btn" class="flex-1 py-3 text-sm font-medium text-gray-300 hover:text-white">
|
||||||
<i data-lucide="sliders" class="inline-block w-4 h-4 mr-1"></i>
|
<i data-lucide="sliders" class="inline-block w-4 h-4 mr-1"></i>
|
||||||
<span data-i18n="settings.preferences">Preferences</span>
|
Preferences
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -423,10 +385,8 @@
|
|||||||
<div class="mb-4 p-3 bg-indigo-900/20 border border-indigo-500/30 rounded-lg flex items-start gap-3">
|
<div class="mb-4 p-3 bg-indigo-900/20 border border-indigo-500/30 rounded-lg flex items-start gap-3">
|
||||||
<i data-lucide="info" class="w-5 h-5 text-indigo-400 flex-shrink-0 mt-0.5"></i>
|
<i data-lucide="info" class="w-5 h-5 text-indigo-400 flex-shrink-0 mt-0.5"></i>
|
||||||
<p class="text-sm text-indigo-200">
|
<p class="text-sm text-indigo-200">
|
||||||
<span data-i18n="settings.shortcutsInfo">Press and hold keys to set a shortcut. Changes are
|
Press and hold keys to set a shortcut. Changes are <strong>auto-saved</strong>.
|
||||||
<strong>auto-saved</strong>.</span>
|
<br><span class="text-yellow-300">⚠️ Avoid common browser shortcuts (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N
|
||||||
<br><span class="text-yellow-300" data-i18n="settings.shortcutsWarning">⚠️ Avoid common browser shortcuts
|
|
||||||
(Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N
|
|
||||||
etc.) as they
|
etc.) as they
|
||||||
may not work reliably.</span>
|
may not work reliably.</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -439,17 +399,15 @@
|
|||||||
|
|
||||||
<!-- Preferences Tab Content -->
|
<!-- Preferences Tab Content -->
|
||||||
<div id="preferences-tab-content" class="hidden p-6 overflow-y-auto flex-grow">
|
<div id="preferences-tab-content" class="hidden p-6 overflow-y-auto flex-grow">
|
||||||
<h4 class="text-lg font-semibold text-white mb-4" data-i18n="settings.displayPreferences">Display Preferences
|
<h4 class="text-lg font-semibold text-white mb-4">Display Preferences</h4>
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex items-center justify-between p-4 bg-gray-900 rounded-lg border border-gray-700">
|
<div class="flex items-center justify-between p-4 bg-gray-900 rounded-lg border border-gray-700">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<label for="full-width-toggle" class="text-sm font-medium text-gray-200 cursor-pointer"
|
<label for="full-width-toggle" class="text-sm font-medium text-gray-200 cursor-pointer">
|
||||||
data-i18n="settings.fullWidthMode">
|
|
||||||
Full Width Mode
|
Full Width Mode
|
||||||
</label>
|
</label>
|
||||||
<p class="text-xs text-gray-400 mt-1" data-i18n="settings.fullWidthDescription">
|
<p class="text-xs text-gray-400 mt-1">
|
||||||
Use the full screen width for all tools instead of a centered container
|
Use the full screen width for all tools instead of a centered container
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -475,8 +433,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="preferences-tab-footer" class="hidden w-full">
|
<div id="preferences-tab-footer" class="hidden w-full">
|
||||||
<p class="text-xs text-gray-400 text-center" data-i18n="settings.settingsAutoSaved">Settings are
|
<p class="text-xs text-gray-400 text-center">Settings are automatically saved</p>
|
||||||
automatically saved</p>
|
|
||||||
</div>
|
</div>
|
||||||
<button id="reset-shortcuts-btn"
|
<button id="reset-shortcuts-btn"
|
||||||
class="text-red-400 hover:text-red-300 text-sm font-medium transition-colors">
|
class="text-red-400 hover:text-red-300 text-sm font-medium transition-colors">
|
||||||
@@ -522,23 +479,21 @@
|
|||||||
<section id="security-compliance-section" class="py-20 hide-section">
|
<section id="security-compliance-section" class="py-20 hide-section">
|
||||||
<div class="mb-8 text-center">
|
<div class="mb-8 text-center">
|
||||||
<h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-white leading-tight mb-6 text-balance">
|
<h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-white leading-tight mb-6 text-balance">
|
||||||
<span data-i18n="compliance.title"></span>
|
Your data never leaves your device
|
||||||
<span class="inline-flex items-center gap-2">
|
<span class="inline-flex items-center gap-2">
|
||||||
<i data-lucide="shield" class="w-8 h-8 md:w-10 md:h-10 text-indigo-400 bg-indigo-900 rounded-lg p-1.5"></i>
|
<i data-lucide="shield" class="w-8 h-8 md:w-10 md:h-10 text-indigo-400 bg-indigo-900 rounded-lg p-1.5"></i>
|
||||||
<span data-i18n="compliance.weKeep">We keep</span>
|
We keep
|
||||||
</span>
|
</span>
|
||||||
<br class="hidden sm:block" />
|
<br class="hidden sm:block" />
|
||||||
<span
|
<span
|
||||||
class="inline-block border-2 border-indigo-400 bg-indigo-900 text-indigo-300 px-4 py-2 rounded-full mx-2 text-2xl md:text-3xl lg:text-4xl font-bold"
|
class="inline-block border-2 border-indigo-400 bg-indigo-900 text-indigo-300 px-4 py-2 rounded-full mx-2 text-2xl md:text-3xl lg:text-4xl font-bold">
|
||||||
data-i18n="compliance.yourInfoSafe">
|
|
||||||
your information safe
|
your information safe
|
||||||
</span>
|
</span>
|
||||||
<span data-i18n="compliance.byFollowingStandards">by following global security standards.</span>
|
by following global security standards.
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-16 text-center">
|
<div class="mb-16 text-center">
|
||||||
<span class="inline-flex items-center gap-2 text-indigo-400 text-lg font-medium transition-colors"
|
<span class="inline-flex items-center gap-2 text-indigo-400 text-lg font-medium transition-colors">
|
||||||
data-i18n="compliance.processingLocal">
|
|
||||||
All the processing happens locally on your device.
|
All the processing happens locally on your device.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -549,11 +504,10 @@
|
|||||||
<div class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4">
|
<div class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4">
|
||||||
<img src="images/gdpr.svg" alt="GDPR compliance" class="w-full h-full" />
|
<img src="images/gdpr.svg" alt="GDPR compliance" class="w-full h-full" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg md:text-xl font-bold text-white mb-3" data-i18n="compliance.gdpr.title">
|
<h3 class="text-lg md:text-xl font-bold text-white mb-3">
|
||||||
GDPR compliance
|
GDPR compliance
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-400 text-sm md:text-base leading-relaxed max-w-xs"
|
<p class="text-gray-400 text-sm md:text-base leading-relaxed max-w-xs">
|
||||||
data-i18n="compliance.gdpr.description">
|
|
||||||
Protects the personal data and privacy of individuals within the
|
Protects the personal data and privacy of individuals within the
|
||||||
European Union.
|
European Union.
|
||||||
</p>
|
</p>
|
||||||
@@ -563,11 +517,10 @@
|
|||||||
<div class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4">
|
<div class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4">
|
||||||
<img src="images/ccpa.svg" alt="CCPA compliance" class="w-full h-full" />
|
<img src="images/ccpa.svg" alt="CCPA compliance" class="w-full h-full" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg md:text-xl font-bold text-white mb-3" data-i18n="compliance.ccpa.title">
|
<h3 class="text-lg md:text-xl font-bold text-white mb-3">
|
||||||
CCPA compliance
|
CCPA compliance
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-400 text-sm md:text-base leading-relaxed max-w-xs"
|
<p class="text-gray-400 text-sm md:text-base leading-relaxed max-w-xs">
|
||||||
data-i18n="compliance.ccpa.description">
|
|
||||||
Gives California residents rights over how their personal
|
Gives California residents rights over how their personal
|
||||||
information is collected, used, and shared.
|
information is collected, used, and shared.
|
||||||
</p>
|
</p>
|
||||||
@@ -577,11 +530,10 @@
|
|||||||
<div class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4">
|
<div class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4">
|
||||||
<img src="images/hipaa.svg" alt="HIPAA compliance" class="w-full h-full" />
|
<img src="images/hipaa.svg" alt="HIPAA compliance" class="w-full h-full" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg md:text-xl font-bold text-white mb-3" data-i18n="compliance.hipaa.title">
|
<h3 class="text-lg md:text-xl font-bold text-white mb-3">
|
||||||
HIPAA compliance
|
HIPAA compliance
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-400 text-sm md:text-base leading-relaxed max-w-xs"
|
<p class="text-gray-400 text-sm md:text-base leading-relaxed max-w-xs">
|
||||||
data-i18n="compliance.hipaa.description">
|
|
||||||
Sets safeguards for handling sensitive health information in the
|
Sets safeguards for handling sensitive health information in the
|
||||||
United States healthcare system.
|
United States healthcare system.
|
||||||
</p>
|
</p>
|
||||||
@@ -595,17 +547,16 @@
|
|||||||
|
|
||||||
<section id="faq-accordion" class="space-y-4 hide-section">
|
<section id="faq-accordion" class="space-y-4 hide-section">
|
||||||
<h2 class="text-3xl md:text-4xl font-bold text-center text-white mb-12 mt-8">
|
<h2 class="text-3xl md:text-4xl font-bold text-center text-white mb-12 mt-8">
|
||||||
<span data-i18n="faq.title">Frequently Asked</span> <span class="marker-slanted"
|
Frequently Asked <span class="marker-slanted">Questions</span>
|
||||||
data-i18n="faq.questions">Questions</span>
|
|
||||||
</h2>
|
</h2>
|
||||||
<!-- Existing FAQs here... -->
|
<!-- Existing FAQs here... -->
|
||||||
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
||||||
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
||||||
<span class="text-lg font-semibold text-white" data-i18n="faq.isFree.question">Is BentoPDF really free?</span>
|
<span class="text-lg font-semibold text-white">Is BentoPDF really free?</span>
|
||||||
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
||||||
<p class="p-6 pt-0 text-gray-400" data-i18n="faq.isFree.answer">
|
<p class="p-6 pt-0 text-gray-400">
|
||||||
Yes, absolutely. All tools on BentoPDF are 100% free to use, with
|
Yes, absolutely. All tools on BentoPDF are 100% free to use, with
|
||||||
no file limits, no sign-ups, and no watermarks. We believe
|
no file limits, no sign-ups, and no watermarks. We believe
|
||||||
everyone deserves access to simple, powerful PDF tools without a
|
everyone deserves access to simple, powerful PDF tools without a
|
||||||
@@ -616,12 +567,11 @@
|
|||||||
|
|
||||||
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
||||||
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
||||||
<span class="text-lg font-semibold text-white" data-i18n="faq.areFilesSecure.question">Are my files secure?
|
<span class="text-lg font-semibold text-white">Are my files secure? Where are they processed?</span>
|
||||||
Where are they processed?</span>
|
|
||||||
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
||||||
<p class="p-6 pt-0 text-gray-400" data-i18n="faq.areFilesSecure.answer">
|
<p class="p-6 pt-0 text-gray-400">
|
||||||
Your files are as secure as possible because they **never leave
|
Your files are as secure as possible because they **never leave
|
||||||
your computer**. All processing happens directly in your web
|
your computer**. All processing happens directly in your web
|
||||||
browser (client-side). We never upload your files to a server, so
|
browser (client-side). We never upload your files to a server, so
|
||||||
@@ -632,12 +582,11 @@
|
|||||||
|
|
||||||
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
||||||
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
||||||
<span class="text-lg font-semibold text-white" data-i18n="faq.platforms.question">Does it work on Mac,
|
<span class="text-lg font-semibold text-white">Does it work on Mac, Windows, and Mobile?</span>
|
||||||
Windows, and Mobile?</span>
|
|
||||||
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
||||||
<p class="p-6 pt-0 text-gray-400" data-i18n="faq.platforms.answer">
|
<p class="p-6 pt-0 text-gray-400">
|
||||||
Yes! Since BentoPDF runs entirely in your browser, it works on any
|
Yes! Since BentoPDF runs entirely in your browser, it works on any
|
||||||
operating system with a modern web browser, including Windows,
|
operating system with a modern web browser, including Windows,
|
||||||
macOS, Linux, iOS, and Android.
|
macOS, Linux, iOS, and Android.
|
||||||
@@ -648,12 +597,11 @@
|
|||||||
<!-- New FAQ: GDPR Compliance -->
|
<!-- New FAQ: GDPR Compliance -->
|
||||||
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
||||||
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
||||||
<span class="text-lg font-semibold text-white" data-i18n="faq.gdprCompliant.question">Is BentoPDF GDPR
|
<span class="text-lg font-semibold text-white">Is BentoPDF GDPR compliant?</span>
|
||||||
compliant?</span>
|
|
||||||
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
||||||
<p class="p-6 pt-0 text-gray-400" data-i18n="faq.gdprCompliant.answer">
|
<p class="p-6 pt-0 text-gray-400">
|
||||||
Yes. BentoPDF is fully GDPR compliant. Since all file processing
|
Yes. BentoPDF is fully GDPR compliant. Since all file processing
|
||||||
happens locally in your browser and we never collect or transmit
|
happens locally in your browser and we never collect or transmit
|
||||||
your files to any server, we have no access to your data. This
|
your files to any server, we have no access to your data. This
|
||||||
@@ -665,12 +613,11 @@
|
|||||||
<!-- New FAQ: Data Storage -->
|
<!-- New FAQ: Data Storage -->
|
||||||
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
||||||
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
||||||
<span class="text-lg font-semibold text-white" data-i18n="faq.dataStorage.question">Do you store or track any
|
<span class="text-lg font-semibold text-white">Do you store or track any of my files?</span>
|
||||||
of my files?</span>
|
|
||||||
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
||||||
<p class="p-6 pt-0 text-gray-400" data-i18n="faq.dataStorage.answer">
|
<p class="p-6 pt-0 text-gray-400">
|
||||||
No. We never store, track, or log your files. Everything you do on
|
No. We never store, track, or log your files. Everything you do on
|
||||||
BentoPDF happens in your browser memory and disappears once you
|
BentoPDF happens in your browser memory and disappears once you
|
||||||
close the page. There are no uploads, no history logs, and no
|
close the page. There are no uploads, no history logs, and no
|
||||||
@@ -682,12 +629,11 @@
|
|||||||
<!-- New FAQ: Privacy -->
|
<!-- New FAQ: Privacy -->
|
||||||
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
||||||
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
||||||
<span class="text-lg font-semibold text-white" data-i18n="faq.different.question">What makes BentoPDF
|
<span class="text-lg font-semibold text-white">What makes BentoPDF different from other PDF tools?</span>
|
||||||
different from other PDF tools?</span>
|
|
||||||
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
||||||
<p class="p-6 pt-0 text-gray-400" data-i18n="faq.different.answer">
|
<p class="p-6 pt-0 text-gray-400">
|
||||||
Most PDF tools upload your files to a server for processing.
|
Most PDF tools upload your files to a server for processing.
|
||||||
BentoPDF never does that. We use secure, modern web technology to
|
BentoPDF never does that. We use secure, modern web technology to
|
||||||
process your files directly in your browser. This means faster
|
process your files directly in your browser. This means faster
|
||||||
@@ -699,12 +645,11 @@
|
|||||||
<!-- New FAQ: Browser-Based -->
|
<!-- New FAQ: Browser-Based -->
|
||||||
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
||||||
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
||||||
<span class="text-lg font-semibold text-white" data-i18n="faq.browserBased.question">How does browser-based
|
<span class="text-lg font-semibold text-white">How does browser-based processing keep me safe?</span>
|
||||||
processing keep me safe?</span>
|
|
||||||
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
||||||
<p class="p-6 pt-0 text-gray-400" data-i18n="faq.browserBased.answer">
|
<p class="p-6 pt-0 text-gray-400">
|
||||||
By running entirely inside your browser, BentoPDF ensures that
|
By running entirely inside your browser, BentoPDF ensures that
|
||||||
your files never leave your device. This eliminates the risks of
|
your files never leave your device. This eliminates the risks of
|
||||||
server hacks, data breaches, or unauthorized access. Your files
|
server hacks, data breaches, or unauthorized access. Your files
|
||||||
@@ -716,12 +661,11 @@
|
|||||||
<!-- New FAQ: Analytics -->
|
<!-- New FAQ: Analytics -->
|
||||||
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
<div class="faq-item bg-gray-800 rounded-lg border border-gray-700">
|
||||||
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
<button class="faq-question w-full flex justify-between items-center text-left p-6">
|
||||||
<span class="text-lg font-semibold text-white" data-i18n="faq.analytics.question">Do you use cookies or
|
<span class="text-lg font-semibold text-white">Do you use cookies or analytics to track me?</span>
|
||||||
analytics to track me?</span>
|
|
||||||
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
<i data-lucide="chevron-down" class="faq-icon w-6 h-6 text-gray-400 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out">
|
||||||
<p class="p-6 pt-0 text-gray-400" data-i18n="faq.analytics.answer">
|
<p class="p-6 pt-0 text-gray-400">
|
||||||
We care about your privacy. BentoPDF does not track personal
|
We care about your privacy. BentoPDF does not track personal
|
||||||
information. We use
|
information. We use
|
||||||
<a href="https://simpleanalytics.com" class="text-indigo-400 hover:underline" target="_blank"
|
<a href="https://simpleanalytics.com" class="text-indigo-400 hover:underline" target="_blank"
|
||||||
@@ -739,8 +683,7 @@
|
|||||||
|
|
||||||
<section id="testimonials-section" class="py-20 hide-section">
|
<section id="testimonials-section" class="py-20 hide-section">
|
||||||
<h2 class="text-3xl md:text-4xl font-bold text-center text-white mb-12">
|
<h2 class="text-3xl md:text-4xl font-bold text-center text-white mb-12">
|
||||||
<span data-i18n="testimonials.title">What Our</span> <span class="marker-slanted"
|
What Our <span class="marker-slanted">Users</span> Say
|
||||||
data-i18n="testimonials.users">Users</span> <span data-i18n="testimonials.say">Say</span>
|
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-7xl mx-auto">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-7xl mx-auto">
|
||||||
<div class="testimonial-card">
|
<div class="testimonial-card">
|
||||||
@@ -837,6 +780,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div class="section-divider"></div>
|
||||||
|
<section id="support-section" class="py-20">
|
||||||
|
<div class="max-w-4xl mx-auto text-center bg-gray-800 p-8 md:p-12 rounded-xl border border-gray-700 shadow-2xl">
|
||||||
|
<h2 class="text-3xl md:text-4xl font-bold text-white mb-4">
|
||||||
|
Like My Work?
|
||||||
|
</h2>
|
||||||
|
<p class="text-gray-400 mb-8 max-w-2xl mx-auto">
|
||||||
|
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!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href="https://ko-fi.com/alio01" target="_blank" rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center gap-3 px-8 py-3 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1 mt-5">
|
||||||
|
<i data-lucide="coffee" class="w-7 h-7"></i>
|
||||||
|
<span>Buy Me a Coffee</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div id="signature-ghost" class="hidden"></div>
|
<div id="signature-ghost" class="hidden"></div>
|
||||||
|
|
||||||
<!-- Scroll to Top Button -->
|
<!-- Scroll to Top Button -->
|
||||||
@@ -855,47 +818,46 @@
|
|||||||
<img src="public/images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
<img src="public/images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
||||||
<span class="text-xl font-bold text-white">BentoPDF</span>
|
<span class="text-xl font-bold text-white">BentoPDF</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
|
<p class="text-gray-400 text-sm">
|
||||||
© 2025 BentoPDF. All rights reserved.
|
© 2025 BentoPDF. All rights reserved.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-500 text-xs mt-2">
|
<p class="text-gray-500 text-xs mt-2">
|
||||||
<span data-i18n="footer.version">Version</span> <span id="app-version"></span>
|
Version <span id="app-version"></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">Company</h3>
|
<h3 class="font-bold text-white mb-4">Company</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/about.html" class="hover:text-indigo-400" data-i18n="footer.aboutUs">About Us</a>
|
<a href="./about.html" class="hover:text-indigo-400">About Us</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/faq.html" class="hover:text-indigo-400" data-i18n="footer.faqLink">FAQ</a>
|
<a href="./faq.html" class="hover:text-indigo-400">FAQ</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/contact.html" class="hover:text-indigo-400" data-i18n="footer.contactUs">Contact Us</a>
|
<a href="./contact.html" class="hover:text-indigo-400">Contact Us</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">Legal</h3>
|
<h3 class="font-bold text-white mb-4">Legal</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/licensing.html" class="hover:text-indigo-400" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="hover:text-indigo-400">Licensing</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/terms.html" class="hover:text-indigo-400" data-i18n="footer.termsAndConditions">Terms and
|
<a href="./terms.html" class="hover:text-indigo-400">Terms and Conditions</a>
|
||||||
Conditions</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/privacy.html" class="hover:text-indigo-400" data-i18n="footer.privacyPolicy">Privacy Policy</a>
|
<a href="./privacy.html" class="hover:text-indigo-400">Privacy Policy</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">Follow Us</h3>
|
<h3 class="font-bold text-white mb-4">Follow Us</h3>
|
||||||
<div class="flex justify-center md:justify-start space-x-4">
|
<div class="flex justify-center md:justify-start space-x-4">
|
||||||
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
||||||
class="text-gray-400 hover:text-indigo-400" title="GitHub">
|
class="text-gray-400 hover:text-indigo-400" title="GitHub">
|
||||||
@@ -905,7 +867,7 @@
|
|||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/Bgq3Ay3f2w" target="_blank" rel="noopener noreferrer"
|
<a href="https://discord.gg/AP2Y97juZT" target="_blank" rel="noopener noreferrer"
|
||||||
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
||||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<path
|
<path
|
||||||
@@ -936,7 +898,6 @@
|
|||||||
<script type="module" src="src/version.ts"></script>
|
<script type="module" src="src/version.ts"></script>
|
||||||
<script type="module" src="src/js/main.ts"></script>
|
<script type="module" src="src/js/main.ts"></script>
|
||||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
<script type="module" src="src/js/sw-register.ts"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
+34
-142
@@ -5,14 +5,6 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Licensing - BentoPDF</title>
|
<title>Licensing - BentoPDF</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
|
||||||
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
|
||||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
||||||
<link rel="alternate" hreflang="en" href="/en/licensing.html" />
|
|
||||||
<link rel="alternate" hreflang="de" href="/de/licensing.html" />
|
|
||||||
<link rel="alternate" hreflang="vi" href="/vi/licensing.html" />
|
|
||||||
<link rel="alternate" hreflang="x-default" href="/en/licensing.html" />
|
|
||||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -21,7 +13,10 @@
|
|||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex justify-between items-center h-16">
|
<div class="flex justify-between items-center h-16">
|
||||||
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8" />
|
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||||
|
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||||
|
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
<span class="text-white font-bold text-xl ml-2">
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
<a href="index.html">BentoPDF</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
</span>
|
</span>
|
||||||
@@ -29,11 +24,11 @@
|
|||||||
|
|
||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
<div class="hidden md:flex items-center space-x-8">
|
||||||
<a href="index.html" class="nav-link" data-i18n="nav.home">Home</a>
|
<a href="index.html" class="nav-link">Home</a>
|
||||||
<a href="/about.html" class="nav-link" data-i18n="nav.about">About</a>
|
<a href="./about.html" class="nav-link">About</a>
|
||||||
<a href="/contact.html" class="nav-link" data-i18n="nav.contact">Contact</a>
|
<a href="./contact.html" class="nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="nav-link" data-i18n="nav.allTools">All Tools</a>
|
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Hamburger Button -->
|
<!-- Mobile Hamburger Button -->
|
||||||
@@ -62,11 +57,11 @@
|
|||||||
<!-- Mobile Menu Dropdown -->
|
<!-- Mobile Menu Dropdown -->
|
||||||
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
||||||
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
||||||
<a href="index.html" class="mobile-nav-link" data-i18n="nav.home">Home</a>
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about">About</a>
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
<a href="/contact.html" class="mobile-nav-link" data-i18n="nav.contact">Contact</a>
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="mobile-nav-link" data-i18n="nav.licensing">Licensing</a>
|
<a href="./licensing.html" class="mobile-nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="mobile-nav-link" data-i18n="nav.allTools">All Tools</a>
|
<a href="index.html#tools-header" class="mobile-nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -74,10 +69,10 @@
|
|||||||
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
||||||
<section id="licensing-hero" class="text-center py-16 md:py-24">
|
<section id="licensing-hero" class="text-center py-16 md:py-24">
|
||||||
<h1 class="text-3xl md:text-6xl font-bold text-white mb-4">
|
<h1 class="text-3xl md:text-6xl font-bold text-white mb-4">
|
||||||
<span data-i18n="licensing.title">Licensing for</span>
|
Licensing for
|
||||||
<span class="marker-slanted">BentoPDF</span>
|
<span class="marker-slanted">BentoPDF</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-lg md:text-xl text-gray-400" data-i18n="licensing.subtitle">
|
<p class="text-lg md:text-xl text-gray-400">
|
||||||
Choose the license that fits your needs.
|
Choose the license that fits your needs.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
@@ -237,9 +232,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mt-4 text-sm text-gray-400 italic text-center">
|
<p class="mt-4 text-sm text-gray-400 italic text-center">
|
||||||
💡 Custom requests and development are available for separate charges. <a href="/contact.html"
|
💡 Custom requests and development are available for separate charges. <a href="./contact.html"
|
||||||
class="text-indigo-400 hover:underline">Contact us</a> for details.
|
class="text-indigo-400 hover:underline">Contact us</a> for details.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -361,7 +355,7 @@
|
|||||||
|
|
||||||
<div class="mt-8 text-center">
|
<div class="mt-8 text-center">
|
||||||
<p class="text-gray-400 text-sm">
|
<p class="text-gray-400 text-sm">
|
||||||
Still not sure? <a href="/contact.html"
|
Still not sure? <a href="./contact.html"
|
||||||
class="text-indigo-400 hover:underline font-semibold">Contact us</a> to discuss your specific
|
class="text-indigo-400 hover:underline font-semibold">Contact us</a> to discuss your specific
|
||||||
use case.
|
use case.
|
||||||
</p>
|
</p>
|
||||||
@@ -574,7 +568,7 @@
|
|||||||
Commercial License</a>.
|
Commercial License</a>.
|
||||||
<br><br>
|
<br><br>
|
||||||
If you have specific requirements or want a custom licensing arrangement,
|
If you have specific requirements or want a custom licensing arrangement,
|
||||||
feel free to <a href="/contact.html" class="text-indigo-400 hover:underline">contact us</a>
|
feel free to <a href="./contact.html" class="text-indigo-400 hover:underline">contact us</a>
|
||||||
with details about your use case, company size, and deployment needs.
|
with details about your use case, company size, and deployment needs.
|
||||||
We're happy to work with you to find the best fit.
|
We're happy to work with you to find the best fit.
|
||||||
</p>
|
</p>
|
||||||
@@ -585,104 +579,6 @@
|
|||||||
|
|
||||||
<div class="section-divider"></div>
|
<div class="section-divider"></div>
|
||||||
|
|
||||||
<section id="commercial-details" class="py-16 max-w-4xl mx-auto">
|
|
||||||
<h2 class="text-3xl md:text-4xl font-bold text-center text-white mb-12">
|
|
||||||
Commercial License <span class="marker-slanted">Details</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-1 gap-6">
|
|
||||||
<div class="bg-gray-800 rounded-xl p-6 border border-gray-700">
|
|
||||||
<h3 class="text-xl font-semibold text-white mb-4 flex items-center gap-3">
|
|
||||||
<i data-lucide="package" class="w-6 h-6 text-indigo-400"></i>
|
|
||||||
Delivery & Licensing Model
|
|
||||||
</h3>
|
|
||||||
<ul class="space-y-3 text-gray-400">
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<i data-lucide="check-circle" class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5"></i>
|
|
||||||
<span><strong class="text-white">No license key is required or provided.</strong> BentoPDF
|
|
||||||
intentionally does not use license keys to streamline deployment and reduce operational
|
|
||||||
friction for users.</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<i data-lucide="check-circle" class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5"></i>
|
|
||||||
<span>Upon purchase, users receive a ZIP archive containing the complete BentoPDF source
|
|
||||||
code and binaries.</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<i data-lucide="check-circle" class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5"></i>
|
|
||||||
<span>The source code provided is identical to the code published in our public open-source
|
|
||||||
repository.</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<i data-lucide="x-circle" class="w-5 h-5 text-red-400 flex-shrink-0 mt-0.5"></i>
|
|
||||||
<span><strong class="text-white">No refunds.</strong> Under no circumstances shall a refund
|
|
||||||
be made after purchase.</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p class="mt-4 text-gray-300">
|
|
||||||
<strong class="text-white">GitHub Repository:</strong>
|
|
||||||
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
|
||||||
class="text-indigo-400 hover:underline">github.com/alam00000/bentopdf</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gray-800 rounded-xl p-6 border border-gray-700">
|
|
||||||
<h3 class="text-xl font-semibold text-white mb-4 flex items-center gap-3">
|
|
||||||
<i data-lucide="alert-triangle" class="w-6 h-6 text-yellow-400"></i>
|
|
||||||
Important Notice on Third-Party Components
|
|
||||||
</h3>
|
|
||||||
<p class="text-gray-300 mb-4">
|
|
||||||
This software includes components licensed under the <strong class="text-white">GNU AGPL
|
|
||||||
v3</strong>, including:
|
|
||||||
</p>
|
|
||||||
<ul class="flex flex-wrap gap-2 mb-4">
|
|
||||||
<li class="px-3 py-1 bg-gray-700 rounded-full text-sm text-gray-300">CPDF</li>
|
|
||||||
<li class="px-3 py-1 bg-gray-700 rounded-full text-sm text-gray-300">PyMuPDF</li>
|
|
||||||
<li class="px-3 py-1 bg-gray-700 rounded-full text-sm text-gray-300">Ghostscript</li>
|
|
||||||
</ul>
|
|
||||||
<ul class="space-y-3 text-gray-400">
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<i data-lucide="alert-circle" class="w-5 h-5 text-yellow-400 flex-shrink-0 mt-0.5"></i>
|
|
||||||
<span>This commercial license <strong class="text-white">does not</strong> grant rights to
|
|
||||||
use AGPL components in a closed-source manner.</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<i data-lucide="alert-circle" class="w-5 h-5 text-yellow-400 flex-shrink-0 mt-0.5"></i>
|
|
||||||
<span>Users must comply with the AGPL v3 terms for these components.</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<i data-lucide="check-circle" class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5"></i>
|
|
||||||
<span>Source code for all AGPL components is included in the distribution.</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gray-800 rounded-xl p-6 border border-gray-700">
|
|
||||||
<h3 class="text-xl font-semibold text-white mb-4 flex items-center gap-3">
|
|
||||||
<i data-lucide="file-text" class="w-6 h-6 text-indigo-400"></i>
|
|
||||||
Invoicing
|
|
||||||
</h3>
|
|
||||||
<ul class="space-y-3 text-gray-400">
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<i data-lucide="info" class="w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5"></i>
|
|
||||||
<span>Ko-fi does not automatically issue invoices.</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<i data-lucide="check-circle" class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5"></i>
|
|
||||||
<span>An official invoice will be provided immediately upon request.</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p class="mt-4 text-gray-300">
|
|
||||||
<strong class="text-white">Contact us:</strong>
|
|
||||||
<a href="mailto:[email protected]"
|
|
||||||
class="text-indigo-400 hover:underline">[email protected]</a> with your purchase details.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="section-divider"></div>
|
|
||||||
|
|
||||||
<section id="cta-section" class="text-center py-16">
|
<section id="cta-section" class="text-center py-16">
|
||||||
<h2 class="text-3xl font-bold text-white mb-4">
|
<h2 class="text-3xl font-bold text-white mb-4">
|
||||||
Still have questions?
|
Still have questions?
|
||||||
@@ -690,7 +586,7 @@
|
|||||||
<p class="text-lg text-gray-400 mb-8 max-w-2xl mx-auto">
|
<p class="text-lg text-gray-400 mb-8 max-w-2xl mx-auto">
|
||||||
We're here to help. Reach out to discuss your licensing needs.
|
We're here to help. Reach out to discuss your licensing needs.
|
||||||
</p>
|
</p>
|
||||||
<a href="/contact.html"
|
<a href="./contact.html"
|
||||||
class="inline-block px-8 py-3 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1">
|
class="inline-block px-8 py-3 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1">
|
||||||
Contact Us
|
Contact Us
|
||||||
</a>
|
</a>
|
||||||
@@ -702,53 +598,49 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
||||||
<div class="mb-8 md:mb-0">
|
<div class="mb-8 md:mb-0">
|
||||||
<div class="flex items-center justify-center md:justify-start mb-4">
|
<div class="flex items-center justify-center md:justify-start mb-4">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
<img src="images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
||||||
<span class="text-xl font-bold text-white">BentoPDF</span>
|
<span class="text-xl font-bold text-white">BentoPDF</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
|
<p class="text-gray-400 text-sm">
|
||||||
© 2025 BentoPDF. All rights reserved.
|
© 2025 BentoPDF. All rights reserved.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-500 text-xs mt-2">
|
<p class="text-gray-500 text-xs mt-2">
|
||||||
<span data-i18n="footer.version">Version</span> <span id="app-version"></span>
|
Version <span id="app-version"></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">Company</h3>
|
<h3 class="font-bold text-white mb-4">Company</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/about.html" class="hover:text-indigo-400" data-i18n="footer.aboutUs">About Us</a>
|
<a href="./about.html" class="hover:text-indigo-400">About Us</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/faq.html" class="hover:text-indigo-400" data-i18n="footer.faqLink">FAQ</a>
|
<a href="./faq.html" class="hover:text-indigo-400">FAQ</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/contact.html" class="hover:text-indigo-400" data-i18n="footer.contactUs">Contact
|
<a href="./contact.html" class="hover:text-indigo-400">Contact Us</a>
|
||||||
Us</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">Legal</h3>
|
<h3 class="font-bold text-white mb-4">Legal</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/licensing.html" class="hover:text-indigo-400"
|
<a href="./licensing.html" class="hover:text-indigo-400">Licensing</a>
|
||||||
data-i18n="nav.licensing">Licensing</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/terms.html" class="hover:text-indigo-400"
|
<a href="./terms.html" class="hover:text-indigo-400">Terms and Conditions</a>
|
||||||
data-i18n="footer.termsAndConditions">Terms and Conditions</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/privacy.html" class="hover:text-indigo-400"
|
<a href="./privacy.html" class="hover:text-indigo-400">Privacy Policy</a>
|
||||||
data-i18n="footer.privacyPolicy">Privacy Policy</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">Follow Us</h3>
|
<h3 class="font-bold text-white mb-4">Follow Us</h3>
|
||||||
<div class="flex justify-center md:justify-start space-x-4">
|
<div class="flex justify-center md:justify-start space-x-4">
|
||||||
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
||||||
class="text-gray-400 hover:text-indigo-400" title="GitHub">
|
class="text-gray-400 hover:text-indigo-400" title="GitHub">
|
||||||
@@ -758,7 +650,7 @@
|
|||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/Bgq3Ay3f2w" target="_blank" rel="noopener noreferrer"
|
<a href="https://discord.gg/AP2Y97juZT" target="_blank" rel="noopener noreferrer"
|
||||||
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
||||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<path
|
<path
|
||||||
@@ -788,7 +680,7 @@
|
|||||||
|
|
||||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||||
<script type="module" src="src/version.ts"></script>
|
<script type="module" src="src/version.ts"></script>
|
||||||
<script type="module" src="src/js/main.ts"></script>
|
<script type="module" src="src/js/faq.ts"></script>
|
||||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
+7
-67
@@ -11,13 +11,10 @@ http {
|
|||||||
}
|
}
|
||||||
default_type application/octet-stream;
|
default_type application/octet-stream;
|
||||||
|
|
||||||
gzip_static on;
|
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_vary on;
|
gzip_vary on;
|
||||||
gzip_min_length 1024;
|
gzip_min_length 1024;
|
||||||
gzip_comp_level 9;
|
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
|
||||||
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 {
|
server {
|
||||||
listen 8080;
|
listen 8080;
|
||||||
@@ -25,71 +22,14 @@ http {
|
|||||||
server_name localhost;
|
server_name localhost;
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
rewrite ^/(en|de|zh|vi)/(.*)$ /$2 last;
|
|
||||||
|
|
||||||
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 / {
|
location / {
|
||||||
try_files $uri $uri/ $uri.html /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
expires 5m;
|
}
|
||||||
add_header Cache-Control "public, must-revalidate";
|
|
||||||
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
}
|
}
|
||||||
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
|||||||
Generated
+7357
-5311
File diff suppressed because it is too large
Load Diff
+6
-42
@@ -1,16 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "bento-pdf",
|
"name": "bento-pdf",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.15.3",
|
"version": "1.10.5",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"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",
|
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"obfuscate": "node scripts/build.mjs",
|
"obfuscate": "node scripts/build.mjs",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
@@ -26,10 +22,7 @@
|
|||||||
"release:major": "node scripts/release.js major",
|
"release:major": "node scripts/release.js major",
|
||||||
"serve:simple": "SIMPLE_MODE=true npm run build && npm run preview -- --port 3000",
|
"serve:simple": "SIMPLE_MODE=true npm run build && npm run preview -- --port 3000",
|
||||||
"serve": "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"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
@@ -42,17 +35,13 @@
|
|||||||
"@vitest/ui": "^3.2.4",
|
"@vitest/ui": "^3.2.4",
|
||||||
"jsdom": "^27.0.1",
|
"jsdom": "^27.0.1",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
|
"ts-migrate": "^0.1.35",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.1.11",
|
"vite": "^7.1.11",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
|
||||||
"vite-plugin-node-polyfills": "^0.24.0",
|
"vite-plugin-node-polyfills": "^0.24.0",
|
||||||
"vitepress": "^1.6.4",
|
"vitest": "^3.2.4"
|
||||||
"vitest": "^3.2.4",
|
|
||||||
"vue": "^3.5.26"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bentopdf/gs-wasm": "^0.1.0",
|
|
||||||
"@bentopdf/pymupdf-wasm": "^0.1.9",
|
|
||||||
"@fontsource/cedarville-cursive": "^5.2.7",
|
"@fontsource/cedarville-cursive": "^5.2.7",
|
||||||
"@fontsource/dancing-script": "^5.2.8",
|
"@fontsource/dancing-script": "^5.2.8",
|
||||||
"@fontsource/dm-sans": "^5.2.8",
|
"@fontsource/dm-sans": "^5.2.8",
|
||||||
@@ -60,41 +49,18 @@
|
|||||||
"@fontsource/kalam": "^5.2.8",
|
"@fontsource/kalam": "^5.2.8",
|
||||||
"@fontsource/lato": "^5.2.7",
|
"@fontsource/lato": "^5.2.7",
|
||||||
"@fontsource/merriweather": "^5.2.11",
|
"@fontsource/merriweather": "^5.2.11",
|
||||||
"@matbee/libreoffice-converter": "^2.3.1",
|
|
||||||
"@neslinesli93/qpdf-wasm": "^0.3.0",
|
"@neslinesli93/qpdf-wasm": "^0.3.0",
|
||||||
"@pdf-lib/fontkit": "^1.1.1",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@phosphor-icons/web": "^2.1.2",
|
|
||||||
"@tailwindcss/vite": "^4.1.15",
|
"@tailwindcss/vite": "^4.1.15",
|
||||||
"@types/markdown-it": "^14.1.2",
|
|
||||||
"@types/papaparse": "^5.5.2",
|
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"blob-stream": "^0.1.3",
|
"blob-stream": "^0.1.3",
|
||||||
"cropperjs": "^1.6.1",
|
"cropperjs": "^1.6.1",
|
||||||
"embedpdf-snippet": "file:vendor/embedpdf/embedpdf-snippet-1.5.0.tgz",
|
|
||||||
"heic2any": "^0.0.4",
|
"heic2any": "^0.0.4",
|
||||||
"highlight.js": "^11.11.1",
|
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"i18next": "^25.7.2",
|
"javascript-obfuscator": "^4.1.1",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
|
||||||
"i18next-http-backend": "^3.0.2",
|
|
||||||
"jspdf": "^3.0.3",
|
"jspdf": "^3.0.3",
|
||||||
"jspdf-autotable": "^5.0.2",
|
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide": "^0.546.0",
|
"lucide": "^0.546.0",
|
||||||
"markdown-it": "^14.1.0",
|
|
||||||
"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.2",
|
|
||||||
"papaparse": "^5.5.3",
|
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^5.4.296",
|
"pdfjs-dist": "^5.4.296",
|
||||||
"pdfkit": "^0.17.2",
|
"pdfkit": "^0.17.2",
|
||||||
@@ -103,8 +69,6 @@
|
|||||||
"terser": "^5.44.0",
|
"terser": "^5.44.0",
|
||||||
"tesseract.js": "^6.0.1",
|
"tesseract.js": "^6.0.1",
|
||||||
"tiff": "^7.1.2",
|
"tiff": "^7.1.2",
|
||||||
"utif": "^3.1.0",
|
"utif": "^3.1.0"
|
||||||
"vite-plugin-static-copy": "^3.1.4",
|
|
||||||
"xlsx": "^0.18.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-20
@@ -5,10 +5,6 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Privacy Policy - BentoPDF</title>
|
<title>Privacy Policy - BentoPDF</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
|
||||||
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
|
||||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
||||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -17,7 +13,10 @@
|
|||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex justify-between items-center h-16">
|
<div class="flex justify-between items-center h-16">
|
||||||
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8" />
|
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||||
|
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||||
|
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
<span class="text-white font-bold text-xl ml-2">
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
<a href="index.html">BentoPDF</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
</span>
|
</span>
|
||||||
@@ -26,9 +25,9 @@
|
|||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
<div class="hidden md:flex items-center space-x-8">
|
||||||
<a href="index.html" class="nav-link">Home</a>
|
<a href="index.html" class="nav-link">Home</a>
|
||||||
<a href="/about.html" class="nav-link">About</a>
|
<a href="./about.html" class="nav-link">About</a>
|
||||||
<a href="/contact.html" class="nav-link">Contact</a>
|
<a href="./contact.html" class="nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="nav-link">Licensing</a>
|
<a href="./licensing.html" class="nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -57,9 +56,9 @@
|
|||||||
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
||||||
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
||||||
<a href="index.html" class="mobile-nav-link">Home</a>
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
<a href="/about.html" class="mobile-nav-link">About</a>
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
<a href="/contact.html" class="mobile-nav-link">Contact</a>
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
<a href="/licensing.html" class="mobile-nav-link">Licensing</a>
|
<a href="./licensing.html" class="mobile-nav-link">Licensing</a>
|
||||||
<a href="index.html#tools-header" class="mobile-nav-link">All Tools</a>
|
<a href="index.html#tools-header" class="mobile-nav-link">All Tools</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,7 +196,7 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
||||||
<div class="mb-8 md:mb-0">
|
<div class="mb-8 md:mb-0">
|
||||||
<div class="flex items-center justify-center md:justify-start mb-4">
|
<div class="flex items-center justify-center md:justify-start mb-4">
|
||||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
<img src="images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
||||||
<span class="text-xl font-bold text-white">BentoPDF</span>
|
<span class="text-xl font-bold text-white">BentoPDF</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 text-sm">
|
<p class="text-gray-400 text-sm">
|
||||||
@@ -212,13 +211,13 @@
|
|||||||
<h3 class="font-bold text-white mb-4">Company</h3>
|
<h3 class="font-bold text-white mb-4">Company</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/about.html" class="hover:text-indigo-400">About Us</a>
|
<a href="./about.html" class="hover:text-indigo-400">About Us</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/faq.html" class="hover:text-indigo-400">FAQ</a>
|
<a href="./faq.html" class="hover:text-indigo-400">FAQ</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/contact.html" class="hover:text-indigo-400">Contact Us</a>
|
<a href="./contact.html" class="hover:text-indigo-400">Contact Us</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -227,13 +226,13 @@
|
|||||||
<h3 class="font-bold text-white mb-4">Legal</h3>
|
<h3 class="font-bold text-white mb-4">Legal</h3>
|
||||||
<ul class="space-y-2 text-gray-400">
|
<ul class="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a href="/licensing.html" class="hover:text-indigo-400">Licensing</a>
|
<a href="./licensing.html" class="hover:text-indigo-400">Licensing</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/terms.html" class="hover:text-indigo-400">Terms and Conditions</a>
|
<a href="./terms.html" class="hover:text-indigo-400">Terms and Conditions</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/privacy.html" class="hover:text-indigo-400">Privacy Policy</a>
|
<a href="./privacy.html" class="hover:text-indigo-400">Privacy Policy</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -249,7 +248,7 @@
|
|||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/Bgq3Ay3f2w" target="_blank" rel="noopener noreferrer"
|
<a href="https://discord.gg/AP2Y97juZT" target="_blank" rel="noopener noreferrer"
|
||||||
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
||||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<path
|
<path
|
||||||
@@ -278,7 +277,6 @@
|
|||||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||||
<script type="module" src="src/version.ts"></script>
|
<script type="module" src="src/version.ts"></script>
|
||||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
<script type="module" src="src/js/main.ts"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
async function Module(moduleArg={}){var moduleRtn;var f=moduleArg,aa="object"==typeof window,ba="undefined"!=typeof WorkerGlobalScope,k="object"==typeof process&&process.versions?.node&&"renderer"!=process.type,ca=!aa&&!k&&!ba;if(k){const {createRequire:a}=await import("module");var require=a(import.meta.url)}var da="./this.program",ea=(a,b)=>{throw b;},ia=import.meta.url,ja="",ka,la;
|
|
||||||
if(k){if("object"!=typeof process||!process.versions?.node||"renderer"==process.type)throw 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 ma=process.versions.node,na=ma.split(".").slice(0,3);na=1E4*na[0]+100*na[1]+1*na[2].split("-")[0];if(16E4>na)throw Error("This emscripten-generated code requires node v16.0.0 (detected v"+ma+")");var fs=require("fs");
|
|
||||||
ia.startsWith("file:")&&(ja=require("path").dirname(require("url").fileURLToPath(ia))+"/");la=a=>{a=oa(a)?new URL(a):a;a=fs.readFileSync(a);m(Buffer.isBuffer(a));return a};ka=async a=>{a=oa(a)?new URL(a):a;a=fs.readFileSync(a,void 0);m(Buffer.isBuffer(a));return a};1<process.argv.length&&(da=process.argv[1].replace(/\\/g,"/"));process.argv.slice(2);ea=(a,b)=>{process.exitCode=a;throw b;}}else if(ca){if("object"==typeof process&&process.versions?.node&&"renderer"!=process.type||"object"==typeof window||
|
|
||||||
"undefined"!=typeof WorkerGlobalScope)throw 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(aa||ba){try{ja=(new URL(".",ia)).href}catch{}if("object"!=typeof window&&"undefined"==typeof WorkerGlobalScope)throw 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?)");
|
|
||||||
ba&&(la=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)});ka=async a=>{if(oa(a))return new Promise((c,d)=>{var e=new XMLHttpRequest;e.open("GET",a,!0);e.responseType="arraybuffer";e.onload=()=>{200==e.status||0==e.status&&e.response?c(e.response):d(e.status)};e.onerror=d;e.send(null)});var b=await fetch(a,{credentials:"same-origin"});if(b.ok)return b.arrayBuffer();throw Error(b.status+" : "+b.url);}}else throw Error("environment detection error");
|
|
||||||
var p=console.log.bind(console),t=console.error.bind(console);m(!ca,"shell environment detected but not enabled at build time. Add `shell` to `-sENVIRONMENT` to enable.");var pa;"object"!=typeof WebAssembly&&t("no native wasm support detected");var qa=!1,ra;function m(a,b){a||v("Assertion failed"+(b?": "+b:""))}var oa=a=>a.startsWith("file://");function sa(){var a=ta();m(0==(a&3));0==a&&(a+=4);x[a>>2]=34821223;x[a+4>>2]=2310721022;x[0]=1668509029}
|
|
||||||
function ua(){if(!qa){var a=ta();0==a&&(a+=4);var b=x[a>>2],c=x[a+4>>2];34821223==b&&2310721022==c||v(`Stack overflow! Stack cookie has been overwritten at ${va(a)}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${va(c)} ${va(b)}`);1668509029!=x[0]&&v("Runtime error: The application has corrupted its heap memory area (address zero)!")}}var wa=new Int16Array(1),xa=new Int8Array(wa.buffer);wa[0]=25459;
|
|
||||||
if(115!==xa[0]||99!==xa[1])throw"Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)";function ya(a){Object.getOwnPropertyDescriptor(f,a)||Object.defineProperty(f,a,{configurable:!0,set(){v(`Attempt to set \`Module.${a}\` after it has already been processed. This can happen, for example, when code is injected via '--post-js' rather than '--pre-js'`)}})}
|
|
||||||
function y(a){return()=>m(!1,`call to '${a}' via reference taken before Wasm module initialization`)}function Aa(a){return"FS_createPath"===a||"FS_createDataFile"===a||"FS_createPreloadedFile"===a||"FS_preloadFile"===a||"FS_unlink"===a||"addRunDependency"===a||"FS_createLazyFile"===a||"FS_createDevice"===a||"removeRunDependency"===a}function Ba(a,b){"undefined"==typeof globalThis||Object.getOwnPropertyDescriptor(globalThis,a)||Object.defineProperty(globalThis,a,{configurable:!0,get(){b()}})}
|
|
||||||
function Ca(a,b){Ba(a,()=>{z(`\`${a}\` is not longer defined by emscripten. ${b}`)})}Ca("buffer","Please use HEAP8.buffer or wasmMemory.buffer");Ca("asm","Please use wasmExports instead");function Da(a){Object.getOwnPropertyDescriptor(f,a)||Object.defineProperty(f,a,{configurable:!0,get(){var b=`'${a}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`;Aa(a)&&(b+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you");v(b)}})}
|
|
||||||
var Ea,Fa,Ga,A,Ha,Ia,B,x,C,Ja=!1;function Ka(){var a=Ga.buffer;A=new Int8Array(a);Ia=new Int16Array(a);Ha=new Uint8Array(a);new Uint16Array(a);B=new Int32Array(a);x=new Uint32Array(a);new Float32Array(a);new Float64Array(a);C=new BigInt64Array(a);new BigUint64Array(a)}m("undefined"!=typeof Int32Array&&"undefined"!==typeof Float64Array&&void 0!=Int32Array.prototype.subarray&&void 0!=Int32Array.prototype.set,"JS engine does not provide full typed array support");var E=0,La=null,Ma={},I=null;
|
|
||||||
function Na(a){E++;f.monitorRunDependencies?.(E);m(a,"addRunDependency requires an ID");m(!Ma[a]);Ma[a]=1;null===I&&"undefined"!=typeof setInterval&&(I=setInterval(()=>{if(qa)clearInterval(I),I=null;else{var b=!1,c;for(c in Ma)b||(b=!0,t("still waiting on run dependencies:")),t(`dependency: ${c}`);b&&t("(end of list)")}},1E4),I.unref?.())}
|
|
||||||
function Oa(a){E--;f.monitorRunDependencies?.(E);m(a,"removeRunDependency requires an ID");m(Ma[a]);delete Ma[a];0==E&&(null!==I&&(clearInterval(I),I=null),La&&(a=La,La=null,a()))}function v(a){f.onAbort?.(a);a="Aborted("+a+")";t(a);qa=!0;a=new WebAssembly.RuntimeError(a);Fa?.(a);throw a;}
|
|
||||||
function K(a,b){return(...c)=>{m(Ja,`native function \`${a}\` called before runtime initialization`);var d=L[a];m(d,`exported native function \`${a}\` not found`);m(c.length<=b,`native function \`${a}\` called with ${c.length} args but expects ${b}`);return d(...c)}}var Pa;async function Qa(a){if(!pa)try{var b=await ka(a);return new Uint8Array(b)}catch{}if(a==Pa&&pa)a=new Uint8Array(pa);else if(la)a=la(a);else throw"both async and sync fetching of the wasm failed";return a}
|
|
||||||
async function Ra(a,b){try{var c=await Qa(a);return await WebAssembly.instantiate(c,b)}catch(d){t(`failed to asynchronously prepare wasm: ${d}`),oa(Pa)&&t(`warning: Loading from a file URI (${Pa}) 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`),v(d)}}
|
|
||||||
async function Sa(a){var b=Pa;if(!pa&&!oa(b)&&!k)try{var c=fetch(b,{credentials:"same-origin"});return await WebAssembly.instantiateStreaming(c,a)}catch(d){t(`wasm streaming compile failed: ${d}`),t("falling back to ArrayBuffer instantiation")}return Ra(b,a)}class Ta{name="ExitStatus";constructor(a){this.message=`Program terminated with exit(${a})`;this.status=a}}
|
|
||||||
var Ua=a=>{for(;0<a.length;)a.shift()(f)},Va=[],Wa=[],Xa=()=>{var a=f.preRun.shift();Wa.push(a)},Ya=!0,va=a=>{m("number"===typeof a);return"0x"+(a>>>0).toString(16).padStart(8,"0")},z=a=>{z.$||(z.$={});z.$[a]||(z.$[a]=1,k&&(a="warning: "+a),t(a))},Za="undefined"!=typeof TextDecoder?new TextDecoder:void 0,$a=(a,b=0)=>{var c=b;for(var d=c+void 0;a[c]&&!(c>=d);)++c;if(16<c-b&&a.buffer&&Za)return Za.decode(a.subarray(b,c));for(d="";b<c;){var e=a[b++];if(e&128){var g=a[b++]&63;if(192==(e&224))d+=String.fromCharCode((e&
|
|
||||||
31)<<6|g);else{var h=a[b++]&63;224==(e&240)?e=(e&15)<<12|g<<6|h:(240!=(e&248)&&z("Invalid UTF-8 leading byte "+va(e)+" encountered when deserializing a UTF-8 string in wasm memory to a JS string!"),e=(e&7)<<18|g<<12|h<<6|a[b++]&63);65536>e?d+=String.fromCharCode(e):(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023))}}else d+=String.fromCharCode(e)}return d},M=a=>{m("number"==typeof a,`UTF8ToString expects a number (got ${typeof a})`);return a?$a(Ha,a):""},ab=(a,b)=>{for(var c=0,d=a.length-
|
|
||||||
1;0<=d;d--){var e=a[d];"."===e?a.splice(d,1):".."===e?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c;c--)a.unshift("..");return a},bb=a=>{var b="/"===a.charAt(0),c="/"===a.slice(-1);(a=ab(a.split("/").filter(d=>!!d),!b).join("/"))||b||(a=".");a&&c&&(a+="/");return(b?"/":"")+a},cb=a=>{var b=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(a).slice(1);a=b[0];b=b[1];if(!a&&!b)return".";b&&=b.slice(0,-1);return a+b},N=a=>a&&a.match(/([^\/]+|\/)\/*$/)[1],db=(a,b)=>bb(a+"/"+
|
|
||||||
b),eb=()=>{if(k){var a=require("crypto");return b=>a.randomFillSync(b)}return b=>crypto.getRandomValues(b)},fb=a=>{(fb=eb())(a)},gb=(...a)=>{for(var b="",c=!1,d=a.length-1;-1<=d&&!c;d--){c=0<=d?a[d]:O.cwd();if("string"!=typeof c)throw new TypeError("Arguments to path.resolve must be strings");if(!c)return"";b=c+"/"+b;c="/"===c.charAt(0)}b=ab(b.split("/").filter(e=>!!e),!c).join("/");return(c?"/":"")+b||"."},hb=(a,b)=>{function c(h){for(var l=0;l<h.length&&""===h[l];l++);for(var r=h.length-1;0<=r&&
|
|
||||||
""===h[r];r--);return l>r?[]:h.slice(l,r-l+1)}a=gb(a).slice(1);b=gb(b).slice(1);a=c(a.split("/"));b=c(b.split("/"));for(var d=Math.min(a.length,b.length),e=d,g=0;g<d;g++)if(a[g]!==b[g]){e=g;break}d=[];for(g=e;g<a.length;g++)d.push("..");d=d.concat(b.slice(e));return d.join("/")},ib=[],jb=a=>{for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);127>=d?b++:2047>=d?b+=2:55296<=d&&57343>=d?(b+=4,++c):b+=3}return b},kb=(a,b,c,d)=>{m("string"===typeof a,`stringToUTF8Array expects a string (got ${typeof a})`);
|
|
||||||
if(!(0<d))return 0;var e=c;d=c+d-1;for(var g=0;g<a.length;++g){var h=a.codePointAt(g);if(127>=h){if(c>=d)break;b[c++]=h}else if(2047>=h){if(c+1>=d)break;b[c++]=192|h>>6;b[c++]=128|h&63}else if(65535>=h){if(c+2>=d)break;b[c++]=224|h>>12;b[c++]=128|h>>6&63;b[c++]=128|h&63}else{if(c+3>=d)break;1114111<h&&z("Invalid Unicode code point "+va(h)+" encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF).");b[c++]=240|h>>18;b[c++]=
|
|
||||||
128|h>>12&63;b[c++]=128|h>>6&63;b[c++]=128|h&63;g++}}b[c]=0;return c-e},lb=a=>{var b=Array(jb(a)+1);a=kb(a,b,0,b.length);b.length=a;return b},mb=[];function nb(a,b){mb[a]={input:[],output:[],H:b};ob(a,pb)}
|
|
||||||
var pb={open(a){var b=mb[a.node.rdev];if(!b)throw new O.g(43);a.tty=b;a.seekable=!1},close(a){a.tty.H.fsync(a.tty)},fsync(a){a.tty.H.fsync(a.tty)},read(a,b,c,d){if(!a.tty||!a.tty.H.ha)throw new O.g(60);for(var e=0,g=0;g<d;g++){try{var h=a.tty.H.ha(a.tty)}catch(l){throw new O.g(29);}if(void 0===h&&0===e)throw new O.g(6);if(null===h||void 0===h)break;e++;b[c+g]=h}e&&(a.node.atime=Date.now());return e},write(a,b,c,d){if(!a.tty||!a.tty.H.Z)throw new O.g(60);try{for(var e=0;e<d;e++)a.tty.H.Z(a.tty,b[c+
|
|
||||||
e])}catch(g){throw new O.g(29);}d&&(a.node.mtime=a.node.ctime=Date.now());return e}},qb={ha(){a:{if(!ib.length){var a=null;if(k){var b=Buffer.alloc(256),c=0,d=process.stdin.fd;try{c=fs.readSync(d,b,0,256)}catch(e){if(e.toString().includes("EOF"))c=0;else throw e;}0<c&&(a=b.slice(0,c).toString("utf-8"))}else"undefined"!=typeof window&&"function"==typeof window.prompt&&(a=window.prompt("Input: "),null!==a&&(a+="\n"));if(!a){a=null;break a}ib=lb(a)}a=ib.shift()}return a},Z(a,b){null===b||10===b?(p($a(a.output)),
|
|
||||||
a.output=[]):0!=b&&a.output.push(b)},fsync(a){0<a.output?.length&&(p($a(a.output)),a.output=[])},xa(){return{Qa:25856,Sa:5,Pa:191,Ra:35387,Oa:[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]}},ya(){return 0},za(){return[24,80]}},rb={Z(a,b){null===b||10===b?(t($a(a.output)),a.output=[]):0!=b&&a.output.push(b)},fsync(a){0<a.output?.length&&(t($a(a.output)),a.output=[])}},sb=()=>{v("internal error: mmapAlloc called but `emscripten_builtin_memalign` native symbol not exported")},
|
|
||||||
P={D:null,m(){return P.createNode(null,"/",16895,0)},createNode(a,b,c,d){if(24576===(c&61440)||O.isFIFO(c))throw new O.g(63);P.D||(P.D={dir:{node:{v:P.h.v,B:P.h.B,lookup:P.h.lookup,G:P.h.G,rename:P.h.rename,unlink:P.h.unlink,rmdir:P.h.rmdir,readdir:P.h.readdir,symlink:P.h.symlink},stream:{s:P.i.s}},file:{node:{v:P.h.v,B:P.h.B},stream:{s:P.i.s,read:P.i.read,write:P.i.write,M:P.i.M,P:P.i.P}},link:{node:{v:P.h.v,B:P.h.B,readlink:P.h.readlink},stream:{}},ba:{node:{v:P.h.v,B:P.h.B},stream:O.ta}});c=O.createNode(a,
|
|
||||||
b,c,d);Q(c.mode)?(c.h=P.D.dir.node,c.i=P.D.dir.stream,c.j={}):O.isFile(c.mode)?(c.h=P.D.file.node,c.i=P.D.file.stream,c.o=0,c.j=null):40960===(c.mode&61440)?(c.h=P.D.link.node,c.i=P.D.link.stream):8192===(c.mode&61440)&&(c.h=P.D.ba.node,c.i=P.D.ba.stream);c.atime=c.mtime=c.ctime=Date.now();a&&(a.j[b]=c,a.atime=a.mtime=a.ctime=c.atime);return c},fb(a){return a.j?a.j.subarray?a.j.subarray(0,a.o):new Uint8Array(a.j):new Uint8Array(0)},h:{v(a){var b={};b.dev=8192===(a.mode&61440)?a.id:1;b.ino=a.id;b.mode=
|
|
||||||
a.mode;b.nlink=1;b.uid=0;b.gid=0;b.rdev=a.rdev;Q(a.mode)?b.size=4096:O.isFile(a.mode)?b.size=a.o:40960===(a.mode&61440)?b.size=a.link.length:b.size=0;b.atime=new Date(a.atime);b.mtime=new Date(a.mtime);b.ctime=new Date(a.ctime);b.blksize=4096;b.blocks=Math.ceil(b.size/b.blksize);return b},B(a,b){for(var c of["mode","atime","mtime","ctime"])null!=b[c]&&(a[c]=b[c]);void 0!==b.size&&(b=b.size,a.o!=b&&(0==b?(a.j=null,a.o=0):(c=a.j,a.j=new Uint8Array(b),c&&a.j.set(c.subarray(0,Math.min(b,a.o))),a.o=b)))},
|
|
||||||
lookup(){throw new O.g(44);},G(a,b,c,d){return P.createNode(a,b,c,d)},rename(a,b,c){try{var d=R(b,c)}catch(g){}if(d){if(Q(a.mode))for(var e in d.j)throw new O.g(55);tb(d)}delete a.parent.j[a.name];b.j[c]=a;a.name=c;b.ctime=b.mtime=a.parent.ctime=a.parent.mtime=Date.now()},unlink(a,b){delete a.j[b];a.ctime=a.mtime=Date.now()},rmdir(a,b){var c=R(a,b),d;for(d in c.j)throw new O.g(55);delete a.j[b];a.ctime=a.mtime=Date.now()},readdir(a){return[".","..",...Object.keys(a.j)]},symlink(a,b,c){a=P.createNode(a,
|
|
||||||
b,41471,0);a.link=c;return a},readlink(a){if(40960!==(a.mode&61440))throw new O.g(28);return a.link}},i:{read(a,b,c,d,e){var g=a.node.j;if(e>=a.node.o)return 0;a=Math.min(a.node.o-e,d);m(0<=a);if(8<a&&g.subarray)b.set(g.subarray(e,e+a),c);else for(d=0;d<a;d++)b[c+d]=g[e+d];return a},write(a,b,c,d,e,g){m(!(b instanceof ArrayBuffer));b.buffer===A.buffer&&(g=!1);if(!d)return 0;a=a.node;a.mtime=a.ctime=Date.now();if(b.subarray&&(!a.j||a.j.subarray)){if(g)return m(0===e,"canOwn must imply no weird position inside the file"),
|
|
||||||
a.j=b.subarray(c,c+d),a.o=d;if(0===a.o&&0===e)return a.j=b.slice(c,c+d),a.o=d;if(e+d<=a.o)return a.j.set(b.subarray(c,c+d),e),d}g=e+d;var h=a.j?a.j.length:0;h>=g||(g=Math.max(g,h*(1048576>h?2:1.125)>>>0),0!=h&&(g=Math.max(g,256)),h=a.j,a.j=new Uint8Array(g),0<a.o&&a.j.set(h.subarray(0,a.o),0));if(a.j.subarray&&b.subarray)a.j.set(b.subarray(c,c+d),e);else for(g=0;g<d;g++)a.j[e+g]=b[c+g];a.o=Math.max(a.o,e+d);return d},s(a,b,c){1===c?b+=a.position:2===c&&O.isFile(a.node.mode)&&(b+=a.node.o);if(0>b)throw new O.g(28);
|
|
||||||
return b},M(a,b,c,d,e){if(!O.isFile(a.node.mode))throw new O.g(43);a=a.node.j;if(e&2||!a||a.buffer!==A.buffer){d=!0;e=sb();if(!e)throw new O.g(48);if(a){if(0<c||c+b<a.length)a.subarray?a=a.subarray(c,c+b):a=Array.prototype.slice.call(a,c,c+b);A.set(a,e)}}else d=!1,e=a.byteOffset;return{Ia:e,sa:d}},P(a,b,c,d){P.i.write(a,b,0,d,c,!1);return 0}}},ub=(a,b)=>{var c=0;a&&(c|=365);b&&(c|=146);return c},vb={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},wb=async a=>{var b=await ka(a);m(b,`Loading data file "${a}" failed (no arrayBuffer).`);return new Uint8Array(b)},xb=[],zb=async(a,b)=>{"undefined"!=typeof Browser&&yb();for(var c of xb)if(c.canHandle(b))return m("AsyncFunction"===
|
|
||||||
c.handle.constructor.name,"Filesystem plugin handlers must be async functions (See #24914)"),c.handle(a,b);return a},Bb=async(a,b,c,d,e,g,h,l)=>{var r=b?gb(bb(a+"/"+b)):a,q;a:for(var u=q=`cp ${r}`;;){if(!Ma[q])break a;q=u+Math.random()}Na(q);try{if(u=c,"string"==typeof c&&(u=await wb(c)),u=await zb(u,r),l?.(),!g){c=u;g=b;a&&(a="string"==typeof a?a:Ab(a),g=b?bb(a+"/"+b):a);var n=ub(d,e),w=O.create(g,n);if(c){if("string"==typeof c){var F=Array(c.length);b=0;for(var G=c.length;b<G;++b)F[b]=c.charCodeAt(b);
|
|
||||||
c=F}O.chmod(w,n|146);var D=O.open(w,577);O.write(D,c,0,c.length,0,h);O.close(D);O.chmod(w,n)}}}finally{Oa(q)}};
|
|
||||||
function yb(){m(!O.T,"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)");O.T=!0;b??=f.stdin;c??=f.stdout;a??=f.stderr;b?O.I("/dev","stdin",b):O.symlink("/dev/tty","/dev/stdin");c?O.I("/dev","stdout",null,c):O.symlink("/dev/tty","/dev/stdout");a?O.I("/dev","stderr",null,a):O.symlink("/dev/tty1","/dev/stderr");var a=O.open("/dev/stdin",0);var b=O.open("/dev/stdout",1);var c=O.open("/dev/stderr",
|
|
||||||
1);m(0===a.fd,`invalid handle for stdin (${a.fd})`);m(1===b.fd,`invalid handle for stdout (${b.fd})`);m(2===c.fd,`invalid handle for stderr (${c.fd})`)}function ob(a,b){O.ea[a]={i:b}}function Q(a){return 16384===(a&61440)}function R(a,b){var c=Q(a.mode)?(c=S(a,"x"))?c:a.h.lookup?0:2:54;if(c)throw new O.g(c);for(c=O.C[Cb(a.id,b)];c;c=c.L){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return O.lookup(a,b)}
|
|
||||||
function tb(a){var b=Cb(a.parent.id,a.name);if(O.C[b]===a)O.C[b]=a.L;else for(b=O.C[b];b;){if(b.L===a){b.L=a.L;break}b=b.L}}
|
|
||||||
function T(a,b={}){if(!a)throw new O.g(44);b.S??(b.S=!0);"/"===a.charAt(0)||(a=O.cwd()+"/"+a);var c=0;a:for(;40>c;c++){a=a.split("/").filter(l=>!!l);for(var d=O.root,e="/",g=0;g<a.length;g++){var h=g===a.length-1;if(h&&b.parent)break;if("."!==a[g])if(".."===a[g])if(e=cb(e),O.N(d)){a=e+"/"+a.slice(g+1).join("/");c--;continue a}else d=d.parent;else{e=bb(e+"/"+a[g]);try{d=R(d,a[g])}catch(l){if(44===l?.l&&h&&b.Ca)return{path:e};throw l;}!d.A||h&&!b.S||(d=d.A.root);if(40960===(d.mode&61440)&&(!h||b.u)){if(!d.h.readlink)throw new O.g(52);
|
|
||||||
d=d.h.readlink(d);"/"===d.charAt(0)||(d=cb(e)+"/"+d);a=d+"/"+a.slice(g+1).join("/");continue a}}}return{path:e,node:d}}throw new O.g(32);}function Ab(a){for(var b;;){if(O.N(a))return a=a.m.ma,b?"/"!==a[a.length-1]?`${a}/${b}`:a+b:a;b=b?`${a.name}/${b}`:a.name;a=a.parent}}function Cb(a,b){for(var c=0,d=0;d<b.length;d++)c=(c<<5)-c+b.charCodeAt(d)|0;return(a+c>>>0)%O.C.length}function Db(a){var b=Cb(a.parent.id,a.name);a.L=O.C[b];O.C[b]=a}
|
|
||||||
function Eb(a){var b=["r","w","rw"][a&3];a&512&&(b+="w");return b}function S(a,b){if(O.ia)return 0;if(!b.includes("r")||a.mode&292){if(b.includes("w")&&!(a.mode&146)||b.includes("x")&&!(a.mode&73))return 2}else return 2;return 0}function Fb(a,b){if(!Q(a.mode))return 54;try{return R(a,b),20}catch(c){}return S(a,"wx")}
|
|
||||||
function Gb(a,b,c){try{var d=R(a,b)}catch(e){return e.l}if(a=S(a,"wx"))return a;if(c){if(!Q(d.mode))return 54;if(O.N(d)||Ab(d)===O.cwd())return 10}else if(Q(d.mode))return 31;return 0}function Hb(a,b){if(!a)throw new O.g(b);return a}function U(a){a=O.ga(a);if(!a)throw new O.g(8);return a}function Ib(a,b=-1){m(-1<=b);a=Object.assign(new O.ra,a);if(-1==b)a:{for(b=0;b<=O.aa;b++)if(!O.streams[b])break a;throw new O.g(33);}a.fd=b;return O.streams[b]=a}
|
|
||||||
function Jb(a,b=-1){a=Ib(a,b);a.i?.Za?.(a);return a}function Kb(a,b,c){var d=a?.i.B;a=d?a:b;d??=b.h.B;Hb(d,63);d(a,c)}function Lb(a){var b=[];for(a=[a];a.length;){var c=a.pop();b.push(c);a.push(...c.O)}return b}function Mb(a){var b={Na:4096,bb:4096,blocks:1E6,Ma:5E5,La:5E5,files:O.Y,$a:O.Y-1,cb:42,flags:2,kb:255};a.h.oa&&Object.assign(b,a.h.oa(a.m.Ea.root));return b}function Nb(a,b,c){"undefined"==typeof c&&(c=b,b=438);return O.G(a,b|8192,c)}
|
|
||||||
function Ob(a,b,c,d){Kb(a,b,{mode:c&4095|b.mode&-4096,ctime:Date.now(),fa:d})}function Pb(a,b,c){if(Q(b.mode))throw new O.g(31);if(!O.isFile(b.mode))throw new O.g(28);var d=S(b,"w");if(d)throw new O.g(d);Kb(a,b,{size:c,timestamp:Date.now()})}
|
|
||||||
function Qb(a,b){try{var c=T(a,{u:!b});a=c.path}catch(e){}var d={N:!1,exists:!1,error:0,name:null,path:null,object:null,Fa:!1,Ha:null,Ga:null};try{c=T(a,{parent:!0}),d.Fa=!0,d.Ha=c.path,d.Ga=c.node,d.name=N(a),c=T(a,{u:!b}),d.exists=!0,d.path=c.path,d.object=c.node,d.name=c.node.name,d.N="/"===c.path}catch(e){d.error=e.l}return d}function Rb(a,b,c,d){a="string"==typeof a?a:Ab(a);b=bb(a+"/"+b);return O.create(b,ub(c,d))}
|
|
||||||
function Sb(a){if(!(a.Aa||a.Ba||a.link||a.j)){if("undefined"!=typeof XMLHttpRequest)throw Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.");try{a.j=la(a.url),a.o=a.j.length}catch(b){throw new O.g(29);}}}
|
|
||||||
var O={root:null,O:[],ea:{},streams:[],Y:1,C:null,da:"/",T:!1,ia:!0,va:null,R:0,na:{},g:class extends Error{name="ErrnoError";constructor(a){super(Ja?M(Tb(a)):"");this.l=a;for(var b in vb)if(vb[b]===a){this.code=b;break}}},ra:class{F={};node=null;get object(){return this.node}set object(a){this.node=a}get flags(){return this.F.flags}set flags(a){this.F.flags=a}get position(){return this.F.position}set position(a){this.F.position=a}},qa:class{h={};i={};A=null;constructor(a,b,c,d){a||=this;this.parent=
|
|
||||||
a;this.m=a.m;this.id=O.Y++;this.name=b;this.mode=c;this.rdev=d;this.atime=this.mtime=this.ctime=Date.now()}get read(){return 365===(this.mode&365)}set read(a){a?this.mode|=365:this.mode&=-366}get write(){return 146===(this.mode&146)}set write(a){a?this.mode|=146:this.mode&=-147}get Ba(){return Q(this.mode)}get Aa(){return 8192===(this.mode&61440)}},createNode(a,b,c,d){m("object"==typeof a);a=new O.qa(a,b,c,d);Db(a);return a},N(a){return a===a.parent},isFile(a){return 32768===(a&61440)},isFIFO(a){return 4096===
|
|
||||||
(a&61440)},isSocket(a){return 49152===(a&49152)},aa:4096,ga:a=>O.streams[a],ta:{open(a){a.i=O.wa(a.node.rdev).i;a.i.open?.(a)},s(){throw new O.g(70);}},X:a=>a>>8,hb:a=>a&255,K:(a,b)=>a<<8|b,wa:a=>O.ea[a],pa(a,b){function c(h){m(0<O.R);O.R--;return b(h)}function d(h){if(h){if(!d.ua)return d.ua=!0,c(h)}else++g>=e.length&&c(null)}"function"==typeof a&&(b=a,a=!1);O.R++;1<O.R&&t(`warning: ${O.R} FS.syncfs operations in flight at once, probably just doing extra work`);var e=Lb(O.root.m),g=0;e.forEach(h=>
|
|
||||||
{if(!h.type.pa)return d(null);h.type.pa(h,a,d)})},m(a,b,c){if("string"==typeof a)throw a;var d="/"===c,e=!c;if(d&&O.root)throw new O.g(10);if(!d&&!e){var g=T(c,{S:!1});c=g.path;g=g.node;if(g.A)throw new O.g(10);if(!Q(g.mode))throw new O.g(54);}b={type:a,Ea:b,ma:c,O:[]};a=a.m(b);a.m=b;b.root=a;d?O.root=a:g&&(g.A=b,g.m&&g.m.O.push(b));return a},qb(a){a=T(a,{S:!1});if(!a.node.A)throw new O.g(28);a=a.node;var b=a.A,c=Lb(b);Object.keys(O.C).forEach(d=>{for(d=O.C[d];d;){var e=d.L;c.includes(d.m)&&tb(d);
|
|
||||||
d=e}});a.A=null;b=a.m.O.indexOf(b);m(-1!==b);a.m.O.splice(b,1)},lookup(a,b){return a.h.lookup(a,b)},G(a,b,c){var d=T(a,{parent:!0}).node;a=N(a);if(!a)throw new O.g(28);if("."===a||".."===a)throw new O.g(20);var e=Fb(d,a);if(e)throw new O.g(e);if(!d.h.G)throw new O.g(63);return d.h.G(d,a,b,c)},oa(a){return Mb(T(a,{u:!0}).node)},ob(a){return Mb(a.node)},create(a,b=438){return O.G(a,b&4095|32768,0)},mkdir(a,b=511){return O.G(a,b&1023|16384,0)},ib(a,b){var c=a.split("/"),d="",e;for(e of c)if(e){if(d||
|
|
||||||
"/"===a.charAt(0))d+="/";d+=e;try{O.mkdir(d,b)}catch(g){if(20!=g.l)throw g;}}},symlink(a,b){if(!gb(a))throw new O.g(44);var c=T(b,{parent:!0}).node;if(!c)throw new O.g(44);b=N(b);var d=Fb(c,b);if(d)throw new O.g(d);if(!c.h.symlink)throw new O.g(63);return c.h.symlink(c,b,a)},rename(a,b){var c=cb(a),d=cb(b),e=N(a),g=N(b);var h=T(a,{parent:!0});var l=h.node;h=T(b,{parent:!0});h=h.node;if(!l||!h)throw new O.g(44);if(l.m!==h.m)throw new O.g(75);var r=R(l,e);a=hb(a,d);if("."!==a.charAt(0))throw new O.g(28);
|
|
||||||
a=hb(b,c);if("."!==a.charAt(0))throw new O.g(55);try{var q=R(h,g)}catch(u){}if(r!==q){b=Q(r.mode);if(e=Gb(l,e,b))throw new O.g(e);if(e=q?Gb(h,g,b):Fb(h,g))throw new O.g(e);if(!l.h.rename)throw new O.g(63);if(r.A||q&&q.A)throw new O.g(10);if(h!==l&&(e=S(l,"w")))throw new O.g(e);tb(r);try{l.h.rename(r,h,g),r.parent=h}catch(u){throw u;}finally{Db(r)}}},rmdir(a){var b=T(a,{parent:!0}).node;a=N(a);var c=R(b,a),d=Gb(b,a,!0);if(d)throw new O.g(d);if(!b.h.rmdir)throw new O.g(63);if(c.A)throw new O.g(10);
|
|
||||||
b.h.rmdir(b,a);tb(c)},readdir(a){a=T(a,{u:!0}).node;return Hb(a.h.readdir,54)(a)},unlink(a){var b=T(a,{parent:!0}).node;if(!b)throw new O.g(44);a=N(a);var c=R(b,a),d=Gb(b,a,!1);if(d)throw new O.g(d);if(!b.h.unlink)throw new O.g(63);if(c.A)throw new O.g(10);b.h.unlink(b,a);tb(c)},readlink(a){a=T(a).node;if(!a)throw new O.g(44);if(!a.h.readlink)throw new O.g(28);return a.h.readlink(a)},stat(a,b){a=T(a,{u:!b}).node;return Hb(a.h.v,63)(a)},fstat(a){var b=U(a);a=b.node;var c=b.i.v;b=c?b:a;c??=a.h.v;Hb(c,
|
|
||||||
63);return c(b)},lstat(a){return O.stat(a,!0)},chmod(a,b,c){a="string"==typeof a?T(a,{u:!c}).node:a;Ob(null,a,b,c)},lchmod(a,b){O.chmod(a,b,!0)},fchmod(a,b){a=U(a);Ob(a,a.node,b,!1)},chown(a,b,c,d){a="string"==typeof a?T(a,{u:!d}).node:a;Kb(null,a,{timestamp:Date.now(),fa:d})},lchown(a,b,c){O.chown(a,b,c,!0)},fchown(a){a=U(a);Kb(a,a.node,{timestamp:Date.now(),fa:!1})},truncate(a,b){if(0>b)throw new O.g(28);a="string"==typeof a?T(a,{u:!0}).node:a;Pb(null,a,b)},eb(a,b){a=U(a);if(0>b||0===(a.flags&2097155))throw new O.g(28);
|
|
||||||
Pb(a,a.node,b)},rb(a,b,c){a=T(a,{u:!0}).node;Hb(a.h.B,63)(a,{atime:b,mtime:c})},open(a,b,c=438){if(""===a)throw new O.g(44);if("string"==typeof b){var d={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090}[b];if("undefined"==typeof d)throw Error(`Unknown file open mode: ${b}`);b=d}c=b&64?c&4095|32768:0;if("object"==typeof a)d=a;else{var e=a.endsWith("/");a=T(a,{u:!(b&131072),Ca:!0});d=a.node;a=a.path}var g=!1;if(b&64)if(d){if(b&128)throw new O.g(20);}else{if(e)throw new O.g(31);d=O.G(a,c|511,0);g=!0}if(!d)throw new O.g(44);
|
|
||||||
8192===(d.mode&61440)&&(b&=-513);if(b&65536&&!Q(d.mode))throw new O.g(54);if(!g&&(e=d?40960===(d.mode&61440)?32:Q(d.mode)&&("r"!==Eb(b)||b&576)?31:S(d,Eb(b)):44))throw new O.g(e);b&512&&!g&&O.truncate(d,0);b&=-131713;e=Ib({node:d,path:Ab(d),flags:b,seekable:!0,position:0,i:d.i,Ja:[],error:!1});e.i.open&&e.i.open(e);g&&O.chmod(d,c&511);!f.logReadFiles||b&1||a in O.na||(O.na[a]=1);return e},close(a){if(null===a.fd)throw new O.g(8);a.J&&(a.J=null);try{a.i.close&&a.i.close(a)}catch(b){throw b;}finally{O.streams[a.fd]=
|
|
||||||
null}a.fd=null},s(a,b,c){if(null===a.fd)throw new O.g(8);if(!a.seekable||!a.i.s)throw new O.g(70);if(0!=c&&1!=c&&2!=c)throw new O.g(28);a.position=a.i.s(a,b,c);a.Ja=[];return a.position},read(a,b,c,d,e){m(0<=c);if(0>d||0>e)throw new O.g(28);if(null===a.fd)throw new O.g(8);if(1===(a.flags&2097155))throw new O.g(8);if(Q(a.node.mode))throw new O.g(31);if(!a.i.read)throw new O.g(28);var g="undefined"!=typeof e;if(!g)e=a.position;else if(!a.seekable)throw new O.g(70);b=a.i.read(a,b,c,d,e);g||(a.position+=
|
|
||||||
b);return b},write(a,b,c,d,e,g){m(0<=c);if(0>d||0>e)throw new O.g(28);if(null===a.fd)throw new O.g(8);if(0===(a.flags&2097155))throw new O.g(8);if(Q(a.node.mode))throw new O.g(31);if(!a.i.write)throw new O.g(28);a.seekable&&a.flags&1024&&O.s(a,0,2);var h="undefined"!=typeof e;if(!h)e=a.position;else if(!a.seekable)throw new O.g(70);b=a.i.write(a,b,c,d,e,g);h||(a.position+=b);return b},M(a,b,c,d,e){if(0!==(d&2)&&0===(e&2)&&2!==(a.flags&2097155))throw new O.g(2);if(1===(a.flags&2097155))throw new O.g(2);
|
|
||||||
if(!a.i.M)throw new O.g(43);if(!b)throw new O.g(28);return a.i.M(a,b,c,d,e)},P(a,b,c,d,e){m(0<=c);return a.i.P?a.i.P(a,b,c,d,e):0},W(a,b,c){if(!a.i.W)throw new O.g(59);return a.i.W(a,b,c)},readFile(a,b={}){b.flags=b.flags||0;b.encoding=b.encoding||"binary";if("utf8"!==b.encoding&&"binary"!==b.encoding)throw Error(`Invalid encoding type "${b.encoding}"`);var c=O.open(a,b.flags);a=O.stat(a).size;var d=new Uint8Array(a);O.read(c,d,0,a,0);"utf8"===b.encoding&&(d=$a(d));O.close(c);return d},writeFile(a,
|
|
||||||
b,c={}){c.flags=c.flags||577;a=O.open(a,c.flags,c.mode);"string"==typeof b&&(b=new Uint8Array(lb(b)));if(ArrayBuffer.isView(b))O.write(a,b,0,b.byteLength,void 0,c.Ta);else throw Error("Unsupported data type");O.close(a)},cwd:()=>O.da,chdir(a){a=T(a,{u:!0});if(null===a.node)throw new O.g(44);if(!Q(a.node.mode))throw new O.g(54);var b=S(a.node,"x");if(b)throw new O.g(b);O.da=a.path},mb(){O.T=!1;Ub(0);for(var a of O.streams)a&&O.close(a)},ab(a,b){a=Qb(a,b);return a.exists?a.object:null},Xa(a,b){a="string"==
|
|
||||||
typeof a?a:Ab(a);for(b=b.split("/").reverse();b.length;){var c=b.pop();if(c){var d=bb(a+"/"+c);try{O.mkdir(d)}catch(e){if(20!=e.l)throw e;}a=d}}return d},I(a,b,c,d){a=db("string"==typeof a?a:Ab(a),b);b=ub(!!c,!!d);var e;(e=O.I).X??(e.X=64);e=O.K(O.I.X++,0);ob(e,{open(g){g.seekable=!1},close(){d?.buffer?.length&&d(10)},read(g,h,l,r){for(var q=0,u=0;u<r;u++){try{var n=c()}catch(w){throw new O.g(29);}if(void 0===n&&0===q)throw new O.g(6);if(null===n||void 0===n)break;q++;h[l+u]=n}q&&(g.node.atime=Date.now());
|
|
||||||
return q},write(g,h,l,r){for(var q=0;q<r;q++)try{d(h[l+q])}catch(u){throw new O.g(29);}r&&(g.node.mtime=g.node.ctime=Date.now());return q}});return Nb(a,b,e)},Va(a,b,c,d,e){function g(n,w,F,G,D){n=n.node.j;if(D>=n.length)return 0;G=Math.min(n.length-D,G);m(0<=G);if(n.slice)for(var H=0;H<G;H++)w[F+H]=n[D+H];else for(H=0;H<G;H++)w[F+H]=n.get(D+H);return G}class h{V=!1;F=[];U=void 0;ka=0;ja=0;get(n){if(!(n>this.length-1||0>n)){var w=n%this.chunkSize;return this.U(n/this.chunkSize|0)[w]}}Da(n){this.U=
|
|
||||||
n}la(){var n=new XMLHttpRequest;n.open("HEAD",c,!1);n.send(null);if(!(200<=n.status&&300>n.status||304===n.status))throw Error("Couldn't load "+c+". Status: "+n.status);var w=Number(n.getResponseHeader("Content-length")),F,G=(F=n.getResponseHeader("Accept-Ranges"))&&"bytes"===F;n=(F=n.getResponseHeader("Content-Encoding"))&&"gzip"===F;var D=1048576;G||(D=w);var H=this;H.Da(fa=>{var za=fa*D,ha=(fa+1)*D-1;ha=Math.min(ha,w-1);if("undefined"==typeof H.F[fa]){var Bc=H.F;if(za>ha)throw Error("invalid range ("+
|
|
||||||
za+", "+ha+") or no bytes requested!");if(ha>w-1)throw Error("only "+w+" bytes available! programmer error!");var J=new XMLHttpRequest;J.open("GET",c,!1);w!==D&&J.setRequestHeader("Range","bytes="+za+"-"+ha);J.responseType="arraybuffer";J.overrideMimeType&&J.overrideMimeType("text/plain; charset=x-user-defined");J.send(null);if(!(200<=J.status&&300>J.status||304===J.status))throw Error("Couldn't load "+c+". Status: "+J.status);za=void 0!==J.response?new Uint8Array(J.response||[]):lb(J.responseText||
|
|
||||||
"");Bc[fa]=za}if("undefined"==typeof H.F[fa])throw Error("doXHR failed!");return H.F[fa]});if(n||!w)D=w=1,D=w=this.U(0).length,p("LazyFiles on gzip forces download of the whole file when length is accessed");this.ka=w;this.ja=D;this.V=!0}get length(){this.V||this.la();return this.ka}get chunkSize(){this.V||this.la();return this.ja}}if("undefined"!=typeof XMLHttpRequest){if(!ba)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";
|
|
||||||
var l=new h;var r=void 0}else r=c,l=void 0;var q=Rb(a,b,d,e);l?q.j=l:r&&(q.j=null,q.url=r);Object.defineProperties(q,{o:{get:function(){return this.j.length}}});var u={};Object.keys(q.i).forEach(n=>{var w=q.i[n];u[n]=(...F)=>{Sb(q);return w(...F)}});u.read=(n,w,F,G,D)=>{Sb(q);return g(n,w,F,G,D)};u.M=(n,w,F)=>{Sb(q);var G=sb();if(!G)throw new O.g(48);g(n,A,G,w,F);return{Ia:G,sa:!0}};q.i=u;return q},Ka(){v("FS.absolutePath has been removed; use PATH_FS.resolve instead")},Ua(){v("FS.createFolder has been removed; use FS.mkdir instead")},
|
|
||||||
Wa(){v("FS.createLink has been removed; use FS.symlink instead")},gb(){v("FS.joinPath has been removed; use PATH.join instead")},jb(){v("FS.mmapAlloc has been replaced by the top level function mmapAlloc")},nb(){v("FS.standardizePath has been removed; use PATH.normalize instead")}};function Vb(a,b,c){if("/"===b.charAt(0))return b;a=-100===a?O.cwd():U(a).path;if(0==b.length){if(!c)throw new O.g(44);return a}return a+"/"+b}
|
|
||||||
function Wb(a,b){x[a>>2]=b.dev;x[a+4>>2]=b.mode;x[a+8>>2]=b.nlink;x[a+12>>2]=b.uid;x[a+16>>2]=b.gid;x[a+20>>2]=b.rdev;C[a+24>>3]=BigInt(b.size);B[a+32>>2]=4096;B[a+36>>2]=b.blocks;var c=b.atime.getTime(),d=b.mtime.getTime(),e=b.ctime.getTime();C[a+40>>3]=BigInt(Math.floor(c/1E3));x[a+48>>2]=c%1E3*1E6;C[a+56>>3]=BigInt(Math.floor(d/1E3));x[a+64>>2]=d%1E3*1E6;C[a+72>>3]=BigInt(Math.floor(e/1E3));x[a+80>>2]=e%1E3*1E6;C[a+88>>3]=BigInt(b.ino);return 0}
|
|
||||||
var Xb=void 0,V=()=>{m(void 0!=Xb);var a=B[+Xb>>2];Xb+=4;return a},W=(a,b,c)=>{m("number"==typeof c,"stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!");return kb(a,Ha,b,c)},Yb=0,Zb=a=>0===a%4&&(0!==a%100||0===a%400),$b=[0,31,60,91,121,152,182,213,244,274,305,335],ac=[0,31,59,90,120,151,181,212,243,273,304,334],bc={},cc=a=>{if(a instanceof Ta||"unwind"==a)return ra;ua();a instanceof WebAssembly.RuntimeError&&0>=X()&&t("Stack overflow detected. You can try increasing -sSTACK_SIZE (currently set to 65536)");
|
|
||||||
ea(1,a)},dc=a=>{ra=a;Ya||0<Yb||(f.onExit?.(a),qa=!0);ea(a,new Ta(a))},fc=(a,b)=>{ra=a;ec();(Ya||0<Yb)&&!b&&(b=`program exited (with status: ${a}), but keepRuntimeAlive() is set (counter=${Yb}) 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)`,Fa?.(b),t(b));dc(a)},gc=a=>{if(qa)t("user callback triggered after runtime exited or application aborted. Ignoring.");
|
|
||||||
else try{if(a(),!(Ya||0<Yb))try{fc(ra)}catch(b){cc(b)}}catch(b){cc(b)}},hc={},jc=()=>{if(!ic){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.language||"C").replace("-","_")+".UTF-8",_:da||"./this.program"},b;for(b in hc)void 0===hc[b]?delete a[b]:a[b]=hc[b];var c=[];for(b in a)c.push(`${b}=${a[b]}`);ic=c}return ic},ic,kc=(a,b,c,d)=>{for(var e=0,g=0;g<c;g++){var h=x[b>>2],l=x[b+4>>2];b+=8;h=O.read(a,A,h,l,d);if(0>h)return-1;
|
|
||||||
e+=h;if(h<l)break;"undefined"!=typeof d&&(d+=h)}return e},lc=(a,b,c,d)=>{for(var e=0,g=0;g<c;g++){var h=x[b>>2],l=x[b+4>>2];b+=8;h=O.write(a,A,h,l,d);if(0>h)return-1;e+=h;if(h<l)break;"undefined"!=typeof d&&(d+=h)}return e},mc;O.Ya=(a,b,c,d,e,g,h,l,r,q)=>{Bb(a,b,c,d,e,l,r,q).then(g).catch(h)};O.lb=Bb;O.C=Array(4096);O.m(P,{},"/");O.mkdir("/tmp");O.mkdir("/home");O.mkdir("/home/web_user");
|
|
||||||
(function(){O.mkdir("/dev");ob(O.K(1,3),{read:()=>0,write:(d,e,g,h)=>h,s:()=>0});Nb("/dev/null",O.K(1,3));nb(O.K(5,0),qb);nb(O.K(6,0),rb);Nb("/dev/tty",O.K(5,0));Nb("/dev/tty1",O.K(6,0));var a=new Uint8Array(1024),b=0,c=()=>{0===b&&(fb(a),b=a.byteLength);return a[--b]};O.I("/dev","random",c);O.I("/dev","urandom",c);O.mkdir("/dev/shm");O.mkdir("/dev/shm/tmp")})();
|
|
||||||
(function(){O.mkdir("/proc");var a=O.mkdir("/proc/self");O.mkdir("/proc/self/fd");O.m({m(){var b=O.createNode(a,"fd",16895,73);b.i={s:P.i.s};b.h={lookup(c,d){c=+d;var e=U(c);c={parent:null,m:{ma:"fake"},h:{readlink:()=>e.path},id:c+1};return c.parent=c},readdir(){return Array.from(O.streams.entries()).filter(([,c])=>c).map(([c])=>c.toString())}};return b}},{},"/proc/self/fd")})();O.va={MEMFS:P};f.noExitRuntime&&(Ya=f.noExitRuntime);f.preloadPlugins&&(xb=f.preloadPlugins);f.print&&(p=f.print);
|
|
||||||
f.printErr&&(t=f.printErr);f.wasmBinary&&(pa=f.wasmBinary);Object.getOwnPropertyDescriptor(f,"fetchSettings")&&v("`Module.fetchSettings` was supplied but `fetchSettings` not included in INCOMING_MODULE_JS_API");f.thisProgram&&(da=f.thisProgram);m("undefined"==typeof f.memoryInitializerPrefixURL,"Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead");m("undefined"==typeof f.pthreadMainPrefixURL,"Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead");
|
|
||||||
m("undefined"==typeof f.cdInitializerPrefixURL,"Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead");m("undefined"==typeof f.filePackagePrefixURL,"Module.filePackagePrefixURL option was removed, use Module.locateFile instead");m("undefined"==typeof f.read,"Module.read option was removed");m("undefined"==typeof f.readAsync,"Module.readAsync option was removed (modify readAsync in JS)");m("undefined"==typeof f.readBinary,"Module.readBinary option was removed (modify readBinary in JS)");
|
|
||||||
m("undefined"==typeof f.setWindowTitle,"Module.setWindowTitle option was removed (modify emscripten_set_window_title in JS)");m("undefined"==typeof f.TOTAL_MEMORY,"Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY");m("undefined"==typeof f.ENVIRONMENT,"Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)");m("undefined"==typeof f.STACK_SIZE,"STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time");
|
|
||||||
m("undefined"==typeof f.wasmMemory,"Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally");m("undefined"==typeof f.INITIAL_MEMORY,"Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically");f.callMain=nc;f.FS=O;
|
|
||||||
"writeI53ToI64 writeI53ToI64Clamped writeI53ToI64Signaling writeI53ToU64Clamped writeI53ToU64Signaling readI53FromI64 readI53FromU64 convertI32PairToI53 convertI32PairToI53Checked convertU32PairToI53 getTempRet0 setTempRet0 zeroMemory withStackSave inetPton4 inetNtop4 inetPton6 inetNtop6 readSockaddr writeSockaddr readEmAsmArgs jstoi_q autoResumeAudioContext dynCallLegacy getDynCaller dynCall runtimeKeepalivePush runtimeKeepalivePop asmjsMangle HandleAllocator getNativeTypeSize addOnInit addOnPostCtor addOnPreMain addOnExit STACK_SIZE STACK_ALIGN POINTER_SIZE ASSERTIONS ccall cwrap convertJsFunctionToWasm getEmptyTableSlot updateTableMap getFunctionAddress addFunction removeFunction intArrayToString AsciiToString stringToAscii UTF16ToString stringToUTF16 lengthBytesUTF16 UTF32ToString stringToUTF32 lengthBytesUTF32 stringToNewUTF8 writeArrayToMemory 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 registerBatteryEventCallback setCanvasElementSize getCanvasElementSize jsStackTrace getCallstack convertPCtoSourceLocation wasiRightsToMuslOFlags wasiOFlagsToMuslOFlags safeSetTimeout setImmediateWrapped safeRequestAnimationFrame clearImmediateWrapped registerPostMainLoop registerPreMainLoop getPromise makePromise idsToPromises makePromiseCallback ExceptionInfo findMatchingCatch Browser_asyncPrepareDataCounter arraySum addDays getSocketFromFD getSocketAddress 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 registerWebGlEventCallback runAndAbortIfError ALLOC_NORMAL ALLOC_STACK allocate writeStringToMemory writeAsciiToMemory demangle stackTrace".split(" ").forEach(function(a){Ba(a,()=>
|
|
||||||
{var b=`\`${a}\` 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`,c=a;c.startsWith("_")||(c="$"+a);b+=` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${c}')`;Aa(a)&&(b+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you");z(b)});Da(a)});"run addRunDependency removeRunDependency out err abort wasmMemory wasmExports HEAPF32 HEAPF64 HEAP8 HEAPU8 HEAP16 HEAPU16 HEAP32 HEAPU32 HEAP64 HEAPU64 writeStackCookie checkStackCookie INT53_MAX INT53_MIN bigintToI53Checked stackSave stackRestore stackAlloc ptrToString exitJS getHeapMax growMemory ENV ERRNO_CODES strError DNS Protocols Sockets timers warnOnce readEmAsmArgsArray getExecutableName handleException keepRuntimeAlive callUserCallback maybeExit asyncLoad alignMemory mmapAlloc wasmTable getUniqueRunDependency noExitRuntime addOnPreRun addOnPostRun freeTableIndexes functionsInTableMap setValue getValue PATH PATH_FS UTF8Decoder UTF8ArrayToString UTF8ToString stringToUTF8Array stringToUTF8 lengthBytesUTF8 intArrayFromString UTF16Decoder stringToUTF8OnStack JSEvents specialHTMLTargets findCanvasEventTarget currentFullscreenStrategy restoreOldWindowedStyle UNWIND_CACHE ExitStatus getEnvStrings checkWasiClock doReadv doWritev initRandomFill randomFill emSetImmediate emClearImmediate_deps emClearImmediate promiseMap uncaughtExceptionCount exceptionLast exceptionCaught Browser requestFullscreen requestFullScreen setCanvasSize getUserMedia createContext getPreloadedImageData__data wget MONTH_DAYS_REGULAR MONTH_DAYS_LEAP MONTH_DAYS_REGULAR_CUMULATIVE MONTH_DAYS_LEAP_CUMULATIVE isLeapYear ydayFromDate SYSCALLS preloadPlugins FS_createPreloadedFile FS_preloadFile FS_modeStringToFlags FS_getMode FS_stdin_getChar_buffer FS_stdin_getChar FS_unlink FS_createPath FS_createDevice FS_readFile FS_root FS_mounts FS_devices FS_streams FS_nextInode FS_nameTable FS_currentPath FS_initialized FS_ignorePermissions FS_filesystems FS_syncFSRequests FS_readFiles FS_lookupPath FS_getPath FS_hashName FS_hashAddNode FS_hashRemoveNode FS_lookupNode FS_createNode FS_destroyNode FS_isRoot FS_isMountpoint FS_isFile FS_isDir FS_isLink FS_isChrdev FS_isBlkdev FS_isFIFO FS_isSocket FS_flagsToPermissionString FS_nodePermissions FS_mayLookup FS_mayCreate FS_mayDelete FS_mayOpen FS_checkOpExists FS_nextfd FS_getStreamChecked FS_getStream FS_createStream FS_closeStream FS_dupStream FS_doSetAttr FS_chrdev_stream_ops FS_major FS_minor FS_makedev FS_registerDevice FS_getDevice FS_getMounts FS_syncfs FS_mount FS_unmount FS_lookup FS_mknod FS_statfs FS_statfsStream FS_statfsNode FS_create FS_mkdir FS_mkdev FS_symlink FS_rename FS_rmdir FS_readdir FS_readlink FS_stat FS_fstat FS_lstat FS_doChmod FS_chmod FS_lchmod FS_fchmod FS_doChown FS_chown FS_lchown FS_fchown FS_doTruncate FS_truncate FS_ftruncate FS_utime FS_open FS_close FS_isClosed FS_llseek FS_read FS_write FS_mmap FS_msync FS_ioctl FS_writeFile FS_cwd FS_chdir FS_createDefaultDirectories FS_createDefaultDevices FS_createSpecialDirectories FS_createStandardStreams FS_staticInit FS_init FS_quit FS_findObject FS_analyzePath FS_createFile FS_createDataFile FS_forceLoadFile FS_createLazyFile FS_absolutePath FS_createFolder FS_createLink FS_joinPath FS_mmapAlloc FS_standardizePath MEMFS TTY PIPEFS SOCKFS tempFixedLengthArray miniTempWebGLFloatBuffers miniTempWebGLIntBuffers GL AL GLUT EGL GLEW IDBStore SDL SDL_gfx allocateUTF8 allocateUTF8OnStack print printErr jstoi_s".split(" ").forEach(Da);
|
|
||||||
var oc=f._main=y("_main"),Tb=y("_strerror"),Ub=y("_fflush"),ta=y("_emscripten_stack_get_end"),pc=y("__emscripten_timeout"),Y=y("_setThrew"),qc=y("_emscripten_stack_init"),Z=y("__emscripten_stack_restore"),rc=y("__emscripten_stack_alloc"),X=y("_emscripten_stack_get_current"),dynCall_v=y("dynCall_v"),dynCall_iii=y("dynCall_iii"),sc=y("dynCall_viii"),tc=y("dynCall_iiii"),uc=y("dynCall_ii"),dynCall_vi=y("dynCall_vi"),vc=y("dynCall_iiiii"),wc=y("dynCall_iiiiiiiii"),dynCall_vii=y("dynCall_vii"),xc=y("dynCall_iiji"),
|
|
||||||
yc=y("dynCall_viiii"),zc=y("dynCall_viiiii"),Ac=y("dynCall_viiiiii"),Oc={__assert_fail:(a,b,c,d)=>v(`Assertion failed: ${M(a)}, at: `+[b?M(b):"unknown filename",c,d?M(d):"unknown function"]),__syscall_dup:function(a){try{var b=U(a);return Jb(b).fd}catch(c){if("undefined"==typeof O||"ErrnoError"!==c.name)throw c;return-c.l}},__syscall_dup3:function(a,b,c){try{var d=U(a);m(!c);if(d.fd===b)return-28;if(0>b||b>=O.aa)return-8;var e=O.ga(b);e&&O.close(e);return Jb(d,b).fd}catch(g){if("undefined"==typeof O||
|
|
||||||
"ErrnoError"!==g.name)throw g;return-g.l}},__syscall_fcntl64:function(a,b,c){Xb=c;try{var d=U(a);switch(b){case 0:var e=V();if(0>e)break;for(;O.streams[e];)e++;return Jb(d,e).fd;case 1:case 2:return 0;case 3:return d.flags;case 4:return e=V(),d.flags|=e,0;case 12:return e=V(),Ia[e+0>>1]=2,0;case 13:case 14:return 0}return-28}catch(g){if("undefined"==typeof O||"ErrnoError"!==g.name)throw g;return-g.l}},__syscall_fstat64:function(a,b){try{return Wb(b,O.fstat(a))}catch(c){if("undefined"==typeof O||"ErrnoError"!==
|
|
||||||
c.name)throw c;return-c.l}},__syscall_getdents64:function(a,b,c){try{var d=U(a);d.J||(d.J=O.readdir(d.path));a=0;var e=O.s(d,0,1),g=Math.floor(e/280),h=Math.min(d.J.length,g+Math.floor(c/280));for(c=g;c<h;c++){var l=d.J[c];if("."===l){var r=d.node.id;var q=4}else if(".."===l)r=T(d.path,{parent:!0}).node.id,q=4;else{try{var u=R(d.node,l)}catch(n){if(28===n?.l)continue;throw n;}r=u.id;q=8192===(u.mode&61440)?2:Q(u.mode)?4:40960===(u.mode&61440)?10:8}m(r);C[b+a>>3]=BigInt(r);C[b+a+8>>3]=BigInt(280*(c+
|
|
||||||
1));Ia[b+a+16>>1]=280;A[b+a+18]=q;W(l,b+a+19,256);a+=280}O.s(d,280*c,0);return a}catch(n){if("undefined"==typeof O||"ErrnoError"!==n.name)throw n;return-n.l}},__syscall_ioctl:function(a,b,c){Xb=c;try{var d=U(a);switch(b){case 21509:return d.tty?0:-59;case 21505:if(!d.tty)return-59;if(d.tty.H.xa){a=[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];var e=V();B[e>>2]=25856;B[e+4>>2]=5;B[e+8>>2]=191;B[e+12>>2]=35387;for(var g=0;32>g;g++)A[e+g+17]=a[g]||0}return 0;case 21510:case 21511:case 21512:return d.tty?
|
|
||||||
0:-59;case 21506:case 21507:case 21508:if(!d.tty)return-59;if(d.tty.H.ya)for(e=V(),a=[],g=0;32>g;g++)a.push(A[e+g+17]);return 0;case 21519:if(!d.tty)return-59;e=V();return B[e>>2]=0;case 21520:return d.tty?-28:-59;case 21537:case 21531:return e=V(),O.W(d,b,e);case 21523:if(!d.tty)return-59;d.tty.H.za&&(g=[24,80],e=V(),Ia[e>>1]=g[0],Ia[e+2>>1]=g[1]);return 0;case 21524:return d.tty?0:-59;case 21515:return d.tty?0:-59;default:return-28}}catch(h){if("undefined"==typeof O||"ErrnoError"!==h.name)throw h;
|
|
||||||
return-h.l}},__syscall_lstat64:function(a,b){try{return a=M(a),Wb(b,O.lstat(a))}catch(c){if("undefined"==typeof O||"ErrnoError"!==c.name)throw c;return-c.l}},__syscall_newfstatat:function(a,b,c,d){try{b=M(b);var e=d&256,g=d&4096;d&=-6401;m(!d,`unknown flags in __syscall_newfstatat: ${d}`);b=Vb(a,b,g);return Wb(c,e?O.lstat(b):O.stat(b))}catch(h){if("undefined"==typeof O||"ErrnoError"!==h.name)throw h;return-h.l}},__syscall_openat:function(a,b,c,d){Xb=d;try{b=M(b);b=Vb(a,b);var e=d?V():0;return O.open(b,
|
|
||||||
c,e).fd}catch(g){if("undefined"==typeof O||"ErrnoError"!==g.name)throw g;return-g.l}},__syscall_renameat:function(a,b,c,d){try{return b=M(b),d=M(d),b=Vb(a,b),d=Vb(c,d),O.rename(b,d),0}catch(e){if("undefined"==typeof O||"ErrnoError"!==e.name)throw e;return-e.l}},__syscall_rmdir:function(a){try{return a=M(a),O.rmdir(a),0}catch(b){if("undefined"==typeof O||"ErrnoError"!==b.name)throw b;return-b.l}},__syscall_stat64:function(a,b){try{return a=M(a),Wb(b,O.stat(a))}catch(c){if("undefined"==typeof O||"ErrnoError"!==
|
|
||||||
c.name)throw c;return-c.l}},__syscall_unlinkat:function(a,b,c){try{b=M(b);b=Vb(a,b);if(c)if(512===c)O.rmdir(b);else return-28;else O.unlink(b);return 0}catch(d){if("undefined"==typeof O||"ErrnoError"!==d.name)throw d;return-d.l}},_abort_js:()=>v("native code called abort()"),_emscripten_runtime_keepalive_clear:()=>{Ya=!1;Yb=0},_emscripten_throw_longjmp:()=>{throw Infinity;},_gmtime_js:function(a,b){a=-9007199254740992>a||9007199254740992<a?NaN:Number(a);a=new Date(1E3*a);B[b>>2]=a.getUTCSeconds();
|
|
||||||
B[b+4>>2]=a.getUTCMinutes();B[b+8>>2]=a.getUTCHours();B[b+12>>2]=a.getUTCDate();B[b+16>>2]=a.getUTCMonth();B[b+20>>2]=a.getUTCFullYear()-1900;B[b+24>>2]=a.getUTCDay();B[b+28>>2]=(a.getTime()-Date.UTC(a.getUTCFullYear(),0,1,0,0,0,0))/864E5|0},_localtime_js:function(a,b){a=-9007199254740992>a||9007199254740992<a?NaN:Number(a);a=new Date(1E3*a);B[b>>2]=a.getSeconds();B[b+4>>2]=a.getMinutes();B[b+8>>2]=a.getHours();B[b+12>>2]=a.getDate();B[b+16>>2]=a.getMonth();B[b+20>>2]=a.getFullYear()-1900;B[b+24>>
|
|
||||||
2]=a.getDay();B[b+28>>2]=(Zb(a.getFullYear())?$b:ac)[a.getMonth()]+a.getDate()-1|0;B[b+36>>2]=-(60*a.getTimezoneOffset());var c=(new Date(a.getFullYear(),6,1)).getTimezoneOffset(),d=(new Date(a.getFullYear(),0,1)).getTimezoneOffset();B[b+32>>2]=(c!=d&&a.getTimezoneOffset()==Math.min(d,c))|0},_mktime_js:function(a){var b=new Date(B[a+20>>2]+1900,B[a+16>>2],B[a+12>>2],B[a+8>>2],B[a+4>>2],B[a>>2],0),c=B[a+32>>2],d=b.getTimezoneOffset(),e=(new Date(b.getFullYear(),6,1)).getTimezoneOffset(),g=(new Date(b.getFullYear(),
|
|
||||||
0,1)).getTimezoneOffset(),h=Math.min(g,e);0>c?B[a+32>>2]=Number(e!=g&&h==d):0<c!=(h==d)&&(e=Math.max(g,e),b.setTime(b.getTime()+6E4*((0<c?h:e)-d)));B[a+24>>2]=b.getDay();B[a+28>>2]=(Zb(b.getFullYear())?$b:ac)[b.getMonth()]+b.getDate()-1|0;B[a>>2]=b.getSeconds();B[a+4>>2]=b.getMinutes();B[a+8>>2]=b.getHours();B[a+12>>2]=b.getDate();B[a+16>>2]=b.getMonth();B[a+20>>2]=b.getYear();a=b.getTime();return BigInt(isNaN(a)?-1:a/1E3)},_setitimer_js:(a,b)=>{bc[a]&&(clearTimeout(bc[a].id),delete bc[a]);if(!b)return 0;
|
|
||||||
var c=setTimeout(()=>{m(a in bc);delete bc[a];gc(()=>pc(a,performance.now()))},b);bc[a]={id:c,pb:b};return 0},_tzset_js:(a,b,c,d)=>{var e=(new Date).getFullYear(),g=(new Date(e,0,1)).getTimezoneOffset();e=(new Date(e,6,1)).getTimezoneOffset();x[a>>2]=60*Math.max(g,e);B[b>>2]=Number(g!=e);b=h=>{var l=Math.abs(h);return`UTC${0<=h?"-":"+"}${String(Math.floor(l/60)).padStart(2,"0")}${String(l%60).padStart(2,"0")}`};a=b(g);b=b(e);m(a);m(b);m(16>=jb(a),`timezone name truncated to fit in TZNAME_MAX (${a})`);
|
|
||||||
m(16>=jb(b),`timezone name truncated to fit in TZNAME_MAX (${b})`);e<g?(W(a,c,17),W(b,d,17)):(W(a,d,17),W(b,c,17))},clock_time_get:function(a,b,c){if(!(0<=a&&3>=a))return 28;C[c>>3]=BigInt(Math.round(1E6*(0===a?Date.now():performance.now())));return 0},emscripten_date_now:()=>Date.now(),emscripten_resize_heap:a=>{var b=Ha.length;a>>>=0;m(a>b);if(2147483648<a)return t(`Cannot enlarge memory, requested ${a} bytes, but the limit is ${2147483648} bytes!`),!1;for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=
|
|
||||||
Math.min(d,a+100663296);var e=Math,g=e.min;d=Math.max(a,d);m(65536,"alignment argument is required");e=g.call(e,2147483648,65536*Math.ceil(d/65536));a:{g=e;d=Ga.buffer.byteLength;try{Ga.grow((g-d+65535)/65536|0);Ka();var h=1;break a}catch(l){t(`growMemory: Attempted to grow heap from ${d} bytes to ${g} bytes, but got error: ${l}`)}h=void 0}if(h)return!0}t(`Failed to grow the heap from ${b} bytes to ${e} bytes, not enough memory!`);return!1},environ_get:(a,b)=>{var c=0,d=0,e;for(e of jc()){var g=b+
|
|
||||||
c;x[a+d>>2]=g;c+=W(e,g,Infinity)+1;d+=4}return 0},environ_sizes_get:(a,b)=>{var c=jc();x[a>>2]=c.length;a=0;for(var d of c)a+=jb(d)+1;x[b>>2]=a;return 0},exit:fc,fd_close:function(a){try{var b=U(a);O.close(b);return 0}catch(c){if("undefined"==typeof O||"ErrnoError"!==c.name)throw c;return c.l}},fd_pread:function(a,b,c,d,e){d=-9007199254740992>d||9007199254740992<d?NaN:Number(d);try{if(isNaN(d))return 61;var g=U(a),h=kc(g,b,c,d);x[e>>2]=h;return 0}catch(l){if("undefined"==typeof O||"ErrnoError"!==
|
|
||||||
l.name)throw l;return l.l}},fd_pwrite:function(a,b,c,d,e){d=-9007199254740992>d||9007199254740992<d?NaN:Number(d);try{if(isNaN(d))return 61;var g=U(a),h=lc(g,b,c,d);x[e>>2]=h;return 0}catch(l){if("undefined"==typeof O||"ErrnoError"!==l.name)throw l;return l.l}},fd_read:function(a,b,c,d){try{var e=U(a),g=kc(e,b,c);x[d>>2]=g;return 0}catch(h){if("undefined"==typeof O||"ErrnoError"!==h.name)throw h;return h.l}},fd_seek:function(a,b,c,d){b=-9007199254740992>b||9007199254740992<b?NaN:Number(b);try{if(isNaN(b))return 61;
|
|
||||||
var e=U(a);O.s(e,b,c);C[d>>3]=BigInt(e.position);e.J&&0===b&&0===c&&(e.J=null);return 0}catch(g){if("undefined"==typeof O||"ErrnoError"!==g.name)throw g;return g.l}},fd_write:function(a,b,c,d){try{var e=U(a),g=lc(e,b,c);x[d>>2]=g;return 0}catch(h){if("undefined"==typeof O||"ErrnoError"!==h.name)throw h;return h.l}},invoke_ii:Cc,invoke_iii:Dc,invoke_iiii:Ec,invoke_iiiii:Fc,invoke_iiiiiiiii:Gc,invoke_iiji:Hc,invoke_vi:Ic,invoke_vii:Jc,invoke_viii:Kc,invoke_viiii:Lc,invoke_viiiii:Mc,invoke_viiiiii:Nc,
|
|
||||||
proc_exit:dc},L=await (async function(){function a(d){L=d.exports;Ga=L.memory;m(Ga,"memory not found in wasm exports");Ka();mc=L.__indirect_function_table;m(mc,"table not found in wasm exports");d=L;f._main=oc=K("__main_argc_argv",2);Tb=K("strerror",1);Ub=K("fflush",1);ta=d.emscripten_stack_get_end;pc=K("_emscripten_timeout",2);Y=K("setThrew",2);qc=d.emscripten_stack_init;Z=d._emscripten_stack_restore;rc=d._emscripten_stack_alloc;X=d.emscripten_stack_get_current;dynCall_v=K("dynCall_v",
|
|
||||||
1);dynCall_iii=K("dynCall_iii",3);sc=K("dynCall_viii",4);tc=K("dynCall_iiii",4);uc=K("dynCall_ii",2);dynCall_vi=K("dynCall_vi",2);vc=K("dynCall_iiiii",5);wc=K("dynCall_iiiiiiiii",9);dynCall_vii=K("dynCall_vii",3);xc=K("dynCall_iiji",4);yc=K("dynCall_viiii",5);zc=K("dynCall_viiiii",6);Ac=K("dynCall_viiiiii",7);Oa("wasm-instantiate");return L}Na("wasm-instantiate");var b=f,c={env:Oc,wasi_snapshot_preview1:Oc};if(f.instantiateWasm)return new Promise((d,e)=>{try{f.instantiateWasm(c,(g,h)=>{d(a(g,h))})}catch(g){t(`Module.instantiateWasm callback failed with error: ${g}`),
|
|
||||||
e(g)}});Pa??=f.locateFile?f.locateFile?f.locateFile("gs.wasm",ja):ja+"gs.wasm":(new URL("gs.wasm",import.meta.url)).href;return function(d){m(f===b,"the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?");b=null;return a(d.instance)}(await Sa(c))}());function Ic(a,b){var c=X();try{dynCall_vi(a,b)}catch(d){Z(c);if(d!==d+0)throw d;Y(1,0)}}
|
|
||||||
function Jc(a,b,c){var d=X();try{dynCall_vii(a,b,c)}catch(e){Z(d);if(e!==e+0)throw e;Y(1,0)}}function Cc(a,b){var c=X();try{return uc(a,b)}catch(d){Z(c);if(d!==d+0)throw d;Y(1,0)}}function Ec(a,b,c,d){var e=X();try{return tc(a,b,c,d)}catch(g){Z(e);if(g!==g+0)throw g;Y(1,0)}}function Dc(a,b,c){var d=X();try{return dynCall_iii(a,b,c)}catch(e){Z(d);if(e!==e+0)throw e;Y(1,0)}}function Kc(a,b,c,d){var e=X();try{sc(a,b,c,d)}catch(g){Z(e);if(g!==g+0)throw g;Y(1,0)}}
|
|
||||||
function Lc(a,b,c,d,e){var g=X();try{yc(a,b,c,d,e)}catch(h){Z(g);if(h!==h+0)throw h;Y(1,0)}}function Hc(a,b,c,d){var e=X();try{return xc(a,b,c,d)}catch(g){Z(e);if(g!==g+0)throw g;Y(1,0)}}function Fc(a,b,c,d,e){var g=X();try{return vc(a,b,c,d,e)}catch(h){Z(g);if(h!==h+0)throw h;Y(1,0)}}function Mc(a,b,c,d,e,g){var h=X();try{zc(a,b,c,d,e,g)}catch(l){Z(h);if(l!==l+0)throw l;Y(1,0)}}function Nc(a,b,c,d,e,g,h){var l=X();try{Ac(a,b,c,d,e,g,h)}catch(r){Z(l);if(r!==r+0)throw r;Y(1,0)}}
|
|
||||||
function Gc(a,b,c,d,e,g,h,l,r){var q=X();try{return wc(a,b,c,d,e,g,h,l,r)}catch(u){Z(q);if(u!==u+0)throw u;Y(1,0)}}var Pc;
|
|
||||||
function nc(a=[]){m(0==E,'cannot call main when async dependencies remain! (listen on Module["onRuntimeInitialized"])');m("undefined"===typeof Wa||0==Wa.length,"cannot call main when preRun functions remain to be called");var b=oc;a.unshift(da);var c=a.length,d=rc(4*(c+1)),e=d;a.forEach(h=>{var l=x,r=e>>2,q=jb(h)+1,u=rc(q);W(h,u,q);l[r]=u;e+=4});x[e>>2]=0;try{var g=b(c,d);fc(g,!0);return g}catch(h){return cc(h)}}
|
|
||||||
function Qc(){function a(){m(!Pc);Pc=!0;f.calledRun=!0;if(!qa){m(!Ja);Ja=!0;ua();f.noFSInit||O.T||yb();L.__wasm_call_ctors();O.ia=!1;ua();Ea?.(f);f.onRuntimeInitialized?.();ya("onRuntimeInitialized");ua();if(f.postRun)for("function"==typeof f.postRun&&(f.postRun=[f.postRun]);f.postRun.length;){var b=f.postRun.shift();Va.push(b)}ya("postRun");Ua(Va)}}if(0<E)La=Qc;else{qc();sa();if(f.preRun)for("function"==typeof f.preRun&&(f.preRun=[f.preRun]);f.preRun.length;)Xa();ya("preRun");Ua(Wa);0<E?La=Qc:(f.setStatus?
|
|
||||||
(f.setStatus("Running..."),setTimeout(()=>{setTimeout(()=>f.setStatus(""),1);a()},1)):a(),ua())}}function ec(){var a=p,b=t,c=!1;p=t=()=>{c=!0};try{Ub(0),["stdout","stderr"].forEach(d=>{(d=Qb("/dev/"+d))&&mb[d.object.rdev]?.output?.length&&(c=!0)})}catch(d){}p=a;t=b;c&&z("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(f.preInit)for("function"==typeof f.preInit&&(f.preInit=[f.preInit]);0<f.preInit.length;)f.preInit.shift()();ya("preInit");Qc();Ja?moduleRtn=f:moduleRtn=new Promise((a,b)=>{Ea=a;Fa=b});for(const a of Object.keys(f))a in moduleArg||Object.defineProperty(moduleArg,a,{configurable:!0,get(){v(`Access to module property ('${a}') is no longer possible via the module constructor argument; Instead, use the result of the module constructor.`)}});
|
|
||||||
;return moduleRtn}export default Module;
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
"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;
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
{
|
|
||||||
"nav": {
|
|
||||||
"home": "Startseite",
|
|
||||||
"about": "Über uns",
|
|
||||||
"contact": "Kontakt",
|
|
||||||
"licensing": "Lizenzierung",
|
|
||||||
"allTools": "Alle Werkzeuge",
|
|
||||||
"openMainMenu": "Hauptmenü öffnen",
|
|
||||||
"language": "Sprache"
|
|
||||||
},
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"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?<br><br>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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,492 +0,0 @@
|
|||||||
{
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"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": "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 <br> 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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
{
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"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?<br><br>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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,492 +0,0 @@
|
|||||||
{
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"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": "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 <br>",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
{
|
|
||||||
"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ữ"
|
|
||||||
},
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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ả"
|
|
||||||
},
|
|
||||||
"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?<br><br>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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,492 +0,0 @@
|
|||||||
{
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"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, 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 <br>",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
{
|
|
||||||
"nav": {
|
|
||||||
"home": "首页",
|
|
||||||
"about": "关于我们",
|
|
||||||
"contact": "联系我们",
|
|
||||||
"licensing": "许可",
|
|
||||||
"allTools": "所有工具",
|
|
||||||
"openMainMenu": "打开主菜单",
|
|
||||||
"language": "语言"
|
|
||||||
},
|
|
||||||
"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": "清空所有"
|
|
||||||
},
|
|
||||||
"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": "确定要将所有快捷键重置为默认值吗?<br><br>此操作无法撤销。",
|
|
||||||
"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": "加载失败"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,489 +0,0 @@
|
|||||||
{
|
|
||||||
"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": "将页码插入到您的文档中。"
|
|
||||||
},
|
|
||||||
"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, 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": "将换行转换为 <br>",
|
|
||||||
"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": "提取文本"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# BentoPDF Robots.txt
|
|
||||||
# https://www.bentopdf.com
|
|
||||||
|
|
||||||
# Allow all crawlers
|
|
||||||
User-agent: *
|
|
||||||
Allow: /
|
|
||||||
|
|
||||||
# Sitemap location
|
|
||||||
Sitemap: https://www.bentopdf.com/sitemap.xml
|
|
||||||
|
|
||||||
# Disallow internal/system paths
|
|
||||||
Disallow: /api/
|
|
||||||
Disallow: /_next/
|
|
||||||
Disallow: /assets/
|
|
||||||
Disallow: /*.wasm$
|
|
||||||
Disallow: /*.wasm.gz$
|
|
||||||
Disallow: /*.data.gz$
|
|
||||||
Disallow: /*.br$
|
|
||||||
|
|
||||||
# Disallow temporary/upload directories
|
|
||||||
Disallow: /tmp/
|
|
||||||
Disallow: /temp/
|
|
||||||
Disallow: /uploads/
|
|
||||||
|
|
||||||
# Disallow docs internal files (but allow /docs/ pages)
|
|
||||||
Disallow: /docs/assets/
|
|
||||||
|
|
||||||
# Crawl delay for polite crawling
|
|
||||||
Crawl-delay: 1
|
|
||||||
|
|
||||||
# Google-specific
|
|
||||||
User-agent: Googlebot
|
|
||||||
Allow: /
|
|
||||||
Crawl-delay: 0
|
|
||||||
|
|
||||||
# Bing-specific
|
|
||||||
User-agent: Bingbot
|
|
||||||
Allow: /
|
|
||||||
Crawl-delay: 1
|
|
||||||
@@ -1,664 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
||||||
<!-- Homepage -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
<priority>1.0</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Documentation -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/docs/</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Merge & Combine Tools -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/merge-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/alternate-merge</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/combine-single-page</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Split & Extract Tools -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/split-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/extract-pages</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/divide-pages</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/delete-pages</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Compress -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/compress-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Edit & Organize -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/edit-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/organize-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/rotate-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/rotate-custom</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/crop-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/reverse-pages</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Convert FROM -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/word-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/excel-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/powerpoint-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/jpg-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/png-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/image-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/webp-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/bmp-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/tiff-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/heic-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/svg-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/psd-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/txt-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/rtf-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/odt-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/ods-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/odp-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/odg-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pages-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/epub-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/mobi-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/fb2-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/cbz-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/xps-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/vsd-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pub-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/wpd-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/wps-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/markdown-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/json-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/xml-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/csv-to-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Convert TO -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-docx</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-excel</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-jpg</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-png</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-webp</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-bmp</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-tiff</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-svg</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-text</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-markdown</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-json</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-csv</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-zip</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-pdfa</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-to-greyscale</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Security -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/encrypt-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/decrypt-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/change-permissions</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/remove-restrictions</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/sign-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/sanitize-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/flatten-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Forms -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/form-filler</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/form-creator</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Annotations & Stamps -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/add-watermark</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/add-stamps</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/header-footer</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/page-numbers</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/remove-annotations</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/background-color</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/text-color</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/invert-colors</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Metadata & Structure -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/edit-metadata</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/view-metadata</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/remove-metadata</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/bookmark</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/table-of-contents</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-layers</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Attachments -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/add-attachments</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/edit-attachments</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/extract-attachments</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Advanced Tools -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/ocr-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/compare-pdfs</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/repair-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/linearize-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-multi-tool</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/prepare-pdf-for-ai</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Page Tools -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/add-blank-page</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/remove-blank-pages</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/fix-page-size</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/page-dimensions</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/n-up-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/pdf-booklet</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.6</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/posterize-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/rasterize-pdf</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- PDF Extract -->
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/extract-images</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.bentopdf.com/extract-tables</loc>
|
|
||||||
<lastmod>2024-12-28</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
</urlset>
|
|
||||||
-217
@@ -1,217 +0,0 @@
|
|||||||
/**
|
|
||||||
* BentoPDF Service Worker
|
|
||||||
* Caches WASM files and static assets for offline support and faster loading
|
|
||||||
* Version: 1.0.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
const CACHE_VERSION = 'bentopdf-v6';
|
|
||||||
const CACHE_NAME = `${CACHE_VERSION}-static`;
|
|
||||||
|
|
||||||
|
|
||||||
const getBasePath = () => {
|
|
||||||
const scope = self.registration?.scope || self.location.href;
|
|
||||||
const url = new URL(scope);
|
|
||||||
return url.pathname.replace(/\/$/, '') || '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildCriticalAssets = (basePath) => [
|
|
||||||
`${basePath}/pymupdf-wasm/pyodide.js`,
|
|
||||||
`${basePath}/pymupdf-wasm/pyodide.asm.js`,
|
|
||||||
`${basePath}/pymupdf-wasm/pyodide.asm.wasm`,
|
|
||||||
`${basePath}/pymupdf-wasm/python_stdlib.zip`,
|
|
||||||
`${basePath}/pymupdf-wasm/pyodide-lock.json`,
|
|
||||||
|
|
||||||
`${basePath}/pymupdf-wasm/pymupdf-1.26.3-cp313-none-pyodide_2025_0_wasm32.whl`,
|
|
||||||
`${basePath}/pymupdf-wasm/numpy-2.2.5-cp313-cp313-pyodide_2025_0_wasm32.whl`,
|
|
||||||
`${basePath}/pymupdf-wasm/opencv_python-4.11.0.86-cp313-cp313-pyodide_2025_0_wasm32.whl`,
|
|
||||||
`${basePath}/pymupdf-wasm/lxml-5.4.0-cp313-cp313-pyodide_2025_0_wasm32.whl`,
|
|
||||||
`${basePath}/pymupdf-wasm/python_docx-1.2.0-py3-none-any.whl`,
|
|
||||||
`${basePath}/pymupdf-wasm/pdf2docx-0.5.8-py3-none-any.whl`,
|
|
||||||
`${basePath}/pymupdf-wasm/fonttools-4.56.0-py3-none-any.whl`,
|
|
||||||
`${basePath}/pymupdf-wasm/typing_extensions-4.12.2-py3-none-any.whl`,
|
|
||||||
`${basePath}/pymupdf-wasm/pymupdf4llm-0.0.27-py3-none-any.whl`,
|
|
||||||
|
|
||||||
`${basePath}/ghostscript-wasm/gs.js`,
|
|
||||||
`${basePath}/ghostscript-wasm/gs.wasm`,
|
|
||||||
];
|
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
|
||||||
const basePath = getBasePath();
|
|
||||||
const CRITICAL_ASSETS = buildCriticalAssets(basePath);
|
|
||||||
console.log('🚀 [ServiceWorker] Installing version:', CACHE_VERSION);
|
|
||||||
console.log('📍 [ServiceWorker] Base path detected:', basePath || '/');
|
|
||||||
console.log('📦 [ServiceWorker] Will cache', CRITICAL_ASSETS.length, 'critical assets');
|
|
||||||
|
|
||||||
event.waitUntil(
|
|
||||||
caches.open(CACHE_NAME)
|
|
||||||
.then((cache) => {
|
|
||||||
console.log('[ServiceWorker] Caching critical assets...');
|
|
||||||
return cacheInBatches(cache, CRITICAL_ASSETS, 5);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
console.log('✅ [ServiceWorker] All critical assets cached successfully!');
|
|
||||||
console.log('⏭️ [ServiceWorker] Skipping waiting, activating immediately...');
|
|
||||||
return self.skipWaiting();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('[ServiceWorker] Cache installation failed:', error);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('activate', (event) => {
|
|
||||||
console.log('🔄 [ServiceWorker] Activating version:', CACHE_VERSION);
|
|
||||||
|
|
||||||
event.waitUntil(
|
|
||||||
caches.keys()
|
|
||||||
.then((cacheNames) => {
|
|
||||||
return Promise.all(
|
|
||||||
cacheNames.map((cacheName) => {
|
|
||||||
if (cacheName.startsWith('bentopdf-') && cacheName !== CACHE_NAME) {
|
|
||||||
console.log('[ServiceWorker] Deleting old cache:', cacheName);
|
|
||||||
return caches.delete(cacheName);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
console.log('✅ [ServiceWorker] Activated successfully!');
|
|
||||||
console.log('🎯 [ServiceWorker] Taking control of all pages...');
|
|
||||||
return self.clients.claim();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
|
||||||
const url = new URL(event.request.url);
|
|
||||||
|
|
||||||
if (url.origin !== location.origin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.searchParams.has('t') || url.searchParams.has('import') || url.searchParams.has('direct')) {
|
|
||||||
console.log('🔧 [Dev Mode] Skipping Vite HMR request:', url.pathname);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.pathname.includes('/@vite') || url.pathname.includes('/@id') || url.pathname.includes('/@fs')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldCache(url.pathname)) {
|
|
||||||
event.respondWith(cacheFirstStrategy(event.request));
|
|
||||||
} else if (url.pathname.endsWith('.html') || url.pathname === '/') {
|
|
||||||
event.respondWith(networkFirstStrategy(event.request));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache-first strategy: Check cache first, fallback to network
|
|
||||||
* Perfect for WASM files and static assets that rarely change
|
|
||||||
*/
|
|
||||||
async function cacheFirstStrategy(request) {
|
|
||||||
try {
|
|
||||||
const cachedResponse = await caches.match(request, {
|
|
||||||
ignoreVary: true,
|
|
||||||
ignoreSearch: true
|
|
||||||
});
|
|
||||||
if (cachedResponse) {
|
|
||||||
console.log('⚡ [Cache HIT] Instant load:', request.url.split('/').pop());
|
|
||||||
return cachedResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('📥 [Cache MISS] Downloading:', request.url.split('/').pop());
|
|
||||||
|
|
||||||
const networkResponse = await fetch(request);
|
|
||||||
|
|
||||||
if (networkResponse && networkResponse.status === 200) {
|
|
||||||
const cache = await caches.open(CACHE_NAME);
|
|
||||||
cache.put(request, networkResponse.clone());
|
|
||||||
console.log('💾 [Cached] Saved for next time:', request.url.split('/').pop());
|
|
||||||
}
|
|
||||||
|
|
||||||
return networkResponse;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[ServiceWorker] Fetch failed for:', request.url, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Network-first strategy: Try network first, fallback to cache
|
|
||||||
* Perfect for HTML files that might update
|
|
||||||
*/
|
|
||||||
async function networkFirstStrategy(request) {
|
|
||||||
try {
|
|
||||||
const networkResponse = await fetch(request);
|
|
||||||
|
|
||||||
if (networkResponse && networkResponse.status === 200) {
|
|
||||||
const cache = await caches.open(CACHE_NAME);
|
|
||||||
cache.put(request, networkResponse.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
return networkResponse;
|
|
||||||
} catch (error) {
|
|
||||||
const cachedResponse = await caches.match(request);
|
|
||||||
if (cachedResponse) {
|
|
||||||
console.log('📴 [Offline Mode] Serving from cache:', request.url.split('/').pop());
|
|
||||||
return cachedResponse;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if a URL should be cached
|
|
||||||
* Handles both root (/) and subdirectory (/test/) deployments
|
|
||||||
*/
|
|
||||||
function shouldCache(pathname) {
|
|
||||||
return (
|
|
||||||
pathname.includes('/libreoffice-wasm/') ||
|
|
||||||
pathname.includes('/pymupdf-wasm/') ||
|
|
||||||
pathname.includes('/ghostscript-wasm/') ||
|
|
||||||
pathname.includes('/embedpdf/') ||
|
|
||||||
pathname.includes('/assets/') ||
|
|
||||||
pathname.match(/\.(js|mjs|css|wasm|whl|zip|json|png|jpg|jpeg|gif|svg|woff|woff2|ttf|gz|br)$/)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache assets in batches to avoid overwhelming the browser
|
|
||||||
*/
|
|
||||||
async function cacheInBatches(cache, urls, batchSize = 5) {
|
|
||||||
for (let i = 0; i < urls.length; i += batchSize) {
|
|
||||||
const batch = urls.slice(i, i + batchSize);
|
|
||||||
console.log(`[ServiceWorker] Caching batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(urls.length / batchSize)}`);
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
batch.map(async (url) => {
|
|
||||||
try {
|
|
||||||
await cache.add(url);
|
|
||||||
const fileName = url.split('/').pop();
|
|
||||||
const fileSize = fileName.includes('.wasm') || fileName.includes('.whl') ? '(large file)' : '';
|
|
||||||
console.log(` ✓ Cached: ${fileName} ${fileSize}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[ServiceWorker] Failed to cache:', url, error.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.addEventListener('message', (event) => {
|
|
||||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
|
||||||
self.skipWaiting();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.data && event.data.type === 'CLEAR_CACHE') {
|
|
||||||
event.waitUntil(
|
|
||||||
caches.delete(CACHE_NAME).then(() => {
|
|
||||||
console.log('[ServiceWorker] Cache cleared');
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('🎉 [ServiceWorker] Script loaded successfully! Ready to cache assets.');
|
|
||||||
console.log('📊 [ServiceWorker] Cache version:', CACHE_VERSION);
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
|
self.importScripts('../coherentpdf.browser.min.js');
|
||||||
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
|
|
||||||
|
|
||||||
function parsePageRange(rangeString, totalPages) {
|
function parsePageRange(rangeString, totalPages) {
|
||||||
const pages = new Set();
|
const pages = new Set();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
|
self.importScripts('../coherentpdf.browser.min.js');
|
||||||
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
|
|
||||||
|
|
||||||
self.onmessage = function (e) {
|
self.onmessage = function (e) {
|
||||||
const { command, files } = e.data;
|
const { command, files } = e.data;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
|
self.importScripts('../coherentpdf.browser.min.js');
|
||||||
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
|
|
||||||
|
|
||||||
function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
|
function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
|
self.importScripts('../coherentpdf.browser.min.js');
|
||||||
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
|
|
||||||
|
|
||||||
function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
|
function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
|
self.importScripts('/coherentpdf.browser.min.js');
|
||||||
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
|
|
||||||
|
|
||||||
function convertJSONsToPDFInWorker(fileBuffers, fileNames) {
|
function convertJSONsToPDFInWorker(fileBuffers, fileNames) {
|
||||||
try {
|
try {
|
||||||
@@ -15,8 +14,8 @@ function convertJSONsToPDFInWorker(fileBuffers, fileNames) {
|
|||||||
try {
|
try {
|
||||||
pdf = coherentpdf.fromJSONMemory(uint8Array);
|
pdf = coherentpdf.fromJSONMemory(uint8Array);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg = error && error.message
|
const errorMsg = error && error.message
|
||||||
? error.message
|
? error.message
|
||||||
: 'Unknown error';
|
: 'Unknown error';
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to convert "${fileName}" to PDF. ` +
|
`Failed to convert "${fileName}" to PDF. ` +
|
||||||
@@ -24,7 +23,7 @@ function convertJSONsToPDFInWorker(fileBuffers, fileNames) {
|
|||||||
`Error: ${errorMsg}`
|
`Error: ${errorMsg}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pdfData = coherentpdf.toMemory(pdf, false, false);
|
const pdfData = coherentpdf.toMemory(pdf, false, false);
|
||||||
|
|
||||||
const pdfBuffer = pdfData.buffer.slice(0);
|
const pdfBuffer = pdfData.buffer.slice(0);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
|
self.importScripts('../coherentpdf.browser.min.js');
|
||||||
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
|
|
||||||
|
|
||||||
self.onmessage = function (e) {
|
self.onmessage = function (e) {
|
||||||
const { command, files, jobs } = e.data;
|
const { command, files, jobs } = e.data;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
|
self.importScripts('/coherentpdf.browser.min.js');
|
||||||
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
|
|
||||||
|
|
||||||
function convertPDFsToJSONInWorker(fileBuffers, fileNames) {
|
function convertPDFsToJSONInWorker(fileBuffers, fileNames) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
|
self.importScripts('/coherentpdf.browser.min.js');
|
||||||
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
|
|
||||||
|
|
||||||
function generateTableOfContentsInWorker(
|
function generateTableOfContentsInWorker(
|
||||||
pdfData,
|
pdfData,
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translation Checker for BentoPDF
|
|
||||||
*
|
|
||||||
* This script compares translation files across languages and reports:
|
|
||||||
* - Missing keys (keys present in English but absent in other languages)
|
|
||||||
* - Extra keys (keys present in other languages but not in English)
|
|
||||||
* - Untranslated keys (keys with the same value as English)
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* node scripts/check-translations.js
|
|
||||||
* node scripts/check-translations.js --verbose
|
|
||||||
* node scripts/check-translations.js --lang=de
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
|
|
||||||
const LOCALES_DIR = path.join(__dirname, '../public/locales');
|
|
||||||
const REFERENCE_LANG = 'en';
|
|
||||||
|
|
||||||
const colors = {
|
|
||||||
reset: '\x1b[0m',
|
|
||||||
red: '\x1b[31m',
|
|
||||||
green: '\x1b[32m',
|
|
||||||
yellow: '\x1b[33m',
|
|
||||||
blue: '\x1b[34m',
|
|
||||||
cyan: '\x1b[36m',
|
|
||||||
dim: '\x1b[2m',
|
|
||||||
};
|
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
const verbose = args.includes('--verbose');
|
|
||||||
const specificLang = args.find(arg => arg.startsWith('--lang='))?.split('=')[1];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flatten nested JSON object into dot notation
|
|
||||||
* { a: { b: 'value' } } => { 'a.b': 'value' }
|
|
||||||
*/
|
|
||||||
function flattenObject(obj, prefix = '') {
|
|
||||||
const flattened = {};
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
|
||||||
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
||||||
|
|
||||||
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
||||||
Object.assign(flattened, flattenObject(value, newKey));
|
|
||||||
} else {
|
|
||||||
flattened[newKey] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return flattened;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load and parse a translation file
|
|
||||||
*/
|
|
||||||
function loadTranslation(lang) {
|
|
||||||
const filePath = path.join(LOCALES_DIR, lang, 'common.json');
|
|
||||||
|
|
||||||
if (!fs.existsSync(filePath)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const content = fs.readFileSync(filePath, 'utf8');
|
|
||||||
return JSON.parse(content);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`${colors.red}✗ Error parsing ${lang}/common.json:${colors.reset}`, error.message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all available languages
|
|
||||||
*/
|
|
||||||
function getAvailableLanguages() {
|
|
||||||
if (!fs.existsSync(LOCALES_DIR)) {
|
|
||||||
console.error(`${colors.red}✗ Locales directory not found: ${LOCALES_DIR}${colors.reset}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.readdirSync(LOCALES_DIR)
|
|
||||||
.filter(item => {
|
|
||||||
const itemPath = path.join(LOCALES_DIR, item);
|
|
||||||
return fs.statSync(itemPath).isDirectory();
|
|
||||||
})
|
|
||||||
.filter(lang => {
|
|
||||||
// Only include if common.json exists
|
|
||||||
return fs.existsSync(path.join(LOCALES_DIR, lang, 'common.json'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare two sets of keys and report differences
|
|
||||||
*/
|
|
||||||
function compareKeys(refKeys, targetKeys, refLang, targetLang, refFlat, targetFlat) {
|
|
||||||
const missing = refKeys.filter(key => !targetKeys.includes(key));
|
|
||||||
const extra = targetKeys.filter(key => !refKeys.includes(key));
|
|
||||||
const untranslated = refKeys.filter(key =>
|
|
||||||
targetKeys.includes(key) && refFlat[key] === targetFlat[key] && typeof refFlat[key] === 'string'
|
|
||||||
);
|
|
||||||
|
|
||||||
return { missing, extra, untranslated };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print section header
|
|
||||||
*/
|
|
||||||
function printHeader(text) {
|
|
||||||
console.log(`\n${colors.cyan}${'='.repeat(60)}${colors.reset}`);
|
|
||||||
console.log(`${colors.cyan}${text}${colors.reset}`);
|
|
||||||
console.log(`${colors.cyan}${'='.repeat(60)}${colors.reset}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print section
|
|
||||||
*/
|
|
||||||
function printSection(title, items, color = colors.yellow) {
|
|
||||||
if (items.length === 0) return;
|
|
||||||
|
|
||||||
console.log(`${color}${title} (${items.length}):${colors.reset}`);
|
|
||||||
items.forEach(item => {
|
|
||||||
console.log(` ${colors.dim}•${colors.reset} ${item}`);
|
|
||||||
});
|
|
||||||
console.log();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main function
|
|
||||||
*/
|
|
||||||
function main() {
|
|
||||||
console.log(`${colors.blue}🌍 BentoPDF Translation Checker${colors.reset}\n`);
|
|
||||||
|
|
||||||
const languages = getAvailableLanguages();
|
|
||||||
|
|
||||||
if (languages.length === 0) {
|
|
||||||
console.error(`${colors.red}✗ No translation files found in ${LOCALES_DIR}${colors.reset}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load reference language (English)
|
|
||||||
const refTranslation = loadTranslation(REFERENCE_LANG);
|
|
||||||
if (!refTranslation) {
|
|
||||||
console.error(`${colors.red}✗ Reference language (${REFERENCE_LANG}) not found${colors.reset}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const refFlat = flattenObject(refTranslation);
|
|
||||||
const refKeys = Object.keys(refFlat);
|
|
||||||
|
|
||||||
console.log(`${colors.green}✓ Reference language (${REFERENCE_LANG}): ${refKeys.length} keys${colors.reset}`);
|
|
||||||
console.log(`${colors.dim} Available languages: ${languages.join(', ')}${colors.reset}\n`);
|
|
||||||
|
|
||||||
// Filter languages to check
|
|
||||||
const langsToCheck = specificLang
|
|
||||||
? languages.filter(lang => lang === specificLang)
|
|
||||||
: languages.filter(lang => lang !== REFERENCE_LANG);
|
|
||||||
|
|
||||||
if (langsToCheck.length === 0) {
|
|
||||||
console.log(`${colors.yellow}⚠ No languages to check${colors.reset}`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasIssues = false;
|
|
||||||
|
|
||||||
// Check each language
|
|
||||||
for (const lang of langsToCheck) {
|
|
||||||
printHeader(`Checking: ${lang.toUpperCase()}`);
|
|
||||||
|
|
||||||
const translation = loadTranslation(lang);
|
|
||||||
if (!translation) {
|
|
||||||
hasIssues = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetFlat = flattenObject(translation);
|
|
||||||
const targetKeys = Object.keys(targetFlat);
|
|
||||||
|
|
||||||
const { missing, extra, untranslated } = compareKeys(
|
|
||||||
refKeys,
|
|
||||||
targetKeys,
|
|
||||||
REFERENCE_LANG,
|
|
||||||
lang,
|
|
||||||
refFlat,
|
|
||||||
targetFlat
|
|
||||||
);
|
|
||||||
|
|
||||||
// Summary
|
|
||||||
console.log(`${colors.dim}Total keys: ${targetKeys.length} / ${refKeys.length}${colors.reset}\n`);
|
|
||||||
|
|
||||||
// Missing keys
|
|
||||||
if (missing.length > 0) {
|
|
||||||
hasIssues = true;
|
|
||||||
printSection(`Missing Keys`, missing, colors.red);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extra keys
|
|
||||||
if (extra.length > 0) {
|
|
||||||
hasIssues = true;
|
|
||||||
printSection(`Extra Keys (not in English)`, extra, colors.yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Untranslated keys (same as English)
|
|
||||||
if (verbose && untranslated.length > 0) {
|
|
||||||
printSection(`Possibly Untranslated (same as English)`, untranslated, colors.cyan);
|
|
||||||
} else if (untranslated.length > 0) {
|
|
||||||
console.log(`${colors.cyan}Possibly Untranslated: ${untranslated.length}${colors.reset}`);
|
|
||||||
console.log(`${colors.dim} (use --verbose to see details)${colors.reset}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// All good
|
|
||||||
if (missing.length === 0 && extra.length === 0) {
|
|
||||||
console.log(`${colors.green} No missing or extra keys!${colors.reset}\n`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final summary
|
|
||||||
console.log(`${colors.cyan}${'='.repeat(60)}${colors.reset}\n`);
|
|
||||||
|
|
||||||
if (!hasIssues) {
|
|
||||||
console.log(`${colors.green} All translations are in sync!${colors.reset}\n`);
|
|
||||||
process.exit(0);
|
|
||||||
} else {
|
|
||||||
console.log(`${colors.yellow} Issues found. Please review the output above.${colors.reset}\n`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
import { createReadStream, createWriteStream, readdirSync, statSync, unlinkSync } from 'fs';
|
|
||||||
import { join, extname } from 'path';
|
|
||||||
import { createBrotliCompress, createGzip, constants } from 'zlib';
|
|
||||||
import { pipeline } from 'stream/promises';
|
|
||||||
|
|
||||||
const DIST_DIR = './dist';
|
|
||||||
|
|
||||||
// File extensions to compress
|
|
||||||
const EXTENSIONS_TO_COMPRESS = [
|
|
||||||
'.wasm',
|
|
||||||
'.whl',
|
|
||||||
'.data',
|
|
||||||
'.zip',
|
|
||||||
'.json'
|
|
||||||
];
|
|
||||||
|
|
||||||
const MIN_SIZE = 1024;
|
|
||||||
|
|
||||||
const mode = process.env.COMPRESSION_MODE || process.argv[2] || 'all';
|
|
||||||
|
|
||||||
const VALID_MODES = ['g', 'b', 'o', 'all'];
|
|
||||||
if (!VALID_MODES.includes(mode)) {
|
|
||||||
console.error(`❌ Invalid compression mode: ${mode}`);
|
|
||||||
console.error(` Valid modes: g (gzip only), b (brotli only), o (original only), all (all formats)`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`🔧 Compression mode: ${mode}`);
|
|
||||||
console.log(` ${getModeDescription(mode)}\n`);
|
|
||||||
|
|
||||||
function getModeDescription(mode) {
|
|
||||||
const descriptions = {
|
|
||||||
g: 'gzip ONLY (keeps .gz, deletes originals and .br)',
|
|
||||||
b: 'brotli ONLY (keeps .br, deletes originals and .gz)',
|
|
||||||
o: 'original ONLY (keeps originals, deletes .gz and .br)',
|
|
||||||
all: 'ALL formats (keeps .gz, .br, and originals)'
|
|
||||||
};
|
|
||||||
return descriptions[mode];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllFiles(dir, files = []) {
|
|
||||||
const entries = readdirSync(dir);
|
|
||||||
|
|
||||||
for (const entry of entries) {
|
|
||||||
const fullPath = join(dir, entry);
|
|
||||||
const stat = statSync(fullPath);
|
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
getAllFiles(fullPath, files);
|
|
||||||
} else {
|
|
||||||
files.push(fullPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compress a file with Brotli (level 11)
|
|
||||||
*/
|
|
||||||
async function compressBrotli(inputPath, outputPath) {
|
|
||||||
const brotli = createBrotliCompress({
|
|
||||||
params: {
|
|
||||||
[constants.BROTLI_PARAM_QUALITY]: 11,
|
|
||||||
[constants.BROTLI_PARAM_MODE]: constants.BROTLI_MODE_GENERIC,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await pipeline(
|
|
||||||
createReadStream(inputPath),
|
|
||||||
brotli,
|
|
||||||
createWriteStream(outputPath)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compress a file with gzip (level 9)
|
|
||||||
*/
|
|
||||||
async function compressGzip(inputPath, outputPath) {
|
|
||||||
const gzip = createGzip({ level: 9 });
|
|
||||||
|
|
||||||
await pipeline(
|
|
||||||
createReadStream(inputPath),
|
|
||||||
gzip,
|
|
||||||
createWriteStream(outputPath)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteCompressedFiles() {
|
|
||||||
console.log('🗑️ Deleting compressed files...\n');
|
|
||||||
const allFiles = getAllFiles(DIST_DIR);
|
|
||||||
let deletedCount = 0;
|
|
||||||
|
|
||||||
for (const file of allFiles) {
|
|
||||||
if (file.endsWith('.br') || file.endsWith('.gz')) {
|
|
||||||
unlinkSync(file);
|
|
||||||
deletedCount++;
|
|
||||||
console.log(` Deleted: ${file}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\n✅ Deleted ${deletedCount} compressed files\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete original files (keep only compressed versions)
|
|
||||||
*/
|
|
||||||
function deleteOriginalFiles(filePaths, extension) {
|
|
||||||
console.log(`🗑️ Deleting original files (keeping only ${extension})...\n`);
|
|
||||||
let deletedCount = 0;
|
|
||||||
|
|
||||||
for (const file of filePaths) {
|
|
||||||
try {
|
|
||||||
unlinkSync(file);
|
|
||||||
deletedCount++;
|
|
||||||
console.log(` Deleted: ${file}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(` ⚠️ Failed to delete ${file}: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\n✅ Deleted ${deletedCount} original files\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete specific compressed format
|
|
||||||
*/
|
|
||||||
function deleteCompressedFormat(extension) {
|
|
||||||
console.log(`🗑️ Deleting ${extension} files...\n`);
|
|
||||||
const allFiles = getAllFiles(DIST_DIR);
|
|
||||||
let deletedCount = 0;
|
|
||||||
|
|
||||||
for (const file of allFiles) {
|
|
||||||
if (file.endsWith(extension)) {
|
|
||||||
unlinkSync(file);
|
|
||||||
deletedCount++;
|
|
||||||
console.log(` Deleted: ${file}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\n✅ Deleted ${deletedCount} ${extension} files\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function compressStaticAssets() {
|
|
||||||
if (mode === 'o') {
|
|
||||||
deleteCompressedFiles();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🔍 Scanning for static assets to compress...\n');
|
|
||||||
|
|
||||||
const allFiles = getAllFiles(DIST_DIR);
|
|
||||||
const filesToCompress = allFiles.filter(file => {
|
|
||||||
const ext = extname(file);
|
|
||||||
const stat = statSync(file);
|
|
||||||
|
|
||||||
if (file.endsWith('.br') || file.endsWith('.gz')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return EXTENSIONS_TO_COMPRESS.includes(ext) && stat.size >= MIN_SIZE;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`📦 Found ${filesToCompress.length} files to compress\n`);
|
|
||||||
|
|
||||||
let processedCount = 0;
|
|
||||||
const shouldCompressBrotli = mode === 'b' || mode === 'all';
|
|
||||||
const shouldCompressGzip = mode === 'g' || mode === 'all';
|
|
||||||
|
|
||||||
for (const file of filesToCompress) {
|
|
||||||
const stat = statSync(file);
|
|
||||||
const sizeMB = (stat.size / (1024 * 1024)).toFixed(2);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let brotliStat, gzipStat;
|
|
||||||
|
|
||||||
if (shouldCompressBrotli) {
|
|
||||||
await compressBrotli(file, `${file}.br`);
|
|
||||||
brotliStat = statSync(`${file}.br`);
|
|
||||||
}
|
|
||||||
if (shouldCompressGzip) {
|
|
||||||
await compressGzip(file, `${file}.gz`);
|
|
||||||
gzipStat = statSync(`${file}.gz`);
|
|
||||||
}
|
|
||||||
|
|
||||||
processedCount++;
|
|
||||||
console.log(`✅ ${file}`);
|
|
||||||
console.log(` Original: ${sizeMB} MB`);
|
|
||||||
|
|
||||||
if (brotliStat) {
|
|
||||||
const brotliSizeMB = (brotliStat.size / (1024 * 1024)).toFixed(2);
|
|
||||||
console.log(` Brotli: ${brotliSizeMB} MB (${((brotliStat.size / stat.size) * 100).toFixed(1)}%)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gzipStat) {
|
|
||||||
const gzipSizeMB = (gzipStat.size / (1024 * 1024)).toFixed(2);
|
|
||||||
console.log(` Gzip: ${gzipSizeMB} MB (${((gzipStat.size / stat.size) * 100).toFixed(1)}%)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('');
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ Failed to compress ${file}:`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\n🎉 Compressed ${processedCount}/${filesToCompress.length} files successfully!\n`);
|
|
||||||
|
|
||||||
if (mode === 'g') {
|
|
||||||
deleteCompressedFormat('.br');
|
|
||||||
deleteOriginalFiles(filesToCompress, '.gz');
|
|
||||||
} else if (mode === 'b') {
|
|
||||||
deleteCompressedFormat('.gz');
|
|
||||||
deleteOriginalFiles(filesToCompress, '.br');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compressStaticAssets().catch(err => {
|
|
||||||
console.error('❌ Compression failed:', err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
+1
-194
@@ -1,196 +1,3 @@
|
|||||||
{
|
{
|
||||||
"signedContributors": [
|
"signedContributors": []
|
||||||
{
|
|
||||||
"name": "Akhrameev",
|
|
||||||
"id": 1945749,
|
|
||||||
"comment_id": 3618670485,
|
|
||||||
"created_at": "2025-12-05T21:32:56Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 225
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Skillkiller",
|
|
||||||
"id": 18444809,
|
|
||||||
"comment_id": 3618954326,
|
|
||||||
"created_at": "2025-12-05T23:29:22Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 215
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ShiranJ",
|
|
||||||
"id": 6860437,
|
|
||||||
"comment_id": 3619730172,
|
|
||||||
"created_at": "2025-12-06T08:13:20Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 222
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "EmeraldPi",
|
|
||||||
"id": 6157937,
|
|
||||||
"comment_id": 3623283119,
|
|
||||||
"created_at": "2025-12-07T20:55:33Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 235
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "munqsy",
|
|
||||||
"id": 188875728,
|
|
||||||
"comment_id": 3632241504,
|
|
||||||
"created_at": "2025-12-09T13:21:16Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 240
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nabaco",
|
|
||||||
"id": 38861757,
|
|
||||||
"comment_id": 3635879929,
|
|
||||||
"created_at": "2025-12-10T08:11:40Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 242
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tuyndoan",
|
|
||||||
"id": 137283898,
|
|
||||||
"comment_id": 3643164805,
|
|
||||||
"created_at": "2025-12-11T18:05:46Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 252
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Cxllxc",
|
|
||||||
"id": 113428130,
|
|
||||||
"comment_id": 3647797392,
|
|
||||||
"created_at": "2025-12-12T19:11:45Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 256
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "StanislasMzx",
|
|
||||||
"id": 54370876,
|
|
||||||
"comment_id": 3649939865,
|
|
||||||
"created_at": "2025-12-13T23:53:10Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 260
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hemantapkh",
|
|
||||||
"id": 58947310,
|
|
||||||
"comment_id": 3650632419,
|
|
||||||
"created_at": "2025-12-14T09:42:06Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 262
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mobiwise-dev",
|
|
||||||
"id": 96530435,
|
|
||||||
"comment_id": 3651623975,
|
|
||||||
"created_at": "2025-12-14T16:35:33Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 263
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Zakmaf",
|
|
||||||
"id": 100925791,
|
|
||||||
"comment_id": 3652328618,
|
|
||||||
"created_at": "2025-12-14T22:38:51Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 267
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "crstian19",
|
|
||||||
"id": 21632768,
|
|
||||||
"comment_id": 3659376907,
|
|
||||||
"created_at": "2025-12-16T08:30:24Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 266
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "topcu-muhammet",
|
|
||||||
"id": 44720965,
|
|
||||||
"comment_id": 3663023003,
|
|
||||||
"created_at": "2025-12-17T00:24:53Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 275
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "alexandreforeis",
|
|
||||||
"id": 463428,
|
|
||||||
"comment_id": 3668047879,
|
|
||||||
"created_at": "2025-12-18T02:42:24Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 282
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "amatosg",
|
|
||||||
"id": 42916953,
|
|
||||||
"comment_id": 3670557934,
|
|
||||||
"created_at": "2025-12-18T14:26:18Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 284
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "htekgulds",
|
|
||||||
"id": 6371054,
|
|
||||||
"comment_id": 3675487949,
|
|
||||||
"created_at": "2025-12-19T15:25:22Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 289
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Stephan-P",
|
|
||||||
"id": 10008599,
|
|
||||||
"comment_id": 3677738511,
|
|
||||||
"created_at": "2025-12-20T11:13:17Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 293
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "AB527",
|
|
||||||
"id": 67513487,
|
|
||||||
"comment_id": 3678813689,
|
|
||||||
"created_at": "2025-12-21T13:52:26Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 297
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "larryclaman",
|
|
||||||
"id": 25161683,
|
|
||||||
"comment_id": 3688128138,
|
|
||||||
"created_at": "2025-12-23T22:28:40Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 307
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "jdalrymple",
|
|
||||||
"id": 3743662,
|
|
||||||
"comment_id": 3693529899,
|
|
||||||
"created_at": "2025-12-27T00:51:13Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 312
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "matteo-genovese",
|
|
||||||
"id": 67902487,
|
|
||||||
"comment_id": 3694053964,
|
|
||||||
"created_at": "2025-12-27T15:47:09Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 315
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ryuupendragon",
|
|
||||||
"id": 51423001,
|
|
||||||
"comment_id": 3694537786,
|
|
||||||
"created_at": "2025-12-28T07:40:08Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 317
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Timmy93",
|
|
||||||
"id": 10899430,
|
|
||||||
"comment_id": 3695167676,
|
|
||||||
"created_at": "2025-12-29T00:17:09Z",
|
|
||||||
"repoId": 1074785178,
|
|
||||||
"pullRequestNo": 325
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
@@ -1,817 +0,0 @@
|
|||||||
.md-editor-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-title {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn-secondary {
|
|
||||||
color: rgb(156, 163, 175);
|
|
||||||
background: rgb(31, 41, 55);
|
|
||||||
border: 1px solid rgb(55, 65, 81);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn-secondary:hover {
|
|
||||||
background: rgb(55, 65, 81);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn-primary {
|
|
||||||
color: white;
|
|
||||||
background: rgb(79, 70, 229);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn-primary:hover {
|
|
||||||
background: rgb(99, 102, 241);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Theme Toggle */
|
|
||||||
.theme-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
background: rgb(31, 41, 55);
|
|
||||||
border: 1px solid rgb(55, 65, 81);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: rgb(156, 163, 175);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-slider {
|
|
||||||
position: relative;
|
|
||||||
width: 40px;
|
|
||||||
height: 20px;
|
|
||||||
background: rgb(55, 65, 81);
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-slider::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
left: 2px;
|
|
||||||
top: 2px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-slider.active {
|
|
||||||
background: rgb(79, 70, 229);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-slider.active::before {
|
|
||||||
transform: translateX(20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Split View Container */
|
|
||||||
.md-editor-main {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
height: 600px;
|
|
||||||
border: 1px solid rgb(55, 65, 81);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-pane {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 0;
|
|
||||||
background: rgb(31, 41, 55);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-pane-header {
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: rgb(156, 163, 175);
|
|
||||||
background: rgb(17, 24, 39);
|
|
||||||
border-bottom: 1px solid rgb(55, 65, 81);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Separator between panes */
|
|
||||||
.md-editor-pane:first-child {
|
|
||||||
border-right: 2px solid rgb(55, 65, 81);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-pane:first-child {
|
|
||||||
border-right-color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Editor Textarea */
|
|
||||||
.md-editor-textarea {
|
|
||||||
flex: 1;
|
|
||||||
width: 100%;
|
|
||||||
padding: 1rem;
|
|
||||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: rgb(229, 231, 235);
|
|
||||||
background: rgb(31, 41, 55);
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
resize: none;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-textarea::placeholder {
|
|
||||||
color: rgb(107, 114, 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-pane {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-pane-header {
|
|
||||||
background: rgb(249, 250, 251);
|
|
||||||
color: rgb(75, 85, 99);
|
|
||||||
border-bottom-color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-textarea {
|
|
||||||
background: white;
|
|
||||||
color: rgb(17, 24, 39);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview {
|
|
||||||
background: white;
|
|
||||||
color: rgb(17, 24, 39);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview {
|
|
||||||
flex: 1;
|
|
||||||
padding: 1.5rem;
|
|
||||||
overflow-y: auto;
|
|
||||||
background: rgb(31, 41, 55);
|
|
||||||
color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview h1,
|
|
||||||
.md-editor-preview h2,
|
|
||||||
.md-editor-preview h3,
|
|
||||||
.md-editor-preview h4,
|
|
||||||
.md-editor-preview h5,
|
|
||||||
.md-editor-preview h6 {
|
|
||||||
margin-top: 1.5em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.25;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview h1:first-child,
|
|
||||||
.md-editor-preview h2:first-child,
|
|
||||||
.md-editor-preview h3:first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview h1 {
|
|
||||||
font-size: 2em;
|
|
||||||
border-bottom: 1px solid rgb(55, 65, 81);
|
|
||||||
padding-bottom: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview h2 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
border-bottom: 1px solid rgb(55, 65, 81);
|
|
||||||
padding-bottom: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview h3 {
|
|
||||||
font-size: 1.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview p {
|
|
||||||
margin: 1em 0;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview a {
|
|
||||||
color: rgb(96, 165, 250);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview code {
|
|
||||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
background: rgb(17, 24, 39);
|
|
||||||
color: rgb(251, 191, 36);
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview pre {
|
|
||||||
background: rgb(17, 24, 39);
|
|
||||||
padding: 1rem;
|
|
||||||
overflow: auto;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
line-height: 1.45;
|
|
||||||
margin: 1em 0;
|
|
||||||
border: 1px solid rgb(55, 65, 81);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview pre code {
|
|
||||||
background: none;
|
|
||||||
color: rgb(229, 231, 235);
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview blockquote {
|
|
||||||
margin: 1em 0;
|
|
||||||
padding: 0 1em;
|
|
||||||
color: rgb(156, 163, 175);
|
|
||||||
border-left: 4px solid rgb(79, 70, 229);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview ul,
|
|
||||||
.md-editor-preview ol {
|
|
||||||
margin: 1em 0;
|
|
||||||
padding-left: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview li {
|
|
||||||
margin: 0.25em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview th,
|
|
||||||
.md-editor-preview td {
|
|
||||||
border: 1px solid rgb(55, 65, 81);
|
|
||||||
padding: 8px 12px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview th {
|
|
||||||
background: rgb(17, 24, 39);
|
|
||||||
font-weight: 600;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview tr:nth-child(even) {
|
|
||||||
background: rgb(17, 24, 39);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview hr {
|
|
||||||
border: none;
|
|
||||||
border-top: 1px solid rgb(55, 65, 81);
|
|
||||||
margin: 2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Light Mode Preview Styles */
|
|
||||||
.md-editor.light-mode .md-editor-preview h1,
|
|
||||||
.md-editor.light-mode .md-editor-preview h2,
|
|
||||||
.md-editor.light-mode .md-editor-preview h3,
|
|
||||||
.md-editor.light-mode .md-editor-preview h4,
|
|
||||||
.md-editor.light-mode .md-editor-preview h5,
|
|
||||||
.md-editor.light-mode .md-editor-preview h6 {
|
|
||||||
color: rgb(17, 24, 39);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview h1,
|
|
||||||
.md-editor.light-mode .md-editor-preview h2 {
|
|
||||||
border-bottom-color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview a {
|
|
||||||
color: rgb(29, 78, 216);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview code {
|
|
||||||
background: rgb(249, 250, 251);
|
|
||||||
color: rgb(220, 38, 38);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview pre {
|
|
||||||
background: rgb(249, 250, 251);
|
|
||||||
border-color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview pre code {
|
|
||||||
color: rgb(17, 24, 39);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview blockquote {
|
|
||||||
color: rgb(75, 85, 99);
|
|
||||||
border-left-color: rgb(99, 102, 241);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview th,
|
|
||||||
.md-editor.light-mode .md-editor-preview td {
|
|
||||||
border-color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview th {
|
|
||||||
background: rgb(249, 250, 251);
|
|
||||||
color: rgb(17, 24, 39);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview tr:nth-child(even) {
|
|
||||||
background: rgb(249, 250, 251);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview hr {
|
|
||||||
border-top-color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Markdown Plugin Styles */
|
|
||||||
.md-editor-preview mark {
|
|
||||||
background-color: rgb(254, 243, 199);
|
|
||||||
color: rgb(17, 24, 39);
|
|
||||||
padding: 0.1em 0.2em;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview ins {
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: rgb(134, 239, 172);
|
|
||||||
color: rgb(17, 24, 39);
|
|
||||||
padding: 0.1em 0.2em;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview .task-list-item {
|
|
||||||
list-style-type: none;
|
|
||||||
margin-left: -1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview .footnotes {
|
|
||||||
margin-top: 2em;
|
|
||||||
padding-top: 1em;
|
|
||||||
border-top: 1px solid rgb(55, 65, 81);
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview .footnotes {
|
|
||||||
border-top-color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview .table-of-contents {
|
|
||||||
background: rgb(17, 24, 39);
|
|
||||||
padding: 1em 1.5em;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
margin: 1em 0;
|
|
||||||
border: 1px solid rgb(55, 65, 81);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview .table-of-contents {
|
|
||||||
background: rgb(249, 250, 251);
|
|
||||||
border-color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mermaid Diagrams */
|
|
||||||
.md-editor-preview .mermaid-diagram {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 1.5em 0;
|
|
||||||
padding: 1em;
|
|
||||||
background: rgb(17, 24, 39);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
border: 1px solid rgb(55, 65, 81);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview .mermaid-diagram svg {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview .mermaid-diagram {
|
|
||||||
background: rgb(249, 250, 251);
|
|
||||||
border-color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview .mermaid-error {
|
|
||||||
color: rgb(248, 113, 113);
|
|
||||||
background: rgba(239, 68, 68, 0.1);
|
|
||||||
padding: 1em;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
border: 1px solid rgb(239, 68, 68);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-preview .mermaid-error {
|
|
||||||
color: rgb(185, 28, 28);
|
|
||||||
background: rgb(254, 242, 242);
|
|
||||||
border-color: rgb(252, 165, 165);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Settings Modal */
|
|
||||||
.md-editor-modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.75);
|
|
||||||
z-index: 9998;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-modal {
|
|
||||||
background: rgb(31, 41, 55);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
max-width: 600px;
|
|
||||||
width: 90%;
|
|
||||||
max-height: 80vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
border: 1px solid rgb(55, 65, 81);
|
|
||||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-modal-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-modal-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-modal-close {
|
|
||||||
padding: 0.5rem;
|
|
||||||
color: rgb(156, 163, 175);
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-modal-close:hover {
|
|
||||||
background: rgb(55, 65, 81);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-settings-group {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-settings-group h3 {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: white;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-checkbox {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.375rem 0;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: rgb(156, 163, 175);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-checkbox:hover {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-settings-group select {
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: white;
|
|
||||||
background: rgb(17, 24, 39);
|
|
||||||
border: 1px solid rgb(55, 65, 81);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-settings-group select:hover {
|
|
||||||
border-color: rgb(75, 85, 99);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-settings-group select:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: rgb(79, 70, 229);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print Styles */
|
|
||||||
@media print {
|
|
||||||
|
|
||||||
/* Reset all backgrounds, margins, and padding */
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
background: white !important;
|
|
||||||
background-color: white !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide all elements by default */
|
|
||||||
body * {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide UI elements completely - don't take up space */
|
|
||||||
.md-editor-header,
|
|
||||||
.md-editor-pane-header,
|
|
||||||
.md-editor-textarea,
|
|
||||||
.md-editor-modal-overlay,
|
|
||||||
.md-editor-pane:first-child,
|
|
||||||
header,
|
|
||||||
nav,
|
|
||||||
footer,
|
|
||||||
#back-to-tools,
|
|
||||||
.bg-gray-800>h1.text-2xl,
|
|
||||||
.bg-gray-800>p.text-gray-400,
|
|
||||||
#markdown-editor-container~h1,
|
|
||||||
#markdown-editor-container~p {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.min-h-screen,
|
|
||||||
.max-w-7xl,
|
|
||||||
.bg-gray-800,
|
|
||||||
.rounded-xl,
|
|
||||||
.shadow-xl,
|
|
||||||
.p-6,
|
|
||||||
.py-8,
|
|
||||||
.px-4,
|
|
||||||
#markdown-editor-container,
|
|
||||||
[class*="container"] {
|
|
||||||
padding: 0 !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
background: white !important;
|
|
||||||
background-color: white !important;
|
|
||||||
border: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset container backgrounds and layouts */
|
|
||||||
.md-editor,
|
|
||||||
.md-editor-wrapper,
|
|
||||||
.md-editor-main,
|
|
||||||
.md-editor-pane {
|
|
||||||
background: white !important;
|
|
||||||
background-color: white !important;
|
|
||||||
border: none !important;
|
|
||||||
height: auto !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
display: block !important;
|
|
||||||
visibility: visible !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
gap: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-main {
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make preview and contents visible */
|
|
||||||
.md-editor-preview,
|
|
||||||
.md-editor-preview * {
|
|
||||||
visibility: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview {
|
|
||||||
position: static !important;
|
|
||||||
width: 100% !important;
|
|
||||||
height: auto !important;
|
|
||||||
max-height: none !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
background: white !important;
|
|
||||||
color: black !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview h1,
|
|
||||||
.md-editor-preview h2,
|
|
||||||
.md-editor-preview h3,
|
|
||||||
.md-editor-preview h4,
|
|
||||||
.md-editor-preview h5,
|
|
||||||
.md-editor-preview h6 {
|
|
||||||
color: black !important;
|
|
||||||
page-break-after: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview>h1:first-child,
|
|
||||||
.md-editor-preview>*:first-child {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview p,
|
|
||||||
.md-editor-preview li,
|
|
||||||
.md-editor-preview span {
|
|
||||||
color: black !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview a {
|
|
||||||
color: #0366d6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview code {
|
|
||||||
background: #f6f8fa !important;
|
|
||||||
color: #24292e !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview pre {
|
|
||||||
background: #f6f8fa !important;
|
|
||||||
border: 1px solid #e1e4e8 !important;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview pre code {
|
|
||||||
color: #24292e !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview blockquote {
|
|
||||||
color: #6a737d !important;
|
|
||||||
border-left-color: #dfe2e5 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview table {
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview th,
|
|
||||||
.md-editor-preview td {
|
|
||||||
border-color: #e1e4e8 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview th {
|
|
||||||
background: #f6f8fa !important;
|
|
||||||
color: black !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview tr:nth-child(even) {
|
|
||||||
background: #f6f8fa !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview .mermaid-diagram {
|
|
||||||
background: #f6f8fa !important;
|
|
||||||
border: 1px solid #e1e4e8 !important;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-preview .mermaid-diagram svg {
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.md-editor-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-actions {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle {
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-main {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
height: auto;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-pane {
|
|
||||||
min-height: 300px;
|
|
||||||
max-height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-textarea,
|
|
||||||
.md-editor-preview {
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: auto;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-pane:first-child {
|
|
||||||
border-right: none;
|
|
||||||
border-bottom: 2px solid rgb(55, 65, 81);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor.light-mode .md-editor-pane:first-child {
|
|
||||||
border-right: none;
|
|
||||||
border-bottom-color: rgb(229, 231, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-modal {
|
|
||||||
width: 95%;
|
|
||||||
max-height: 90vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-settings-group {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1024px) and (min-width: 769px) {
|
|
||||||
.md-editor-main {
|
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn {
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.md-editor-pane {
|
|
||||||
min-height: 250px;
|
|
||||||
max-height: 350px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-btn {
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-header {
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-editor-actions {
|
|
||||||
gap: 0.375rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+170
-375
@@ -4,70 +4,70 @@ export const categories = [
|
|||||||
name: 'Popular Tools',
|
name: 'Popular Tools',
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-multi-tool.html',
|
href: import.meta.env.BASE_URL + 'src/pages/pdf-multi-tool.html',
|
||||||
name: 'PDF Multi Tool',
|
name: 'PDF Multi Tool',
|
||||||
icon: 'ph-pencil-ruler',
|
icon: 'pencil-ruler',
|
||||||
subtitle: 'Merge, Split, Organize, Delete, Rotate, Add Blank Pages, Extract and Duplicate in an unified interface.',
|
subtitle: 'Merge, Split, Organize, Delete, Rotate, Add Blank Pages, Extract and Duplicate in an unified interface.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'merge-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/merge-pdf.html',
|
||||||
name: 'Merge PDF',
|
name: 'Merge PDF',
|
||||||
icon: 'ph-browsers',
|
icon: 'combine',
|
||||||
subtitle: 'Combine multiple PDFs into one file. Preserves Bookmarks.',
|
subtitle: 'Combine multiple PDFs into one file. Preserves Bookmarks.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'split-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/split-pdf.html',
|
||||||
name: 'Split PDF',
|
name: 'Split PDF',
|
||||||
icon: 'ph-scissors',
|
icon: 'scissors',
|
||||||
subtitle: 'Extract a range of pages into a new PDF.',
|
subtitle: 'Extract a range of pages into a new PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'compress-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/compress-pdf.html',
|
||||||
name: 'Compress PDF',
|
name: 'Compress PDF',
|
||||||
icon: 'ph-lightning',
|
icon: 'zap',
|
||||||
subtitle: 'Reduce the file size of your PDF.',
|
subtitle: 'Reduce the file size of your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'edit-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/edit-pdf.html',
|
||||||
name: 'PDF Editor',
|
name: 'PDF Editor',
|
||||||
icon: 'ph-pencil-simple',
|
icon: 'pocket-knife',
|
||||||
subtitle:
|
subtitle:
|
||||||
'Annotate, highlight, redact, comment, add shapes/images, search, and view PDFs',
|
'Annotate, highlight, redact, comment, add shapes/images, search, and view PDFs',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'jpg-to-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/jpg-to-pdf.html',
|
||||||
name: 'JPG to PDF',
|
name: 'JPG to PDF',
|
||||||
icon: 'ph-file-jpg',
|
icon: 'image-up',
|
||||||
subtitle: 'Create a PDF from one or more JPG images.',
|
subtitle: 'Create a PDF from one or more JPG images.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'sign-pdf.html',
|
id: 'sign-pdf',
|
||||||
name: 'Sign PDF',
|
name: 'Sign PDF',
|
||||||
icon: 'ph-pen-nib',
|
icon: 'pen-tool',
|
||||||
subtitle: 'Draw, type, or upload your signature.',
|
subtitle: 'Draw, type, or upload your signature.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'crop-pdf.html',
|
id: 'cropper',
|
||||||
name: 'Crop PDF',
|
name: 'Crop PDF',
|
||||||
icon: 'ph-crop',
|
icon: 'crop',
|
||||||
subtitle: 'Trim the margins of every page in your PDF.',
|
subtitle: 'Trim the margins of every page in your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'extract-pages.html',
|
id: 'extract-pages',
|
||||||
name: 'Extract Pages',
|
name: 'Extract Pages',
|
||||||
icon: 'ph-squares-four',
|
icon: 'ungroup',
|
||||||
subtitle: 'Save a selection of pages as new files.',
|
subtitle: 'Save a selection of pages as new files.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'organize-pdf.html',
|
id: 'duplicate-organize',
|
||||||
name: 'Duplicate & Organize',
|
name: 'Duplicate & Organize',
|
||||||
icon: 'ph-files',
|
icon: 'files',
|
||||||
subtitle: 'Duplicate, reorder, and delete pages.',
|
subtitle: 'Duplicate, reorder, and delete pages.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'delete-pages.html',
|
id: 'delete-pages',
|
||||||
name: 'Delete Pages',
|
name: 'Delete Pages',
|
||||||
icon: 'ph-trash',
|
icon: 'trash-2',
|
||||||
subtitle: 'Remove specific pages from your document.',
|
subtitle: 'Remove specific pages from your document.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -76,100 +76,101 @@ export const categories = [
|
|||||||
name: 'Edit & Annotate',
|
name: 'Edit & Annotate',
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'edit-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/edit-pdf.html',
|
||||||
name: 'PDF Editor',
|
name: 'PDF Editor',
|
||||||
icon: 'ph-pencil-simple',
|
icon: 'pocket-knife',
|
||||||
subtitle:
|
subtitle:
|
||||||
'Annotate, highlight, redact, comment, add shapes/images, search, and view PDFs.',
|
'Annotate, highlight, redact, comment, add shapes/images, search, and view PDFs.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'bookmark.html',
|
// id: 'bookmark-pdf',
|
||||||
|
href: import.meta.env.BASE_URL + 'src/pages/bookmark.html',
|
||||||
name: 'Edit Bookmarks',
|
name: 'Edit Bookmarks',
|
||||||
icon: 'ph-bookmark',
|
icon: 'bookmark',
|
||||||
subtitle: 'Add, edit, import, delete and extract PDF bookmarks.',
|
subtitle: 'Add, edit, import, delete and extract PDF bookmarks.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'table-of-contents.html',
|
href: import.meta.env.BASE_URL + 'src/pages/table-of-contents.html',
|
||||||
name: 'Table of Contents',
|
name: 'Table of Contents',
|
||||||
icon: 'ph-list',
|
icon: 'list',
|
||||||
subtitle: 'Generate a table of contents page from PDF bookmarks.',
|
subtitle: 'Generate a table of contents page from PDF bookmarks.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'page-numbers.html',
|
id: 'add-page-numbers',
|
||||||
name: 'Page Numbers',
|
name: 'Page Numbers',
|
||||||
icon: 'ph-list-numbers',
|
icon: 'list-ordered',
|
||||||
subtitle: 'Insert page numbers into your document.',
|
subtitle: 'Insert page numbers into your document.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'add-watermark.html',
|
id: 'add-watermark',
|
||||||
name: 'Add Watermark',
|
name: 'Add Watermark',
|
||||||
icon: 'ph-drop',
|
icon: 'droplets',
|
||||||
subtitle: 'Stamp text or an image over your PDF pages.',
|
subtitle: 'Stamp text or an image over your PDF pages.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'header-footer.html',
|
id: 'add-header-footer',
|
||||||
name: 'Header & Footer',
|
name: 'Header & Footer',
|
||||||
icon: 'ph-paragraph',
|
icon: 'pilcrow',
|
||||||
subtitle: 'Add text to the top and bottom of pages.',
|
subtitle: 'Add text to the top and bottom of pages.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'invert-colors.html',
|
id: 'invert-colors',
|
||||||
name: 'Invert Colors',
|
name: 'Invert Colors',
|
||||||
icon: 'ph-circle-half',
|
icon: 'contrast',
|
||||||
subtitle: 'Create a "dark mode" version of your PDF.',
|
subtitle: 'Create a "dark mode" version of your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'background-color.html',
|
id: 'change-background-color',
|
||||||
name: 'Background Color',
|
name: 'Background Color',
|
||||||
icon: 'ph-palette',
|
icon: 'palette',
|
||||||
subtitle: 'Change the background color of your PDF.',
|
subtitle: 'Change the background color of your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'text-color.html',
|
id: 'change-text-color',
|
||||||
name: 'Change Text Color',
|
name: 'Change Text Color',
|
||||||
icon: 'ph-eyedropper',
|
icon: 'type',
|
||||||
subtitle: 'Change the color of text in your PDF.',
|
subtitle: 'Change the color of text in your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'sign-pdf.html',
|
id: 'sign-pdf',
|
||||||
name: 'Sign PDF',
|
name: 'Sign PDF',
|
||||||
icon: 'ph-pen-nib',
|
icon: 'pen-tool',
|
||||||
subtitle: 'Draw, type, or upload your signature.',
|
subtitle: 'Draw, type, or upload your signature.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'add-stamps.html',
|
href: import.meta.env.BASE_URL + 'src/pages/add-stamps.html',
|
||||||
name: 'Add Stamps',
|
name: 'Add Stamps',
|
||||||
icon: 'ph-stamp',
|
icon: 'stamp',
|
||||||
subtitle: 'Add image stamps to your PDF using the annotation toolbar.',
|
subtitle: 'Add image stamps to your PDF using the annotation toolbar.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'remove-annotations.html',
|
id: 'remove-annotations',
|
||||||
name: 'Remove Annotations',
|
name: 'Remove Annotations',
|
||||||
icon: 'ph-eraser',
|
icon: 'eraser',
|
||||||
subtitle: 'Strip comments, highlights, and links.',
|
subtitle: 'Strip comments, highlights, and links.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'crop-pdf.html',
|
id: 'cropper',
|
||||||
name: 'Crop PDF',
|
name: 'Crop PDF',
|
||||||
icon: 'ph-crop',
|
icon: 'crop',
|
||||||
subtitle: 'Trim the margins of every page in your PDF.',
|
subtitle: 'Trim the margins of every page in your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'form-filler.html',
|
id: 'form-filler',
|
||||||
name: 'PDF Form Filler',
|
name: 'PDF Form Filler',
|
||||||
icon: 'ph-pencil-line',
|
icon: 'square-pen',
|
||||||
subtitle: 'Fill in forms directly in the browser. Also supports XFA forms.',
|
subtitle: 'Fill in forms directly in the browser. Also supports XFA forms.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'form-creator.html',
|
href: import.meta.env.BASE_URL + 'src/pages/form-creator.html',
|
||||||
name: 'Create PDF Form',
|
name: 'Create PDF Form',
|
||||||
icon: 'ph-file-plus',
|
icon: 'file-input',
|
||||||
subtitle: 'Create fillable PDF forms with drag-and-drop text fields.',
|
subtitle: 'Create fillable PDF forms with drag-and-drop text fields.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'remove-blank-pages.html',
|
id: 'remove-blank-pages',
|
||||||
name: 'Remove Blank Pages',
|
name: 'Remove Blank Pages',
|
||||||
icon: 'ph-file-minus',
|
icon: 'file-minus-2',
|
||||||
subtitle: 'Automatically detect and delete blank pages.',
|
subtitle: 'Automatically detect and delete blank pages.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -178,451 +179,257 @@ export const categories = [
|
|||||||
name: 'Convert to PDF',
|
name: 'Convert to PDF',
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'image-to-pdf.html',
|
id: 'image-to-pdf',
|
||||||
name: 'Images to PDF',
|
name: 'Image to PDF',
|
||||||
icon: 'ph-images',
|
icon: 'images',
|
||||||
subtitle: 'Convert JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP to PDF.',
|
subtitle: 'Convert JPG, PNG, WebP, BMP, TIFF, SVG, HEIC to PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'jpg-to-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/jpg-to-pdf.html',
|
||||||
name: 'JPG to PDF',
|
name: 'JPG to PDF',
|
||||||
icon: 'ph-file-jpg',
|
icon: 'image-up',
|
||||||
subtitle: 'Create a PDF from one or more JPG images.',
|
subtitle: 'Create a PDF from one or more JPG images.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'png-to-pdf.html',
|
id: 'png-to-pdf',
|
||||||
name: 'PNG to PDF',
|
name: 'PNG to PDF',
|
||||||
icon: 'ph-file-png',
|
icon: 'image-up',
|
||||||
subtitle: 'Create a PDF from one or more PNG images.',
|
subtitle: 'Create a PDF from one or more PNG images.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'webp-to-pdf.html',
|
id: 'webp-to-pdf',
|
||||||
name: 'WebP to PDF',
|
name: 'WebP to PDF',
|
||||||
icon: 'ph-image',
|
icon: 'image-up',
|
||||||
subtitle: 'Create a PDF from one or more WebP images.',
|
subtitle: 'Create a PDF from one or more WebP images.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'svg-to-pdf.html',
|
id: 'svg-to-pdf',
|
||||||
name: 'SVG to PDF',
|
name: 'SVG to PDF',
|
||||||
icon: 'ph-file-svg',
|
icon: 'pen-tool',
|
||||||
subtitle: 'Create a PDF from one or more SVG images.',
|
subtitle: 'Create a PDF from one or more SVG images.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'bmp-to-pdf.html',
|
id: 'bmp-to-pdf',
|
||||||
name: 'BMP to PDF',
|
name: 'BMP to PDF',
|
||||||
icon: 'ph-image',
|
icon: 'image',
|
||||||
subtitle: 'Create a PDF from one or more BMP images.',
|
subtitle: 'Create a PDF from one or more BMP images.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'heic-to-pdf.html',
|
id: 'heic-to-pdf',
|
||||||
name: 'HEIC to PDF',
|
name: 'HEIC to PDF',
|
||||||
icon: 'ph-device-mobile',
|
icon: 'smartphone',
|
||||||
subtitle: 'Create a PDF from one or more HEIC images.',
|
subtitle: 'Create a PDF from one or more HEIC images.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'tiff-to-pdf.html',
|
id: 'tiff-to-pdf',
|
||||||
name: 'TIFF to PDF',
|
name: 'TIFF to PDF',
|
||||||
icon: 'ph-image',
|
icon: 'layers',
|
||||||
subtitle: 'Create a PDF from one or more TIFF images.',
|
subtitle: 'Create a PDF from one or more TIFF images.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'txt-to-pdf.html',
|
id: 'txt-to-pdf',
|
||||||
name: 'Text to PDF',
|
name: 'Text to PDF',
|
||||||
icon: 'ph-text-t',
|
icon: 'file-pen',
|
||||||
subtitle: 'Convert a plain text file into a PDF.',
|
subtitle: 'Convert a plain text file into a PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'markdown-to-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/json-to-pdf.html',
|
||||||
name: 'Markdown to PDF',
|
|
||||||
icon: 'ph-markdown-logo',
|
|
||||||
subtitle: 'Convert Markdown to PDF with live preview and syntax highlighting.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'json-to-pdf.html',
|
|
||||||
name: 'JSON to PDF',
|
name: 'JSON to PDF',
|
||||||
icon: 'ph-file-code',
|
icon: 'file-code',
|
||||||
subtitle: 'Convert JSON files to PDF format.',
|
subtitle: 'Convert JSON files to PDF format.',
|
||||||
},
|
},
|
||||||
{
|
// { id: 'md-to-pdf', name: 'Markdown to PDF', icon: 'file-text', subtitle: 'Convert a Markdown file into a PDF.' },
|
||||||
href: import.meta.env.BASE_URL + 'odt-to-pdf.html',
|
// { id: 'scan-to-pdf', name: 'Scan to PDF', icon: 'camera', subtitle: 'Use your camera to create a scanned PDF.' },
|
||||||
name: 'ODT to PDF',
|
// { id: 'word-to-pdf', name: 'Word to PDF', icon: 'file-text', subtitle: 'Convert .docx documents to PDF.' },
|
||||||
icon: 'ph-file',
|
|
||||||
subtitle: 'Convert ODT (OpenDocument Text) files to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'csv-to-pdf.html',
|
|
||||||
name: 'CSV to PDF',
|
|
||||||
icon: 'ph-file-csv',
|
|
||||||
subtitle: 'Convert CSV (Comma-Separated Values) spreadsheets to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'rtf-to-pdf.html',
|
|
||||||
name: 'RTF to PDF',
|
|
||||||
icon: 'ph-file-text',
|
|
||||||
subtitle: 'Convert RTF (Rich Text Format) documents to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'word-to-pdf.html',
|
|
||||||
name: 'Word to PDF',
|
|
||||||
icon: 'ph-microsoft-word-logo',
|
|
||||||
subtitle: 'Convert Word documents (DOCX, DOC, ODT) to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'excel-to-pdf.html',
|
|
||||||
name: 'Excel to PDF',
|
|
||||||
icon: 'ph-microsoft-excel-logo',
|
|
||||||
subtitle: 'Convert Excel spreadsheets (XLSX, XLS, ODS) to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'powerpoint-to-pdf.html',
|
|
||||||
name: 'PowerPoint to PDF',
|
|
||||||
icon: 'ph-microsoft-powerpoint-logo',
|
|
||||||
subtitle: 'Convert PowerPoint presentations (PPTX, PPT, ODP) to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'xps-to-pdf.html',
|
|
||||||
name: 'XPS to PDF',
|
|
||||||
icon: 'ph-scan',
|
|
||||||
subtitle: 'Convert XPS/OXPS documents to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'mobi-to-pdf.html',
|
|
||||||
name: 'MOBI to PDF',
|
|
||||||
icon: 'ph-book-open-text',
|
|
||||||
subtitle: 'Convert MOBI e-books to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'epub-to-pdf.html',
|
|
||||||
name: 'EPUB to PDF',
|
|
||||||
icon: 'ph-book-open-text',
|
|
||||||
subtitle: 'Convert EPUB e-books to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'fb2-to-pdf.html',
|
|
||||||
name: 'FB2 to PDF',
|
|
||||||
icon: 'ph-book-bookmark',
|
|
||||||
subtitle: 'Convert FictionBook (FB2) e-books to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'cbz-to-pdf.html',
|
|
||||||
name: 'CBZ to PDF',
|
|
||||||
icon: 'ph-book-open',
|
|
||||||
subtitle: 'Convert comic book archives (CBZ/CBR) to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'wpd-to-pdf.html',
|
|
||||||
name: 'WPD to PDF',
|
|
||||||
icon: 'ph-file-text',
|
|
||||||
subtitle: 'Convert WordPerfect documents (WPD) to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'wps-to-pdf.html',
|
|
||||||
name: 'WPS to PDF',
|
|
||||||
icon: 'ph-file-text',
|
|
||||||
subtitle: 'Convert WPS Office documents to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'xml-to-pdf.html',
|
|
||||||
name: 'XML to PDF',
|
|
||||||
icon: 'ph-file-code',
|
|
||||||
subtitle: 'Convert XML documents to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'pages-to-pdf.html',
|
|
||||||
name: 'Pages to PDF',
|
|
||||||
icon: 'ph-file-text',
|
|
||||||
subtitle: 'Convert Apple Pages documents to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'odg-to-pdf.html',
|
|
||||||
name: 'ODG to PDF',
|
|
||||||
icon: 'ph-image',
|
|
||||||
subtitle: 'Convert OpenDocument Graphics (ODG) to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'ods-to-pdf.html',
|
|
||||||
name: 'ODS to PDF',
|
|
||||||
icon: 'ph-table',
|
|
||||||
subtitle: 'Convert OpenDocument Spreadsheet (ODS) to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'odp-to-pdf.html',
|
|
||||||
name: 'ODP to PDF',
|
|
||||||
icon: 'ph-presentation',
|
|
||||||
subtitle: 'Convert OpenDocument Presentation (ODP) to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'pub-to-pdf.html',
|
|
||||||
name: 'PUB to PDF',
|
|
||||||
icon: 'ph-book-open',
|
|
||||||
subtitle: 'Convert Microsoft Publisher (PUB) to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'vsd-to-pdf.html',
|
|
||||||
name: 'VSD to PDF',
|
|
||||||
icon: 'ph-git-branch',
|
|
||||||
subtitle: 'Convert Microsoft Visio (VSD, VSDX) to PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'psd-to-pdf.html',
|
|
||||||
name: 'PSD to PDF',
|
|
||||||
icon: 'ph-image',
|
|
||||||
subtitle: 'Convert Adobe Photoshop (PSD) files to PDF. Multiple files supported.',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Convert from PDF',
|
name: 'Convert from PDF',
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-jpg.html',
|
id: 'pdf-to-jpg',
|
||||||
name: 'PDF to JPG',
|
name: 'PDF to JPG',
|
||||||
icon: 'ph-file-image',
|
icon: 'file-image',
|
||||||
subtitle: 'Convert each PDF page into a JPG image.',
|
subtitle: 'Convert each PDF page into a JPG image.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-png.html',
|
id: 'pdf-to-png',
|
||||||
name: 'PDF to PNG',
|
name: 'PDF to PNG',
|
||||||
icon: 'ph-file-image',
|
icon: 'file-image',
|
||||||
subtitle: 'Convert each PDF page into a PNG image.',
|
subtitle: 'Convert each PDF page into a PNG image.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-webp.html',
|
id: 'pdf-to-webp',
|
||||||
name: 'PDF to WebP',
|
name: 'PDF to WebP',
|
||||||
icon: 'ph-file-image',
|
icon: 'file-image',
|
||||||
subtitle: 'Convert each PDF page into a WebP image.',
|
subtitle: 'Convert each PDF page into a WebP image.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-bmp.html',
|
id: 'pdf-to-bmp',
|
||||||
name: 'PDF to BMP',
|
name: 'PDF to BMP',
|
||||||
icon: 'ph-file-image',
|
icon: 'file-image',
|
||||||
subtitle: 'Convert each PDF page into a BMP image.',
|
subtitle: 'Convert each PDF page into a BMP image.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-tiff.html',
|
id: 'pdf-to-tiff',
|
||||||
name: 'PDF to TIFF',
|
name: 'PDF to TIFF',
|
||||||
icon: 'ph-file-image',
|
icon: 'file-image',
|
||||||
subtitle: 'Convert each PDF page into a TIFF image.',
|
subtitle: 'Convert each PDF page into a TIFF image.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-svg.html',
|
id: 'pdf-to-greyscale',
|
||||||
name: 'PDF to SVG',
|
|
||||||
icon: 'ph-file-code',
|
|
||||||
subtitle: 'Convert each PDF page into a scalable vector graphic.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-csv.html',
|
|
||||||
name: 'PDF to CSV',
|
|
||||||
icon: 'ph-file-csv',
|
|
||||||
subtitle: 'Extract tables from PDF and convert to CSV format.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-excel.html',
|
|
||||||
name: 'PDF to Excel',
|
|
||||||
icon: 'ph-microsoft-excel-logo',
|
|
||||||
subtitle: 'Extract tables from PDF and convert to Excel (XLSX).',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-greyscale.html',
|
|
||||||
name: 'PDF to Greyscale',
|
name: 'PDF to Greyscale',
|
||||||
icon: 'ph-palette',
|
icon: 'palette',
|
||||||
subtitle: 'Convert all colors to black and white.',
|
subtitle: 'Convert all colors to black and white.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-json.html',
|
href: import.meta.env.BASE_URL + 'src/pages/pdf-to-json.html',
|
||||||
name: 'PDF to JSON',
|
name: 'PDF to JSON',
|
||||||
icon: 'ph-file-code',
|
icon: 'file-code',
|
||||||
subtitle: 'Convert PDF files to JSON format.',
|
subtitle: 'Convert PDF files to JSON format.',
|
||||||
},
|
},
|
||||||
{
|
// { id: 'pdf-to-markdown', name: 'PDF to Markdown', icon: 'file-pen', subtitle: 'Extract text into a Markdown file.' },
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-docx.html',
|
|
||||||
name: 'PDF to Word',
|
|
||||||
icon: 'ph-microsoft-word-logo',
|
|
||||||
subtitle: 'Convert PDF files to editable Word documents.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'extract-images.html',
|
|
||||||
name: 'Extract Images',
|
|
||||||
icon: 'ph-download-simple',
|
|
||||||
subtitle: 'Extract all embedded images from your PDF files.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-markdown.html',
|
|
||||||
name: 'PDF to Markdown',
|
|
||||||
icon: 'ph-markdown-logo',
|
|
||||||
subtitle: 'Convert PDF text and tables to Markdown format.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'prepare-pdf-for-ai.html',
|
|
||||||
name: 'Prepare PDF for AI',
|
|
||||||
icon: 'ph-sparkle',
|
|
||||||
subtitle: 'Extract PDF content as LlamaIndex JSON for RAG/LLM pipelines.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-text.html',
|
|
||||||
name: 'PDF to Text',
|
|
||||||
icon: 'ph-text-aa',
|
|
||||||
subtitle: 'Extract text from PDF files and save as plain text (.txt).',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Organize & Manage',
|
name: 'Organize & Manage',
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'ocr-pdf.html',
|
id: 'ocr-pdf',
|
||||||
name: 'OCR PDF',
|
name: 'OCR PDF',
|
||||||
icon: 'ph-barcode',
|
icon: 'scan-text',
|
||||||
subtitle: 'Make a PDF searchable and copyable.',
|
subtitle: 'Make a PDF searchable and copyable.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'merge-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/merge-pdf.html',
|
||||||
name: 'Merge PDF',
|
name: 'Merge PDF',
|
||||||
icon: 'ph-browsers',
|
icon: 'combine',
|
||||||
subtitle: 'Combine multiple PDFs into one file.',
|
subtitle: 'Combine multiple PDFs into one file.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'alternate-merge.html',
|
id: 'alternate-merge',
|
||||||
name: 'Alternate & Mix Pages',
|
name: 'Alternate & Mix Pages',
|
||||||
icon: 'ph-shuffle',
|
icon: 'shuffle',
|
||||||
subtitle: 'Merge PDFs by alternating pages from each PDF. Preserves Bookmarks',
|
subtitle: 'Merge PDFs by alternating pages from each PDF. Preserves Bookmarks',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'organize-pdf.html',
|
id: 'organize',
|
||||||
name: 'Organize & Duplicate',
|
name: 'Organize PDF',
|
||||||
icon: 'ph-files',
|
icon: 'grip',
|
||||||
|
subtitle: 'Reorder pages by dragging and dropping.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'duplicate-organize',
|
||||||
|
name: 'Duplicate & Organize',
|
||||||
|
icon: 'files',
|
||||||
subtitle: 'Duplicate, reorder, and delete pages.',
|
subtitle: 'Duplicate, reorder, and delete pages.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'add-attachments.html',
|
id: 'add-attachments',
|
||||||
name: 'Add Attachments',
|
name: 'Add Attachments',
|
||||||
icon: 'ph-paperclip',
|
icon: 'paperclip',
|
||||||
subtitle: 'Embed one or more files into your PDF.',
|
subtitle: 'Embed one or more files into your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'extract-attachments.html',
|
id: 'extract-attachments',
|
||||||
name: 'Extract Attachments',
|
name: 'Extract Attachments',
|
||||||
icon: 'ph-download',
|
icon: 'download',
|
||||||
subtitle: 'Extract all embedded files from PDF(s) as a ZIP.',
|
subtitle: 'Extract all embedded files from PDF(s) as a ZIP.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'edit-attachments.html',
|
id: 'edit-attachments',
|
||||||
name: 'Edit Attachments',
|
name: 'Edit Attachments',
|
||||||
icon: 'ph-paperclip-horizontal',
|
icon: 'file-edit',
|
||||||
subtitle: 'View or remove attachments in your PDF.',
|
subtitle: 'View or remove attachments in your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-multi-tool.html',
|
href: import.meta.env.BASE_URL + 'src/pages/pdf-multi-tool.html',
|
||||||
name: 'PDF Multi Tool',
|
name: 'PDF Multi Tool',
|
||||||
icon: 'ph-pencil-ruler',
|
icon: 'pencil-ruler',
|
||||||
subtitle: 'Full-featured PDF editor with page management.',
|
subtitle: 'Full-featured PDF editor with page management.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-layers.html',
|
id: 'split',
|
||||||
name: 'PDF OCG',
|
|
||||||
icon: 'ph-stack-simple',
|
|
||||||
subtitle: 'View, toggle, add, and delete OCG layers in your PDF.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'extract-tables.html',
|
|
||||||
name: 'Extract Tables',
|
|
||||||
icon: 'ph-table',
|
|
||||||
subtitle: 'Extract tables from PDFs as CSV, JSON, or Markdown.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'split-pdf.html',
|
|
||||||
name: 'Split PDF',
|
name: 'Split PDF',
|
||||||
icon: 'ph-scissors',
|
icon: 'scissors',
|
||||||
subtitle: 'Extract a range of pages into a new PDF.',
|
subtitle: 'Extract a range of pages into a new PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'divide-pages.html',
|
id: 'split-in-half',
|
||||||
name: 'Divide Pages',
|
name: 'Divide Pages',
|
||||||
icon: 'ph-columns',
|
icon: 'table-columns-split',
|
||||||
subtitle: 'Divide pages horizontally or vertically.',
|
subtitle: 'Divide pages horizontally or vertically.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'extract-pages.html',
|
id: 'extract-pages',
|
||||||
name: 'Extract Pages',
|
name: 'Extract Pages',
|
||||||
icon: 'ph-squares-four',
|
icon: 'ungroup',
|
||||||
subtitle: 'Save a selection of pages as new files.',
|
subtitle: 'Save a selection of pages as new files.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'delete-pages.html',
|
id: 'delete-pages',
|
||||||
name: 'Delete Pages',
|
name: 'Delete Pages',
|
||||||
icon: 'ph-trash',
|
icon: 'trash-2',
|
||||||
subtitle: 'Remove specific pages from your document.',
|
subtitle: 'Remove specific pages from your document.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'add-blank-page.html',
|
id: 'add-blank-page',
|
||||||
name: 'Add Blank Page',
|
name: 'Add Blank Page',
|
||||||
icon: 'ph-file-plus',
|
icon: 'file-plus-2',
|
||||||
subtitle: 'Insert an empty page anywhere in your PDF.',
|
subtitle: 'Insert an empty page anywhere in your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'reverse-pages.html',
|
id: 'reverse-pages',
|
||||||
name: 'Reverse Pages',
|
name: 'Reverse Pages',
|
||||||
icon: 'ph-sort-descending',
|
icon: 'arrow-down-z-a',
|
||||||
subtitle: 'Flip the order of all pages in your document.',
|
subtitle: 'Flip the order of all pages in your document.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'rotate-pdf.html',
|
id: 'rotate',
|
||||||
name: 'Rotate PDF',
|
name: 'Rotate PDF',
|
||||||
icon: 'ph-arrow-clockwise',
|
icon: 'rotate-cw',
|
||||||
subtitle: 'Turn pages in 90-degree increments.',
|
subtitle: 'Turn pages in 90-degree increments.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'rotate-custom.html',
|
id: 'n-up',
|
||||||
name: 'Rotate by Custom Degrees',
|
|
||||||
icon: 'ph-arrows-clockwise',
|
|
||||||
subtitle: 'Rotate pages by any custom angle.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'n-up-pdf.html',
|
|
||||||
name: 'N-Up PDF',
|
name: 'N-Up PDF',
|
||||||
icon: 'ph-squares-four',
|
icon: 'layout-grid',
|
||||||
subtitle: 'Arrange multiple pages onto a single sheet.',
|
subtitle: 'Arrange multiple pages onto a single sheet.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-booklet.html',
|
id: 'combine-single-page',
|
||||||
name: 'PDF Booklet',
|
|
||||||
icon: 'ph-book-open',
|
|
||||||
subtitle: 'Rearrange pages for double-sided booklet printing.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'combine-single-page.html',
|
|
||||||
name: 'Combine to Single Page',
|
name: 'Combine to Single Page',
|
||||||
icon: 'ph-arrows-out-line-vertical',
|
icon: 'unfold-vertical',
|
||||||
subtitle: 'Stitch all pages into one continuous scroll.',
|
subtitle: 'Stitch all pages into one continuous scroll.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'view-metadata.html',
|
id: 'view-metadata',
|
||||||
name: 'View Metadata',
|
name: 'View Metadata',
|
||||||
icon: 'ph-info',
|
icon: 'info',
|
||||||
subtitle: 'Inspect the hidden properties of your PDF.',
|
subtitle: 'Inspect the hidden properties of your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'edit-metadata.html',
|
id: 'edit-metadata',
|
||||||
name: 'Edit Metadata',
|
name: 'Edit Metadata',
|
||||||
icon: 'ph-file-code',
|
icon: 'file-cog',
|
||||||
subtitle: 'Change the author, title, and other properties.',
|
subtitle: 'Change the author, title, and other properties.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-zip.html',
|
id: 'pdf-to-zip',
|
||||||
name: 'PDFs to ZIP',
|
name: 'PDFs to ZIP',
|
||||||
icon: 'ph-file-zip',
|
icon: 'stretch-horizontal',
|
||||||
subtitle: 'Package multiple PDF files into a ZIP archive.',
|
subtitle: 'Package multiple PDF files into a ZIP archive.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'compare-pdfs.html',
|
id: 'compare-pdfs',
|
||||||
name: 'Compare PDFs',
|
name: 'Compare PDFs',
|
||||||
icon: 'ph-git-diff',
|
icon: 'git-compare',
|
||||||
subtitle: 'Compare two PDFs side by side.',
|
subtitle: 'Compare two PDFs side by side.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'posterize-pdf.html',
|
id: 'posterize',
|
||||||
name: 'Posterize PDF',
|
name: 'Posterize PDF',
|
||||||
icon: 'ph-notepad',
|
icon: 'notepad-text-dashed',
|
||||||
subtitle: 'Split a large page into multiple smaller pages.',
|
subtitle: 'Split a large page into multiple smaller pages.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -631,93 +438,81 @@ export const categories = [
|
|||||||
name: 'Optimize & Repair',
|
name: 'Optimize & Repair',
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'compress-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/compress-pdf.html',
|
||||||
name: 'Compress PDF',
|
name: 'Compress PDF',
|
||||||
icon: 'ph-lightning',
|
icon: 'zap',
|
||||||
subtitle: 'Reduce the file size of your PDF.',
|
subtitle: 'Reduce the file size of your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'pdf-to-pdfa.html',
|
id: 'fix-dimensions',
|
||||||
name: 'PDF to PDF/A',
|
|
||||||
icon: 'ph-archive',
|
|
||||||
subtitle: 'Convert PDF to PDF/A for long-term archiving.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'fix-page-size.html',
|
|
||||||
name: 'Fix Page Size',
|
name: 'Fix Page Size',
|
||||||
icon: 'ph-ruler',
|
icon: 'ruler-dimension-line',
|
||||||
subtitle: 'Standardize all pages to a uniform size.',
|
subtitle: 'Standardize all pages to a uniform size.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'linearize-pdf.html',
|
id: 'linearize',
|
||||||
name: 'Linearize PDF',
|
name: 'Linearize PDF',
|
||||||
icon: 'ph-gauge',
|
icon: 'gauge',
|
||||||
subtitle: 'Optimize PDF for fast web viewing.',
|
subtitle: 'Optimize PDF for fast web viewing.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'page-dimensions.html',
|
id: 'page-dimensions',
|
||||||
name: 'Page Dimensions',
|
name: 'Page Dimensions',
|
||||||
icon: 'ph-ruler',
|
icon: 'ruler',
|
||||||
subtitle: 'Analyze page size, orientation, and units.',
|
subtitle: 'Analyze page size, orientation, and units.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'remove-restrictions.html',
|
id: 'remove-restrictions',
|
||||||
name: 'Remove Restrictions',
|
name: 'Remove Restrictions',
|
||||||
icon: 'ph-link-break',
|
icon: 'unlink',
|
||||||
subtitle:
|
subtitle:
|
||||||
'Remove password protection and security restrictions associated with digitally signed PDF files.',
|
'Remove password protection and security restrictions associated with digitally signed PDF files.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'repair-pdf.html',
|
href: import.meta.env.BASE_URL + 'src/pages/repair-pdf.html',
|
||||||
name: 'Repair PDF',
|
name: 'Repair PDF',
|
||||||
icon: 'ph-wrench',
|
icon: 'wrench',
|
||||||
subtitle: 'Recover data from corrupted or damaged PDF files.',
|
subtitle: 'Recover data from corrupted or damaged PDF files.',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
href: import.meta.env.BASE_URL + 'rasterize-pdf.html',
|
|
||||||
name: 'Rasterize PDF',
|
|
||||||
icon: 'ph-image',
|
|
||||||
subtitle: 'Convert PDF to image-based PDF. Flatten layers and remove selectable text.',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Secure PDF',
|
name: 'Secure PDF',
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'encrypt-pdf.html',
|
id: 'encrypt',
|
||||||
name: 'Encrypt PDF',
|
name: 'Encrypt PDF',
|
||||||
icon: 'ph-lock',
|
icon: 'lock',
|
||||||
subtitle: 'Lock your PDF by adding a password.',
|
subtitle: 'Lock your PDF by adding a password.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'sanitize-pdf.html',
|
id: 'sanitize-pdf',
|
||||||
name: 'Sanitize PDF',
|
name: 'Sanitize PDF',
|
||||||
icon: 'ph-broom',
|
icon: 'brush-cleaning',
|
||||||
subtitle: 'Remove metadata, annotations, scripts, and more.',
|
subtitle: 'Remove metadata, annotations, scripts, and more.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'decrypt-pdf.html',
|
id: 'decrypt',
|
||||||
name: 'Decrypt PDF',
|
name: 'Decrypt PDF',
|
||||||
icon: 'ph-lock-open',
|
icon: 'unlock',
|
||||||
subtitle: 'Unlock PDF by removing password protection.',
|
subtitle: 'Unlock PDF by removing password protection.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'flatten-pdf.html',
|
id: 'flatten',
|
||||||
name: 'Flatten PDF',
|
name: 'Flatten PDF',
|
||||||
icon: 'ph-stack',
|
icon: 'layers',
|
||||||
subtitle: 'Make form fields and annotations non-editable.',
|
subtitle: 'Make form fields and annotations non-editable.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'remove-metadata.html',
|
id: 'remove-metadata',
|
||||||
name: 'Remove Metadata',
|
name: 'Remove Metadata',
|
||||||
icon: 'ph-file-x',
|
icon: 'file-x',
|
||||||
subtitle: 'Strip hidden data from your PDF.',
|
subtitle: 'Strip hidden data from your PDF.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'change-permissions.html',
|
id: 'change-permissions',
|
||||||
name: 'Change Permissions',
|
name: 'Change Permissions',
|
||||||
icon: 'ph-shield-check',
|
icon: 'shield-check',
|
||||||
subtitle: 'Set or change user permissions on a PDF.',
|
subtitle: 'Set or change user permissions on a PDF.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Simple FAQ accordion handler for standalone pages
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const faqAccordion = document.getElementById('faq-accordion');
|
||||||
|
if (faqAccordion) {
|
||||||
|
faqAccordion.addEventListener('click', (e) => {
|
||||||
|
const questionButton = (e.target as HTMLElement).closest('.faq-question');
|
||||||
|
if (!questionButton) return;
|
||||||
|
|
||||||
|
const faqItem = questionButton.parentElement;
|
||||||
|
const answer = faqItem?.querySelector('.faq-answer') as HTMLElement;
|
||||||
|
|
||||||
|
if (!faqItem || !answer) return;
|
||||||
|
|
||||||
|
faqItem.classList.toggle('open');
|
||||||
|
|
||||||
|
if (faqItem.classList.contains('open')) {
|
||||||
|
answer.style.maxHeight = answer.scrollHeight + 'px';
|
||||||
|
} else {
|
||||||
|
answer.style.maxHeight = '0px';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -23,13 +23,23 @@ 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();
|
||||||
|
|
||||||
// Re-export rotation state utilities
|
|
||||||
export { getRotationState, updateRotationState, resetRotationState, initializeRotationState } from '../utils/rotation-state.js';
|
|
||||||
|
|
||||||
const rotationState: number[] = [];
|
const rotationState: number[] = [];
|
||||||
let imageSortableInstance: Sortable | null = null;
|
let imageSortableInstance: Sortable | null = null;
|
||||||
const activeImageUrls = new Map<File, string>();
|
const activeImageUrls = new Map<File, string>();
|
||||||
|
|
||||||
|
export function getRotationState(): readonly number[] {
|
||||||
|
return rotationState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateRotationState(pageIndex: number, rotation: number) {
|
||||||
|
if (pageIndex >= 0 && pageIndex < rotationState.length) {
|
||||||
|
rotationState[pageIndex] = rotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetRotationState() {
|
||||||
|
rotationState.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSinglePdfUpload(toolId, file) {
|
async function handleSinglePdfUpload(toolId, file) {
|
||||||
showLoader('Loading PDF...');
|
showLoader('Loading PDF...');
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { state } from '../state.js';
|
||||||
|
import { dom, switchView, toolTemplates } from '../ui.js';
|
||||||
|
import { setupFileInputHandler } from './fileHandler.js';
|
||||||
|
import { toolLogic } from '../logic/index.js';
|
||||||
|
import { createIcons, icons } from 'lucide';
|
||||||
|
|
||||||
|
const SETUP_AFTER_UPLOAD = ['sign-pdf'];
|
||||||
|
|
||||||
|
export function setupToolInterface(toolId: any) {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
behavior: 'instant' as ScrollBehavior,
|
||||||
|
});
|
||||||
|
|
||||||
|
state.activeTool = toolId;
|
||||||
|
dom.toolContent.innerHTML = toolTemplates[toolId]();
|
||||||
|
createIcons({ icons });
|
||||||
|
switchView('tool');
|
||||||
|
|
||||||
|
const fileInput = document.getElementById('file-input');
|
||||||
|
const processBtn = document.getElementById('process-btn');
|
||||||
|
|
||||||
|
if (!fileInput && processBtn) {
|
||||||
|
const logic = toolLogic[toolId];
|
||||||
|
if (logic) {
|
||||||
|
const func = typeof logic.process === 'function' ? logic.process : logic;
|
||||||
|
processBtn.onclick = func;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolLogic[toolId] && typeof toolLogic[toolId].setup === 'function') {
|
||||||
|
if (!SETUP_AFTER_UPLOAD.includes(toolId)) {
|
||||||
|
toolLogic[toolId].setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileInput) {
|
||||||
|
setupFileInputHandler(toolId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import i18next from 'i18next';
|
|
||||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
|
||||||
import HttpBackend from 'i18next-http-backend';
|
|
||||||
|
|
||||||
// Supported languages
|
|
||||||
export const supportedLanguages = ['en', 'de', 'zh', 'vi'] as const;
|
|
||||||
export type SupportedLanguage = (typeof supportedLanguages)[number];
|
|
||||||
|
|
||||||
export const languageNames: Record<SupportedLanguage, string> = {
|
|
||||||
en: 'English',
|
|
||||||
de: 'Deutsch',
|
|
||||||
zh: '中文',
|
|
||||||
vi: 'Tiếng Việt',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getLanguageFromUrl = (): SupportedLanguage => {
|
|
||||||
const path = window.location.pathname;
|
|
||||||
const langMatch = path.match(/^\/(en|de|zh|vi)(?:\/|$)/);
|
|
||||||
if (langMatch && supportedLanguages.includes(langMatch[1] as SupportedLanguage)) {
|
|
||||||
return langMatch[1] as SupportedLanguage;
|
|
||||||
}
|
|
||||||
const storedLang = localStorage.getItem('i18nextLng');
|
|
||||||
if (storedLang && supportedLanguages.includes(storedLang as SupportedLanguage)) {
|
|
||||||
return storedLang as SupportedLanguage;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'en';
|
|
||||||
};
|
|
||||||
|
|
||||||
let initialized = false;
|
|
||||||
|
|
||||||
export const initI18n = async (): Promise<typeof i18next> => {
|
|
||||||
if (initialized) return i18next;
|
|
||||||
|
|
||||||
const currentLang = getLanguageFromUrl();
|
|
||||||
|
|
||||||
await i18next
|
|
||||||
.use(HttpBackend)
|
|
||||||
.use(LanguageDetector)
|
|
||||||
.init({
|
|
||||||
lng: currentLang,
|
|
||||||
fallbackLng: 'en',
|
|
||||||
supportedLngs: supportedLanguages as unknown as string[],
|
|
||||||
ns: ['common', 'tools'],
|
|
||||||
defaultNS: 'common',
|
|
||||||
backend: {
|
|
||||||
loadPath: `${import.meta.env.BASE_URL.replace(/\/?$/, '/')}locales/{{lng}}/{{ns}}.json`,
|
|
||||||
},
|
|
||||||
detection: {
|
|
||||||
order: ['path', 'localStorage', 'navigator'],
|
|
||||||
lookupFromPathIndex: 0,
|
|
||||||
caches: ['localStorage'],
|
|
||||||
},
|
|
||||||
interpolation: {
|
|
||||||
escapeValue: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
initialized = true;
|
|
||||||
return i18next;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const t = (key: string, options?: Record<string, unknown>): string => {
|
|
||||||
return i18next.t(key, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const changeLanguage = (lang: SupportedLanguage): void => {
|
|
||||||
if (!supportedLanguages.includes(lang)) return;
|
|
||||||
|
|
||||||
const currentPath = window.location.pathname;
|
|
||||||
const currentLang = getLanguageFromUrl();
|
|
||||||
|
|
||||||
let newPath: string;
|
|
||||||
if (currentPath.match(/^\/(en|de|zh|vi)\//)) {
|
|
||||||
newPath = currentPath.replace(/^\/(en|de|zh|vi)\//, `/${lang}/`);
|
|
||||||
} else if (currentPath.match(/^\/(en|de|zh|vi)$/)) {
|
|
||||||
newPath = `/${lang}`;
|
|
||||||
} else {
|
|
||||||
newPath = `/${lang}${currentPath}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newUrl = newPath + window.location.search + window.location.hash;
|
|
||||||
window.location.href = newUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply translations to all elements with data-i18n attribute
|
|
||||||
export const applyTranslations = (): void => {
|
|
||||||
document.querySelectorAll('[data-i18n]').forEach((element) => {
|
|
||||||
const key = element.getAttribute('data-i18n');
|
|
||||||
if (key) {
|
|
||||||
const translation = t(key);
|
|
||||||
if (translation && translation !== key) {
|
|
||||||
element.textContent = translation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-i18n-placeholder]').forEach((element) => {
|
|
||||||
const key = element.getAttribute('data-i18n-placeholder');
|
|
||||||
if (key && element instanceof HTMLInputElement) {
|
|
||||||
const translation = t(key);
|
|
||||||
if (translation && translation !== key) {
|
|
||||||
element.placeholder = translation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-i18n-title]').forEach((element) => {
|
|
||||||
const key = element.getAttribute('data-i18n-title');
|
|
||||||
if (key) {
|
|
||||||
const translation = t(key);
|
|
||||||
if (translation && translation !== key) {
|
|
||||||
(element as HTMLElement).title = translation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.documentElement.lang = i18next.language;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const rewriteLinks = (): void => {
|
|
||||||
const currentLang = getLanguageFromUrl();
|
|
||||||
if (currentLang === 'en') return;
|
|
||||||
|
|
||||||
const links = document.querySelectorAll('a[href]');
|
|
||||||
links.forEach((link) => {
|
|
||||||
const href = link.getAttribute('href');
|
|
||||||
if (!href) return;
|
|
||||||
|
|
||||||
if (href.startsWith('http') ||
|
|
||||||
href.startsWith('mailto:') ||
|
|
||||||
href.startsWith('tel:') ||
|
|
||||||
href.startsWith('#') ||
|
|
||||||
href.startsWith('javascript:')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (href.match(/^\/(en|de|zh|vi)\//)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let newHref: string;
|
|
||||||
if (href.startsWith('/')) {
|
|
||||||
newHref = `/${currentLang}${href}`;
|
|
||||||
} else if (href.startsWith('./')) {
|
|
||||||
newHref = href.replace('./', `/${currentLang}/`);
|
|
||||||
} else if (href === '/' || href === '') {
|
|
||||||
newHref = `/${currentLang}/`;
|
|
||||||
} else {
|
|
||||||
newHref = `/${currentLang}/${href}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
link.setAttribute('href', newHref);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default i18next;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export { initI18n, t, changeLanguage, applyTranslations, rewriteLinks, getLanguageFromUrl, supportedLanguages, languageNames } from './i18n';
|
|
||||||
export type { SupportedLanguage } from './i18n';
|
|
||||||
export { createLanguageSwitcher, injectLanguageSwitcher } from './language-switcher';
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
import {
|
|
||||||
supportedLanguages,
|
|
||||||
languageNames,
|
|
||||||
getLanguageFromUrl,
|
|
||||||
changeLanguage,
|
|
||||||
} from './i18n';
|
|
||||||
|
|
||||||
export const createLanguageSwitcher = (): HTMLElement => {
|
|
||||||
const currentLang = getLanguageFromUrl();
|
|
||||||
|
|
||||||
const container = document.createElement('div');
|
|
||||||
container.className = 'relative';
|
|
||||||
container.id = 'language-switcher';
|
|
||||||
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.className = `
|
|
||||||
inline-flex items-center gap-1.5 text-sm font-medium
|
|
||||||
bg-gray-800 text-gray-200 border border-gray-600
|
|
||||||
px-3 py-1.5 rounded-full transition-colors duration-200
|
|
||||||
shadow-sm hover:shadow-md hover:bg-gray-700
|
|
||||||
`.trim();
|
|
||||||
button.setAttribute('aria-haspopup', 'true');
|
|
||||||
button.setAttribute('aria-expanded', 'false');
|
|
||||||
|
|
||||||
const textSpan = document.createElement('span');
|
|
||||||
textSpan.className = 'font-medium';
|
|
||||||
textSpan.textContent = languageNames[currentLang];
|
|
||||||
|
|
||||||
const chevron = document.createElement('svg');
|
|
||||||
chevron.className = 'w-4 h-4';
|
|
||||||
chevron.setAttribute('fill', 'none');
|
|
||||||
chevron.setAttribute('stroke', 'currentColor');
|
|
||||||
chevron.setAttribute('viewBox', '0 0 24 24');
|
|
||||||
chevron.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>';
|
|
||||||
|
|
||||||
button.appendChild(textSpan);
|
|
||||||
button.appendChild(chevron);
|
|
||||||
|
|
||||||
const dropdown = document.createElement('div');
|
|
||||||
dropdown.className = `
|
|
||||||
hidden absolute right-0 mt-2 w-40 rounded-lg
|
|
||||||
bg-gray-800 border border-gray-700 shadow-xl
|
|
||||||
py-1 z-50
|
|
||||||
`.trim();
|
|
||||||
dropdown.setAttribute('role', 'menu');
|
|
||||||
|
|
||||||
supportedLanguages.forEach((lang) => {
|
|
||||||
const option = document.createElement('button');
|
|
||||||
option.className = `
|
|
||||||
w-full px-4 py-2 text-left text-sm text-gray-200
|
|
||||||
hover:bg-gray-700 flex items-center gap-2
|
|
||||||
${lang === currentLang ? 'bg-gray-700' : ''}
|
|
||||||
`.trim();
|
|
||||||
option.setAttribute('role', 'menuitem');
|
|
||||||
|
|
||||||
const name = document.createElement('span');
|
|
||||||
name.textContent = languageNames[lang];
|
|
||||||
|
|
||||||
option.appendChild(name);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
option.addEventListener('click', () => {
|
|
||||||
if (lang !== currentLang) {
|
|
||||||
changeLanguage(lang);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dropdown.appendChild(option);
|
|
||||||
});
|
|
||||||
|
|
||||||
container.appendChild(button);
|
|
||||||
container.appendChild(dropdown);
|
|
||||||
|
|
||||||
button.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const isExpanded = button.getAttribute('aria-expanded') === 'true';
|
|
||||||
button.setAttribute('aria-expanded', (!isExpanded).toString());
|
|
||||||
dropdown.classList.toggle('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', () => {
|
|
||||||
button.setAttribute('aria-expanded', 'false');
|
|
||||||
dropdown.classList.add('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
return container;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const injectLanguageSwitcher = (): void => {
|
|
||||||
const footer = document.querySelector('footer');
|
|
||||||
if (!footer) return;
|
|
||||||
|
|
||||||
const headings = footer.querySelectorAll('h3');
|
|
||||||
let followUsColumn: HTMLElement | null = null;
|
|
||||||
|
|
||||||
headings.forEach((h3) => {
|
|
||||||
if (h3.textContent?.trim() === 'Follow Us' || h3.textContent?.trim() === 'Folgen Sie uns' || h3.textContent?.trim() === 'Theo dõi chúng tôi') {
|
|
||||||
followUsColumn = h3.parentElement;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (followUsColumn) {
|
|
||||||
const socialIconsContainer = followUsColumn.querySelector('.space-x-4');
|
|
||||||
|
|
||||||
if (socialIconsContainer) {
|
|
||||||
const wrapper = document.createElement('div');
|
|
||||||
wrapper.className = 'inline-flex flex-col gap-4'; // gap-4 adds space between icons and switcher
|
|
||||||
|
|
||||||
socialIconsContainer.parentNode?.insertBefore(wrapper, socialIconsContainer);
|
|
||||||
|
|
||||||
wrapper.appendChild(socialIconsContainer);
|
|
||||||
const switcher = createLanguageSwitcher();
|
|
||||||
|
|
||||||
switcher.className = 'relative w-full';
|
|
||||||
|
|
||||||
const button = switcher.querySelector('button');
|
|
||||||
if (button) {
|
|
||||||
button.className = `
|
|
||||||
flex items-center justify-between w-full text-sm font-medium
|
|
||||||
bg-gray-800 text-gray-400 border border-gray-700
|
|
||||||
px-3 py-2 rounded-lg transition-colors duration-200
|
|
||||||
hover:text-white hover:border-gray-600
|
|
||||||
`.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
const dropdown = switcher.querySelector('div[role="menu"]');
|
|
||||||
if (dropdown) {
|
|
||||||
dropdown.classList.remove('mt-2', 'w-40');
|
|
||||||
dropdown.classList.add('bottom-full', 'mb-2', 'w-full');
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.appendChild(switcher);
|
|
||||||
} else {
|
|
||||||
const switcherContainer = document.createElement('div');
|
|
||||||
switcherContainer.className = 'mt-4 w-full';
|
|
||||||
const switcher = createLanguageSwitcher();
|
|
||||||
switcherContainer.appendChild(switcher);
|
|
||||||
followUsColumn.appendChild(switcherContainer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
|
||||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
|
||||||
import { createIcons, icons } from 'lucide';
|
|
||||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
|
||||||
|
|
||||||
const worker = new Worker(import.meta.env.BASE_URL + 'workers/add-attachments.worker.js');
|
|
||||||
|
|
||||||
interface AddAttachmentState {
|
|
||||||
file: File | null;
|
|
||||||
pdfDoc: PDFLibDocument | null;
|
|
||||||
attachments: File[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageState: AddAttachmentState = {
|
|
||||||
file: null,
|
|
||||||
pdfDoc: null,
|
|
||||||
attachments: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
function resetState() {
|
|
||||||
pageState.file = null;
|
|
||||||
pageState.pdfDoc = null;
|
|
||||||
pageState.attachments = [];
|
|
||||||
|
|
||||||
const fileDisplayArea = document.getElementById('file-display-area');
|
|
||||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
|
||||||
|
|
||||||
const toolOptions = document.getElementById('tool-options');
|
|
||||||
if (toolOptions) toolOptions.classList.add('hidden');
|
|
||||||
|
|
||||||
const attachmentFileList = document.getElementById('attachment-file-list');
|
|
||||||
if (attachmentFileList) attachmentFileList.innerHTML = '';
|
|
||||||
|
|
||||||
const attachmentInput = document.getElementById('attachment-files-input') as HTMLInputElement;
|
|
||||||
if (attachmentInput) attachmentInput.value = '';
|
|
||||||
|
|
||||||
const attachmentLevelOptions = document.getElementById('attachment-level-options');
|
|
||||||
if (attachmentLevelOptions) attachmentLevelOptions.classList.add('hidden');
|
|
||||||
|
|
||||||
const pageRangeWrapper = document.getElementById('page-range-wrapper');
|
|
||||||
if (pageRangeWrapper) pageRangeWrapper.classList.add('hidden');
|
|
||||||
|
|
||||||
const processBtn = document.getElementById('process-btn');
|
|
||||||
if (processBtn) processBtn.classList.add('hidden');
|
|
||||||
|
|
||||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
|
||||||
if (fileInput) fileInput.value = '';
|
|
||||||
|
|
||||||
const documentRadio = document.querySelector('input[name="attachment-level"][value="document"]') as HTMLInputElement;
|
|
||||||
if (documentRadio) documentRadio.checked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.onmessage = function (e) {
|
|
||||||
const data = e.data;
|
|
||||||
|
|
||||||
if (data.status === 'success' && data.modifiedPDF !== undefined) {
|
|
||||||
hideLoader();
|
|
||||||
|
|
||||||
const originalName = pageState.file?.name.replace(/\.pdf$/i, '') || 'document';
|
|
||||||
downloadFile(
|
|
||||||
new Blob([new Uint8Array(data.modifiedPDF)], { type: 'application/pdf' }),
|
|
||||||
`${originalName}_with_attachments.pdf`
|
|
||||||
);
|
|
||||||
|
|
||||||
showAlert('Success', `${pageState.attachments.length} file(s) attached successfully.`, 'success', function () {
|
|
||||||
resetState();
|
|
||||||
});
|
|
||||||
} else if (data.status === 'error') {
|
|
||||||
hideLoader();
|
|
||||||
showAlert('Error', data.message || 'Unknown error occurred.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
worker.onerror = function (error) {
|
|
||||||
hideLoader();
|
|
||||||
console.error('Worker error:', error);
|
|
||||||
showAlert('Error', 'Worker error occurred. Check console for details.');
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
|
||||||
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, {
|
|
||||||
ignoreEncryption: true,
|
|
||||||
throwOnInvalidObject: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const pageCount = pageState.pdfDoc.getPageCount();
|
|
||||||
metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageCount} pages`;
|
|
||||||
|
|
||||||
const totalPagesSpan = document.getElementById('attachment-total-pages');
|
|
||||||
if (totalPagesSpan) totalPagesSpan.textContent = pageCount.toString();
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateAttachmentList() {
|
|
||||||
const attachmentFileList = document.getElementById('attachment-file-list');
|
|
||||||
const attachmentLevelOptions = document.getElementById('attachment-level-options');
|
|
||||||
const processBtn = document.getElementById('process-btn');
|
|
||||||
|
|
||||||
if (!attachmentFileList) return;
|
|
||||||
|
|
||||||
attachmentFileList.innerHTML = '';
|
|
||||||
|
|
||||||
pageState.attachments.forEach(function (file) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'flex justify-between items-center p-2 bg-gray-800 rounded-md text-white';
|
|
||||||
|
|
||||||
const nameSpan = document.createElement('span');
|
|
||||||
nameSpan.className = 'truncate text-sm';
|
|
||||||
nameSpan.textContent = file.name;
|
|
||||||
|
|
||||||
const sizeSpan = document.createElement('span');
|
|
||||||
sizeSpan.className = 'text-xs text-gray-400';
|
|
||||||
sizeSpan.textContent = formatBytes(file.size);
|
|
||||||
|
|
||||||
div.append(nameSpan, sizeSpan);
|
|
||||||
attachmentFileList.appendChild(div);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pageState.attachments.length > 0) {
|
|
||||||
if (attachmentLevelOptions) attachmentLevelOptions.classList.remove('hidden');
|
|
||||||
if (processBtn) processBtn.classList.remove('hidden');
|
|
||||||
} else {
|
|
||||||
if (attachmentLevelOptions) attachmentLevelOptions.classList.add('hidden');
|
|
||||||
if (processBtn) processBtn.classList.add('hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addAttachments() {
|
|
||||||
if (!pageState.file || !pageState.pdfDoc) {
|
|
||||||
showAlert('Error', 'Please upload a PDF first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageState.attachments.length === 0) {
|
|
||||||
showAlert('No Files', 'Please select at least one file to attach.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachmentLevel = (
|
|
||||||
document.querySelector('input[name="attachment-level"]:checked') as HTMLInputElement
|
|
||||||
)?.value || 'document';
|
|
||||||
|
|
||||||
let pageRange: string = '';
|
|
||||||
|
|
||||||
if (attachmentLevel === 'page') {
|
|
||||||
const pageRangeInput = document.getElementById('attachment-page-range') as HTMLInputElement;
|
|
||||||
pageRange = pageRangeInput?.value?.trim() || '';
|
|
||||||
|
|
||||||
if (!pageRange) {
|
|
||||||
showAlert('Error', 'Please specify a page range for page-level attachments.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showLoader('Embedding files into PDF...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const pdfBuffer = await pageState.file.arrayBuffer();
|
|
||||||
|
|
||||||
const attachmentBuffers: ArrayBuffer[] = [];
|
|
||||||
const attachmentNames: string[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < pageState.attachments.length; i++) {
|
|
||||||
const file = pageState.attachments[i];
|
|
||||||
showLoader(`Reading ${file.name} (${i + 1}/${pageState.attachments.length})...`);
|
|
||||||
|
|
||||||
const fileBuffer = await file.arrayBuffer();
|
|
||||||
attachmentBuffers.push(fileBuffer);
|
|
||||||
attachmentNames.push(file.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
showLoader('Attaching files to PDF...');
|
|
||||||
|
|
||||||
const message = {
|
|
||||||
command: 'add-attachments',
|
|
||||||
pdfBuffer: pdfBuffer,
|
|
||||||
attachmentBuffers: attachmentBuffers,
|
|
||||||
attachmentNames: attachmentNames,
|
|
||||||
attachmentLevel: attachmentLevel,
|
|
||||||
pageRange: pageRange
|
|
||||||
};
|
|
||||||
|
|
||||||
const transferables = [pdfBuffer, ...attachmentBuffers];
|
|
||||||
worker.postMessage(message, transferables);
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Error attaching files:', error);
|
|
||||||
hideLoader();
|
|
||||||
showAlert('Error', `Failed to attach files: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleAttachmentSelect(files: FileList | null) {
|
|
||||||
if (files && files.length > 0) {
|
|
||||||
pageState.attachments = Array.from(files);
|
|
||||||
updateAttachmentList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
|
||||||
const dropZone = document.getElementById('drop-zone');
|
|
||||||
const attachmentInput = document.getElementById('attachment-files-input') as HTMLInputElement;
|
|
||||||
const attachmentDropZone = document.getElementById('attachment-drop-zone');
|
|
||||||
const processBtn = document.getElementById('process-btn');
|
|
||||||
const backBtn = document.getElementById('back-to-tools');
|
|
||||||
const pageRangeWrapper = document.getElementById('page-range-wrapper');
|
|
||||||
|
|
||||||
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 (attachmentInput && attachmentDropZone) {
|
|
||||||
attachmentInput.addEventListener('change', function (e) {
|
|
||||||
handleAttachmentSelect((e.target as HTMLInputElement).files);
|
|
||||||
});
|
|
||||||
|
|
||||||
attachmentDropZone.addEventListener('dragover', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
attachmentDropZone.classList.add('bg-gray-700');
|
|
||||||
});
|
|
||||||
|
|
||||||
attachmentDropZone.addEventListener('dragleave', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
attachmentDropZone.classList.remove('bg-gray-700');
|
|
||||||
});
|
|
||||||
|
|
||||||
attachmentDropZone.addEventListener('drop', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
attachmentDropZone.classList.remove('bg-gray-700');
|
|
||||||
const files = e.dataTransfer?.files;
|
|
||||||
if (files) {
|
|
||||||
handleAttachmentSelect(files);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
attachmentInput.addEventListener('click', function () {
|
|
||||||
attachmentInput.value = '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachmentLevelRadios = document.querySelectorAll('input[name="attachment-level"]');
|
|
||||||
attachmentLevelRadios.forEach(function (radio) {
|
|
||||||
radio.addEventListener('change', function (e) {
|
|
||||||
const value = (e.target as HTMLInputElement).value;
|
|
||||||
if (value === 'page' && pageRangeWrapper) {
|
|
||||||
pageRangeWrapper.classList.remove('hidden');
|
|
||||||
} else if (pageRangeWrapper) {
|
|
||||||
pageRangeWrapper.classList.add('hidden');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (processBtn) {
|
|
||||||
processBtn.addEventListener('click', addAttachments);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
import { showLoader, hideLoader, showAlert } from '../ui';
|
||||||
|
import { readFileAsArrayBuffer, downloadFile } from '../utils/helpers';
|
||||||
|
import { state } from '../state';
|
||||||
|
|
||||||
|
const worker = new Worker(import.meta.env.BASE_URL + 'workers/add-attachments.worker.js');
|
||||||
|
|
||||||
|
let attachments: File[] = [];
|
||||||
|
|
||||||
|
worker.onmessage = (e) => {
|
||||||
|
const data = e.data;
|
||||||
|
|
||||||
|
if (data.status === 'success' && data.modifiedPDF !== undefined) {
|
||||||
|
hideLoader();
|
||||||
|
|
||||||
|
downloadFile(
|
||||||
|
new Blob([new Uint8Array(data.modifiedPDF)], { type: 'application/pdf' }),
|
||||||
|
`attached-${state.files[0].name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
showAlert('Success', `${attachments.length} file(s) attached successfully.`);
|
||||||
|
clearAttachments();
|
||||||
|
} else if (data.status === 'error') {
|
||||||
|
hideLoader();
|
||||||
|
showAlert('Error', data.message || 'Unknown error occurred.');
|
||||||
|
clearAttachments();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
worker.onerror = (error) => {
|
||||||
|
hideLoader();
|
||||||
|
console.error('Worker error:', error);
|
||||||
|
showAlert('Error', 'Worker error occurred. Check console for details.');
|
||||||
|
clearAttachments();
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function addAttachments() {
|
||||||
|
if (!state.files || state.files.length === 0) {
|
||||||
|
showAlert('Error', 'Main PDF is not loaded.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (attachments.length === 0) {
|
||||||
|
showAlert('No Files', 'Please select at least one file to attach.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachmentLevel = (
|
||||||
|
document.querySelector('input[name="attachment-level"]:checked') as HTMLInputElement
|
||||||
|
)?.value || 'document';
|
||||||
|
|
||||||
|
let pageRange: string = '';
|
||||||
|
|
||||||
|
if (attachmentLevel === 'page') {
|
||||||
|
const pageRangeInput = document.getElementById('attachment-page-range') as HTMLInputElement;
|
||||||
|
pageRange = pageRangeInput?.value?.trim() || '';
|
||||||
|
|
||||||
|
if (!pageRange) {
|
||||||
|
showAlert('Error', 'Please specify a page range for page-level attachments.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoader('Embedding files into PDF...');
|
||||||
|
try {
|
||||||
|
const pdfFile = state.files[0];
|
||||||
|
const pdfBuffer = (await readFileAsArrayBuffer(pdfFile)) as ArrayBuffer;
|
||||||
|
|
||||||
|
const attachmentBuffers: ArrayBuffer[] = [];
|
||||||
|
const attachmentNames: string[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < attachments.length; i++) {
|
||||||
|
const file = attachments[i];
|
||||||
|
showLoader(`Reading ${file.name} (${i + 1}/${attachments.length})...`);
|
||||||
|
|
||||||
|
const fileBuffer = (await readFileAsArrayBuffer(file)) as ArrayBuffer;
|
||||||
|
attachmentBuffers.push(fileBuffer);
|
||||||
|
attachmentNames.push(file.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoader('Attaching files to PDF...');
|
||||||
|
|
||||||
|
const message = {
|
||||||
|
command: 'add-attachments',
|
||||||
|
pdfBuffer: pdfBuffer,
|
||||||
|
attachmentBuffers: attachmentBuffers,
|
||||||
|
attachmentNames: attachmentNames,
|
||||||
|
attachmentLevel: attachmentLevel,
|
||||||
|
pageRange: pageRange
|
||||||
|
};
|
||||||
|
|
||||||
|
const transferables = [pdfBuffer, ...attachmentBuffers];
|
||||||
|
worker.postMessage(message, transferables);
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error attaching files:', error);
|
||||||
|
hideLoader();
|
||||||
|
showAlert('Error', `Failed to attach files: ${error.message}`);
|
||||||
|
clearAttachments();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAttachments() {
|
||||||
|
attachments = [];
|
||||||
|
const fileListDiv = document.getElementById('attachment-file-list');
|
||||||
|
const attachmentInput = document.getElementById(
|
||||||
|
'attachment-files-input'
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const processBtn = document.getElementById(
|
||||||
|
'process-btn'
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
const attachmentLevelOptions = document.getElementById('attachment-level-options');
|
||||||
|
const pageRangeWrapper = document.getElementById('page-range-wrapper');
|
||||||
|
|
||||||
|
if (fileListDiv) fileListDiv.innerHTML = '';
|
||||||
|
if (attachmentInput) attachmentInput.value = '';
|
||||||
|
if (processBtn) {
|
||||||
|
processBtn.disabled = true;
|
||||||
|
processBtn.classList.add('hidden');
|
||||||
|
}
|
||||||
|
if (attachmentLevelOptions) {
|
||||||
|
attachmentLevelOptions.classList.add('hidden');
|
||||||
|
}
|
||||||
|
if (pageRangeWrapper) {
|
||||||
|
pageRangeWrapper.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentRadio = document.querySelector('input[name="attachment-level"][value="document"]') as HTMLInputElement;
|
||||||
|
if (documentRadio) {
|
||||||
|
documentRadio.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupAddAttachmentsTool() {
|
||||||
|
const optionsDiv = document.getElementById('attachment-options');
|
||||||
|
const attachmentInput = document.getElementById(
|
||||||
|
'attachment-files-input'
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const fileListDiv = document.getElementById('attachment-file-list');
|
||||||
|
const processBtn = document.getElementById(
|
||||||
|
'process-btn'
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
const attachmentLevelOptions = document.getElementById('attachment-level-options');
|
||||||
|
const pageRangeWrapper = document.getElementById('page-range-wrapper');
|
||||||
|
const totalPagesSpan = document.getElementById('attachment-total-pages');
|
||||||
|
|
||||||
|
if (!optionsDiv || !attachmentInput || !fileListDiv || !processBtn) {
|
||||||
|
console.error('Attachment tool UI elements not found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.files || state.files.length === 0) {
|
||||||
|
console.error('No PDF file loaded for adding attachments.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsDiv.classList.remove('hidden');
|
||||||
|
|
||||||
|
if (totalPagesSpan && state.pdfDoc) {
|
||||||
|
totalPagesSpan.textContent = state.pdfDoc.getPageCount().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachmentInput.dataset.listenerAttached) return;
|
||||||
|
attachmentInput.dataset.listenerAttached = 'true';
|
||||||
|
|
||||||
|
attachmentInput.addEventListener('change', (e) => {
|
||||||
|
const files = (e.target as HTMLInputElement).files;
|
||||||
|
if (files && files.length > 0) {
|
||||||
|
attachments = Array.from(files);
|
||||||
|
|
||||||
|
fileListDiv.innerHTML = '';
|
||||||
|
attachments.forEach((file) => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className =
|
||||||
|
'flex justify-between items-center p-2 bg-gray-800 rounded-md text-white';
|
||||||
|
|
||||||
|
const nameSpan = document.createElement('span');
|
||||||
|
nameSpan.className = 'truncate text-sm';
|
||||||
|
nameSpan.textContent = file.name;
|
||||||
|
|
||||||
|
const sizeSpan = document.createElement('span');
|
||||||
|
sizeSpan.className = 'text-xs text-gray-400';
|
||||||
|
sizeSpan.textContent = `${Math.round(file.size / 1024)} KB`;
|
||||||
|
|
||||||
|
div.appendChild(nameSpan);
|
||||||
|
div.appendChild(sizeSpan);
|
||||||
|
fileListDiv.appendChild(div);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (attachmentLevelOptions) {
|
||||||
|
attachmentLevelOptions.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
processBtn.disabled = false;
|
||||||
|
processBtn.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
clearAttachments();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const attachmentLevelRadios = document.querySelectorAll('input[name="attachment-level"]');
|
||||||
|
attachmentLevelRadios.forEach((radio) => {
|
||||||
|
radio.addEventListener('change', (e) => {
|
||||||
|
const value = (e.target as HTMLInputElement).value;
|
||||||
|
if (value === 'page' && pageRangeWrapper) {
|
||||||
|
pageRangeWrapper.classList.remove('hidden');
|
||||||
|
} else if (pageRangeWrapper) {
|
||||||
|
pageRangeWrapper.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
processBtn.onclick = addAttachments;
|
||||||
|
}
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
|
||||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
|
||||||
import { createIcons, icons } from 'lucide';
|
|
||||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
|
||||||
|
|
||||||
interface AddBlankPageState {
|
|
||||||
file: File | null;
|
|
||||||
pdfDoc: PDFLibDocument | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageState: AddBlankPageState = {
|
|
||||||
file: null,
|
|
||||||
pdfDoc: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
function resetState() {
|
|
||||||
pageState.file = null;
|
|
||||||
pageState.pdfDoc = 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 pagePositionInput = document.getElementById('page-position') as HTMLInputElement;
|
|
||||||
if (pagePositionInput) pagePositionInput.value = '0';
|
|
||||||
|
|
||||||
const pageCountInput = document.getElementById('page-count') as HTMLInputElement;
|
|
||||||
if (pageCountInput) pageCountInput.value = '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateUI() {
|
|
||||||
const fileDisplayArea = document.getElementById('file-display-area');
|
|
||||||
const toolOptions = document.getElementById('tool-options');
|
|
||||||
const pagePositionHint = document.getElementById('page-position-hint');
|
|
||||||
const pagePositionInput = document.getElementById('page-position') as HTMLInputElement;
|
|
||||||
|
|
||||||
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 = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
|
||||||
removeBtn.onclick = function () {
|
|
||||||
resetState();
|
|
||||||
};
|
|
||||||
|
|
||||||
fileDiv.append(infoContainer, removeBtn);
|
|
||||||
fileDisplayArea.appendChild(fileDiv);
|
|
||||||
createIcons({ icons });
|
|
||||||
|
|
||||||
// Load PDF document
|
|
||||||
try {
|
|
||||||
showLoader('Loading PDF...');
|
|
||||||
const arrayBuffer = await pageState.file.arrayBuffer();
|
|
||||||
pageState.pdfDoc = await PDFLibDocument.load(arrayBuffer, {
|
|
||||||
ignoreEncryption: true,
|
|
||||||
throwOnInvalidObject: false
|
|
||||||
});
|
|
||||||
hideLoader();
|
|
||||||
|
|
||||||
const pageCount = pageState.pdfDoc.getPageCount();
|
|
||||||
metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageCount} pages`;
|
|
||||||
|
|
||||||
if (pagePositionHint) {
|
|
||||||
pagePositionHint.textContent = `Enter 0 to insert at the beginning, or ${pageCount} to insert at the end.`;
|
|
||||||
}
|
|
||||||
if (pagePositionInput) {
|
|
||||||
pagePositionInput.max = pageCount.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 addBlankPages() {
|
|
||||||
if (!pageState.pdfDoc || !pageState.file) {
|
|
||||||
showAlert('Error', 'Please upload a PDF first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pagePositionInput = document.getElementById('page-position') as HTMLInputElement;
|
|
||||||
const pageCountInput = document.getElementById('page-count') as HTMLInputElement;
|
|
||||||
|
|
||||||
const position = parseInt(pagePositionInput.value);
|
|
||||||
const insertCount = parseInt(pageCountInput.value);
|
|
||||||
const totalPages = pageState.pdfDoc.getPageCount();
|
|
||||||
|
|
||||||
if (isNaN(position) || position < 0 || position > totalPages) {
|
|
||||||
showAlert('Invalid Input', `Please enter a number between 0 and ${totalPages}.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(insertCount) || insertCount < 1) {
|
|
||||||
showAlert('Invalid Input', 'Please enter a valid number of pages (1 or more).');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showLoader(`Adding ${insertCount} blank page${insertCount > 1 ? 's' : ''}...`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const newPdf = await PDFLibDocument.create();
|
|
||||||
const { width, height } = pageState.pdfDoc.getPage(0).getSize();
|
|
||||||
const allIndices = Array.from({ length: totalPages }, function (_, i) { return i; });
|
|
||||||
|
|
||||||
const indicesBefore = allIndices.slice(0, position);
|
|
||||||
const indicesAfter = allIndices.slice(position);
|
|
||||||
|
|
||||||
if (indicesBefore.length > 0) {
|
|
||||||
const copied = await newPdf.copyPages(pageState.pdfDoc, indicesBefore);
|
|
||||||
copied.forEach(function (p) { newPdf.addPage(p); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the specified number of blank pages
|
|
||||||
for (let i = 0; i < insertCount; i++) {
|
|
||||||
newPdf.addPage([width, height]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indicesAfter.length > 0) {
|
|
||||||
const copied = await newPdf.copyPages(pageState.pdfDoc, indicesAfter);
|
|
||||||
copied.forEach(function (p) { newPdf.addPage(p); });
|
|
||||||
}
|
|
||||||
|
|
||||||
const newPdfBytes = await newPdf.save();
|
|
||||||
const originalName = pageState.file.name.replace(/\.pdf$/i, '');
|
|
||||||
|
|
||||||
downloadFile(
|
|
||||||
new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }),
|
|
||||||
`${originalName}_blank-pages-added.pdf`
|
|
||||||
);
|
|
||||||
|
|
||||||
showAlert('Success', `Added ${insertCount} blank page${insertCount > 1 ? 's' : ''} successfully!`, 'success', function () {
|
|
||||||
resetState();
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
showAlert('Error', `Could not add blank page${insertCount > 1 ? 's' : ''}.`);
|
|
||||||
} 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');
|
|
||||||
|
|
||||||
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 (processBtn) {
|
|
||||||
processBtn.addEventListener('click', addBlankPages);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||||
|
import { downloadFile } from '../utils/helpers.js';
|
||||||
|
import { state } from '../state.js';
|
||||||
|
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||||
|
|
||||||
|
export async function addBlankPage() {
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
const pageNumberInput = document.getElementById('page-number').value;
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
const pageCountInput = document.getElementById('page-count').value;
|
||||||
|
|
||||||
|
if (pageNumberInput.trim() === '') {
|
||||||
|
showAlert('Invalid Input', 'Please enter a page number.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageCountInput.trim() === '') {
|
||||||
|
showAlert('Invalid Input', 'Please enter the number of pages to insert.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = parseInt(pageNumberInput);
|
||||||
|
const pageCount = parseInt(pageCountInput);
|
||||||
|
const totalPages = state.pdfDoc.getPageCount();
|
||||||
|
if (isNaN(position) || position < 0 || position > totalPages) {
|
||||||
|
showAlert(
|
||||||
|
'Invalid Input',
|
||||||
|
`Please enter a number between 0 and ${totalPages}.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(pageCount) || pageCount < 1) {
|
||||||
|
showAlert(
|
||||||
|
'Invalid Input',
|
||||||
|
'Please enter a valid number of pages (1 or more).'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoader(`Adding ${pageCount} blank page${pageCount > 1 ? 's' : ''}...`);
|
||||||
|
try {
|
||||||
|
const newPdf = await PDFLibDocument.create();
|
||||||
|
const { width, height } = state.pdfDoc.getPage(0).getSize();
|
||||||
|
const allIndices = Array.from({ length: totalPages }, (_, i) => i);
|
||||||
|
|
||||||
|
const indicesBefore = allIndices.slice(0, position);
|
||||||
|
const indicesAfter = allIndices.slice(position);
|
||||||
|
|
||||||
|
if (indicesBefore.length > 0) {
|
||||||
|
const copied = await newPdf.copyPages(state.pdfDoc, indicesBefore);
|
||||||
|
copied.forEach((p: any) => newPdf.addPage(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the specified number of blank pages
|
||||||
|
for (let i = 0; i < pageCount; i++) {
|
||||||
|
newPdf.addPage([width, height]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indicesAfter.length > 0) {
|
||||||
|
const copied = await newPdf.copyPages(state.pdfDoc, indicesAfter);
|
||||||
|
copied.forEach((p: any) => newPdf.addPage(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPdfBytes = await newPdf.save();
|
||||||
|
downloadFile(
|
||||||
|
new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }),
|
||||||
|
`blank-page${pageCount > 1 ? 's' : ''}-added.pdf`
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
showAlert('Error', `Could not add blank page${pageCount > 1 ? 's' : ''}.`);
|
||||||
|
} finally {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||||
|
import { downloadFile, hexToRgb, parsePageRanges } from '../utils/helpers.js';
|
||||||
|
import { state } from '../state.js';
|
||||||
|
|
||||||
|
import { PDFDocument as PDFLibDocument, rgb, StandardFonts } from 'pdf-lib';
|
||||||
|
|
||||||
|
export function setupHeaderFooterUI() {
|
||||||
|
const totalPagesSpan = document.getElementById('total-pages');
|
||||||
|
if (totalPagesSpan && state.pdfDoc) {
|
||||||
|
totalPagesSpan.textContent = state.pdfDoc.getPageCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addHeaderFooter() {
|
||||||
|
showLoader('Adding header & footer...');
|
||||||
|
try {
|
||||||
|
const helveticaFont = await state.pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||||
|
const allPages = state.pdfDoc.getPages();
|
||||||
|
const totalPages = allPages.length;
|
||||||
|
const margin = 40;
|
||||||
|
|
||||||
|
// --- 1. Get new formatting options from the UI ---
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
const fontSize = parseInt(document.getElementById('font-size').value) || 10;
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
const colorHex = document.getElementById('font-color').value;
|
||||||
|
const fontColor = hexToRgb(colorHex);
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
const pageRangeInput = document.getElementById('page-range').value;
|
||||||
|
|
||||||
|
// --- 2. Get text values ---
|
||||||
|
const texts = {
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
headerLeft: document.getElementById('header-left').value,
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
headerCenter: document.getElementById('header-center').value,
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
headerRight: document.getElementById('header-right').value,
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
footerLeft: document.getElementById('footer-left').value,
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
footerCenter: document.getElementById('footer-center').value,
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
footerRight: document.getElementById('footer-right').value,
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 3. Parse page range to determine which pages to modify ---
|
||||||
|
const indicesToProcess = parsePageRanges(pageRangeInput, totalPages);
|
||||||
|
if (indicesToProcess.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
"Invalid page range specified. Please check your input (e.g., '1-3, 5')."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 4. Define drawing options with new values ---
|
||||||
|
const drawOptions = {
|
||||||
|
font: helveticaFont,
|
||||||
|
size: fontSize,
|
||||||
|
color: rgb(fontColor.r, fontColor.g, fontColor.b),
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 5. Loop over only the selected pages ---
|
||||||
|
for (const pageIndex of indicesToProcess) {
|
||||||
|
// @ts-expect-error TS(2538) FIXME: Type 'unknown' cannot be used as an index type.
|
||||||
|
const page = allPages[pageIndex];
|
||||||
|
const { width, height } = page.getSize();
|
||||||
|
// @ts-expect-error TS(2365) FIXME: Operator '+' cannot be applied to types 'unknown' ... Remove this comment to see the full error message
|
||||||
|
const pageNumber = pageIndex + 1; // For dynamic text
|
||||||
|
|
||||||
|
// Helper to replace placeholders like {page} and {total}
|
||||||
|
const processText = (text: any) =>
|
||||||
|
text.replace(/{page}/g, pageNumber).replace(/{total}/g, totalPages);
|
||||||
|
|
||||||
|
// Get processed text for the current page
|
||||||
|
const processedTexts = {
|
||||||
|
headerLeft: processText(texts.headerLeft),
|
||||||
|
headerCenter: processText(texts.headerCenter),
|
||||||
|
headerRight: processText(texts.headerRight),
|
||||||
|
footerLeft: processText(texts.footerLeft),
|
||||||
|
footerCenter: processText(texts.footerCenter),
|
||||||
|
footerRight: processText(texts.footerRight),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (processedTexts.headerLeft)
|
||||||
|
page.drawText(processedTexts.headerLeft, {
|
||||||
|
...drawOptions,
|
||||||
|
x: margin,
|
||||||
|
y: height - margin,
|
||||||
|
});
|
||||||
|
if (processedTexts.headerCenter)
|
||||||
|
page.drawText(processedTexts.headerCenter, {
|
||||||
|
...drawOptions,
|
||||||
|
x:
|
||||||
|
width / 2 -
|
||||||
|
helveticaFont.widthOfTextAtSize(
|
||||||
|
processedTexts.headerCenter,
|
||||||
|
fontSize
|
||||||
|
) /
|
||||||
|
2,
|
||||||
|
y: height - margin,
|
||||||
|
});
|
||||||
|
if (processedTexts.headerRight)
|
||||||
|
page.drawText(processedTexts.headerRight, {
|
||||||
|
...drawOptions,
|
||||||
|
x:
|
||||||
|
width -
|
||||||
|
margin -
|
||||||
|
helveticaFont.widthOfTextAtSize(
|
||||||
|
processedTexts.headerRight,
|
||||||
|
fontSize
|
||||||
|
),
|
||||||
|
y: height - margin,
|
||||||
|
});
|
||||||
|
if (processedTexts.footerLeft)
|
||||||
|
page.drawText(processedTexts.footerLeft, {
|
||||||
|
...drawOptions,
|
||||||
|
x: margin,
|
||||||
|
y: margin,
|
||||||
|
});
|
||||||
|
if (processedTexts.footerCenter)
|
||||||
|
page.drawText(processedTexts.footerCenter, {
|
||||||
|
...drawOptions,
|
||||||
|
x:
|
||||||
|
width / 2 -
|
||||||
|
helveticaFont.widthOfTextAtSize(
|
||||||
|
processedTexts.footerCenter,
|
||||||
|
fontSize
|
||||||
|
) /
|
||||||
|
2,
|
||||||
|
y: margin,
|
||||||
|
});
|
||||||
|
if (processedTexts.footerRight)
|
||||||
|
page.drawText(processedTexts.footerRight, {
|
||||||
|
...drawOptions,
|
||||||
|
x:
|
||||||
|
width -
|
||||||
|
margin -
|
||||||
|
helveticaFont.widthOfTextAtSize(
|
||||||
|
processedTexts.footerRight,
|
||||||
|
fontSize
|
||||||
|
),
|
||||||
|
y: margin,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPdfBytes = await state.pdfDoc.save();
|
||||||
|
downloadFile(
|
||||||
|
new Blob([newPdfBytes], { type: 'application/pdf' }),
|
||||||
|
'header-footer-added.pdf'
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
showAlert('Error', e.message || 'Could not add header or footer.');
|
||||||
|
} finally {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||||
|
import { downloadFile, hexToRgb } from '../utils/helpers.js';
|
||||||
|
import { state } from '../state.js';
|
||||||
|
|
||||||
|
import { rgb, StandardFonts } from 'pdf-lib';
|
||||||
|
|
||||||
|
export async function addPageNumbers() {
|
||||||
|
showLoader('Adding page numbers...');
|
||||||
|
try {
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
const position = document.getElementById('position').value;
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
const fontSize = parseInt(document.getElementById('font-size').value) || 12;
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
const format = document.getElementById('number-format').value;
|
||||||
|
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||||
|
const colorHex = document.getElementById('text-color').value;
|
||||||
|
const textColor = hexToRgb(colorHex);
|
||||||
|
|
||||||
|
const pages = state.pdfDoc.getPages();
|
||||||
|
const totalPages = pages.length;
|
||||||
|
const helveticaFont = await state.pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||||
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure text doesn't go outside visible page boundaries
|
||||||
|
const safeHorizontalMargin = Math.max(
|
||||||
|
horizontalMargin,
|
||||||
|
textWidth / 2 + 3
|
||||||
|
);
|
||||||
|
const safeVerticalMargin = Math.max(verticalMargin, textHeight + 3);
|
||||||
|
|
||||||
|
let x, y;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final safety check to ensure coordinates are within visible page bounds
|
||||||
|
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 state.pdfDoc.save();
|
||||||
|
downloadFile(
|
||||||
|
new Blob([newPdfBytes], { type: 'application/pdf' }),
|
||||||
|
'paginated.pdf'
|
||||||
|
);
|
||||||
|
showAlert('Success', 'Page numbers added successfully!');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
showAlert('Error', 'Could not add page numbers.');
|
||||||
|
} finally {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user