mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-11 01:14:35 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fc873d343 | ||
|
|
739dac55ee | ||
|
|
dbc1b8eb7f | ||
|
|
11c24c1ff1 | ||
|
|
9af9c33f74 | ||
|
|
835c3c9423 | ||
|
|
a8df98f20d | ||
|
|
11c9e14803 | ||
|
|
4e2d04fb9c | ||
|
|
edc0f27e0f | ||
|
|
bf57f36cb5 |
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
name: build docker image and push to dockerhub
|
name: Build and Push Docker Images (Default + Simple Mode)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -6,6 +6,7 @@ on:
|
|||||||
- 'main'
|
- 'main'
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker-build-and-push:
|
docker-build-and-push:
|
||||||
@@ -13,6 +14,15 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: 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
|
||||||
@@ -35,12 +45,16 @@ jobs:
|
|||||||
echo "version_without_v=latest" >> $GITHUB_OUTPUT
|
echo "version_without_v=latest" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push
|
- 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
|
||||||
|
build-args: |
|
||||||
|
SIMPLE_MODE=${{ matrix.mode.simple_mode }}
|
||||||
tags: |
|
tags: |
|
||||||
bentopdf/bentopdf:latest
|
bentopdf/bentopdf${{ matrix.mode.suffix }}:latest
|
||||||
bentopdf/bentopdf:${{ steps.version.outputs.version_without_v }}
|
bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }}
|
||||||
bentopdf/bentopdf:${{ steps.version.outputs.version }}
|
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
|
||||||
@@ -241,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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+98
-14
@@ -19,40 +19,89 @@ When enabled, Simple Mode will:
|
|||||||
|
|
||||||
## How to Enable Simple Mode
|
## How to Enable Simple Mode
|
||||||
|
|
||||||
### Using Docker Compose
|
### Method 1: Using Pre-built Simple Mode Image (Recommended)
|
||||||
|
|
||||||
1. Create a `.env` file in your project root:
|
Use the pre-built Simple Mode image directly:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
SIMPLE_MODE=true
|
docker run -p 3000:80 bentopdf/bentopdf-simple:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Run with docker-compose:
|
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
|
```bash
|
||||||
docker-compose up -d
|
docker compose -f docker-compose.dev.yml build --build-arg SIMPLE_MODE=true
|
||||||
|
docker compose -f docker-compose.dev.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Docker Build
|
### Method 3: Using Docker Build
|
||||||
|
|
||||||
Build the image with the SIMPLE_MODE environment variable:
|
Build the image with the SIMPLE_MODE build argument:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build --build-arg SIMPLE_MODE=true -t bentopdf-simple .
|
docker build --build-arg SIMPLE_MODE=true -t bentopdf-simple .
|
||||||
|
docker run -p 3000:80 bentopdf-simple
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Environment Variables
|
### 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:
|
Set the environment variable before building:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export SIMPLE_MODE=true
|
export SIMPLE_MODE=true
|
||||||
npm run build
|
npm run build
|
||||||
|
npx serve dist -p 3000
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🧪 Testing Simple Mode Locally
|
## 🧪 Testing Simple Mode Locally
|
||||||
|
|
||||||
After building with Simple Mode enabled, you need to serve the built files 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
|
```bash
|
||||||
# Build with simple mode
|
# Build with simple mode
|
||||||
@@ -62,7 +111,20 @@ SIMPLE_MODE=true npm run build
|
|||||||
npx serve dist -p 3000
|
npx serve dist -p 3000
|
||||||
```
|
```
|
||||||
|
|
||||||
Then open `http://localhost:3000` in your browser.
|
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
|
## 🔍 What to Look For
|
||||||
|
|
||||||
@@ -76,16 +138,38 @@ When Simple Mode is working correctly, you should see:
|
|||||||
- ❌ No hero section with "The PDF Toolkit built for privacy"
|
- ❌ No hero section with "The PDF Toolkit built for privacy"
|
||||||
- ❌ No features, FAQ, testimonials, or footer sections
|
- ❌ No features, FAQ, testimonials, or footer sections
|
||||||
|
|
||||||
## Example Docker Compose Configuration
|
## 📦 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
|
```yaml
|
||||||
services:
|
services:
|
||||||
bentopdf:
|
bentopdf:
|
||||||
image: bentopdf/bentopdf:latest
|
image: bentopdf/bentopdf-simple:latest
|
||||||
container_name: bentopdf
|
container_name: bentopdf
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- '3000:80'
|
- '80:80'
|
||||||
environment:
|
environment:
|
||||||
- SIMPLE_MODE=true
|
- 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
|
||||||
+3
-3
@@ -1,9 +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'
|
||||||
environment:
|
|
||||||
- SIMPLE_MODE=${SIMPLE_MODE:-false}
|
|
||||||
+3
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "bento-pdf",
|
"name": "bento-pdf",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -18,7 +18,8 @@
|
|||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"release": "node scripts/release.js patch",
|
"release": "node scripts/release.js patch",
|
||||||
"release:minor": "node scripts/release.js minor",
|
"release:minor": "node scripts/release.js minor",
|
||||||
"release:major": "node scripts/release.js major"
|
"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",
|
||||||
|
|||||||
@@ -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)
|
||||||
+133
-17
@@ -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');
|
||||||
|
|||||||
+107
-71
@@ -5,84 +5,120 @@ import { createIcons, icons } from 'lucide';
|
|||||||
import * as pdfjsLib from 'pdfjs-dist';
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
import '../css/styles.css';
|
import '../css/styles.css';
|
||||||
|
|
||||||
const hideBrandingSections = () => {
|
|
||||||
const nav = document.querySelector('nav');
|
|
||||||
if (nav) {
|
|
||||||
nav.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
|
|
||||||
const footer = document.querySelector('footer');
|
|
||||||
if (footer) {
|
|
||||||
footer.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
const sectionDividers = document.querySelectorAll('.section-divider');
|
|
||||||
sectionDividers.forEach((divider) => {
|
|
||||||
(divider as HTMLElement).style.display = 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
document.title = '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 = '2rem';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||||
import.meta.url
|
import.meta.url
|
||||||
).toString();
|
).toString();
|
||||||
|
|
||||||
// Handle simple mode - hide branding sections
|
// Handle simple mode - hide branding sections but keep logo and copyright
|
||||||
if (__SIMPLE_MODE__) {
|
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();
|
hideBrandingSections();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user