feat: add support for disabling specific tools in self-hosting

- Introduced build-time and runtime options to disable tools for compliance or security.
- Updated documentation to include instructions for disabling tools in Docker and Kubernetes setups.
- Added translations for disabled tool messages in multiple languages.
- Implemented logic to filter out disabled tools from the toolbox and shortcuts in the application.
- Created utility functions to manage disabled tools configuration.
This commit is contained in:
alam00000
2026-03-28 23:45:17 +05:30
parent 59ebb4d358
commit 9a7cf1636b
30 changed files with 417 additions and 7 deletions

View File

@@ -59,6 +59,9 @@ ENV VITE_BRAND_NAME=$VITE_BRAND_NAME
ENV VITE_BRAND_LOGO=$VITE_BRAND_LOGO
ENV VITE_FOOTER_TEXT=$VITE_FOOTER_TEXT
ARG DISABLE_TOOLS
ENV DISABLE_TOOLS=$DISABLE_TOOLS
ENV NODE_OPTIONS="--max-old-space-size=3072"
RUN --mount=type=secret,id=VITE_CORS_PROXY_URL \

View File

@@ -106,6 +106,7 @@ docker run -d -p 3000:8080 bentopdf:custom
| `VITE_BRAND_NAME` | Custom brand name | `BentoPDF` |
| `VITE_BRAND_LOGO` | Logo path relative to `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Custom footer/copyright text | `© 2026 BentoPDF. All rights reserved.` |
| `DISABLE_TOOLS` | Comma-separated tool IDs to hide | _(empty; all tools enabled)_ |
WASM module URLs are pre-configured with CDN defaults — all advanced features work out of the box. Override these for air-gapped or self-hosted deployments.
@@ -135,6 +136,74 @@ docker build \
Branding works in both full mode and Simple Mode, and can be combined with all other build-time options.
### Disabling Specific Tools
Hide tools from the UI for compliance or security requirements. Disabled tools are removed from the homepage, search results, keyboard shortcuts, and the workflow builder. Direct URL access shows a "tool unavailable" page.
Tool IDs are the page URL without `.html`. For example, if the tool lives at `bentopdf.com/edit-pdf.html`, the ID is `edit-pdf`.
#### Finding Tool IDs
The easiest way: open any tool in BentoPDF and look at the URL. The last part of the path (without `.html`) is the tool ID.
<details>
<summary>Full list of tool IDs</summary>
**Edit & Annotate:** `edit-pdf`, `bookmark`, `table-of-contents`, `page-numbers`, `add-page-labels`, `bates-numbering`, `add-watermark`, `header-footer`, `invert-colors`, `scanner-effect`, `adjust-colors`, `background-color`, `text-color`, `sign-pdf`, `add-stamps`, `remove-annotations`, `crop-pdf`, `form-filler`, `form-creator`, `remove-blank-pages`
**Convert to PDF:** `image-to-pdf`, `jpg-to-pdf`, `png-to-pdf`, `webp-to-pdf`, `svg-to-pdf`, `bmp-to-pdf`, `heic-to-pdf`, `tiff-to-pdf`, `txt-to-pdf`, `markdown-to-pdf`, `json-to-pdf`, `csv-to-pdf`, `rtf-to-pdf`, `odt-to-pdf`, `word-to-pdf`, `excel-to-pdf`, `powerpoint-to-pdf`, `xps-to-pdf`, `mobi-to-pdf`, `epub-to-pdf`, `fb2-to-pdf`, `cbz-to-pdf`, `wpd-to-pdf`, `wps-to-pdf`, `xml-to-pdf`, `pages-to-pdf`, `odg-to-pdf`, `ods-to-pdf`, `odp-to-pdf`, `pub-to-pdf`, `vsd-to-pdf`, `psd-to-pdf`, `email-to-pdf`
**Convert from PDF:** `pdf-to-jpg`, `pdf-to-png`, `pdf-to-webp`, `pdf-to-bmp`, `pdf-to-tiff`, `pdf-to-cbz`, `pdf-to-svg`, `pdf-to-csv`, `pdf-to-excel`, `pdf-to-greyscale`, `pdf-to-json`, `pdf-to-docx`, `extract-images`, `pdf-to-markdown`, `prepare-pdf-for-ai`, `pdf-to-text`
**Organize & Manage:** `ocr-pdf`, `merge-pdf`, `alternate-merge`, `organize-pdf`, `add-attachments`, `extract-attachments`, `edit-attachments`, `pdf-multi-tool`, `pdf-layers`, `extract-tables`, `split-pdf`, `divide-pages`, `extract-pages`, `delete-pages`, `add-blank-page`, `reverse-pages`, `rotate-pdf`, `rotate-custom`, `n-up-pdf`, `pdf-booklet`, `combine-single-page`, `view-metadata`, `edit-metadata`, `pdf-to-zip`, `compare-pdfs`, `posterize-pdf`, `page-dimensions`
**Optimize & Repair:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
**Security:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
</details>
#### Option 1: Build-time (Docker build arg)
```bash
docker build \
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
-t bentopdf .
```
This bakes the disabled list into the JavaScript bundle. Requires a rebuild to change.
#### Option 2: Runtime (config.json)
Mount a `config.json` file into the served directory — no rebuild needed:
```json
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
```bash
docker run -d \
-p 3000:8080 \
-v ./config.json:/usr/share/nginx/html/config.json:ro \
ghcr.io/alam00000/bentopdf:latest
```
Or with Docker Compose:
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf:latest
ports:
- '3000:8080'
volumes:
- ./config.json:/usr/share/nginx/html/config.json:ro
```
Both methods can be combined — the lists are merged. If a tool appears in either, it is disabled.
### Custom WASM URLs (Air-Gapped / Self-Hosted)
> [!IMPORTANT]

View File

@@ -138,6 +138,34 @@ docker build \
Branding works in both full mode and Simple Mode, and can be combined with all other build-time options (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
### Disabling Specific Tools
Hide individual tools for compliance or security. Disabled tools are removed from the homepage, search, shortcuts, workflow builder, and direct URL access.
Tool IDs are the page URL without `.html` — open any tool and look at the URL (e.g., `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
**Build-time** (baked into the bundle):
```bash
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
```
**Runtime** (no rebuild needed — mount a `config.json`):
```json
{
"disabledTools": ["edit-pdf", "sign-pdf"]
}
```
```bash
docker run -d -p 3000:8080 \
-v ./config.json:/usr/share/nginx/html/config.json:ro \
ghcr.io/alam00000/bentopdf:latest
```
Both methods can be combined — the lists are merged. See the [Docker guide](/self-hosting/docker#disabling-specific-tools) for full details.
## Deployment Guides
Choose your platform:

View File

@@ -6,6 +6,7 @@ Kubernetes may be overkill for a static site, but it can be a great fit if you a
> **Required Headers for Office File Conversion**
>
> LibreOffice-based tools (Word, Excel, PowerPoint conversion) require these HTTP headers for `SharedArrayBuffer` support:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
@@ -155,3 +156,39 @@ httpRoute:
```
Support for specific filters depends on your Gateway controller; if a filter is ignored, add headers at the edge/controller layer instead.
## Disabling Specific Tools
Use a ConfigMap to disable tools at runtime without rebuilding the image:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bentopdf-config
namespace: bentopdf
data:
config.json: |
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
Mount it into the served directory:
```yaml
spec:
containers:
- name: bentopdf
volumeMounts:
- name: config
mountPath: /usr/share/nginx/html/config.json
subPath: config.json
readOnly: true
volumes:
- name: config
configMap:
name: bentopdf-config
```
Tool IDs are the page URL without `.html` — open any tool and look at the URL (e.g., `edit-pdf`, `merge-pdf`, `compress-pdf`). Disabled tools are hidden from the homepage, search, shortcuts, workflow builder, and direct URL access. See the [Docker guide](/self-hosting/docker#disabling-specific-tools) for the full list of options.

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "أدوات PDF",
"subtitle": "اختر أداة للبدء"
},
"disabledTool": {
"title": "الأداة غير متاحة",
"heading": "هذه الأداة معطّلة",
"message": "هذه الأداة غير متوفرة في بيئة النشر الخاصة بك. تواصل مع المسؤول للحصول على مزيد من المعلومات.",
"backHome": "العودة إلى الرئيسية"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "Інструменты PDF",
"subtitle": "Выберыце інструмент, каб пачаць"
},
"disabledTool": {
"title": "Інструмент недаступны",
"heading": "Гэты інструмент адключаны",
"message": "Гэты інструмент недаступны ў вашым разгортванні. Звяжыцеся з адміністратарам для атрымання дадатковай інфармацыі.",
"backHome": "Вярнуцца на галоўную"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "PDF-værktøjer",
"subtitle": "Vælg et værktøj for at komme i gang"
},
"disabledTool": {
"title": "Værktøj utilgængeligt",
"heading": "Dette værktøj er deaktiveret",
"message": "Dette værktøj er ikke tilgængeligt i din installation. Kontakt din administrator for yderligere oplysninger.",
"backHome": "Tilbage til forsiden"
}
}

View File

@@ -362,5 +362,11 @@
"simpleMode": {
"title": "PDF-Werkzeuge",
"subtitle": "Wählen Sie ein Werkzeug aus, um zu beginnen"
},
"disabledTool": {
"title": "Werkzeug nicht verfügbar",
"heading": "Dieses Werkzeug wurde deaktiviert",
"message": "Dieses Werkzeug ist in Ihrer Installation nicht verfügbar. Wenden Sie sich an Ihren Administrator, um weitere Informationen zu erhalten.",
"backHome": "Zurück zur Startseite"
}
}

View File

@@ -363,5 +363,11 @@
"simpleMode": {
"title": "PDF Tools",
"subtitle": "Select a tool to get started"
},
"disabledTool": {
"title": "Tool Unavailable",
"heading": "This tool has been disabled",
"message": "This tool is not available in your deployment. Contact your administrator for more information.",
"backHome": "Back to Home"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "Herramientas PDF",
"subtitle": "Selecciona una herramienta para comenzar"
},
"disabledTool": {
"title": "Herramienta no disponible",
"heading": "Esta herramienta ha sido desactivada",
"message": "Esta herramienta no está disponible en tu instalación. Contacta con tu administrador para obtener más información.",
"backHome": "Volver al inicio"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "Outils PDF",
"subtitle": "Sélectionnez un outil pour commencer"
},
"disabledTool": {
"title": "Outil indisponible",
"heading": "Cet outil a été désactivé",
"message": "Cet outil n'est pas disponible dans votre déploiement. Contactez votre administrateur pour plus d'informations.",
"backHome": "Retour à l'accueil"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "Alat PDF",
"subtitle": "Pilih alat untuk memulai"
},
"disabledTool": {
"title": "Alat Tidak Tersedia",
"heading": "Alat ini telah dinonaktifkan",
"message": "Alat ini tidak tersedia dalam penerapan Anda. Hubungi administrator Anda untuk informasi lebih lanjut.",
"backHome": "Kembali ke Beranda"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "Strumenti PDF",
"subtitle": "Seleziona uno strumento per iniziare"
},
"disabledTool": {
"title": "Strumento Non Disponibile",
"heading": "Questo strumento è stato disabilitato",
"message": "Questo strumento non è disponibile nella tua distribuzione. Contatta il tuo amministratore per ulteriori informazioni.",
"backHome": "Torna alla Home"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "PDF 도구",
"subtitle": "사용할 도구를 선택하세요"
},
"disabledTool": {
"title": "도구를 사용할 수 없음",
"heading": "이 도구는 비활성화되었습니다",
"message": "이 도구는 현재 배포 환경에서 사용할 수 없습니다. 자세한 내용은 관리자에게 문의하세요.",
"backHome": "홈으로 돌아가기"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "PDF-tools",
"subtitle": "Selecteer een tool om te beginnen"
},
"disabledTool": {
"title": "Tool Niet Beschikbaar",
"heading": "Deze tool is uitgeschakeld",
"message": "Deze tool is niet beschikbaar in uw implementatie. Neem contact op met uw beheerder voor meer informatie.",
"backHome": "Terug naar Home"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "Ferramentas PDF",
"subtitle": "Selecione uma ferramenta para começar"
},
"disabledTool": {
"title": "Ferramenta Indisponível",
"heading": "Esta ferramenta foi desativada",
"message": "Esta ferramenta não está disponível na sua implantação. Entre em contato com o seu administrador para mais informações.",
"backHome": "Voltar para o Início"
}
}

View File

@@ -357,5 +357,11 @@
"errorRendering": "Не удалось отрисовать миниатюры страниц",
"error": "Ошибка",
"failedToLoad": "Не удалось загрузить"
},
"disabledTool": {
"title": "Инструмент недоступен",
"heading": "Этот инструмент отключён",
"message": "Этот инструмент недоступен в вашей конфигурации. Обратитесь к администратору для получения дополнительной информации.",
"backHome": "На главную"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "PDF-verktyg",
"subtitle": "Välj ett verktyg för att komma igång"
},
"disabledTool": {
"title": "Verktyget är inte tillgängligt",
"heading": "Det här verktyget har inaktiverats",
"message": "Det här verktyget är inte tillgängligt i din installation. Kontakta din administratör för mer information.",
"backHome": "Tillbaka till startsidan"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "PDF Araçları",
"subtitle": "Başlamak için bir araç seçin"
},
"disabledTool": {
"title": "Araç Kullanılamıyor",
"heading": "Bu araç devre dışı bırakıldı",
"message": "Bu araç, dağıtımınızda mevcut değil. Daha fazla bilgi için yöneticinizle iletişime geçin.",
"backHome": "Ana Sayfaya Dön"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "Công cụ PDF",
"subtitle": "Chọn một công cụ để bắt đầu"
},
"disabledTool": {
"title": "Công cụ không khả dụng",
"heading": "Công cụ này đã bị vô hiệu hóa",
"message": "Công cụ này không có sẵn trong môi trường triển khai của bạn. Vui lòng liên hệ quản trị viên để biết thêm thông tin.",
"backHome": "Quay về trang chủ"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "PDF 工具",
"subtitle": "選擇一個工具開始使用"
},
"disabledTool": {
"title": "工具無法使用",
"heading": "此工具已被停用",
"message": "此工具在您的部署環境中無法使用。請聯絡管理員以取得更多資訊。",
"backHome": "返回首頁"
}
}

View File

@@ -361,5 +361,11 @@
"simpleMode": {
"title": "PDF 工具",
"subtitle": "选择一个工具开始使用"
},
"disabledTool": {
"title": "工具不可用",
"heading": "此工具已被禁用",
"message": "此工具在您的部署环境中不可用。请联系管理员以获取更多信息。",
"backHome": "返回首页"
}
}

View File

@@ -7,6 +7,7 @@ import {
getNodesByCategory,
createNodeByType,
} from '../workflow/nodes/registry';
import { isToolDisabled } from '../utils/disabled-tools.js';
import type { BaseWorkflowNode } from '../workflow/nodes/base-node';
import type { WorkflowEditor } from '../workflow/editor';
import {
@@ -447,7 +448,9 @@ function buildToolbox() {
];
for (const cat of categoryOrder) {
const entries = categorized[cat.key as keyof typeof categorized] ?? [];
const entries = (
categorized[cat.key as keyof typeof categorized] ?? []
).filter((entry) => !entry.toolPageId || !isToolDisabled(entry.toolPageId));
if (entries.length === 0) continue;
const section = document.createElement('div');

View File

@@ -15,13 +15,38 @@ import {
createLanguageSwitcher,
t,
} from './i18n/index.js';
import {
loadRuntimeConfig,
isToolDisabled,
isCurrentPageDisabled,
} from './utils/disabled-tools.js';
declare const __BRAND_NAME__: string;
const init = async () => {
await initI18n();
await loadRuntimeConfig();
injectLanguageSwitcher();
applyTranslations();
if (isCurrentPageDisabled()) {
document.title = t('disabledTool.title') || 'Tool Unavailable';
const main = document.querySelector('main') || document.body;
const heading = t('disabledTool.heading') || 'This tool has been disabled';
const message =
t('disabledTool.message') ||
'This tool is not available in your deployment. Contact your administrator for more information.';
const backHome = t('disabledTool.backHome') || 'Back to Home';
main.innerHTML = `
<div class="flex flex-col items-center justify-center min-h-[60vh] text-center px-4">
<i class="ph ph-prohibit text-6xl text-gray-500 mb-4"></i>
<h1 class="text-2xl font-bold text-white mb-2">${heading}</h1>
<p class="text-gray-400 mb-6">${message}</p>
<a href="${import.meta.env.BASE_URL}" class="px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg transition">${backHome}</a>
</div>
`;
return;
}
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url
@@ -275,7 +300,14 @@ const init = async () => {
);
}
categories.forEach((category) => {
const filteredCategories = categories
.map((category) => ({
...category,
tools: category.tools.filter((tool) => !isToolDisabled(tool.id)),
}))
.filter((category) => category.tools.length > 0);
filteredCategories.forEach((category) => {
const categoryGroup = document.createElement('div');
categoryGroup.className = 'category-group col-span-full';
@@ -872,16 +904,21 @@ const init = async () => {
const allShortcuts = ShortcutsManager.getAllShortcuts();
const isMac = navigator.userAgent.toUpperCase().includes('MAC');
const allTools = categories.flatMap((c) => c.tools);
const shortcutCategories = categories
.map((category) => ({
...category,
tools: category.tools.filter((tool) => !isToolDisabled(tool.id)),
}))
.filter((category) => category.tools.length > 0);
const allTools = shortcutCategories.flatMap((c) => c.tools);
categories.forEach((category) => {
shortcutCategories.forEach((category) => {
const section = document.createElement('div');
section.className = 'category-section mb-6 last:mb-0';
const header = document.createElement('h3');
header.className =
'text-gray-400 text-xs font-bold uppercase tracking-wider mb-3 pl-1';
// Translate category name
const categoryKey = categoryTranslationKeys[category.name];
header.textContent = categoryKey ? t(categoryKey) : category.name;
section.appendChild(header);

View File

@@ -0,0 +1,3 @@
export interface AppConfig {
disabledTools?: string[];
}

View File

@@ -55,3 +55,4 @@ export * from './add-page-labels-type.ts';
export * from './pdf-to-tiff-type.ts';
export * from './pdf-to-cbz-type.ts';
export * from './password-prompt-type.ts';
export * from './config-types.ts';

View File

@@ -0,0 +1,43 @@
import type { AppConfig } from '@/types';
const disabledToolsSet = new Set<string>(__DISABLED_TOOLS__);
let runtimeConfigLoaded = false;
export async function loadRuntimeConfig(): Promise<void> {
if (runtimeConfigLoaded) return;
runtimeConfigLoaded = true;
try {
const response = await fetch(`${import.meta.env.BASE_URL}config.json`, {
cache: 'no-cache',
});
if (!response.ok) return;
const config: AppConfig = await response.json();
if (Array.isArray(config.disabledTools)) {
for (const toolId of config.disabledTools) {
if (typeof toolId === 'string') {
disabledToolsSet.add(toolId);
}
}
}
} catch {}
}
export function isToolDisabled(toolId: string): boolean {
return disabledToolsSet.has(toolId);
}
export function getToolIdFromPath(): string | null {
const path = window.location.pathname;
const withExt = path.match(/\/([^/]+)\.html$/);
if (withExt) return withExt[1];
const withoutExt = path.match(/\/([^/]+)\/?$/);
return withoutExt?.[1] ?? null;
}
export function isCurrentPageDisabled(): boolean {
const toolId = getToolIdFromPath();
if (!toolId) return false;
return isToolDisabled(toolId);
}

View File

@@ -80,6 +80,7 @@ export interface NodeRegistryEntry {
description: string;
factory: () => BaseWorkflowNode;
hidden?: boolean;
toolPageId?: string;
}
export const nodeRegistry: Record<string, NodeRegistryEntry> = {
@@ -96,6 +97,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-image',
description: 'Upload images and convert to PDF',
factory: () => new ImageInputNode(),
toolPageId: 'image-to-pdf',
},
WordToPdfNode: {
label: 'Word to PDF',
@@ -103,6 +105,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-microsoft-word-logo',
description: 'Convert Word documents to PDF',
factory: () => new WordToPdfNode(),
toolPageId: 'word-to-pdf',
},
ExcelToPdfNode: {
label: 'Excel to PDF',
@@ -110,6 +113,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-microsoft-excel-logo',
description: 'Convert Excel spreadsheets to PDF',
factory: () => new ExcelToPdfNode(),
toolPageId: 'excel-to-pdf',
},
PowerPointToPdfNode: {
label: 'PowerPoint to PDF',
@@ -117,6 +121,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-microsoft-powerpoint-logo',
description: 'Convert PowerPoint presentations to PDF',
factory: () => new PowerPointToPdfNode(),
toolPageId: 'powerpoint-to-pdf',
},
TextToPdfNode: {
label: 'Text to PDF',
@@ -124,6 +129,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-text-t',
description: 'Convert plain text to PDF',
factory: () => new TextToPdfNode(),
toolPageId: 'txt-to-pdf',
},
SvgToPdfNode: {
label: 'SVG to PDF',
@@ -131,6 +137,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-svg',
description: 'Convert SVG files to PDF',
factory: () => new SvgToPdfNode(),
toolPageId: 'svg-to-pdf',
},
EpubToPdfNode: {
label: 'EPUB to PDF',
@@ -138,6 +145,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-book-open-text',
description: 'Convert EPUB ebooks to PDF',
factory: () => new EpubToPdfNode(),
toolPageId: 'epub-to-pdf',
},
EmailToPdfNode: {
label: 'Email to PDF',
@@ -145,6 +153,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-envelope',
description: 'Convert email files (.eml, .msg) to PDF',
factory: () => new EmailToPdfNode(),
toolPageId: 'email-to-pdf',
},
XpsToPdfNode: {
label: 'XPS to PDF',
@@ -152,6 +161,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-scan',
description: 'Convert XPS/OXPS documents to PDF',
factory: () => new XpsToPdfNode(),
toolPageId: 'xps-to-pdf',
},
MobiToPdfNode: {
label: 'MOBI to PDF',
@@ -159,6 +169,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-book-open-text',
description: 'Convert MOBI e-books to PDF',
factory: () => new MobiToPdfNode(),
toolPageId: 'mobi-to-pdf',
},
Fb2ToPdfNode: {
label: 'FB2 to PDF',
@@ -166,6 +177,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-book-bookmark',
description: 'Convert FB2 e-books to PDF',
factory: () => new Fb2ToPdfNode(),
toolPageId: 'fb2-to-pdf',
},
CbzToPdfNode: {
label: 'CBZ to PDF',
@@ -173,6 +185,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-book-open',
description: 'Convert comic book archives (CBZ/CBR) to PDF',
factory: () => new CbzToPdfNode(),
toolPageId: 'cbz-to-pdf',
},
MarkdownToPdfNode: {
label: 'Markdown to PDF',
@@ -180,6 +193,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-markdown-logo',
description: 'Convert Markdown files to PDF',
factory: () => new MarkdownToPdfNode(),
toolPageId: 'markdown-to-pdf',
},
JsonToPdfNode: {
label: 'JSON to PDF',
@@ -187,6 +201,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-code',
description: 'Convert JSON files to PDF',
factory: () => new JsonToPdfNode(),
toolPageId: 'json-to-pdf',
},
XmlToPdfNode: {
label: 'XML to PDF',
@@ -194,6 +209,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-code',
description: 'Convert XML documents to PDF',
factory: () => new XmlToPdfNode(),
toolPageId: 'xml-to-pdf',
},
WpdToPdfNode: {
label: 'WPD to PDF',
@@ -201,6 +217,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-text',
description: 'Convert WordPerfect documents to PDF',
factory: () => new WpdToPdfNode(),
toolPageId: 'wpd-to-pdf',
},
WpsToPdfNode: {
label: 'WPS to PDF',
@@ -208,6 +225,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-text',
description: 'Convert WPS Office documents to PDF',
factory: () => new WpsToPdfNode(),
toolPageId: 'wps-to-pdf',
},
PagesToPdfNode: {
label: 'Pages to PDF',
@@ -215,6 +233,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-text',
description: 'Convert Apple Pages documents to PDF',
factory: () => new PagesToPdfNode(),
toolPageId: 'pages-to-pdf',
},
OdgToPdfNode: {
label: 'ODG to PDF',
@@ -222,6 +241,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-image',
description: 'Convert OpenDocument Graphics to PDF',
factory: () => new OdgToPdfNode(),
toolPageId: 'odg-to-pdf',
},
PubToPdfNode: {
label: 'PUB to PDF',
@@ -229,6 +249,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-book-open',
description: 'Convert Microsoft Publisher to PDF',
factory: () => new PubToPdfNode(),
toolPageId: 'pub-to-pdf',
},
VsdToPdfNode: {
label: 'VSD to PDF',
@@ -236,6 +257,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-git-branch',
description: 'Convert Visio diagrams (VSD/VSDX) to PDF',
factory: () => new VsdToPdfNode(),
toolPageId: 'vsd-to-pdf',
},
MergeNode: {
label: 'Merge PDFs',
@@ -243,6 +265,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-browsers',
description: 'Combine multiple PDFs into one',
factory: () => new MergeNode(),
toolPageId: 'merge-pdf',
},
SplitNode: {
label: 'Split PDF',
@@ -250,6 +273,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-scissors',
description: 'Extract a range of pages',
factory: () => new SplitNode(),
toolPageId: 'split-pdf',
},
ExtractPagesNode: {
label: 'Extract Pages',
@@ -257,6 +281,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-squares-four',
description: 'Extract pages as separate PDFs',
factory: () => new ExtractPagesNode(),
toolPageId: 'extract-pages',
},
RotateNode: {
label: 'Rotate',
@@ -264,6 +289,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-arrow-clockwise',
description: 'Rotate all pages',
factory: () => new RotateNode(),
toolPageId: 'rotate-pdf',
},
DeletePagesNode: {
label: 'Delete Pages',
@@ -271,6 +297,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-trash',
description: 'Remove specific pages',
factory: () => new DeletePagesNode(),
toolPageId: 'delete-pages',
},
ReversePagesNode: {
label: 'Reverse Pages',
@@ -278,6 +305,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-sort-descending',
description: 'Reverse page order',
factory: () => new ReversePagesNode(),
toolPageId: 'reverse-pages',
},
AddBlankPageNode: {
label: 'Add Blank Page',
@@ -285,6 +313,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-plus',
description: 'Insert blank pages',
factory: () => new AddBlankPageNode(),
toolPageId: 'add-blank-page',
},
DividePagesNode: {
label: 'Divide Pages',
@@ -292,6 +321,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-columns',
description: 'Split pages vertically or horizontally',
factory: () => new DividePagesNode(),
toolPageId: 'divide-pages',
},
NUpNode: {
label: 'N-Up',
@@ -299,6 +329,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-squares-four',
description: 'Arrange multiple pages per sheet',
factory: () => new NUpNode(),
toolPageId: 'n-up-pdf',
},
FixPageSizeNode: {
label: 'Fix Page Size',
@@ -306,6 +337,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-frame-corners',
description: 'Standardize all pages to a target size',
factory: () => new FixPageSizeNode(),
toolPageId: 'fix-page-size',
},
CombineSinglePageNode: {
label: 'Combine to Single Page',
@@ -313,6 +345,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-arrows-out-line-vertical',
description: 'Stitch all pages into one continuous page',
factory: () => new CombineSinglePageNode(),
toolPageId: 'combine-single-page',
},
BookletNode: {
label: 'Booklet',
@@ -320,6 +353,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-book-open',
description: 'Arrange pages for booklet printing',
factory: () => new BookletNode(),
toolPageId: 'pdf-booklet',
},
PosterizeNode: {
label: 'Posterize',
@@ -327,6 +361,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-notepad',
description: 'Split pages into tile grid for poster printing',
factory: () => new PosterizeNode(),
toolPageId: 'posterize-pdf',
},
EditMetadataNode: {
label: 'Edit Metadata',
@@ -334,6 +369,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-code',
description: 'Edit PDF metadata',
factory: () => new EditMetadataNode(),
toolPageId: 'edit-metadata',
},
TableOfContentsNode: {
label: 'Table of Contents',
@@ -341,6 +377,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-list',
description: 'Generate table of contents from bookmarks',
factory: () => new TableOfContentsNode(),
toolPageId: 'table-of-contents',
},
OCRNode: {
label: 'OCR',
@@ -348,6 +385,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-barcode',
description: 'Add searchable text layer via OCR',
factory: () => new OCRNode(),
toolPageId: 'ocr-pdf',
},
CropNode: {
label: 'Crop',
@@ -355,6 +393,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-crop',
description: 'Trim margins from all pages',
factory: () => new CropNode(),
toolPageId: 'crop-pdf',
},
GreyscaleNode: {
label: 'Greyscale',
@@ -362,6 +401,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-palette',
description: 'Convert to greyscale',
factory: () => new GreyscaleNode(),
toolPageId: 'pdf-to-greyscale',
},
InvertColorsNode: {
label: 'Invert Colors',
@@ -369,6 +409,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-circle-half',
description: 'Invert all colors',
factory: () => new InvertColorsNode(),
toolPageId: 'invert-colors',
},
ScannerEffectNode: {
label: 'Scanner Effect',
@@ -376,6 +417,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-scan',
description: 'Apply scanner simulation effect',
factory: () => new ScannerEffectNode(),
toolPageId: 'scanner-effect',
},
AdjustColorsNode: {
label: 'Adjust Colors',
@@ -383,6 +425,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-sliders-horizontal',
description: 'Adjust brightness, contrast, and colors',
factory: () => new AdjustColorsNode(),
toolPageId: 'adjust-colors',
},
BackgroundColorNode: {
label: 'Background Color',
@@ -390,6 +433,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-palette',
description: 'Change background color',
factory: () => new BackgroundColorNode(),
toolPageId: 'background-color',
},
WatermarkNode: {
label: 'Watermark',
@@ -397,6 +441,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-drop',
description: 'Add text watermark',
factory: () => new WatermarkNode(),
toolPageId: 'add-watermark',
},
PageNumbersNode: {
label: 'Page Numbers',
@@ -404,6 +449,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-list-numbers',
description: 'Add page numbers',
factory: () => new PageNumbersNode(),
toolPageId: 'page-numbers',
},
HeaderFooterNode: {
label: 'Header & Footer',
@@ -411,6 +457,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-paragraph',
description: 'Add header and footer text',
factory: () => new HeaderFooterNode(),
toolPageId: 'header-footer',
},
RemoveBlankPagesNode: {
label: 'Remove Blank Pages',
@@ -418,6 +465,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-minus',
description: 'Remove blank pages automatically',
factory: () => new RemoveBlankPagesNode(),
toolPageId: 'remove-blank-pages',
},
RemoveAnnotationsNode: {
label: 'Remove Annotations',
@@ -425,6 +473,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-eraser',
description: 'Strip all annotations',
factory: () => new RemoveAnnotationsNode(),
toolPageId: 'remove-annotations',
},
CompressNode: {
label: 'Compress',
@@ -432,6 +481,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-lightning',
description: 'Reduce PDF file size',
factory: () => new CompressNode(),
toolPageId: 'compress-pdf',
},
RasterizeNode: {
label: 'Rasterize',
@@ -439,6 +489,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-image',
description: 'Convert to image-based PDF',
factory: () => new RasterizeNode(),
toolPageId: 'rasterize-pdf',
},
LinearizeNode: {
label: 'Linearize',
@@ -446,6 +497,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-gauge',
description: 'Optimize PDF for fast web viewing',
factory: () => new LinearizeNode(),
toolPageId: 'linearize-pdf',
},
DeskewNode: {
label: 'Deskew',
@@ -453,6 +505,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-perspective',
description: 'Straighten skewed PDF pages',
factory: () => new DeskewNode(),
toolPageId: 'deskew-pdf',
},
PdfToPdfANode: {
label: 'PDF to PDF/A',
@@ -460,6 +513,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-archive',
description: 'Convert PDF to PDF/A for archiving',
factory: () => new PdfToPdfANode(),
toolPageId: 'pdf-to-pdfa',
},
FontToOutlineNode: {
label: 'Font to Outline',
@@ -467,6 +521,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-text-outdent',
description: 'Convert fonts to vector outlines',
factory: () => new FontToOutlineNode(),
toolPageId: 'font-to-outline',
},
RepairNode: {
label: 'Repair',
@@ -474,6 +529,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-wrench',
description: 'Repair corrupted PDF',
factory: () => new RepairNode(),
toolPageId: 'repair-pdf',
},
EncryptNode: {
label: 'Encrypt',
@@ -481,6 +537,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-lock',
description: 'Encrypt PDF with password',
factory: () => new EncryptNode(),
toolPageId: 'encrypt-pdf',
},
DecryptNode: {
label: 'Decrypt',
@@ -488,6 +545,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-lock-open',
description: 'Remove PDF password protection',
factory: () => new DecryptNode(),
toolPageId: 'decrypt-pdf',
},
SanitizeNode: {
label: 'Sanitize',
@@ -495,6 +553,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-broom',
description: 'Remove metadata, scripts, and hidden data',
factory: () => new SanitizeNode(),
toolPageId: 'sanitize-pdf',
},
FlattenNode: {
label: 'Flatten',
@@ -502,6 +561,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-stack',
description: 'Flatten forms and annotations',
factory: () => new FlattenNode(),
toolPageId: 'flatten-pdf',
},
DigitalSignNode: {
label: 'Digital Sign',
@@ -509,6 +569,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-certificate',
description: 'Apply a digital signature to PDF',
factory: () => new DigitalSignNode(),
toolPageId: 'digital-sign-pdf',
},
TimestampNode: {
label: 'Timestamp',
@@ -516,6 +577,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-clock',
description: 'Add an RFC 3161 document timestamp',
factory: () => new TimestampNode(),
toolPageId: 'timestamp-pdf',
},
RedactNode: {
label: 'Redact',
@@ -523,6 +585,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-eye-slash',
description: 'Redact text from PDF',
factory: () => new RedactNode(),
toolPageId: 'edit-pdf',
},
DownloadNode: {
label: 'Download',
@@ -554,6 +617,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-image',
description: 'Convert PDF pages to images (ZIP)',
factory: () => new PdfToImagesNode(),
toolPageId: 'pdf-to-jpg',
},
PdfToTextNode: {
label: 'PDF to Text',
@@ -561,6 +625,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-text-aa',
description: 'Extract text from PDF',
factory: () => new PdfToTextNode(),
toolPageId: 'pdf-to-text',
},
PdfToDocxNode: {
label: 'PDF to DOCX',
@@ -568,6 +633,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-microsoft-word-logo',
description: 'Convert PDF to Word document',
factory: () => new PdfToDocxNode(),
toolPageId: 'pdf-to-docx',
},
PdfToXlsxNode: {
label: 'PDF to XLSX',
@@ -575,6 +641,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-microsoft-excel-logo',
description: 'Convert PDF tables to Excel',
factory: () => new PdfToXlsxNode(),
toolPageId: 'pdf-to-excel',
},
PdfToCsvNode: {
label: 'PDF to CSV',
@@ -582,6 +649,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-csv',
description: 'Convert PDF tables to CSV',
factory: () => new PdfToCsvNode(),
toolPageId: 'pdf-to-csv',
},
PdfToSvgNode: {
label: 'PDF to SVG',
@@ -589,6 +657,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-file-code',
description: 'Convert PDF pages to SVG',
factory: () => new PdfToSvgNode(),
toolPageId: 'pdf-to-svg',
},
PdfToMarkdownNode: {
label: 'PDF to Markdown',
@@ -596,6 +665,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-markdown-logo',
description: 'Convert PDF to Markdown text',
factory: () => new PdfToMarkdownNode(),
toolPageId: 'pdf-to-markdown',
},
ExtractImagesNode: {
label: 'Extract Images',
@@ -603,6 +673,7 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
icon: 'ph-download-simple',
description: 'Extract all images from PDF',
factory: () => new ExtractImagesNode(),
toolPageId: 'extract-images',
},
};

View File

@@ -13,3 +13,4 @@ interface ImportMeta {
}
declare const __SIMPLE_MODE__: boolean;
declare const __DISABLED_TOOLS__: string[];

View File

@@ -420,6 +420,12 @@ export default defineConfig(() => {
define: {
__SIMPLE_MODE__: JSON.stringify(process.env.SIMPLE_MODE === 'true'),
__BRAND_NAME__: JSON.stringify(process.env.VITE_BRAND_NAME || ''),
__DISABLED_TOOLS__: JSON.stringify(
(process.env.DISABLE_TOOLS || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
),
},
resolve: {
alias: {