mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-05 20:02:47 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0403a9665f | ||
|
|
0ddf54cba3 | ||
|
|
25c221f8b9 | ||
|
|
d4f1aa30f5 | ||
|
|
c9faee4fd9 | ||
|
|
3fc873d343 | ||
|
|
739dac55ee | ||
|
|
dbc1b8eb7f | ||
|
|
11c24c1ff1 | ||
|
|
9af9c33f74 | ||
|
|
835c3c9423 | ||
|
|
a8df98f20d | ||
|
|
11c9e14803 | ||
|
|
4e2d04fb9c | ||
|
|
edc0f27e0f | ||
|
|
bf57f36cb5 | ||
|
|
6ad6a7d730 | ||
|
|
0e7c53560a | ||
|
|
59b351eee4 | ||
|
|
48fe49ac37 | ||
|
|
ccb1b119c5 | ||
|
|
6971c18e5e | ||
|
|
8a72063ef7 | ||
|
|
4e6cc77a28 | ||
|
|
f037e27535 | ||
|
|
6270080045 | ||
|
|
f9a06b54f5 | ||
|
|
584f2f09d1 | ||
|
|
8ac4793f9b | ||
|
|
d163bd29e6 | ||
|
|
34a00ad74a | ||
|
|
797d8f5b9d | ||
|
|
ef7b9d0365 | ||
|
|
6bfcb30592 | ||
|
|
a605e56834 | ||
|
|
2e2c634b41 | ||
|
|
caf23b0ab9 | ||
|
|
88e05e6713 | ||
|
|
b3ab445538 | ||
|
|
ddeebc8142 | ||
|
|
f11efe7278 |
@@ -0,0 +1,15 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
groups:
|
||||||
|
npm-dependencies:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
@@ -1,13 +1,28 @@
|
|||||||
name: build docker image and push to dockerhub
|
name: Build and Push Docker Images (Default + Simple Mode)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker-build-and-push:
|
docker-build-and-push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
mode:
|
||||||
|
- name: default
|
||||||
|
simple_mode: false
|
||||||
|
suffix: ''
|
||||||
|
- name: simple
|
||||||
|
simple_mode: true
|
||||||
|
suffix: '-simple'
|
||||||
steps:
|
steps:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
@@ -18,9 +33,28 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
- name: Build and push
|
- name: Extract version from tag
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/}
|
||||||
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "version=latest" >> $GITHUB_OUTPUT
|
||||||
|
echo "version_without_v=latest" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build and push ${{ matrix.mode.name }} image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
tags: bentopdf/bentopdf:latest
|
build-args: |
|
||||||
|
SIMPLE_MODE=${{ matrix.mode.simple_mode }}
|
||||||
|
tags: |
|
||||||
|
bentopdf/bentopdf${{ matrix.mode.suffix }}:latest
|
||||||
|
bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }}
|
||||||
|
bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ RUN npm ci
|
|||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build without type checking (vite build only)
|
# 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
|
RUN npm run build -- --mode production
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# BentoPDF
|
# BentoPDF
|
||||||
|
|
||||||
**BentoPDF** is a powerful, privacy-first, client-side PDF toolkit that 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 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/alio0) 
|
 [](https://ko-fi.com/alio0) 
|
||||||
@@ -129,6 +130,42 @@ docker-compose up -d
|
|||||||
|
|
||||||
The application will be available at `http://localhost:3000`.
|
The application will be available at `http://localhost:3000`.
|
||||||
|
|
||||||
|
### 🏢 Simple Mode for Internal Use
|
||||||
|
|
||||||
|
For organizations that want a clean, distraction-free interface focused solely on PDF tools, BentoPDF supports a **Simple Mode** that hides all branding and marketing content.
|
||||||
|
|
||||||
|
**What Simple Mode does:**
|
||||||
|
|
||||||
|
- Hides navigation, hero section, features, FAQ, testimonials, and footer
|
||||||
|
- Shows only the essential PDF tools
|
||||||
|
- Updates page title to "PDF Tools"
|
||||||
|
- Perfect for internal company tools and educational institutions
|
||||||
|
|
||||||
|
For more details, see [SIMPLE_MODE.md](SIMPLE_MODE.md).
|
||||||
|
|
||||||
|
### 📦 Version Management
|
||||||
|
|
||||||
|
BentoPDF supports semantic versioning with multiple Docker tags:
|
||||||
|
|
||||||
|
- **Latest**: `bentopdf/bentopdf:latest`
|
||||||
|
- **Specific Version**: `bentopdf/bentopdf:1.0.0`
|
||||||
|
- **Version with Prefix**: `bentopdf/bentopdf:v1.0.0`
|
||||||
|
|
||||||
|
#### Quick Release
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Release a patch version (0.0.1 → 0.0.2)
|
||||||
|
npm run release
|
||||||
|
|
||||||
|
# Release a minor version (0.0.1 → 0.1.0)
|
||||||
|
npm run release:minor
|
||||||
|
|
||||||
|
# Release a major version (0.0.1 → 1.0.0)
|
||||||
|
npm run release:major
|
||||||
|
```
|
||||||
|
|
||||||
|
````
|
||||||
|
For detailed release instructions, see [RELEASE.md](RELEASE.md).
|
||||||
### 🚀 Development Setup
|
### 🚀 Development Setup
|
||||||
|
|
||||||
#### Option 1: Run with npm
|
#### Option 1: Run with npm
|
||||||
@@ -138,7 +175,7 @@ The application will be available at `http://localhost:3000`.
|
|||||||
```bash
|
```bash
|
||||||
git clone https://github.com/alam00000/bentopdf.git
|
git clone https://github.com/alam00000/bentopdf.git
|
||||||
cd bentopdf
|
cd bentopdf
|
||||||
```
|
````
|
||||||
|
|
||||||
2. **Install Dependencies**:
|
2. **Install Dependencies**:
|
||||||
|
|
||||||
@@ -204,7 +241,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/q42xWQmJ).
|
Contributions and discussions on the roadmap are welcome! Join the conversation via [Discord](https://discord.gg/AP2Y97juZT).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+317
@@ -0,0 +1,317 @@
|
|||||||
|
# 🚀 Release Process - Real-World Scenarios
|
||||||
|
|
||||||
|
## 📋 Common Release Scenarios
|
||||||
|
|
||||||
|
### **Scenario 1: I Just Finished a Feature and Want to Release**
|
||||||
|
|
||||||
|
**Situation:** You've completed a new feature, tested it locally, and want to release it.
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git status
|
||||||
|
On branch main
|
||||||
|
Your branch is up to date with 'origin/main'.
|
||||||
|
|
||||||
|
Changes not staged for commit:
|
||||||
|
modified: src/js/new-feature.js
|
||||||
|
modified: src/css/styles.css
|
||||||
|
modified: README.md
|
||||||
|
|
||||||
|
Untracked files:
|
||||||
|
src/js/feature-helper.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Commit your feature changes
|
||||||
|
git add .
|
||||||
|
git commit -m "Add new PDF watermark feature"
|
||||||
|
|
||||||
|
# 2. Choose your release type and run
|
||||||
|
npm run release # Patch: 1.0.0 → 1.0.1 (bug fixes, small improvements)
|
||||||
|
npm run release:minor # Minor: 1.0.0 → 1.1.0 (new features, backward compatible)
|
||||||
|
npm run release:major # Major: 1.0.0 → 2.0.0 (breaking changes)
|
||||||
|
```
|
||||||
|
|
||||||
|
**What Happens:**
|
||||||
|
|
||||||
|
- ✅ Your feature commit stays as-is
|
||||||
|
- ✅ Version gets bumped in `package.json`
|
||||||
|
- ✅ New release commit is created
|
||||||
|
- ✅ Git tag is created (e.g., `v1.0.1`)
|
||||||
|
- ✅ Everything gets pushed to GitHub
|
||||||
|
- ✅ Docker image gets built and published
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Scenario 2: I Have Uncommitted Changes and Want to Release**
|
||||||
|
|
||||||
|
**Situation:** You have local changes but haven't committed them yet.
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git status
|
||||||
|
Changes not staged for commit:
|
||||||
|
modified: package.json
|
||||||
|
modified: src/js/main.js
|
||||||
|
modified: README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ This Will Fail:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run release
|
||||||
|
# Error: Your local changes would be overwritten by merge
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Solution Options:**
|
||||||
|
|
||||||
|
**Option A: Commit Everything First (Recommended)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "Add new features and improvements"
|
||||||
|
npm run release
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Stash Changes Temporarily**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git stash
|
||||||
|
npm run release
|
||||||
|
git stash pop # Restore your changes after release
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option C: Commit Only What's Needed**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add package.json src/js/main.js
|
||||||
|
git commit -m "Add core improvements"
|
||||||
|
npm run release
|
||||||
|
git add README.md
|
||||||
|
git commit -m "Update documentation"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Scenario 3: I Want to Release a Hotfix**
|
||||||
|
|
||||||
|
**Situation:** There's a critical bug in production that needs immediate fixing.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Fix the bug
|
||||||
|
git add src/js/bug-fix.js
|
||||||
|
git commit -m "Fix critical PDF rendering issue"
|
||||||
|
|
||||||
|
# 2. Release as patch (bug fix)
|
||||||
|
npm run release
|
||||||
|
# This creates: 1.0.0 → 1.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:**
|
||||||
|
|
||||||
|
- ✅ Bug fix gets released immediately
|
||||||
|
- ✅ Docker image with fix is available
|
||||||
|
- ✅ Users can pull the fixed version
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Scenario 4: I Want to Release a Major Update**
|
||||||
|
|
||||||
|
**Situation:** You've added significant new features that might break existing functionality.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Commit all your changes
|
||||||
|
git add .
|
||||||
|
git commit -m "Add major PDF editing features and API changes"
|
||||||
|
|
||||||
|
# 2. Release as major version
|
||||||
|
npm run release:major
|
||||||
|
# This creates: 1.0.0 → 2.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:**
|
||||||
|
|
||||||
|
- ✅ Major version bump indicates breaking changes
|
||||||
|
- ✅ Users know to check compatibility
|
||||||
|
- ✅ Both old and new versions available
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Scenario 5: I Want to Release Multiple Features at Once**
|
||||||
|
|
||||||
|
**Situation:** You've been working on multiple features and want to release them together.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Commit all features
|
||||||
|
git add .
|
||||||
|
git commit -m "Add multiple PDF tools: watermark, encryption, and compression"
|
||||||
|
|
||||||
|
# 2. Choose appropriate release type
|
||||||
|
npm run release:minor # For new features (1.0.0 → 1.1.0)
|
||||||
|
# OR
|
||||||
|
npm run release:major # For breaking changes (1.0.0 → 2.0.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Scenario 6: I Want to Test the Release Process**
|
||||||
|
|
||||||
|
**Situation:** You want to test the release system without affecting production.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Make a small test change
|
||||||
|
echo "// Test comment" >> src/js/main.js
|
||||||
|
git add src/js/main.js
|
||||||
|
git commit -m "Test release process"
|
||||||
|
|
||||||
|
# 2. Run patch release
|
||||||
|
npm run release
|
||||||
|
# This creates: 1.0.0 → 1.0.1
|
||||||
|
|
||||||
|
# 3. Verify everything works
|
||||||
|
# Check GitHub Actions, Docker Hub, etc.
|
||||||
|
|
||||||
|
# 4. If you want to undo the test release
|
||||||
|
git tag -d v1.0.1
|
||||||
|
git push origin :refs/tags/v1.0.1
|
||||||
|
git reset --hard HEAD~1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 **Release Type Guidelines**
|
||||||
|
|
||||||
|
| Scenario | Command | Version Change | When to Use |
|
||||||
|
| ------------------- | ----------------------- | --------------- | ------------------------------------ |
|
||||||
|
| **Bug Fix** | `npm run release` | `1.0.0 → 1.0.1` | Fixing bugs, small improvements |
|
||||||
|
| **New Feature** | `npm run release:minor` | `1.0.0 → 1.1.0` | Adding features, backward compatible |
|
||||||
|
| **Breaking Change** | `npm run release:major` | `1.0.0 → 2.0.0` | API changes, major rewrites |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 **What Happens After You Run a Release Command**
|
||||||
|
|
||||||
|
### **Immediate Actions (Local):**
|
||||||
|
|
||||||
|
1. **Version Update**: `package.json` version gets bumped
|
||||||
|
2. **Git Commit**: New commit created with "Release vX.X.X"
|
||||||
|
3. **Git Tag**: Tag created (e.g., `v1.0.1`)
|
||||||
|
4. **Git Push**: Everything pushed to GitHub
|
||||||
|
|
||||||
|
### **Automatic Actions (GitHub):**
|
||||||
|
|
||||||
|
1. **GitHub Actions Triggered**: Workflow starts building Docker image
|
||||||
|
2. **Docker Build**: Multi-architecture image created
|
||||||
|
3. **Docker Push**: Images pushed to Docker Hub with tags:
|
||||||
|
- `bentopdf/bentopdf:latest`
|
||||||
|
- `bentopdf/bentopdf:1.0.1`
|
||||||
|
- `bentopdf/bentopdf:v1.0.1`
|
||||||
|
|
||||||
|
### **End Result:**
|
||||||
|
|
||||||
|
Users can immediately pull your new version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull bentopdf/bentopdf:1.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 **Before You Release - Prerequisites**
|
||||||
|
|
||||||
|
### **1. Docker Hub Credentials Setup**
|
||||||
|
|
||||||
|
You need to add these secrets to your GitHub repository:
|
||||||
|
|
||||||
|
1. Go to **Settings** → **Secrets and variables** → **Actions**
|
||||||
|
2. Add these secrets:
|
||||||
|
- `DOCKER_USERNAME`: Your Docker Hub username
|
||||||
|
- `DOCKER_TOKEN`: Your Docker Hub access token
|
||||||
|
|
||||||
|
### **2. Get Docker Hub Token**
|
||||||
|
|
||||||
|
1. Go to [Docker Hub](https://hub.docker.com)
|
||||||
|
2. Account Settings → Security → New Access Token
|
||||||
|
3. Set permissions to "Read, Write, Delete"
|
||||||
|
4. Copy the token and add it to GitHub Secrets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 **Troubleshooting Common Issues**
|
||||||
|
|
||||||
|
### **❌ "Your local changes would be overwritten by merge"**
|
||||||
|
|
||||||
|
**Problem:** You have uncommitted changes
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "Your commit message"
|
||||||
|
npm run release
|
||||||
|
```
|
||||||
|
|
||||||
|
### **❌ "Permission denied" in GitHub Actions**
|
||||||
|
|
||||||
|
**Problem:** Missing Docker Hub credentials
|
||||||
|
**Solution:** Add `DOCKER_USERNAME` and `DOCKER_TOKEN` to GitHub Secrets
|
||||||
|
|
||||||
|
### **❌ "Tag already exists"**
|
||||||
|
|
||||||
|
**Problem:** You've run the same release before
|
||||||
|
**Solution:** This is normal! The script will skip creating duplicate tags
|
||||||
|
|
||||||
|
### **❌ GitHub Actions fails**
|
||||||
|
|
||||||
|
**Problem:** Various build issues
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
1. Check Actions tab for detailed logs
|
||||||
|
2. Verify Docker Hub credentials
|
||||||
|
3. Check Dockerfile for syntax errors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 **Testing Your Release System**
|
||||||
|
|
||||||
|
### **Quick Test:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Make a small change
|
||||||
|
echo "// Test" >> src/js/main.js
|
||||||
|
git add src/js/main.js
|
||||||
|
git commit -m "Test release"
|
||||||
|
npm run release
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Verify Results:**
|
||||||
|
|
||||||
|
1. **GitHub Actions**: Check Actions tab for successful build
|
||||||
|
2. **Docker Hub**: Verify images are published
|
||||||
|
3. **Git Tags**: `git tag --list` should show new tag
|
||||||
|
4. **Version**: `cat package.json | grep version` should show updated version
|
||||||
|
|
||||||
|
### **Undo Test Release:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag -d v1.0.1
|
||||||
|
git push origin :refs/tags/v1.0.1
|
||||||
|
git reset --hard HEAD~1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 **That's It!**
|
||||||
|
|
||||||
|
Your release system is now ready! Just follow the scenarios above based on your situation and run the appropriate `npm run release` command.
|
||||||
+175
@@ -0,0 +1,175 @@
|
|||||||
|
# Simple Mode for BentoPDF
|
||||||
|
|
||||||
|
Simple Mode is designed for internal organizational use where you want to hide all branding and marketing content, showing only the essential PDF tools for your users.
|
||||||
|
|
||||||
|
## What Simple Mode Does
|
||||||
|
|
||||||
|
When enabled, Simple Mode will:
|
||||||
|
|
||||||
|
- Hide the navigation bar
|
||||||
|
- Hide the hero section with marketing content
|
||||||
|
- Hide the features section
|
||||||
|
- Hide the security/compliance section
|
||||||
|
- Hide the FAQ section
|
||||||
|
- Hide the testimonials section
|
||||||
|
- Hide the support section
|
||||||
|
- Hide the footer
|
||||||
|
- Update the page title to "PDF Tools"
|
||||||
|
- Make the tools section more prominent
|
||||||
|
|
||||||
|
## How to Enable Simple Mode
|
||||||
|
|
||||||
|
### Method 1: Using Pre-built Simple Mode Image (Recommended)
|
||||||
|
|
||||||
|
Use the pre-built Simple Mode image directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -p 3000:80 bentopdf/bentopdf-simple:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with Docker Compose:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
bentopdf:
|
||||||
|
image: bentopdf/bentopdf-simple:latest
|
||||||
|
container_name: bentopdf
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '3000:80'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: Using Docker Compose with Build
|
||||||
|
|
||||||
|
Build the image locally with Simple Mode enabled:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.dev.yml build --build-arg SIMPLE_MODE=true
|
||||||
|
docker compose -f docker-compose.dev.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 3: Using Docker Build
|
||||||
|
|
||||||
|
Build the image with the SIMPLE_MODE build argument:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build --build-arg SIMPLE_MODE=true -t bentopdf-simple .
|
||||||
|
docker run -p 3000:80 bentopdf-simple
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 4: Using npm Script (Easiest for Local Development)
|
||||||
|
|
||||||
|
Use the built-in npm script that handles everything:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run serve:simple
|
||||||
|
```
|
||||||
|
|
||||||
|
This command automatically:
|
||||||
|
|
||||||
|
- Sets `SIMPLE_MODE=true`
|
||||||
|
- Builds the project with Simple Mode enabled
|
||||||
|
- Serves the built files on `http://localhost:3000`
|
||||||
|
|
||||||
|
### Method 5: Using Environment Variables
|
||||||
|
|
||||||
|
Set the environment variable before building:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SIMPLE_MODE=true
|
||||||
|
npm run build
|
||||||
|
npx serve dist -p 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 Testing Simple Mode Locally
|
||||||
|
|
||||||
|
### Method 1: Using npm Script (Easiest for Development)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run serve:simple
|
||||||
|
```
|
||||||
|
|
||||||
|
This automatically builds and serves Simple Mode on `http://localhost:3000`.
|
||||||
|
|
||||||
|
### Method 2: Using Pre-built Image (Easiest for Production)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull and run the Simple Mode image
|
||||||
|
docker pull bentopdf/bentopdf-simple:latest
|
||||||
|
docker run -p 3000:80 bentopdf/bentopdf-simple:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `http://localhost:3000` in your browser.
|
||||||
|
|
||||||
|
### Method 3: Build and Test Locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build with simple mode
|
||||||
|
SIMPLE_MODE=true npm run build
|
||||||
|
|
||||||
|
# Serve the built files
|
||||||
|
npx serve dist -p 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `http://localhost:3000` in your browser.
|
||||||
|
|
||||||
|
### Method 4: Compare Both Modes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test Normal Mode
|
||||||
|
docker run -p 3000:80 bentopdf/bentopdf:latest
|
||||||
|
|
||||||
|
# Test Simple Mode
|
||||||
|
docker run -p 3001:80 bentopdf/bentopdf-simple:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
- Normal Mode: `http://localhost:3000`
|
||||||
|
- Simple Mode: `http://localhost:3001`
|
||||||
|
|
||||||
|
## 🔍 What to Look For
|
||||||
|
|
||||||
|
When Simple Mode is working correctly, you should see:
|
||||||
|
|
||||||
|
- ✅ Clean "PDF Tools" header (no marketing hero section)
|
||||||
|
- ✅ "Select a tool to get started" subtitle
|
||||||
|
- ✅ Search bar for tools
|
||||||
|
- ✅ All PDF tool cards organized by category
|
||||||
|
- ❌ No navigation bar
|
||||||
|
- ❌ No hero section with "The PDF Toolkit built for privacy"
|
||||||
|
- ❌ No features, FAQ, testimonials, or footer sections
|
||||||
|
|
||||||
|
## 📦 Available Docker Images
|
||||||
|
|
||||||
|
### Normal Mode (Full Branding)
|
||||||
|
|
||||||
|
- `bentopdf/bentopdf:latest`
|
||||||
|
- `bentopdf/bentopdf:v1.0.0` (versioned)
|
||||||
|
|
||||||
|
### Simple Mode (Clean Interface)
|
||||||
|
|
||||||
|
- `bentopdf/bentopdf-simple:latest`
|
||||||
|
- `bentopdf/bentopdf-simple:v1.0.0` (versioned)
|
||||||
|
|
||||||
|
## 🚀 Production Deployment Examples
|
||||||
|
|
||||||
|
### Internal Company Tool
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
bentopdf:
|
||||||
|
image: bentopdf/bentopdf-simple:latest
|
||||||
|
container_name: bentopdf
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '80:80'
|
||||||
|
environment:
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=1000
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ Important Notes
|
||||||
|
|
||||||
|
- **Pre-built images**: Use `bentopdf/bentopdf-simple:latest` for Simple Mode
|
||||||
|
- **Environment variables**: `SIMPLE_MODE=true` only works during build, not runtime
|
||||||
|
- **Build-time optimization**: Simple Mode uses dead code elimination for smaller bundles
|
||||||
|
- **Same functionality**: All PDF tools work identically in both modes
|
||||||
+70
-9
@@ -15,18 +15,21 @@
|
|||||||
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
||||||
<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">
|
||||||
<a
|
<div
|
||||||
href="index.html"
|
|
||||||
class="flex-shrink-0 flex items-center cursor-pointer"
|
class="flex-shrink-0 flex items-center cursor-pointer"
|
||||||
id="home-logo"
|
id="home-logo"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="images/favicon.svg"
|
src="images/favicon.svg"
|
||||||
alt="Bentopdf Logo"
|
alt="Bento PDF Logo"
|
||||||
class="h-8 w-8 mr-2"
|
class="h-8 w-8"
|
||||||
/>
|
/>
|
||||||
<span class="text-white font-bold text-xl">BentoPDF</span>
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
@@ -34,12 +37,69 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<div class="md:hidden flex items-center gap-4">
|
<!-- Mobile Hamburger Button -->
|
||||||
<a href="index.html" class="nav-link">Home</a>
|
<div class="md:hidden flex items-center">
|
||||||
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
<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"
|
||||||
|
aria-controls="mobile-menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Open main menu</span>
|
||||||
|
<!-- 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"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<!-- Close Icon -->
|
||||||
|
<svg
|
||||||
|
id="close-icon"
|
||||||
|
class="hidden h-6 w-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Menu Dropdown -->
|
||||||
|
<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">
|
||||||
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
|
<a href="index.html#tools-header" class="mobile-nav-link"
|
||||||
|
>All Tools</a
|
||||||
|
>
|
||||||
|
</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">
|
||||||
@@ -286,5 +346,6 @@
|
|||||||
<script>
|
<script>
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
</script>
|
</script>
|
||||||
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+70
-8
@@ -13,17 +13,21 @@
|
|||||||
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
||||||
<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">
|
||||||
<a
|
<div
|
||||||
href="index.html"
|
|
||||||
class="flex-shrink-0 flex items-center cursor-pointer"
|
class="flex-shrink-0 flex items-center cursor-pointer"
|
||||||
|
id="home-logo"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="images/favicon.svg"
|
src="images/favicon.svg"
|
||||||
alt="Bento PDF Logo"
|
alt="Bento PDF Logo"
|
||||||
class="h-8 w-8 mr-2"
|
class="h-8 w-8"
|
||||||
/>
|
/>
|
||||||
<span class="text-white font-bold text-xl"> BentoPDF</span>
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
@@ -31,12 +35,69 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<div class="md:hidden flex items-center gap-4">
|
<!-- Mobile Hamburger Button -->
|
||||||
<a href="index.html" class="nav-link">Home</a>
|
<div class="md:hidden flex items-center">
|
||||||
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
<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"
|
||||||
|
aria-controls="mobile-menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Open main menu</span>
|
||||||
|
<!-- 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"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<!-- Close Icon -->
|
||||||
|
<svg
|
||||||
|
id="close-icon"
|
||||||
|
class="hidden h-6 w-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Menu Dropdown -->
|
||||||
|
<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">
|
||||||
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
|
<a href="index.html#tools-header" class="mobile-nav-link"
|
||||||
|
>All Tools</a
|
||||||
|
>
|
||||||
|
</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">
|
||||||
@@ -150,5 +211,6 @@
|
|||||||
<script>
|
<script>
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
</script>
|
</script>
|
||||||
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+4
-2
@@ -1,7 +1,9 @@
|
|||||||
services:
|
services:
|
||||||
bentopdf:
|
bentopdf:
|
||||||
image: bentopdf/bentopdf:latest
|
# simple mode - bentopdf/bentopdf-simple:latest
|
||||||
|
# default mode - bentopdf/bentopdf:latest
|
||||||
|
image: bentopdf/bentopdf-simple:latest
|
||||||
container_name: bentopdf
|
container_name: bentopdf
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "3000:80"
|
- '3000:80'
|
||||||
|
|||||||
@@ -16,18 +16,21 @@
|
|||||||
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
||||||
<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">
|
||||||
<a
|
<div
|
||||||
href="index.html"
|
|
||||||
class="flex-shrink-0 flex items-center cursor-pointer"
|
class="flex-shrink-0 flex items-center cursor-pointer"
|
||||||
id="home-logo"
|
id="home-logo"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="images/favicon.svg"
|
src="images/favicon.svg"
|
||||||
alt="Bento PDF Logo"
|
alt="Bento PDF Logo"
|
||||||
class="h-8 w-8 mr-2"
|
class="h-8 w-8"
|
||||||
/>
|
/>
|
||||||
<span class="text-white font-bold text-xl">BentoPDF</span>
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
@@ -35,12 +38,69 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<div class="md:hidden flex items-center gap-4">
|
<!-- Mobile Hamburger Button -->
|
||||||
<a href="index.html" class="nav-link">Home</a>
|
<div class="md:hidden flex items-center">
|
||||||
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
<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"
|
||||||
|
aria-controls="mobile-menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Open main menu</span>
|
||||||
|
<!-- 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"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<!-- Close Icon -->
|
||||||
|
<svg
|
||||||
|
id="close-icon"
|
||||||
|
class="hidden h-6 w-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Menu Dropdown -->
|
||||||
|
<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">
|
||||||
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
|
<a href="index.html#tools-header" class="mobile-nav-link"
|
||||||
|
>All Tools</a
|
||||||
|
>
|
||||||
|
</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">
|
||||||
@@ -339,5 +399,6 @@
|
|||||||
<script>
|
<script>
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
</script>
|
</script>
|
||||||
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+62
-11
@@ -48,6 +48,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
@@ -55,22 +56,66 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<div class="md:hidden flex items-center gap-2">
|
<!-- Mobile Hamburger Button -->
|
||||||
<a href="index.html" class="nav-link">Home</a>
|
<div class="md:hidden flex items-center">
|
||||||
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
<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"
|
||||||
|
aria-controls="mobile-menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Open main menu</span>
|
||||||
|
<!-- 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"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<!-- Close Icon -->
|
||||||
|
<svg
|
||||||
|
id="close-icon"
|
||||||
|
class="hidden h-6 w-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mobile-menu" class="hidden md:hidden">
|
|
||||||
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
|
<!-- Mobile Menu Dropdown -->
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
<hr class="border-gray-700 my-2" />
|
<a href="index.html#tools-header" class="mobile-nav-link"
|
||||||
<a
|
>All Tools</a
|
||||||
href="#tool-grid"
|
|
||||||
id="mobile-all-tools-link"
|
|
||||||
class="mobile-nav-link bg-gray-700 text-white"
|
|
||||||
>View All Tools</a
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -218,6 +263,11 @@
|
|||||||
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'...)"
|
||||||
/>
|
/>
|
||||||
|
<span
|
||||||
|
class="absolute inset-y-0 right-0 flex items-center rounded-lg pr-2"
|
||||||
|
>
|
||||||
|
<kbd id="shortcut" class="bg-gray-800 px-1 rounded-lg"></kbd>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -888,5 +938,6 @@
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<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>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Generated
+218
-221
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "bento-pdf",
|
"name": "bento-pdf",
|
||||||
"version": "0.0.0",
|
"version": "1.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bento-pdf",
|
"name": "bento-pdf",
|
||||||
"version": "0.0.0",
|
"version": "1.0.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@neslinesli93/qpdf-wasm": "^0.3.0",
|
"@neslinesli93/qpdf-wasm": "^0.3.0",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.15",
|
||||||
"blob-stream": "^0.1.3",
|
"blob-stream": "^0.1.3",
|
||||||
"cropperjs": "^1.6.1",
|
"cropperjs": "^1.6.1",
|
||||||
"heic2any": "^0.0.4",
|
"heic2any": "^0.0.4",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"javascript-obfuscator": "^4.1.1",
|
"javascript-obfuscator": "^4.1.1",
|
||||||
"jspdf": "^3.0.3",
|
"jspdf": "^3.0.3",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide": "^0.545.0",
|
"lucide": "^0.546.0",
|
||||||
"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",
|
||||||
@@ -32,17 +32,17 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@types/blob-stream": "^0.1.33",
|
"@types/blob-stream": "^0.1.33",
|
||||||
"@types/html2canvas": "^0.5.35",
|
"@types/html2canvas": "^1.0.0",
|
||||||
"@types/pdfkit": "^0.17.3",
|
"@types/pdfkit": "^0.17.3",
|
||||||
"@types/sortablejs": "^1.15.8",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@types/utif": "^3.0.6",
|
"@types/utif": "^3.0.6",
|
||||||
"@vitest/coverage-v8": "^3.2.4",
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
"@vitest/ui": "^3.2.4",
|
"@vitest/ui": "^3.2.4",
|
||||||
"jsdom": "^27.0.0",
|
"jsdom": "^27.0.1",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"ts-migrate": "^0.1.35",
|
"ts-migrate": "^0.1.35",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.1.7",
|
"vite": "^7.1.11",
|
||||||
"vite-plugin-node-polyfills": "^0.24.0",
|
"vite-plugin-node-polyfills": "^0.24.0",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
}
|
}
|
||||||
@@ -86,9 +86,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/dom-selector": {
|
"node_modules/@asamuzakjp/dom-selector": {
|
||||||
"version": "6.6.2",
|
"version": "6.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.2.tgz",
|
||||||
"integrity": "sha512-+AG0jN9HTwfDLBhjhX1FKi6zlIAc/YGgEHlN/OMaHD1pOPFsC5CpYQpLkPX0aFjyaVmoq9330cQDCU4qnSL1qA==",
|
"integrity": "sha512-ccKogJI+0aiDhOahdjANIc9SDixSud1gbwdVrhn7kMopAtLXqsz9MKmQQtIl6Y5aC2IYq+j4dz/oedL2AVMmVQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2703,18 +2703,6 @@
|
|||||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@isaacs/fs-minipass": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"minipass": "^7.0.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@istanbuljs/schema": {
|
"node_modules/@istanbuljs/schema": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
|
||||||
@@ -3420,52 +3408,47 @@
|
|||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.15.tgz",
|
||||||
"integrity": "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==",
|
"integrity": "sha512-HF4+7QxATZWY3Jr8OlZrBSXmwT3Watj0OogeDvdUY/ByXJHQ+LBtqA2brDb3sBxYslIFx6UP94BJ4X6a4L9Bmw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/remapping": "^2.3.4",
|
"@jridgewell/remapping": "^2.3.4",
|
||||||
"enhanced-resolve": "^5.18.3",
|
"enhanced-resolve": "^5.18.3",
|
||||||
"jiti": "^2.6.0",
|
"jiti": "^2.6.0",
|
||||||
"lightningcss": "1.30.1",
|
"lightningcss": "1.30.2",
|
||||||
"magic-string": "^0.30.19",
|
"magic-string": "^0.30.19",
|
||||||
"source-map-js": "^1.2.1",
|
"source-map-js": "^1.2.1",
|
||||||
"tailwindcss": "4.1.14"
|
"tailwindcss": "4.1.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide": {
|
"node_modules/@tailwindcss/oxide": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.15.tgz",
|
||||||
"integrity": "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==",
|
"integrity": "sha512-krhX+UOOgnsUuks2SR7hFafXmLQrKxB4YyRTERuCE59JlYL+FawgaAlSkOYmDRJdf1Q+IFNDMl9iRnBW7QBDfQ==",
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"detect-libc": "^2.0.4",
|
|
||||||
"tar": "^7.5.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tailwindcss/oxide-android-arm64": "4.1.14",
|
"@tailwindcss/oxide-android-arm64": "4.1.15",
|
||||||
"@tailwindcss/oxide-darwin-arm64": "4.1.14",
|
"@tailwindcss/oxide-darwin-arm64": "4.1.15",
|
||||||
"@tailwindcss/oxide-darwin-x64": "4.1.14",
|
"@tailwindcss/oxide-darwin-x64": "4.1.15",
|
||||||
"@tailwindcss/oxide-freebsd-x64": "4.1.14",
|
"@tailwindcss/oxide-freebsd-x64": "4.1.15",
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.14",
|
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.15",
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.14",
|
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.15",
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.14",
|
"@tailwindcss/oxide-linux-arm64-musl": "4.1.15",
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.14",
|
"@tailwindcss/oxide-linux-x64-gnu": "4.1.15",
|
||||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.14",
|
"@tailwindcss/oxide-linux-x64-musl": "4.1.15",
|
||||||
"@tailwindcss/oxide-wasm32-wasi": "4.1.14",
|
"@tailwindcss/oxide-wasm32-wasi": "4.1.15",
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.14",
|
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.15",
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.14"
|
"@tailwindcss/oxide-win32-x64-msvc": "4.1.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.15.tgz",
|
||||||
"integrity": "sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==",
|
"integrity": "sha512-TkUkUgAw8At4cBjCeVCRMc/guVLKOU1D+sBPrHt5uVcGhlbVKxrCaCW9OKUIBv1oWkjh4GbunD/u/Mf0ql6kEA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -3479,9 +3462,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.15.tgz",
|
||||||
"integrity": "sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==",
|
"integrity": "sha512-xt5XEJpn2piMSfvd1UFN6jrWXyaKCwikP4Pidcf+yfHTSzSpYhG3dcMktjNkQO3JiLCp+0bG0HoWGvz97K162w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -3495,9 +3478,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.15.tgz",
|
||||||
"integrity": "sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==",
|
"integrity": "sha512-TnWaxP6Bx2CojZEXAV2M01Yl13nYPpp0EtGpUrY+LMciKfIXiLL2r/SiSRpagE5Fp2gX+rflp/Os1VJDAyqymg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3511,9 +3494,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.15.tgz",
|
||||||
"integrity": "sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==",
|
"integrity": "sha512-quISQDWqiB6Cqhjc3iWptXVZHNVENsWoI77L1qgGEHNIdLDLFnw3/AfY7DidAiiCIkGX/MjIdB3bbBZR/G2aJg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3527,9 +3510,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.15.tgz",
|
||||||
"integrity": "sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==",
|
"integrity": "sha512-ObG76+vPlab65xzVUQbExmDU9FIeYLQ5k2LrQdR2Ud6hboR+ZobXpDoKEYXf/uOezOfIYmy2Ta3w0ejkTg9yxg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -3543,9 +3526,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.15.tgz",
|
||||||
"integrity": "sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==",
|
"integrity": "sha512-4WbBacRmk43pkb8/xts3wnOZMDKsPFyEH/oisCm2q3aLZND25ufvJKcDUpAu0cS+CBOL05dYa8D4U5OWECuH/Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -3559,9 +3542,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.15.tgz",
|
||||||
"integrity": "sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==",
|
"integrity": "sha512-AbvmEiteEj1nf42nE8skdHv73NoR+EwXVSgPY6l39X12Ex8pzOwwfi3Kc8GAmjsnsaDEbk+aj9NyL3UeyHcTLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -3575,9 +3558,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.15.tgz",
|
||||||
"integrity": "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==",
|
"integrity": "sha512-+rzMVlvVgrXtFiS+ES78yWgKqpThgV19ISKD58Ck+YO5pO5KjyxLt7AWKsWMbY0R9yBDC82w6QVGz837AKQcHg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3591,9 +3574,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.15.tgz",
|
||||||
"integrity": "sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==",
|
"integrity": "sha512-fPdEy7a8eQN9qOIK3Em9D3TO1z41JScJn8yxl/76mp4sAXFDfV4YXxsiptJcOwy6bGR+70ZSwFIZhTXzQeqwQg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3607,9 +3590,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.15.tgz",
|
||||||
"integrity": "sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==",
|
"integrity": "sha512-sJ4yd6iXXdlgIMfIBXuVGp/NvmviEoMVWMOAGxtxhzLPp9LOj5k0pMEMZdjeMCl4C6Up+RM8T3Zgk+BMQ0bGcQ==",
|
||||||
"bundleDependencies": [
|
"bundleDependencies": [
|
||||||
"@napi-rs/wasm-runtime",
|
"@napi-rs/wasm-runtime",
|
||||||
"@emnapi/core",
|
"@emnapi/core",
|
||||||
@@ -3627,7 +3610,7 @@
|
|||||||
"@emnapi/core": "^1.5.0",
|
"@emnapi/core": "^1.5.0",
|
||||||
"@emnapi/runtime": "^1.5.0",
|
"@emnapi/runtime": "^1.5.0",
|
||||||
"@emnapi/wasi-threads": "^1.1.0",
|
"@emnapi/wasi-threads": "^1.1.0",
|
||||||
"@napi-rs/wasm-runtime": "^1.0.5",
|
"@napi-rs/wasm-runtime": "^1.0.7",
|
||||||
"@tybys/wasm-util": "^0.10.1",
|
"@tybys/wasm-util": "^0.10.1",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
},
|
},
|
||||||
@@ -3635,10 +3618,64 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.1.0",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/core": "^1.5.0",
|
||||||
|
"@emnapi/runtime": "^1.5.0",
|
||||||
|
"@tybys/wasm-util": "^0.10.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||||
|
"version": "0.10.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "0BSD",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.15.tgz",
|
||||||
"integrity": "sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==",
|
"integrity": "sha512-sJGE5faXnNQ1iXeqmRin7Ds/ru2fgCiaQZQQz3ZGIDtvbkeV85rAZ0QJFMDg0FrqsffZG96H1U9AQlNBRLsHVg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -3652,9 +3689,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.15.tgz",
|
||||||
"integrity": "sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==",
|
"integrity": "sha512-NLeHE7jUV6HcFKS504bpOohyi01zPXi2PXmjFfkzTph8xRxDdxkRsXm/xDO5uV5K3brrE1cCwbUYmFUSHR3u1w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3668,14 +3705,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/vite": {
|
"node_modules/@tailwindcss/vite": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.15.tgz",
|
||||||
"integrity": "sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA==",
|
"integrity": "sha512-B6s60MZRTUil+xKoZoGe6i0Iar5VuW+pmcGlda2FX+guDuQ1G1sjiIy1W0frneVpeL/ZjZ4KEgWZHNrIm++2qA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/node": "4.1.14",
|
"@tailwindcss/node": "4.1.15",
|
||||||
"@tailwindcss/oxide": "4.1.14",
|
"@tailwindcss/oxide": "4.1.15",
|
||||||
"tailwindcss": "4.1.14"
|
"tailwindcss": "4.1.15"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vite": "^5.2.0 || ^6 || ^7"
|
"vite": "^5.2.0 || ^6 || ^7"
|
||||||
@@ -3801,23 +3838,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/html2canvas": {
|
"node_modules/@types/html2canvas": {
|
||||||
"version": "0.5.35",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/html2canvas/-/html2canvas-0.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/@types/html2canvas/-/html2canvas-1.0.0.tgz",
|
||||||
"integrity": "sha512-1A2dtWZbOIZ+rUK8jmAx1We/EiNV+5vScpphU3AF14Vby6COIazi/9StosrvlVCqlQegRhsEgZf7QYOuWbwuuA==",
|
"integrity": "sha512-BJpVf+FIN9UERmzhbtUgpXj6XBZpG67FMgBLLoj9HZKd9XifcCpSV+UnFcwTZfEyun4U/KmCrrVOG7829L589w==",
|
||||||
|
"deprecated": "This is a stub types definition. html2canvas provides its own type definitions, so you do not need this installed.",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/jquery": "*"
|
"html2canvas": "*"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/jquery": {
|
|
||||||
"version": "3.5.33",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.33.tgz",
|
|
||||||
"integrity": "sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/sizzle": "*"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/minimatch": {
|
"node_modules/@types/minimatch": {
|
||||||
@@ -3859,13 +3887,6 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/sizzle": {
|
|
||||||
"version": "2.3.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz",
|
|
||||||
"integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/sortablejs": {
|
"node_modules/@types/sortablejs": {
|
||||||
"version": "1.15.8",
|
"version": "1.15.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz",
|
||||||
@@ -5046,15 +5067,6 @@
|
|||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chownr": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cipher-base": {
|
"node_modules/cipher-base": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz",
|
||||||
@@ -8088,21 +8100,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsdom": {
|
"node_modules/jsdom": {
|
||||||
"version": "27.0.0",
|
"version": "27.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz",
|
||||||
"integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==",
|
"integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asamuzakjp/dom-selector": "^6.5.4",
|
"@asamuzakjp/dom-selector": "^6.7.2",
|
||||||
"cssstyle": "^5.3.0",
|
"cssstyle": "^5.3.1",
|
||||||
"data-urls": "^6.0.0",
|
"data-urls": "^6.0.0",
|
||||||
"decimal.js": "^10.5.0",
|
"decimal.js": "^10.6.0",
|
||||||
"html-encoding-sniffer": "^4.0.0",
|
"html-encoding-sniffer": "^4.0.0",
|
||||||
"http-proxy-agent": "^7.0.2",
|
"http-proxy-agent": "^7.0.2",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"is-potential-custom-element-name": "^1.0.1",
|
"is-potential-custom-element-name": "^1.0.1",
|
||||||
"parse5": "^7.3.0",
|
"parse5": "^8.0.0",
|
||||||
"rrweb-cssom": "^0.8.0",
|
"rrweb-cssom": "^0.8.0",
|
||||||
"saxes": "^6.0.0",
|
"saxes": "^6.0.0",
|
||||||
"symbol-tree": "^3.2.4",
|
"symbol-tree": "^3.2.4",
|
||||||
@@ -8111,8 +8123,8 @@
|
|||||||
"webidl-conversions": "^8.0.0",
|
"webidl-conversions": "^8.0.0",
|
||||||
"whatwg-encoding": "^3.1.1",
|
"whatwg-encoding": "^3.1.1",
|
||||||
"whatwg-mimetype": "^4.0.0",
|
"whatwg-mimetype": "^4.0.0",
|
||||||
"whatwg-url": "^15.0.0",
|
"whatwg-url": "^15.1.0",
|
||||||
"ws": "^8.18.2",
|
"ws": "^8.18.3",
|
||||||
"xml-name-validator": "^5.0.0"
|
"xml-name-validator": "^5.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -8306,9 +8318,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
||||||
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
|
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"detect-libc": "^2.0.3"
|
"detect-libc": "^2.0.3"
|
||||||
@@ -8321,22 +8333,43 @@
|
|||||||
"url": "https://opencollective.com/parcel"
|
"url": "https://opencollective.com/parcel"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"lightningcss-darwin-arm64": "1.30.1",
|
"lightningcss-android-arm64": "1.30.2",
|
||||||
"lightningcss-darwin-x64": "1.30.1",
|
"lightningcss-darwin-arm64": "1.30.2",
|
||||||
"lightningcss-freebsd-x64": "1.30.1",
|
"lightningcss-darwin-x64": "1.30.2",
|
||||||
"lightningcss-linux-arm-gnueabihf": "1.30.1",
|
"lightningcss-freebsd-x64": "1.30.2",
|
||||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
"lightningcss-linux-arm-gnueabihf": "1.30.2",
|
||||||
"lightningcss-linux-arm64-musl": "1.30.1",
|
"lightningcss-linux-arm64-gnu": "1.30.2",
|
||||||
"lightningcss-linux-x64-gnu": "1.30.1",
|
"lightningcss-linux-arm64-musl": "1.30.2",
|
||||||
"lightningcss-linux-x64-musl": "1.30.1",
|
"lightningcss-linux-x64-gnu": "1.30.2",
|
||||||
"lightningcss-win32-arm64-msvc": "1.30.1",
|
"lightningcss-linux-x64-musl": "1.30.2",
|
||||||
"lightningcss-win32-x64-msvc": "1.30.1"
|
"lightningcss-win32-arm64-msvc": "1.30.2",
|
||||||
|
"lightningcss-win32-x64-msvc": "1.30.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-android-arm64": {
|
||||||
|
"version": "1.30.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
|
||||||
|
"integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-darwin-arm64": {
|
"node_modules/lightningcss-darwin-arm64": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
|
||||||
"integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
|
"integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -8354,9 +8387,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-darwin-x64": {
|
"node_modules/lightningcss-darwin-x64": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
|
||||||
"integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
|
"integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8374,9 +8407,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-freebsd-x64": {
|
"node_modules/lightningcss-freebsd-x64": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
|
||||||
"integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
|
"integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8394,9 +8427,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
|
||||||
"integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
|
"integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -8414,9 +8447,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
|
||||||
"integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
|
"integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -8434,9 +8467,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-arm64-musl": {
|
"node_modules/lightningcss-linux-arm64-musl": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
|
||||||
"integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
|
"integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -8454,9 +8487,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-x64-gnu": {
|
"node_modules/lightningcss-linux-x64-gnu": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
|
||||||
"integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
|
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8474,9 +8507,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-x64-musl": {
|
"node_modules/lightningcss-linux-x64-musl": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
|
||||||
"integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
|
"integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8494,9 +8527,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
|
||||||
"integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
|
"integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -8514,9 +8547,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-win32-x64-msvc": {
|
"node_modules/lightningcss-win32-x64-msvc": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
|
||||||
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
|
"integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8630,9 +8663,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/lucide": {
|
"node_modules/lucide": {
|
||||||
"version": "0.545.0",
|
"version": "0.546.0",
|
||||||
"resolved": "https://registry.npmjs.org/lucide/-/lucide-0.545.0.tgz",
|
"resolved": "https://registry.npmjs.org/lucide/-/lucide-0.546.0.tgz",
|
||||||
"integrity": "sha512-mrBH0upkb1TH8ZLkf0XERQAKMdTJ1C+Offr7eSvanmdQ7JrV8jkrFw5jgRAS8fygiZgG4X9mfEg/BXCL+mNMRw==",
|
"integrity": "sha512-YJES3MM/naQS4wJ0JLzTY3anooqWw5iTsPCffHbSMncdxJT2C5tmkCDAwIHMHG8TMtaQcu40KREMPss2qH/+yA==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/lz-string": {
|
"node_modules/lz-string": {
|
||||||
@@ -8855,23 +8888,12 @@
|
|||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minizlib": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"minipass": "^7.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mixin-deep": {
|
"node_modules/mixin-deep": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
||||||
@@ -9470,9 +9492,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/parse5": {
|
"node_modules/parse5": {
|
||||||
"version": "7.3.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
|
||||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
"integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -11263,9 +11285,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.15.tgz",
|
||||||
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
|
"integrity": "sha512-k2WLnWkYFkdpRv+Oby3EBXIyQC8/s1HOFMBUViwtAh6Z5uAozeUSMQlIsn/c6Q2iJzqG6aJT3wdPaRNj70iYxQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tapable": {
|
"node_modules/tapable": {
|
||||||
@@ -11281,22 +11303,6 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar": {
|
|
||||||
"version": "7.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz",
|
|
||||||
"integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"@isaacs/fs-minipass": "^4.0.0",
|
|
||||||
"chownr": "^3.0.0",
|
|
||||||
"minipass": "^7.1.2",
|
|
||||||
"minizlib": "^3.1.0",
|
|
||||||
"yallist": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/temp": {
|
"node_modules/temp": {
|
||||||
"version": "0.8.4",
|
"version": "0.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz",
|
||||||
@@ -12256,9 +12262,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.1.9",
|
"version": "7.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz",
|
||||||
"integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==",
|
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
@@ -12771,15 +12777,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/yallist": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
"version": "15.4.1",
|
"version": "15.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||||
|
|||||||
+11
-7
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "bento-pdf",
|
"name": "bento-pdf",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "1.0.2",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -15,28 +15,32 @@
|
|||||||
"test:coverage": "vitest run --coverage",
|
"test:coverage": "vitest run --coverage",
|
||||||
"test:watch": "vitest watch",
|
"test:watch": "vitest watch",
|
||||||
"build:docker": "vite build",
|
"build:docker": "vite build",
|
||||||
"format": "prettier --write ."
|
"format": "prettier --write .",
|
||||||
|
"release": "node scripts/release.js patch",
|
||||||
|
"release:minor": "node scripts/release.js minor",
|
||||||
|
"release:major": "node scripts/release.js major",
|
||||||
|
"serve:simple": "SIMPLE_MODE=true npm run build && npx serve dist -p 3000"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@types/blob-stream": "^0.1.33",
|
"@types/blob-stream": "^0.1.33",
|
||||||
"@types/html2canvas": "^0.5.35",
|
"@types/html2canvas": "^1.0.0",
|
||||||
"@types/pdfkit": "^0.17.3",
|
"@types/pdfkit": "^0.17.3",
|
||||||
"@types/sortablejs": "^1.15.8",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@types/utif": "^3.0.6",
|
"@types/utif": "^3.0.6",
|
||||||
"@vitest/coverage-v8": "^3.2.4",
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
"@vitest/ui": "^3.2.4",
|
"@vitest/ui": "^3.2.4",
|
||||||
"jsdom": "^27.0.0",
|
"jsdom": "^27.0.1",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"ts-migrate": "^0.1.35",
|
"ts-migrate": "^0.1.35",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.1.7",
|
"vite": "^7.1.11",
|
||||||
"vite-plugin-node-polyfills": "^0.24.0",
|
"vite-plugin-node-polyfills": "^0.24.0",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@neslinesli93/qpdf-wasm": "^0.3.0",
|
"@neslinesli93/qpdf-wasm": "^0.3.0",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.15",
|
||||||
"blob-stream": "^0.1.3",
|
"blob-stream": "^0.1.3",
|
||||||
"cropperjs": "^1.6.1",
|
"cropperjs": "^1.6.1",
|
||||||
"heic2any": "^0.0.4",
|
"heic2any": "^0.0.4",
|
||||||
@@ -44,7 +48,7 @@
|
|||||||
"javascript-obfuscator": "^4.1.1",
|
"javascript-obfuscator": "^4.1.1",
|
||||||
"jspdf": "^3.0.3",
|
"jspdf": "^3.0.3",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide": "^0.545.0",
|
"lucide": "^0.546.0",
|
||||||
"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",
|
||||||
|
|||||||
+71
-9
@@ -15,17 +15,21 @@
|
|||||||
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
||||||
<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">
|
||||||
<a
|
<div
|
||||||
href="index.html"
|
|
||||||
class="flex-shrink-0 flex items-center cursor-pointer"
|
class="flex-shrink-0 flex items-center cursor-pointer"
|
||||||
|
id="home-logo"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="images/favicon.svg"
|
src="images/favicon.svg"
|
||||||
alt="BentoPDF Logo"
|
alt="Bento PDF Logo"
|
||||||
class="h-8 w-8 mr-2"
|
class="h-8 w-8"
|
||||||
/>
|
/>
|
||||||
<span class="text-white font-bold text-xl">BentoPDF</span>
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
@@ -33,12 +37,69 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<div class="md:hidden flex items-center gap-4">
|
<!-- Mobile Hamburger Button -->
|
||||||
<a href="index.html" class="nav-link">Home</a>
|
<div class="md:hidden flex items-center">
|
||||||
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
<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"
|
||||||
|
aria-controls="mobile-menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Open main menu</span>
|
||||||
|
<!-- 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"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<!-- Close Icon -->
|
||||||
|
<svg
|
||||||
|
id="close-icon"
|
||||||
|
class="hidden h-6 w-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Menu Dropdown -->
|
||||||
|
<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">
|
||||||
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
|
<a href="index.html#tools-header" class="mobile-nav-link"
|
||||||
|
>All Tools</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div id="app" class="container mx-auto p-4 md:p-8">
|
<div id="app" class="container mx-auto p-4 md:p-8">
|
||||||
@@ -260,5 +321,6 @@
|
|||||||
<script>
|
<script>
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
</script>
|
</script>
|
||||||
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
### Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
||||||
|
|
||||||
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
### [v1.0.0](https://github.com/alam00000/bentopdf/compare/v0.0.1...v1.0.0)
|
||||||
|
|
||||||
|
> 20 October 2025
|
||||||
|
|
||||||
|
- feat(ocr): add whitelist presets and improve UI for OCR tool [`0e7c535`](https://github.com/alam00000/bentopdf/commit/0e7c53560a23d3e26a18904aa47ca99d3f10d9a6)
|
||||||
|
- Release v1.0.0 [`6ad6a7d`](https://github.com/alam00000/bentopdf/commit/6ad6a7d7305eb77fe9ebdb55d93059bf92304a98)
|
||||||
|
|
||||||
|
#### v0.0.1
|
||||||
|
|
||||||
|
> 20 October 2025
|
||||||
|
|
||||||
|
- feat: add Simple Mode for internal organizational use [`#64`](https://github.com/alam00000/bentopdf/pull/64)
|
||||||
|
- Feature/reverse multi pdfs [`#58`](https://github.com/alam00000/bentopdf/pull/58)
|
||||||
|
- added description to Unraid XML [`#65`](https://github.com/alam00000/bentopdf/pull/65)
|
||||||
|
- Added hamburger Navbar [`#62`](https://github.com/alam00000/bentopdf/pull/62)
|
||||||
|
- feat(versioning): Added version management sysytem [`#52`](https://github.com/alam00000/bentopdf/pull/52)
|
||||||
|
- Fix#20 : fixed the tesseract issue [`#60`](https://github.com/alam00000/bentopdf/pull/60)
|
||||||
|
- feat: add search bar keyboard shortcut [`#49`](https://github.com/alam00000/bentopdf/pull/49)
|
||||||
|
- feat(pdf-tools): add attachments feature to embed files in PDFs [`#53`](https://github.com/alam00000/bentopdf/pull/53)
|
||||||
|
- feat(pdf-tools): add linearize pdf tool for web optimization [`#51`](https://github.com/alam00000/bentopdf/pull/51)
|
||||||
|
- feat: add support for multiple blank pages insertion [`#42`](https://github.com/alam00000/bentopdf/pull/42)
|
||||||
|
- Multiupload handle encrypted pdf [`#43`](https://github.com/alam00000/bentopdf/pull/43)
|
||||||
|
- feat:Setup Prettier for code formatting [`#30`](https://github.com/alam00000/bentopdf/pull/30)
|
||||||
|
- created png as unraid does not supprot svg [`#24`](https://github.com/alam00000/bentopdf/pull/24)
|
||||||
|
- adding unraid template [`#23`](https://github.com/alam00000/bentopdf/pull/23)
|
||||||
|
- feat(pdf-tools): add alternate merge tool for mixing pdf pages [`#21`](https://github.com/alam00000/bentopdf/pull/21)
|
||||||
|
- feat: add remove blank pages tool with sensitivity control [`#19`](https://github.com/alam00000/bentopdf/pull/19)
|
||||||
|
- Posterize [`#17`](https://github.com/alam00000/bentopdf/pull/17)
|
||||||
|
- feat: cicd for build and push to docker registry [`#15`](https://github.com/alam00000/bentopdf/pull/15)
|
||||||
|
- Fix(posterize): Resolve incorrect vertical tiling/cropping [`#10`](https://github.com/alam00000/bentopdf/pull/10)
|
||||||
|
- feat: add initial project setup with core PDF tools and utilities [`6712973`](https://github.com/alam00000/bentopdf/commit/671297320ec6ff93d05ad150836dd0a2b291c83c)
|
||||||
|
- feat(test): add comprehensive test suite and testing infrastructure [`5e72d7c`](https://github.com/alam00000/bentopdf/commit/5e72d7ca2debe62fc3e74606b967cdb8334ce827)
|
||||||
|
- refactor: fix type errors and improve type safety in PDF logic [`cc83d79`](https://github.com/alam00000/bentopdf/commit/cc83d7951f8748bb3b40223fc1ffd5d397faad72)
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const packageJsonPath = path.join(__dirname, '../package.json');
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
|
||||||
|
function getCurrentVersion() {
|
||||||
|
return packageJson.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateVersion(type) {
|
||||||
|
const currentVersion = getCurrentVersion();
|
||||||
|
const [major, minor, patch] = currentVersion.split('.').map(Number);
|
||||||
|
|
||||||
|
let newVersion;
|
||||||
|
switch (type) {
|
||||||
|
case 'major':
|
||||||
|
newVersion = `${major + 1}.0.0`;
|
||||||
|
break;
|
||||||
|
case 'minor':
|
||||||
|
newVersion = `${major}.${minor + 1}.0`;
|
||||||
|
break;
|
||||||
|
case 'patch':
|
||||||
|
default:
|
||||||
|
newVersion = `${major}.${minor}.${patch + 1}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
packageJson.version = newVersion;
|
||||||
|
fs.writeFileSync(
|
||||||
|
packageJsonPath,
|
||||||
|
JSON.stringify(packageJson, null, 2) + '\n'
|
||||||
|
);
|
||||||
|
return newVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGitTag(version) {
|
||||||
|
const tagName = `v${version}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if tag already exists
|
||||||
|
execSync(`git rev-parse "v${version}" >/dev/null 2>&1`, {
|
||||||
|
stdio: 'ignore',
|
||||||
|
});
|
||||||
|
console.log(`✅ Tag v${version} already exists`);
|
||||||
|
return tagName;
|
||||||
|
} catch {
|
||||||
|
// Tag doesn't exist, create it
|
||||||
|
execSync(`git tag -a "v${version}" -m "Release v${version}"`, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
console.log(`✅ Created tag v${version}`);
|
||||||
|
return tagName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const type = process.argv[2] || 'patch';
|
||||||
|
|
||||||
|
if (!['major', 'minor', 'patch'].includes(type)) {
|
||||||
|
console.error('❌ Invalid version type. Use: major, minor, or patch');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🚀 Releasing ${type} version...`);
|
||||||
|
|
||||||
|
// 1. Update version in package.json
|
||||||
|
const newVersion = updateVersion(type);
|
||||||
|
console.log(`📦 Updated version to ${newVersion}`);
|
||||||
|
|
||||||
|
// 2. Add and commit changes
|
||||||
|
execSync('git add package.json', { stdio: 'inherit' });
|
||||||
|
execSync(`git commit -m "Release v${newVersion}"`, { stdio: 'inherit' });
|
||||||
|
console.log(`💾 Committed version change`);
|
||||||
|
|
||||||
|
// 3. Create git tag
|
||||||
|
const tagName = createGitTag(newVersion);
|
||||||
|
|
||||||
|
// 4. Push everything to main
|
||||||
|
console.log(`📤 Pushing to main...`);
|
||||||
|
execSync('git push origin main', { stdio: 'inherit' });
|
||||||
|
execSync(`git push origin ${tagName}`, { stdio: 'inherit' });
|
||||||
|
|
||||||
|
console.log(`🎉 Release v${newVersion} complete!`);
|
||||||
|
console.log(`📦 Docker image: bentopdf/bentopdf:${newVersion}`);
|
||||||
|
console.log(
|
||||||
|
`🏷️ GitHub release: https://github.com/AltuisticIsopod/bentopdf/releases/tag/${tagName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -18,7 +18,6 @@ export const singlePdfLoadTools = [
|
|||||||
'add-header-footer',
|
'add-header-footer',
|
||||||
'invert-colors',
|
'invert-colors',
|
||||||
'view-metadata',
|
'view-metadata',
|
||||||
'reverse-pages',
|
|
||||||
'crop',
|
'crop',
|
||||||
'redact',
|
'redact',
|
||||||
'pdf-to-bmp',
|
'pdf-to-bmp',
|
||||||
@@ -38,6 +37,7 @@ export const singlePdfLoadTools = [
|
|||||||
'form-filler',
|
'form-filler',
|
||||||
'posterize',
|
'posterize',
|
||||||
'remove-blank-pages',
|
'remove-blank-pages',
|
||||||
|
'add-attachments',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const simpleTools = [
|
export const simpleTools = [
|
||||||
@@ -61,4 +61,5 @@ export const multiFileTools = [
|
|||||||
'tiff-to-pdf',
|
'tiff-to-pdf',
|
||||||
'alternate-merge',
|
'alternate-merge',
|
||||||
'linearize',
|
'linearize',
|
||||||
|
'reverse-pages',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -282,6 +282,12 @@ export const categories = [
|
|||||||
icon: 'files',
|
icon: 'files',
|
||||||
subtitle: 'Duplicate, reorder, and delete pages.',
|
subtitle: 'Duplicate, reorder, and delete pages.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'add-attachments',
|
||||||
|
name: 'Add Attachments',
|
||||||
|
icon: 'paperclip',
|
||||||
|
subtitle: 'Embed one or more files into your PDF.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'split',
|
id: 'split',
|
||||||
name: 'Split PDF',
|
name: 'Split PDF',
|
||||||
@@ -383,7 +389,7 @@ export const categories = [
|
|||||||
icon: 'ruler-dimension-line',
|
icon: 'ruler-dimension-line',
|
||||||
subtitle: 'Standardize all pages to a uniform size.',
|
subtitle: 'Standardize all pages to a uniform size.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'linearize',
|
id: 'linearize',
|
||||||
name: 'Linearize PDF',
|
name: 'Linearize PDF',
|
||||||
icon: 'zap',
|
icon: 'zap',
|
||||||
|
|||||||
+138
-18
@@ -7,7 +7,7 @@ import {
|
|||||||
renderFileDisplay,
|
renderFileDisplay,
|
||||||
switchView,
|
switchView,
|
||||||
} from '../ui.js';
|
} from '../ui.js';
|
||||||
import { readFileAsArrayBuffer } from '../utils/helpers.js';
|
import { formatIsoDate, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||||
import { setupCanvasEditor } from '../canvasEditor.js';
|
import { setupCanvasEditor } from '../canvasEditor.js';
|
||||||
import { toolLogic } from '../logic/index.js';
|
import { toolLogic } from '../logic/index.js';
|
||||||
import { renderDuplicateOrganizeThumbnails } from '../logic/duplicate-organize.js';
|
import { renderDuplicateOrganizeThumbnails } from '../logic/duplicate-organize.js';
|
||||||
@@ -124,12 +124,13 @@ async function handleSinglePdfUpload(toolId, file) {
|
|||||||
const pdfjsDoc = await pdfjsLib.getDocument({
|
const pdfjsDoc = await pdfjsLib.getDocument({
|
||||||
data: pdfBytes as ArrayBuffer,
|
data: pdfBytes as ArrayBuffer,
|
||||||
}).promise;
|
}).promise;
|
||||||
const [metadata, fieldObjects] = await Promise.all([
|
const [metadataResult, fieldObjects] = await Promise.all([
|
||||||
pdfjsDoc.getMetadata(),
|
pdfjsDoc.getMetadata(),
|
||||||
pdfjsDoc.getFieldObjects(),
|
pdfjsDoc.getFieldObjects(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { info, metadata: rawXmpString } = metadata;
|
const { info, metadata } = metadataResult;
|
||||||
|
const rawXmpString = metadata ? metadata.getRaw() : null;
|
||||||
|
|
||||||
resultsDiv.textContent = ''; // Clear safely
|
resultsDiv.textContent = ''; // Clear safely
|
||||||
|
|
||||||
@@ -184,14 +185,29 @@ async function handleSinglePdfUpload(toolId, file) {
|
|||||||
const infoSection = createSection('Info Dictionary');
|
const infoSection = createSection('Info Dictionary');
|
||||||
if (info && Object.keys(info).length > 0) {
|
if (info && Object.keys(info).length > 0) {
|
||||||
for (const key in info) {
|
for (const key in info) {
|
||||||
let value = info[key] || '- Not Set -';
|
let value = info[key];
|
||||||
if (
|
let displayValue;
|
||||||
|
|
||||||
|
if (value === null || typeof value === 'undefined') {
|
||||||
|
displayValue = '- Not Set -';
|
||||||
|
} else if (typeof value === 'object' && value.name) {
|
||||||
|
displayValue = value.name;
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
try {
|
||||||
|
displayValue = JSON.stringify(value);
|
||||||
|
} catch {
|
||||||
|
displayValue = '[object Object]';
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
(key === 'CreationDate' || key === 'ModDate') &&
|
(key === 'CreationDate' || key === 'ModDate') &&
|
||||||
typeof value === 'string'
|
typeof value === 'string'
|
||||||
) {
|
) {
|
||||||
value = parsePdfDate(value);
|
displayValue = parsePdfDate(value);
|
||||||
|
} else {
|
||||||
|
displayValue = String(value);
|
||||||
}
|
}
|
||||||
infoSection.ul.appendChild(createListItem(key, String(value)));
|
|
||||||
|
infoSection.ul.appendChild(createListItem(key, displayValue));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
infoSection.ul.innerHTML = `<li><span class="text-gray-500 italic">- No Info Dictionary data found -</span></li>`;
|
infoSection.ul.innerHTML = `<li><span class="text-gray-500 italic">- No Info Dictionary data found -</span></li>`;
|
||||||
@@ -212,19 +228,119 @@ async function handleSinglePdfUpload(toolId, file) {
|
|||||||
}
|
}
|
||||||
resultsDiv.appendChild(fieldsSection.wrapper);
|
resultsDiv.appendChild(fieldsSection.wrapper);
|
||||||
|
|
||||||
const xmpSection = createSection('XMP Metadata (Raw XML)');
|
const createXmpListItem = (key, value, indent = 0) => {
|
||||||
const xmpContainer = document.createElement('div');
|
const li = document.createElement('li');
|
||||||
xmpContainer.className =
|
li.className = 'flex flex-col sm:flex-row';
|
||||||
'bg-gray-900 p-4 rounded-lg border border-gray-700';
|
|
||||||
|
const strong = document.createElement('strong');
|
||||||
|
strong.className = 'w-56 flex-shrink-0 text-gray-400';
|
||||||
|
strong.textContent = key;
|
||||||
|
strong.style.paddingLeft = `${indent * 1.2}rem`;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'flex-grow text-white break-all';
|
||||||
|
div.textContent = value;
|
||||||
|
|
||||||
|
li.append(strong, div);
|
||||||
|
return li;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createXmpHeaderItem = (key, indent = 0) => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'flex pt-2';
|
||||||
|
const strong = document.createElement('strong');
|
||||||
|
strong.className = 'w-full flex-shrink-0 text-gray-300 font-medium';
|
||||||
|
strong.textContent = key;
|
||||||
|
strong.style.paddingLeft = `${indent * 1.2}rem`;
|
||||||
|
li.append(strong);
|
||||||
|
return li;
|
||||||
|
};
|
||||||
|
|
||||||
|
const appendXmpNodes = (xmlNode, ulElement, indentLevel) => {
|
||||||
|
const xmpDateKeys = [
|
||||||
|
'xap:CreateDate',
|
||||||
|
'xap:ModifyDate',
|
||||||
|
'xap:MetadataDate',
|
||||||
|
];
|
||||||
|
|
||||||
|
const childNodes = Array.from(xmlNode.children);
|
||||||
|
|
||||||
|
for (const child of childNodes) {
|
||||||
|
if ((child as Element).nodeType !== 1) continue;
|
||||||
|
|
||||||
|
let key = (child as Element).tagName;
|
||||||
|
const elementChildren = Array.from((child as Element).children).filter(
|
||||||
|
(c) => c.nodeType === 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (key === 'rdf:li') {
|
||||||
|
appendXmpNodes(child, ulElement, indentLevel);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (key === 'rdf:Alt') {
|
||||||
|
key = '(alt container)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(child as Element).getAttribute('rdf:parseType') === 'Resource' &&
|
||||||
|
elementChildren.length === 0
|
||||||
|
) {
|
||||||
|
ulElement.appendChild(
|
||||||
|
createXmpListItem(key, '(Empty Resource)', indentLevel)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementChildren.length > 0) {
|
||||||
|
ulElement.appendChild(createXmpHeaderItem(key, indentLevel));
|
||||||
|
appendXmpNodes(child, ulElement, indentLevel + 1);
|
||||||
|
} else {
|
||||||
|
let value = (child as Element).textContent.trim();
|
||||||
|
if (value) {
|
||||||
|
if (xmpDateKeys.includes(key)) {
|
||||||
|
value = formatIsoDate(value);
|
||||||
|
}
|
||||||
|
ulElement.appendChild(
|
||||||
|
createXmpListItem(key, value, indentLevel)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const xmpSection = createSection('XMP Metadata');
|
||||||
if (rawXmpString) {
|
if (rawXmpString) {
|
||||||
const pre = document.createElement('pre');
|
try {
|
||||||
pre.className = 'text-xs text-gray-300 whitespace-pre-wrap break-all';
|
const parser = new DOMParser();
|
||||||
pre.textContent = String(rawXmpString);
|
const xmlDoc = parser.parseFromString(
|
||||||
xmpContainer.appendChild(pre);
|
rawXmpString,
|
||||||
|
'application/xml'
|
||||||
|
);
|
||||||
|
|
||||||
|
const descriptions = xmlDoc.getElementsByTagName('rdf:Description');
|
||||||
|
if (descriptions.length > 0) {
|
||||||
|
for (const desc of descriptions) {
|
||||||
|
appendXmpNodes(desc, xmpSection.ul, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
appendXmpNodes(xmlDoc.documentElement, xmpSection.ul, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xmpSection.ul.children.length === 0) {
|
||||||
|
xmpSection.ul.innerHTML = `<li><span class="text-gray-500 italic">- No parseable XMP properties found -</span></li>`;
|
||||||
|
}
|
||||||
|
} catch (xmlError) {
|
||||||
|
console.error('Failed to parse XMP XML:', xmlError);
|
||||||
|
xmpSection.ul.innerHTML = `<li><span class="text-red-500 italic">- Error parsing XMP XML. Displaying raw. -</span></li>`;
|
||||||
|
const pre = document.createElement('pre');
|
||||||
|
pre.className =
|
||||||
|
'text-xs text-gray-300 whitespace-pre-wrap break-all';
|
||||||
|
pre.textContent = rawXmpString;
|
||||||
|
xmpSection.ul.appendChild(pre);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
xmpContainer.innerHTML = `<p class="text-gray-500 italic">- No XMP metadata found -</p>`;
|
xmpSection.ul.innerHTML = `<li><span class="text-gray-500 italic">- No XMP metadata found -</span></li>`;
|
||||||
}
|
}
|
||||||
xmpSection.wrapper.appendChild(xmpContainer);
|
|
||||||
resultsDiv.appendChild(xmpSection.wrapper);
|
resultsDiv.appendChild(xmpSection.wrapper);
|
||||||
|
|
||||||
resultsDiv.classList.remove('hidden');
|
resultsDiv.classList.remove('hidden');
|
||||||
@@ -324,7 +440,11 @@ async function handleSinglePdfUpload(toolId, file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleMultiFileUpload(toolId) {
|
async function handleMultiFileUpload(toolId) {
|
||||||
if (toolId === 'merge' || toolId === 'alternate-merge') {
|
if (
|
||||||
|
toolId === 'merge' ||
|
||||||
|
toolId === 'alternate-merge' ||
|
||||||
|
toolId === 'reverse-pages'
|
||||||
|
) {
|
||||||
const pdfFilesUnloaded: File[] = [];
|
const pdfFilesUnloaded: File[] = [];
|
||||||
|
|
||||||
state.files.forEach((file) => {
|
state.files.forEach((file) => {
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { showLoader, hideLoader, showAlert } from '../ui';
|
||||||
|
import { readFileAsArrayBuffer, downloadFile } from '../utils/helpers';
|
||||||
|
import { state } from '../state';
|
||||||
|
let attachments: File[] = [];
|
||||||
|
|
||||||
|
export async function addAttachments() {
|
||||||
|
if (!state.pdfDoc) {
|
||||||
|
showAlert('Error', 'Main PDF is not loaded.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (attachments.length === 0) {
|
||||||
|
showAlert('No Files', 'Please select at least one file to attach.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoader('Embedding files into PDF...');
|
||||||
|
try {
|
||||||
|
const pdfDoc = state.pdfDoc;
|
||||||
|
|
||||||
|
for (let i = 0; i < attachments.length; i++) {
|
||||||
|
const file = attachments[i];
|
||||||
|
showLoader(`Attaching ${file.name} (${i + 1}/${attachments.length})...`);
|
||||||
|
|
||||||
|
const fileBytes = await readFileAsArrayBuffer(file);
|
||||||
|
|
||||||
|
await pdfDoc.attach(fileBytes as ArrayBuffer, file.name, {
|
||||||
|
mimeType: file.type || 'application/octet-stream',
|
||||||
|
description: `Attached file: ${file.name}`,
|
||||||
|
creationDate: new Date(),
|
||||||
|
modificationDate: new Date(file.lastModified),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfBytes = await pdfDoc.save();
|
||||||
|
downloadFile(
|
||||||
|
new Blob([pdfBytes], { type: 'application/pdf' }),
|
||||||
|
`attached-${state.files[0].name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
showAlert(
|
||||||
|
'Success',
|
||||||
|
`${attachments.length} file(s) attached successfully.`
|
||||||
|
);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error attaching files:', error);
|
||||||
|
showAlert('Error', `Failed to attach files: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
hideLoader();
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (fileListDiv) fileListDiv.innerHTML = '';
|
||||||
|
if (attachmentInput) attachmentInput.value = '';
|
||||||
|
if (processBtn) {
|
||||||
|
processBtn.disabled = true;
|
||||||
|
processBtn.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let isSetup = false; // Prevent duplicate setup
|
||||||
|
|
||||||
|
export function setupAddAttachmentsTool() {
|
||||||
|
if (isSetup) return; // Already set up
|
||||||
|
isSetup = true;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (!optionsDiv || !attachmentInput || !fileListDiv || !processBtn) {
|
||||||
|
console.error('Attachment tool UI elements not found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsDiv.classList.remove('hidden');
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
processBtn.disabled = false;
|
||||||
|
processBtn.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
clearAttachments();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
processBtn.onclick = addAttachments;
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ export async function addBlankPage() {
|
|||||||
const pageNumberInput = document.getElementById('page-number').value;
|
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
|
// @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;
|
const pageCountInput = document.getElementById('page-count').value;
|
||||||
|
|
||||||
if (pageNumberInput.trim() === '') {
|
if (pageNumberInput.trim() === '') {
|
||||||
showAlert('Invalid Input', 'Please enter a page number.');
|
showAlert('Invalid Input', 'Please enter a page number.');
|
||||||
return;
|
return;
|
||||||
@@ -31,7 +31,10 @@ export async function addBlankPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isNaN(pageCount) || pageCount < 1) {
|
if (isNaN(pageCount) || pageCount < 1) {
|
||||||
showAlert('Invalid Input', 'Please enter a valid number of pages (1 or more).');
|
showAlert(
|
||||||
|
'Invalid Input',
|
||||||
|
'Please enter a valid number of pages (1 or more).'
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import {
|
|||||||
} from './remove-blank-pages.js';
|
} from './remove-blank-pages.js';
|
||||||
import { alternateMerge, setupAlternateMergeTool } from './alternate-merge.js';
|
import { alternateMerge, setupAlternateMergeTool } from './alternate-merge.js';
|
||||||
import { linearizePdf } from './linearize.js';
|
import { linearizePdf } from './linearize.js';
|
||||||
|
import { addAttachments, setupAddAttachmentsTool } from './add-attachments.js';
|
||||||
|
|
||||||
export const toolLogic = {
|
export const toolLogic = {
|
||||||
merge: { process: merge, setup: setupMergeTool },
|
merge: { process: merge, setup: setupMergeTool },
|
||||||
@@ -130,4 +131,8 @@ export const toolLogic = {
|
|||||||
setup: setupAlternateMergeTool,
|
setup: setupAlternateMergeTool,
|
||||||
},
|
},
|
||||||
linearize: linearizePdf,
|
linearize: linearizePdf,
|
||||||
|
'add-attachments': {
|
||||||
|
process: addAttachments,
|
||||||
|
setup: setupAddAttachmentsTool,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+20
-19
@@ -30,7 +30,9 @@ async function initializeQpdf() {
|
|||||||
|
|
||||||
export async function linearizePdf() {
|
export async function linearizePdf() {
|
||||||
// Check if there are files and at least one PDF
|
// Check if there are files and at least one PDF
|
||||||
const pdfFiles = state.files.filter((file: File) => file.type === 'application/pdf');
|
const pdfFiles = state.files.filter(
|
||||||
|
(file: File) => file.type === 'application/pdf'
|
||||||
|
);
|
||||||
if (!pdfFiles || pdfFiles.length === 0) {
|
if (!pdfFiles || pdfFiles.length === 0) {
|
||||||
showAlert('No PDF Files', 'Please upload at least one PDF file.');
|
showAlert('No PDF Files', 'Please upload at least one PDF file.');
|
||||||
return;
|
return;
|
||||||
@@ -43,12 +45,12 @@ export async function linearizePdf() {
|
|||||||
let errorCount = 0;
|
let errorCount = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
qpdf = await initializeQpdf();
|
qpdf = await initializeQpdf();
|
||||||
|
|
||||||
for (let i = 0; i < pdfFiles.length; i++) {
|
for (let i = 0; i < pdfFiles.length; i++) {
|
||||||
const file = pdfFiles[i];
|
const file = pdfFiles[i];
|
||||||
const inputPath = `/input_${i}.pdf`;
|
const inputPath = `/input_${i}.pdf`;
|
||||||
const outputPath = `/output_${i}.pdf`;
|
const outputPath = `/output_${i}.pdf`;
|
||||||
|
|
||||||
showLoader(`Optimizing ${file.name} (${i + 1}/${pdfFiles.length})...`);
|
showLoader(`Optimizing ${file.name} (${i + 1}/${pdfFiles.length})...`);
|
||||||
|
|
||||||
@@ -58,23 +60,20 @@ export async function linearizePdf() {
|
|||||||
|
|
||||||
qpdf.FS.writeFile(inputPath, uint8Array);
|
qpdf.FS.writeFile(inputPath, uint8Array);
|
||||||
|
|
||||||
const args = [
|
const args = [inputPath, '--linearize', outputPath];
|
||||||
inputPath,
|
|
||||||
'--linearize',
|
|
||||||
outputPath,
|
|
||||||
];
|
|
||||||
|
|
||||||
qpdf.callMain(args);
|
qpdf.callMain(args);
|
||||||
|
|
||||||
const outputFile = qpdf.FS.readFile(outputPath, { encoding: 'binary' });
|
const outputFile = qpdf.FS.readFile(outputPath, { encoding: 'binary' });
|
||||||
if (!outputFile || outputFile.length === 0) {
|
if (!outputFile || outputFile.length === 0) {
|
||||||
console.error(`Linearization resulted in an empty file for ${file.name}.`);
|
console.error(
|
||||||
throw new Error(`Processing failed for ${file.name}.`);
|
`Linearization resulted in an empty file for ${file.name}.`
|
||||||
}
|
);
|
||||||
|
throw new Error(`Processing failed for ${file.name}.`);
|
||||||
|
}
|
||||||
|
|
||||||
zip.file(`linearized-${file.name}`, outputFile, { binary: true });
|
zip.file(`linearized-${file.name}`, outputFile, { binary: true });
|
||||||
successCount++;
|
successCount++;
|
||||||
|
|
||||||
} catch (fileError: any) {
|
} catch (fileError: any) {
|
||||||
errorCount++;
|
errorCount++;
|
||||||
console.error(`Failed to linearize ${file.name}:`, fileError);
|
console.error(`Failed to linearize ${file.name}:`, fileError);
|
||||||
@@ -91,13 +90,16 @@ export async function linearizePdf() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (cleanupError) {
|
} catch (cleanupError) {
|
||||||
console.warn(`Failed to cleanup WASM FS for ${file.name}:`, cleanupError);
|
console.warn(
|
||||||
|
`Failed to cleanup WASM FS for ${file.name}:`,
|
||||||
|
cleanupError
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (successCount === 0) {
|
if (successCount === 0) {
|
||||||
throw new Error('No PDF files could be linearized.');
|
throw new Error('No PDF files could be linearized.');
|
||||||
}
|
}
|
||||||
|
|
||||||
showLoader('Generating ZIP file...');
|
showLoader('Generating ZIP file...');
|
||||||
@@ -109,7 +111,6 @@ export async function linearizePdf() {
|
|||||||
alertMessage += ` ${errorCount} file(s) failed.`;
|
alertMessage += ` ${errorCount} file(s) failed.`;
|
||||||
}
|
}
|
||||||
showAlert('Processing Complete', alertMessage);
|
showAlert('Processing Complete', alertMessage);
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Linearization process error:', error);
|
console.error('Linearization process error:', error);
|
||||||
showAlert(
|
showAlert(
|
||||||
@@ -119,4 +120,4 @@ export async function linearizePdf() {
|
|||||||
} finally {
|
} finally {
|
||||||
hideLoader();
|
hideLoader();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-15
@@ -113,11 +113,9 @@ async function runOCR() {
|
|||||||
tessjs_create_hocr: '1',
|
tessjs_create_hocr: '1',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (whitelist.trim()) {
|
await worker.setParameters({
|
||||||
await worker.setParameters({
|
tessedit_char_whitelist: whitelist,
|
||||||
tessedit_char_whitelist: whitelist.trim(),
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||||
const pdf = await pdfjsLib.getDocument(
|
const pdf = await pdfjsLib.getDocument(
|
||||||
@@ -144,7 +142,11 @@ async function runOCR() {
|
|||||||
binarizeCanvas(context);
|
binarizeCanvas(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await worker.recognize(canvas);
|
const result = await worker.recognize(
|
||||||
|
canvas,
|
||||||
|
{},
|
||||||
|
{ text: true, hocr: true }
|
||||||
|
);
|
||||||
const data = result.data;
|
const data = result.data;
|
||||||
const newPage = newPdfDoc.addPage([viewport.width, viewport.height]);
|
const newPage = newPdfDoc.addPage([viewport.width, viewport.height]);
|
||||||
const pngImageBytes = await new Promise((resolve) =>
|
const pngImageBytes = await new Promise((resolve) =>
|
||||||
@@ -281,9 +283,51 @@ export function setupOcrTool() {
|
|||||||
);
|
);
|
||||||
const processBtn = document.getElementById('process-btn');
|
const processBtn = document.getElementById('process-btn');
|
||||||
|
|
||||||
|
// Whitelist presets
|
||||||
|
const whitelistPresets: Record<string, string> = {
|
||||||
|
alphanumeric:
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,!?-\'"',
|
||||||
|
'numbers-currency': '0123456789$€£¥.,- ',
|
||||||
|
'letters-only': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ',
|
||||||
|
'numbers-only': '0123456789',
|
||||||
|
invoice: '0123456789$.,/-#: ',
|
||||||
|
forms:
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,()-_/@#:',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle whitelist preset selection
|
||||||
|
const presetSelect = document.getElementById(
|
||||||
|
'whitelist-preset'
|
||||||
|
) as HTMLSelectElement;
|
||||||
|
const whitelistInput = document.getElementById(
|
||||||
|
'ocr-whitelist'
|
||||||
|
) as HTMLInputElement;
|
||||||
|
|
||||||
|
presetSelect?.addEventListener('change', (e) => {
|
||||||
|
const preset = (e.target as HTMLSelectElement).value;
|
||||||
|
if (preset && preset !== 'custom') {
|
||||||
|
whitelistInput.value = whitelistPresets[preset];
|
||||||
|
whitelistInput.disabled = true;
|
||||||
|
} else {
|
||||||
|
whitelistInput.disabled = false;
|
||||||
|
if (preset === '') {
|
||||||
|
whitelistInput.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle details toggle icon rotation
|
||||||
|
document.querySelectorAll('details').forEach((details) => {
|
||||||
|
details.addEventListener('toggle', () => {
|
||||||
|
const icon = details.querySelector('.details-icon') as HTMLElement;
|
||||||
|
if (icon) {
|
||||||
|
icon.style.transform = details.open ? 'rotate(180deg)' : 'rotate(0deg)';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
langSearch.addEventListener('input', () => {
|
langSearch.addEventListener('input', () => {
|
||||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
const searchTerm = (langSearch as HTMLInputElement).value.toLowerCase();
|
||||||
const searchTerm = langSearch.value.toLowerCase();
|
|
||||||
langList.querySelectorAll('label').forEach((label) => {
|
langList.querySelectorAll('label').forEach((label) => {
|
||||||
label.style.display = label.textContent.toLowerCase().includes(searchTerm)
|
label.style.display = label.textContent.toLowerCase().includes(searchTerm)
|
||||||
? ''
|
? ''
|
||||||
@@ -291,19 +335,14 @@ export function setupOcrTool() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the display of selected languages
|
|
||||||
langList.addEventListener('change', () => {
|
langList.addEventListener('change', () => {
|
||||||
const selected = Array.from(
|
const selected = Array.from(
|
||||||
langList.querySelectorAll('.lang-checkbox:checked')
|
langList.querySelectorAll('.lang-checkbox:checked')
|
||||||
)
|
).map((cb) => tesseractLanguages[(cb as HTMLInputElement).value]);
|
||||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'Element'.
|
|
||||||
.map((cb) => tesseractLanguages[cb.value]);
|
|
||||||
selectedLangsDisplay.textContent =
|
selectedLangsDisplay.textContent =
|
||||||
selected.length > 0 ? selected.join(', ') : 'None';
|
selected.length > 0 ? selected.join(', ') : 'None';
|
||||||
// @ts-expect-error TS(2339) FIXME: Property 'disabled' does not exist on type 'HTMLEl... Remove this comment to see the full error message
|
(processBtn as HTMLButtonElement).disabled = selected.length === 0;
|
||||||
processBtn.disabled = selected.length === 0;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attach the main OCR function to the process button
|
|
||||||
processBtn.addEventListener('click', runOCR);
|
processBtn.addEventListener('click', runOCR);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,29 +3,40 @@ import { downloadFile } from '../utils/helpers.js';
|
|||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
|
|
||||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||||
|
import JSZip from 'jszip';
|
||||||
|
|
||||||
export async function reversePages() {
|
export async function reversePages() {
|
||||||
if (!state.pdfDoc) {
|
const pdfDocs = state.files.filter(
|
||||||
|
(file: File) => file.type === 'application/pdf'
|
||||||
|
);
|
||||||
|
if (!pdfDocs.length) {
|
||||||
showAlert('Error', 'PDF not loaded.');
|
showAlert('Error', 'PDF not loaded.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showLoader('Reversing page order...');
|
showLoader('Reversing page order...');
|
||||||
try {
|
try {
|
||||||
const newPdf = await PDFLibDocument.create();
|
const zip = new JSZip();
|
||||||
const pageCount = state.pdfDoc.getPageCount();
|
for (let j = 0; j < pdfDocs.length; j++) {
|
||||||
const reversedIndices = Array.from(
|
const file = pdfDocs[j];
|
||||||
{ length: pageCount },
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
(_, i) => pageCount - 1 - i
|
const pdfDoc = await PDFLibDocument.load(arrayBuffer);
|
||||||
);
|
const newPdf = await PDFLibDocument.create();
|
||||||
|
const pageCount = pdfDoc.getPageCount();
|
||||||
|
const reversedIndices = Array.from(
|
||||||
|
{ length: pageCount },
|
||||||
|
(_, i) => pageCount - 1 - i
|
||||||
|
);
|
||||||
|
|
||||||
const copiedPages = await newPdf.copyPages(state.pdfDoc, reversedIndices);
|
const copiedPages = await newPdf.copyPages(pdfDoc, reversedIndices);
|
||||||
copiedPages.forEach((page: any) => newPdf.addPage(page));
|
copiedPages.forEach((page: any) => newPdf.addPage(page));
|
||||||
|
|
||||||
const newPdfBytes = await newPdf.save();
|
const newPdfBytes = await newPdf.save();
|
||||||
downloadFile(
|
const originalName = file.name.replace(/\.pdf$/i, '');
|
||||||
new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }),
|
const fileName = `${originalName}_reversed.pdf`;
|
||||||
'reversed.pdf'
|
zip.file(fileName, newPdfBytes);
|
||||||
);
|
}
|
||||||
|
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||||
|
downloadFile(zipBlob, 'reversed_pdfs.zip');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
showAlert('Error', 'Could not reverse the PDF pages.');
|
showAlert('Error', 'Could not reverse the PDF pages.');
|
||||||
|
|||||||
+134
@@ -11,6 +11,117 @@ const init = () => {
|
|||||||
import.meta.url
|
import.meta.url
|
||||||
).toString();
|
).toString();
|
||||||
|
|
||||||
|
// Handle simple mode - hide branding sections but keep logo and copyright
|
||||||
|
if (__SIMPLE_MODE__) {
|
||||||
|
const hideBrandingSections = () => {
|
||||||
|
// Hide navigation but keep logo
|
||||||
|
const nav = document.querySelector('nav');
|
||||||
|
if (nav) {
|
||||||
|
// Hide the entire nav but we'll create a minimal one with just logo
|
||||||
|
nav.style.display = 'none';
|
||||||
|
|
||||||
|
// Create a simple nav with just logo on the right
|
||||||
|
const simpleNav = document.createElement('nav');
|
||||||
|
simpleNav.className =
|
||||||
|
'bg-gray-800 border-b border-gray-700 sticky top-0 z-30';
|
||||||
|
simpleNav.innerHTML = `
|
||||||
|
<div class="container mx-auto px-4">
|
||||||
|
<div class="flex justify-start items-center h-16">
|
||||||
|
<div class="flex-shrink-0 flex items-center">
|
||||||
|
<img src="images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8">
|
||||||
|
<span class="text-white font-bold text-xl ml-2">BentoPDF</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.insertBefore(simpleNav, document.body.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
const heroSection = document.getElementById('hero-section');
|
||||||
|
if (heroSection) {
|
||||||
|
heroSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
const featuresSection = document.getElementById('features-section');
|
||||||
|
if (featuresSection) {
|
||||||
|
featuresSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
const securitySection = document.getElementById(
|
||||||
|
'security-compliance-section'
|
||||||
|
);
|
||||||
|
if (securitySection) {
|
||||||
|
securitySection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
const faqSection = document.getElementById('faq-accordion');
|
||||||
|
if (faqSection) {
|
||||||
|
faqSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
const testimonialsSection = document.getElementById(
|
||||||
|
'testimonials-section'
|
||||||
|
);
|
||||||
|
if (testimonialsSection) {
|
||||||
|
testimonialsSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportSection = document.getElementById('support-section');
|
||||||
|
if (supportSection) {
|
||||||
|
supportSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide footer but keep copyright
|
||||||
|
const footer = document.querySelector('footer');
|
||||||
|
if (footer) {
|
||||||
|
footer.style.display = 'none';
|
||||||
|
|
||||||
|
const simpleFooter = document.createElement('footer');
|
||||||
|
simpleFooter.className = 'mt-16 border-t-2 border-gray-700 py-8';
|
||||||
|
simpleFooter.innerHTML = `
|
||||||
|
<div class="container mx-auto px-4">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<img src="images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8 mr-2">
|
||||||
|
<span class="text-white font-bold text-lg">BentoPDF</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-400 text-sm">
|
||||||
|
© 2025 BentoPDF. All rights reserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(simpleFooter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sectionDividers = document.querySelectorAll('.section-divider');
|
||||||
|
sectionDividers.forEach((divider) => {
|
||||||
|
(divider as HTMLElement).style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.title = 'BentoPDF - PDF Tools';
|
||||||
|
|
||||||
|
const toolsHeader = document.getElementById('tools-header');
|
||||||
|
if (toolsHeader) {
|
||||||
|
const title = toolsHeader.querySelector('h2');
|
||||||
|
const subtitle = toolsHeader.querySelector('p');
|
||||||
|
if (title) {
|
||||||
|
title.textContent = 'PDF Tools';
|
||||||
|
title.className = 'text-4xl md:text-5xl font-bold text-white mb-3';
|
||||||
|
}
|
||||||
|
if (subtitle) {
|
||||||
|
subtitle.textContent = 'Select a tool to get started';
|
||||||
|
subtitle.className = 'text-lg text-gray-400';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = document.getElementById('app');
|
||||||
|
if (app) {
|
||||||
|
app.style.paddingTop = '1rem';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
hideBrandingSections();
|
||||||
|
}
|
||||||
|
|
||||||
dom.toolGrid.textContent = '';
|
dom.toolGrid.textContent = '';
|
||||||
|
|
||||||
categories.forEach((category) => {
|
categories.forEach((category) => {
|
||||||
@@ -83,6 +194,29 @@ const init = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.addEventListener('keydown', function (e) {
|
||||||
|
const key = e.key.toLowerCase();
|
||||||
|
const isMac = navigator.userAgent.toUpperCase().includes('MAC');
|
||||||
|
const isCtrlK = e.ctrlKey && key === 'k';
|
||||||
|
const isCmdK = isMac && e.metaKey && key === 'k';
|
||||||
|
|
||||||
|
if (isCtrlK || isCmdK) {
|
||||||
|
e.preventDefault();
|
||||||
|
searchBar.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const shortcutK = document.getElementById('shortcut');
|
||||||
|
const isIosOrAndroid = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
||||||
|
|
||||||
|
if (isIosOrAndroid) {
|
||||||
|
shortcutK.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
shortcutK.textContent = navigator.userAgent.toUpperCase().includes('MAC')
|
||||||
|
? '⌘ + K'
|
||||||
|
: 'Ctrl + K';
|
||||||
|
}
|
||||||
|
|
||||||
dom.toolGrid.addEventListener('click', (e) => {
|
dom.toolGrid.addEventListener('click', (e) => {
|
||||||
// @ts-expect-error TS(2339) FIXME: Property 'closest' does not exist on type 'EventTa... Remove this comment to see the full error message
|
// @ts-expect-error TS(2339) FIXME: Property 'closest' does not exist on type 'EventTa... Remove this comment to see the full error message
|
||||||
const card = e.target.closest('.tool-card');
|
const card = e.target.closest('.tool-card');
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||||
|
const mobileMenu = document.getElementById('mobile-menu');
|
||||||
|
const menuIcon = document.getElementById('menu-icon');
|
||||||
|
const closeIcon = document.getElementById('close-icon');
|
||||||
|
|
||||||
|
if (mobileMenuButton && mobileMenu && menuIcon && closeIcon) {
|
||||||
|
// Toggle menu on button click
|
||||||
|
mobileMenuButton.addEventListener('click', () => {
|
||||||
|
const isExpanded =
|
||||||
|
mobileMenuButton.getAttribute('aria-expanded') === 'true';
|
||||||
|
|
||||||
|
// Toggle menu visibility
|
||||||
|
mobileMenu.classList.toggle('hidden');
|
||||||
|
|
||||||
|
// Toggle icons
|
||||||
|
menuIcon.classList.toggle('hidden');
|
||||||
|
closeIcon.classList.toggle('hidden');
|
||||||
|
|
||||||
|
// Update aria-expanded for accessibility
|
||||||
|
mobileMenuButton.setAttribute('aria-expanded', (!isExpanded).toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu when clicking on a link
|
||||||
|
const mobileLinks = mobileMenu.querySelectorAll('a');
|
||||||
|
mobileLinks.forEach((link) => {
|
||||||
|
link.addEventListener('click', () => {
|
||||||
|
mobileMenu.classList.add('hidden');
|
||||||
|
menuIcon.classList.remove('hidden');
|
||||||
|
closeIcon.classList.add('hidden');
|
||||||
|
mobileMenuButton.setAttribute('aria-expanded', 'false');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu when clicking outside
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
const target = event.target as Node;
|
||||||
|
const isClickInsideMenu = mobileMenu.contains(target);
|
||||||
|
const isClickOnButton = mobileMenuButton.contains(target);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isClickInsideMenu &&
|
||||||
|
!isClickOnButton &&
|
||||||
|
!mobileMenu.classList.contains('hidden')
|
||||||
|
) {
|
||||||
|
mobileMenu.classList.add('hidden');
|
||||||
|
menuIcon.classList.remove('hidden');
|
||||||
|
closeIcon.classList.add('hidden');
|
||||||
|
mobileMenuButton.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
+60
-4
@@ -969,7 +969,7 @@ export const toolTemplates = {
|
|||||||
'reverse-pages': () => `
|
'reverse-pages': () => `
|
||||||
<h2 class="text-2xl font-bold text-white mb-4">Reverse PDF Pages</h2>
|
<h2 class="text-2xl font-bold text-white mb-4">Reverse PDF Pages</h2>
|
||||||
<p class="mb-6 text-gray-400">Flip the order of all pages in your document, making the last page the first.</p>
|
<p class="mb-6 text-gray-400">Flip the order of all pages in your document, making the last page the first.</p>
|
||||||
${createFileInputHTML()}
|
${createFileInputHTML({ multiple: true, accept: 'application/pdf', showControls: true })}
|
||||||
<div id="file-display-area" class="mt-4 space-y-2"></div>
|
<div id="file-display-area" class="mt-4 space-y-2"></div>
|
||||||
<button id="process-btn" class="hidden btn-gradient w-full mt-6">Reverse & Download</button>
|
<button id="process-btn" class="hidden btn-gradient w-full mt-6">Reverse & Download</button>
|
||||||
`,
|
`,
|
||||||
@@ -1367,6 +1367,17 @@ export const toolTemplates = {
|
|||||||
'ocr-pdf': () => `
|
'ocr-pdf': () => `
|
||||||
<h2 class="text-2xl font-bold text-white mb-4">OCR PDF</h2>
|
<h2 class="text-2xl font-bold text-white mb-4">OCR PDF</h2>
|
||||||
<p class="mb-6 text-gray-400">Convert scanned PDFs into searchable documents. Select one or more languages present in your file for the best results.</p>
|
<p class="mb-6 text-gray-400">Convert scanned PDFs into searchable documents. Select one or more languages present in your file for the best results.</p>
|
||||||
|
|
||||||
|
<div class="p-3 bg-gray-900 rounded-lg border border-gray-700 mb-6">
|
||||||
|
<p class="text-sm text-gray-300"><strong class="text-white">How it works:</strong></p>
|
||||||
|
<ul class="list-disc list-inside text-xs text-gray-400 mt-1 space-y-1">
|
||||||
|
<li><strong class="text-white">Extract Text:</strong> Uses Tesseract OCR to recognize text from scanned images or PDFs.</li>
|
||||||
|
<li><strong class="text-white">Searchable Output:</strong> Creates a new PDF with an invisible text layer, making your document fully searchable while preserving the original appearance.</li>
|
||||||
|
<li><strong class="text-white">Character Filtering:</strong> Use whitelists to filter out unwanted characters and improve accuracy for specific document types (invoices, forms, etc.).</li>
|
||||||
|
<li><strong class="text-white">Multi-language Support:</strong> Select multiple languages for documents containing mixed language content.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
${createFileInputHTML()}
|
${createFileInputHTML()}
|
||||||
<div id="file-display-area" class="mt-4 space-y-2"></div>
|
<div id="file-display-area" class="mt-4 space-y-2"></div>
|
||||||
|
|
||||||
@@ -1393,7 +1404,10 @@ export const toolTemplates = {
|
|||||||
|
|
||||||
<!-- Advanced settings section -->
|
<!-- Advanced settings section -->
|
||||||
<details class="bg-gray-900 border border-gray-700 rounded-lg p-3">
|
<details class="bg-gray-900 border border-gray-700 rounded-lg p-3">
|
||||||
<summary class="text-sm font-medium text-gray-300 cursor-pointer">Advanced Settings</summary>
|
<summary class="text-sm font-medium text-gray-300 cursor-pointer flex items-center justify-between">
|
||||||
|
<span>Advanced Settings (Recommended to improve accuracy)</span>
|
||||||
|
<i data-lucide="chevron-down" class="w-4 h-4 transition-transform details-icon"></i>
|
||||||
|
</summary>
|
||||||
<div class="mt-4 space-y-4">
|
<div class="mt-4 space-y-4">
|
||||||
<!-- Resolution Setting -->
|
<!-- Resolution Setting -->
|
||||||
<div>
|
<div>
|
||||||
@@ -1409,10 +1423,28 @@ export const toolTemplates = {
|
|||||||
<input type="checkbox" id="ocr-binarize" class="w-4 h-4 rounded text-indigo-600 bg-gray-700 border-gray-600">
|
<input type="checkbox" id="ocr-binarize" class="w-4 h-4 rounded text-indigo-600 bg-gray-700 border-gray-600">
|
||||||
Binarize Image (Enhance Contrast for Clean Scans)
|
Binarize Image (Enhance Contrast for Clean Scans)
|
||||||
</label>
|
</label>
|
||||||
<!-- Character Whitelist -->
|
|
||||||
|
<!-- Character Whitelist Presets -->
|
||||||
|
<div>
|
||||||
|
<label for="whitelist-preset" class="block mb-1 text-xs font-medium text-gray-400">Character Whitelist Preset</label>
|
||||||
|
<select id="whitelist-preset" class="w-full bg-gray-700 border border-gray-600 text-white rounded-lg p-2 text-sm mb-2">
|
||||||
|
<option value="">None (All characters)</option>
|
||||||
|
<option value="alphanumeric">Alphanumeric + Basic Punctuation</option>
|
||||||
|
<option value="numbers-currency">Numbers + Currency Symbols</option>
|
||||||
|
<option value="letters-only">Letters Only (A-Z, a-z)</option>
|
||||||
|
<option value="numbers-only">Numbers Only (0-9)</option>
|
||||||
|
<option value="invoice">Invoice/Receipt (Numbers, $, ., -, /)</option>
|
||||||
|
<option value="forms">Forms (Alphanumeric + Common Symbols)</option>
|
||||||
|
<option value="custom">Custom...</option>
|
||||||
|
</select>
|
||||||
|
<p class="text-xs text-gray-500 mt-1">Only these characters will be recognized. Leave empty for all characters.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Character Whitelist Input -->
|
||||||
<div>
|
<div>
|
||||||
<label for="ocr-whitelist" class="block mb-1 text-xs font-medium text-gray-400">Character Whitelist (Optional)</label>
|
<label for="ocr-whitelist" class="block mb-1 text-xs font-medium text-gray-400">Character Whitelist (Optional)</label>
|
||||||
<input type="text" id="ocr-whitelist" class="w-full bg-gray-700 border border-gray-600 text-white rounded-lg p-2 text-sm" placeholder="e.g., abcdefghijklmnopqrstuvwxyz0123456789$.,">
|
<input type="text" id="ocr-whitelist" class="w-full bg-gray-700 border border-gray-600 text-white rounded-lg p-2 text-sm" placeholder="e.g., abcdefghijklmnopqrstuvwxyz0123456789$.,">
|
||||||
|
<p class="text-xs text-gray-500 mt-1">Only these characters will be recognized. Leave empty for all characters.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
@@ -1847,11 +1879,35 @@ export const toolTemplates = {
|
|||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
linearize: () => `
|
linearize: () => `
|
||||||
<h2 class="text-2xl font-bold text-white mb-4">Linearize PDFs (Fast Web View)</h2>
|
<h2 class="text-2xl font-bold text-white mb-4">Linearize PDFs (Fast Web View)</h2>
|
||||||
<p class="mb-6 text-gray-400">Optimize multiple PDFs for faster loading over the web. Files will be downloaded in a ZIP archive.</p>
|
<p class="mb-6 text-gray-400">Optimize multiple PDFs for faster loading over the web. Files will be downloaded in a ZIP archive.</p>
|
||||||
${createFileInputHTML({ multiple: true, accept: 'application/pdf', showControls: true })}
|
${createFileInputHTML({ multiple: true, accept: 'application/pdf', showControls: true })}
|
||||||
<div id="file-display-area" class="mt-4 space-y-2"></div>
|
<div id="file-display-area" class="mt-4 space-y-2"></div>
|
||||||
<button id="process-btn" class="hidden btn-gradient w-full mt-6" disabled>Linearize PDFs & Download ZIP</button>
|
<button id="process-btn" class="hidden btn-gradient w-full mt-6" disabled>Linearize PDFs & Download ZIP</button>
|
||||||
`,
|
`,
|
||||||
|
'add-attachments': () => `
|
||||||
|
<h2 class="text-2xl font-bold text-white mb-4">Add Attachments to PDF</h2>
|
||||||
|
<p class="mb-6 text-gray-400">First, upload the PDF document you want to add files to.</p>
|
||||||
|
${createFileInputHTML({ accept: 'application/pdf' })}
|
||||||
|
<div id="file-display-area" class="mt-4 space-y-2"></div>
|
||||||
|
|
||||||
|
<div id="attachment-options" class="hidden mt-8">
|
||||||
|
<h3 class="text-lg font-semibold text-white mb-3">Upload Files to Attach</h3>
|
||||||
|
<p class="mb-4 text-gray-400">Select one or more files to embed within the PDF. You can attach any file type (images, documents, spreadsheets, etc.).</p>
|
||||||
|
|
||||||
|
<label for="attachment-files-input" class="w-full flex justify-center items-center px-6 py-10 bg-gray-900 text-gray-400 rounded-lg border-2 border-dashed border-gray-600 hover:bg-gray-800 hover:border-gray-500 cursor-pointer transition-colors">
|
||||||
|
<div class="text-center">
|
||||||
|
<svg class="mx-auto h-12 w-12" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true"><path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||||
|
<span class="mt-2 block text-sm font-medium">Click to upload files</span>
|
||||||
|
<span class="mt-1 block text-xs">Any file type, multiple files allowed</span>
|
||||||
|
</div>
|
||||||
|
<input id="attachment-files-input" name="attachment-files" type="file" class="sr-only" multiple>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div id="attachment-file-list" class="mt-4 space-y-2"></div>
|
||||||
|
|
||||||
|
<button id="process-btn" class="hidden btn-gradient w-full mt-6" disabled>Embed Files & Download</button>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -123,3 +123,26 @@ export function parsePageRanges(rangeString: any, totalPages: any) {
|
|||||||
// @ts-expect-error TS(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
|
// @ts-expect-error TS(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
|
||||||
return Array.from(indices).sort((a, b) => a - b);
|
return Array.from(indices).sort((a, b) => a - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an ISO 8601 date string (e.g., "2008-02-21T17:15:56-08:00")
|
||||||
|
* into a localized, human-readable string.
|
||||||
|
* @param {string} isoDateString - The ISO 8601 date string.
|
||||||
|
* @returns {string} A localized date and time string, or the original string if parsing fails.
|
||||||
|
*/
|
||||||
|
export function formatIsoDate(isoDateString) {
|
||||||
|
if (!isoDateString || typeof isoDateString !== 'string') {
|
||||||
|
return isoDateString; // Return original value if it's not a valid string
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const date = new Date(isoDateString);
|
||||||
|
// Check if the date object is valid
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
return isoDateString; // Return original string if the date is invalid
|
||||||
|
}
|
||||||
|
return date.toLocaleString();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Could not parse ISO date:', e);
|
||||||
|
return isoDateString; // Return original string on any error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -181,7 +181,10 @@ describe('Add Blank Page Tool', () => {
|
|||||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(state.pdfDoc, [0, 1]);
|
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(state.pdfDoc, [0, 1]);
|
||||||
// Should add 1 blank page + 5 existing pages = 6 total calls
|
// Should add 1 blank page + 5 existing pages = 6 total calls
|
||||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(6);
|
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(6);
|
||||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(state.pdfDoc, [2, 3, 4]);
|
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(
|
||||||
|
state.pdfDoc,
|
||||||
|
[2, 3, 4]
|
||||||
|
);
|
||||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||||
expect.any(Blob),
|
expect.any(Blob),
|
||||||
'blank-page-added.pdf'
|
'blank-page-added.pdf'
|
||||||
@@ -196,7 +199,10 @@ describe('Add Blank Page Tool', () => {
|
|||||||
|
|
||||||
await addBlankPage();
|
await addBlankPage();
|
||||||
|
|
||||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(state.pdfDoc, [0, 1, 2, 3, 4]);
|
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(
|
||||||
|
state.pdfDoc,
|
||||||
|
[0, 1, 2, 3, 4]
|
||||||
|
);
|
||||||
// Should add 1 blank page + 5 existing pages = 6 total calls
|
// Should add 1 blank page + 5 existing pages = 6 total calls
|
||||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(6);
|
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(6);
|
||||||
// When adding at the end, there are no pages after, so copyPages is not called for indicesAfter
|
// When adding at the end, there are no pages after, so copyPages is not called for indicesAfter
|
||||||
@@ -239,7 +245,10 @@ describe('Add Blank Page Tool', () => {
|
|||||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(state.pdfDoc, [0, 1]);
|
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(state.pdfDoc, [0, 1]);
|
||||||
// Should add 5 blank pages + 5 existing pages = 10 total calls
|
// Should add 5 blank pages + 5 existing pages = 10 total calls
|
||||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(10);
|
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(10);
|
||||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(state.pdfDoc, [2, 3, 4]);
|
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(
|
||||||
|
state.pdfDoc,
|
||||||
|
[2, 3, 4]
|
||||||
|
);
|
||||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||||
expect.any(Blob),
|
expect.any(Blob),
|
||||||
'blank-pages-added.pdf'
|
'blank-pages-added.pdf'
|
||||||
@@ -255,7 +264,10 @@ describe('Add Blank Page Tool', () => {
|
|||||||
await addBlankPage();
|
await addBlankPage();
|
||||||
|
|
||||||
expect(ui.showLoader).toHaveBeenCalledWith('Adding 2 blank pages...');
|
expect(ui.showLoader).toHaveBeenCalledWith('Adding 2 blank pages...');
|
||||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(state.pdfDoc, [0, 1, 2, 3, 4]);
|
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(
|
||||||
|
state.pdfDoc,
|
||||||
|
[0, 1, 2, 3, 4]
|
||||||
|
);
|
||||||
// Should add 2 blank pages + 5 existing pages = 7 total calls
|
// Should add 2 blank pages + 5 existing pages = 7 total calls
|
||||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(7);
|
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(7);
|
||||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||||
@@ -268,7 +280,9 @@ describe('Add Blank Page Tool', () => {
|
|||||||
// -------------------- Error Handling Tests --------------------
|
// -------------------- Error Handling Tests --------------------
|
||||||
describe('Error Handling', () => {
|
describe('Error Handling', () => {
|
||||||
it('should handle PDF creation errors', async () => {
|
it('should handle PDF creation errors', async () => {
|
||||||
vi.mocked(PDFLibDocument.create).mockRejectedValue(new Error('PDF creation failed'));
|
vi.mocked(PDFLibDocument.create).mockRejectedValue(
|
||||||
|
new Error('PDF creation failed')
|
||||||
|
);
|
||||||
|
|
||||||
document.body.innerHTML = `
|
document.body.innerHTML = `
|
||||||
<input id="page-number" value="2" />
|
<input id="page-number" value="2" />
|
||||||
@@ -277,7 +291,10 @@ describe('Add Blank Page Tool', () => {
|
|||||||
|
|
||||||
await addBlankPage();
|
await addBlankPage();
|
||||||
|
|
||||||
expect(ui.showAlert).toHaveBeenCalledWith('Error', 'Could not add blank page.');
|
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||||
|
'Error',
|
||||||
|
'Could not add blank page.'
|
||||||
|
);
|
||||||
expect(ui.hideLoader).toHaveBeenCalled();
|
expect(ui.hideLoader).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -291,7 +308,10 @@ describe('Add Blank Page Tool', () => {
|
|||||||
|
|
||||||
await addBlankPage();
|
await addBlankPage();
|
||||||
|
|
||||||
expect(ui.showAlert).toHaveBeenCalledWith('Error', 'Could not add blank page.');
|
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||||
|
'Error',
|
||||||
|
'Could not add blank page.'
|
||||||
|
);
|
||||||
expect(ui.hideLoader).toHaveBeenCalled();
|
expect(ui.hideLoader).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -305,7 +325,10 @@ describe('Add Blank Page Tool', () => {
|
|||||||
|
|
||||||
await addBlankPage();
|
await addBlankPage();
|
||||||
|
|
||||||
expect(ui.showAlert).toHaveBeenCalledWith('Error', 'Could not add blank page.');
|
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||||
|
'Error',
|
||||||
|
'Could not add blank page.'
|
||||||
|
);
|
||||||
expect(ui.hideLoader).toHaveBeenCalled();
|
expect(ui.hideLoader).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ describe('Tool Configuration Arrays', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have the correct number of tools', () => {
|
it('should have the correct number of tools', () => {
|
||||||
expect(multiFileTools).toHaveLength(12);
|
expect(multiFileTools).toHaveLength(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not contain any duplicate tools', () => {
|
it('should not contain any duplicate tools', () => {
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||||
|
import { reversePages } from '../js/logic/reverse-pages';
|
||||||
|
import { state } from '../js/state';
|
||||||
|
import * as helpers from '../js/utils/helpers';
|
||||||
|
import * as ui from '../js/ui';
|
||||||
|
import JSZip from 'jszip';
|
||||||
|
|
||||||
|
vi.mock('../js/ui', () => ({
|
||||||
|
showLoader: vi.fn(),
|
||||||
|
hideLoader: vi.fn(),
|
||||||
|
showAlert: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../js/utils/helpers', () => ({
|
||||||
|
downloadFile: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('pdf-lib', () => ({
|
||||||
|
PDFDocument: {
|
||||||
|
create: vi.fn(),
|
||||||
|
load: vi.fn().mockResolvedValue({
|
||||||
|
getPageCount: vi.fn(() => 2),
|
||||||
|
copyPages: vi.fn((_, indices) =>
|
||||||
|
Promise.resolve(indices.map((i) => ({ page: `page-${i}` })))
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('reversePages - multi PDF support', () => {
|
||||||
|
let mockNewDoc: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
state.files = []; // ✅ now using files, not pdfDocs
|
||||||
|
|
||||||
|
mockNewDoc = {
|
||||||
|
copyPages: vi.fn((doc: any, indices: number[]) =>
|
||||||
|
Promise.resolve(indices.map((i: number) => ({ page: `page-${i}` })))
|
||||||
|
),
|
||||||
|
addPage: vi.fn(),
|
||||||
|
save: vi.fn(() => Promise.resolve(new Uint8Array([1, 2, 3]))),
|
||||||
|
};
|
||||||
|
vi.mocked(PDFLibDocument.create).mockResolvedValue(mockNewDoc);
|
||||||
|
|
||||||
|
vi.mocked(helpers.downloadFile).mockImplementation(() => {});
|
||||||
|
vi.mocked(ui.showLoader).mockImplementation(() => {});
|
||||||
|
vi.mocked(ui.hideLoader).mockImplementation(() => {});
|
||||||
|
vi.mocked(ui.showAlert).mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reverse pages for multiple PDFs and create a zip', async () => {
|
||||||
|
const mockFile = (name: string) => ({
|
||||||
|
name,
|
||||||
|
type: 'application/pdf',
|
||||||
|
arrayBuffer: vi.fn().mockResolvedValue(new ArrayBuffer(8)),
|
||||||
|
});
|
||||||
|
|
||||||
|
state.files = [mockFile('a.pdf'), mockFile('b.pdf')]; // ✅ now matches function
|
||||||
|
|
||||||
|
await reversePages();
|
||||||
|
|
||||||
|
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||||
|
expect.any(Blob),
|
||||||
|
'reversed_pdfs.zip'
|
||||||
|
);
|
||||||
|
expect(mockNewDoc.copyPages).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mockNewDoc.addPage).toHaveBeenCalled();
|
||||||
|
expect(mockNewDoc.save).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty PDF list gracefully', async () => {
|
||||||
|
state.files = [];
|
||||||
|
await reversePages();
|
||||||
|
expect(ui.showAlert).toHaveBeenCalledWith('Error', 'PDF not loaded.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle PDF creation errors', async () => {
|
||||||
|
vi.mocked(PDFLibDocument.create).mockRejectedValue(
|
||||||
|
new Error('Create failed')
|
||||||
|
);
|
||||||
|
state.files = [
|
||||||
|
{
|
||||||
|
name: 'x.pdf',
|
||||||
|
type: 'application/pdf',
|
||||||
|
arrayBuffer: vi.fn().mockResolvedValue(new ArrayBuffer(8)),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await reversePages();
|
||||||
|
|
||||||
|
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||||
|
'Error',
|
||||||
|
'Could not reverse the PDF pages.'
|
||||||
|
);
|
||||||
|
expect(ui.hideLoader).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle PDF processing errors', async () => {
|
||||||
|
mockNewDoc.copyPages.mockRejectedValue(new Error('Copy failed'));
|
||||||
|
state.files = [
|
||||||
|
{
|
||||||
|
name: 'y.pdf',
|
||||||
|
type: 'application/pdf',
|
||||||
|
arrayBuffer: vi.fn().mockResolvedValue(new ArrayBuffer(8)),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await reversePages();
|
||||||
|
|
||||||
|
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||||
|
'Error',
|
||||||
|
'Could not reverse the PDF pages.'
|
||||||
|
);
|
||||||
|
expect(ui.hideLoader).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle save errors', async () => {
|
||||||
|
mockNewDoc.save.mockRejectedValue(new Error('Save failed'));
|
||||||
|
state.files = [
|
||||||
|
{
|
||||||
|
name: 'z.pdf',
|
||||||
|
type: 'application/pdf',
|
||||||
|
arrayBuffer: vi.fn().mockResolvedValue(new ArrayBuffer(8)),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await reversePages();
|
||||||
|
|
||||||
|
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||||
|
'Error',
|
||||||
|
'Could not reverse the PDF pages.'
|
||||||
|
);
|
||||||
|
expect(ui.hideLoader).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
declare const __SIMPLE_MODE__: boolean;
|
||||||
+71
-9
@@ -14,17 +14,21 @@
|
|||||||
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
||||||
<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">
|
||||||
<a
|
<div
|
||||||
href="index.html"
|
|
||||||
class="flex-shrink-0 flex items-center cursor-pointer"
|
class="flex-shrink-0 flex items-center cursor-pointer"
|
||||||
|
id="home-logo"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="images/favicon.svg"
|
src="images/favicon.svg"
|
||||||
alt="BentoPDF Logo"
|
alt="Bento PDF Logo"
|
||||||
class="h-8 w-8 mr-2"
|
class="h-8 w-8"
|
||||||
/>
|
/>
|
||||||
<span class="text-white font-bold text-xl">BentoPDF</span>
|
<span class="text-white font-bold text-xl ml-2">
|
||||||
</a>
|
<a href="index.html">BentoPDF</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
@@ -32,12 +36,69 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<div class="md:hidden flex items-center gap-4">
|
<!-- Mobile Hamburger Button -->
|
||||||
<a href="index.html" class="nav-link">Home</a>
|
<div class="md:hidden flex items-center">
|
||||||
<a href="index.html#tools-header" class="nav-link">All Tools</a>
|
<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"
|
||||||
|
aria-controls="mobile-menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Open main menu</span>
|
||||||
|
<!-- 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"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<!-- Close Icon -->
|
||||||
|
<svg
|
||||||
|
id="close-icon"
|
||||||
|
class="hidden h-6 w-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Menu Dropdown -->
|
||||||
|
<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">
|
||||||
|
<a href="index.html" class="mobile-nav-link">Home</a>
|
||||||
|
<a href="./about.html" class="mobile-nav-link">About</a>
|
||||||
|
<a href="./contact.html" class="mobile-nav-link">Contact</a>
|
||||||
|
<a href="index.html#tools-header" class="mobile-nav-link"
|
||||||
|
>All Tools</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div id="app" class="container mx-auto p-4 md:p-8">
|
<div id="app" class="container mx-auto p-4 md:p-8">
|
||||||
<section class="max-w-4xl mx-auto py-12">
|
<section class="max-w-4xl mx-auto py-12">
|
||||||
@@ -249,5 +310,6 @@
|
|||||||
<script>
|
<script>
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
</script>
|
</script>
|
||||||
|
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ https://hub.docker.com/r/bentopdf/bentopdf/</Overview>
|
|||||||
<DonateText/>
|
<DonateText/>
|
||||||
<DonateLink/>
|
<DonateLink/>
|
||||||
<Requires/>
|
<Requires/>
|
||||||
<Config Name="Container Port 1" Target="3000" Default="80" Mode="tcp" Description="" Type="Port" Display="always" Required="false" Mask="false">80</Config>
|
<Config Name="Web Port for GUI" Target="3000" Default="80" Mode="tcp" Description="Port used for BentoPDF's web GUI" Type="Port" Display="always" Required="false" Mask="false">80</Config>
|
||||||
<TailscaleStateDir/>
|
<TailscaleStateDir/>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -3,7 +3,7 @@ import tailwindcss from '@tailwindcss/vite';
|
|||||||
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => ({
|
||||||
plugins: [
|
plugins: [
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
nodePolyfills({
|
nodePolyfills({
|
||||||
@@ -15,6 +15,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
define: {
|
||||||
|
__SIMPLE_MODE__: JSON.stringify(process.env.SIMPLE_MODE === 'true'),
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
stream: 'stream-browserify',
|
stream: 'stream-browserify',
|
||||||
@@ -52,4 +55,4 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user