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_BRAND_LOGO=$VITE_BRAND_LOGO
ENV VITE_FOOTER_TEXT=$VITE_FOOTER_TEXT ENV VITE_FOOTER_TEXT=$VITE_FOOTER_TEXT
ARG DISABLE_TOOLS
ENV DISABLE_TOOLS=$DISABLE_TOOLS
ENV NODE_OPTIONS="--max-old-space-size=3072" ENV NODE_OPTIONS="--max-old-space-size=3072"
RUN --mount=type=secret,id=VITE_CORS_PROXY_URL \ 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_NAME` | Custom brand name | `BentoPDF` |
| `VITE_BRAND_LOGO` | Logo path relative to `public/` | `images/favicon-no-bg.svg` | | `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.` | | `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. 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. 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) ### Custom WASM URLs (Air-Gapped / Self-Hosted)
> [!IMPORTANT] > [!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`). 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 ## Deployment Guides
Choose your platform: 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** > **Required Headers for Office File Conversion**
> >
> LibreOffice-based tools (Word, Excel, PowerPoint conversion) require these HTTP headers for `SharedArrayBuffer` support: > LibreOffice-based tools (Word, Excel, PowerPoint conversion) require these HTTP headers for `SharedArrayBuffer` support:
>
> - `Cross-Origin-Opener-Policy: same-origin` > - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp` > - `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. 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": { "simpleMode": {
"title": "أدوات PDF", "title": "أدوات PDF",
"subtitle": "اختر أداة للبدء" "subtitle": "اختر أداة للبدء"
},
"disabledTool": {
"title": "الأداة غير متاحة",
"heading": "هذه الأداة معطّلة",
"message": "هذه الأداة غير متوفرة في بيئة النشر الخاصة بك. تواصل مع المسؤول للحصول على مزيد من المعلومات.",
"backHome": "العودة إلى الرئيسية"
} }
} }

View File

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

View File

@@ -361,5 +361,11 @@
"simpleMode": { "simpleMode": {
"title": "PDF-værktøjer", "title": "PDF-værktøjer",
"subtitle": "Vælg et værktøj for at komme i gang" "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": { "simpleMode": {
"title": "PDF-Werkzeuge", "title": "PDF-Werkzeuge",
"subtitle": "Wählen Sie ein Werkzeug aus, um zu beginnen" "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": { "simpleMode": {
"title": "PDF Tools", "title": "PDF Tools",
"subtitle": "Select a tool to get started" "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": { "simpleMode": {
"title": "Herramientas PDF", "title": "Herramientas PDF",
"subtitle": "Selecciona una herramienta para comenzar" "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": { "simpleMode": {
"title": "Outils PDF", "title": "Outils PDF",
"subtitle": "Sélectionnez un outil pour commencer" "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": { "simpleMode": {
"title": "Alat PDF", "title": "Alat PDF",
"subtitle": "Pilih alat untuk memulai" "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": { "simpleMode": {
"title": "Strumenti PDF", "title": "Strumenti PDF",
"subtitle": "Seleziona uno strumento per iniziare" "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": { "simpleMode": {
"title": "PDF 도구", "title": "PDF 도구",
"subtitle": "사용할 도구를 선택하세요" "subtitle": "사용할 도구를 선택하세요"
},
"disabledTool": {
"title": "도구를 사용할 수 없음",
"heading": "이 도구는 비활성화되었습니다",
"message": "이 도구는 현재 배포 환경에서 사용할 수 없습니다. 자세한 내용은 관리자에게 문의하세요.",
"backHome": "홈으로 돌아가기"
} }
} }

View File

@@ -361,5 +361,11 @@
"simpleMode": { "simpleMode": {
"title": "PDF-tools", "title": "PDF-tools",
"subtitle": "Selecteer een tool om te beginnen" "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": { "simpleMode": {
"title": "Ferramentas PDF", "title": "Ferramentas PDF",
"subtitle": "Selecione uma ferramenta para começar" "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": "Не удалось отрисовать миниатюры страниц", "errorRendering": "Не удалось отрисовать миниатюры страниц",
"error": "Ошибка", "error": "Ошибка",
"failedToLoad": "Не удалось загрузить" "failedToLoad": "Не удалось загрузить"
},
"disabledTool": {
"title": "Инструмент недоступен",
"heading": "Этот инструмент отключён",
"message": "Этот инструмент недоступен в вашей конфигурации. Обратитесь к администратору для получения дополнительной информации.",
"backHome": "На главную"
} }
} }

View File

@@ -361,5 +361,11 @@
"simpleMode": { "simpleMode": {
"title": "PDF-verktyg", "title": "PDF-verktyg",
"subtitle": "Välj ett verktyg för att komma igång" "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": { "simpleMode": {
"title": "PDF Araçları", "title": "PDF Araçları",
"subtitle": "Başlamak için bir araç seçin" "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": { "simpleMode": {
"title": "Công cụ PDF", "title": "Công cụ PDF",
"subtitle": "Chọn một công cụ để bắt đầu" "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": { "simpleMode": {
"title": "PDF 工具", "title": "PDF 工具",
"subtitle": "選擇一個工具開始使用" "subtitle": "選擇一個工具開始使用"
},
"disabledTool": {
"title": "工具無法使用",
"heading": "此工具已被停用",
"message": "此工具在您的部署環境中無法使用。請聯絡管理員以取得更多資訊。",
"backHome": "返回首頁"
} }
} }

View File

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

View File

@@ -7,6 +7,7 @@ import {
getNodesByCategory, getNodesByCategory,
createNodeByType, createNodeByType,
} from '../workflow/nodes/registry'; } from '../workflow/nodes/registry';
import { isToolDisabled } from '../utils/disabled-tools.js';
import type { BaseWorkflowNode } from '../workflow/nodes/base-node'; import type { BaseWorkflowNode } from '../workflow/nodes/base-node';
import type { WorkflowEditor } from '../workflow/editor'; import type { WorkflowEditor } from '../workflow/editor';
import { import {
@@ -447,7 +448,9 @@ function buildToolbox() {
]; ];
for (const cat of categoryOrder) { 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; if (entries.length === 0) continue;
const section = document.createElement('div'); const section = document.createElement('div');

View File

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

View File

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

View File

@@ -420,6 +420,12 @@ export default defineConfig(() => {
define: { define: {
__SIMPLE_MODE__: JSON.stringify(process.env.SIMPLE_MODE === 'true'), __SIMPLE_MODE__: JSON.stringify(process.env.SIMPLE_MODE === 'true'),
__BRAND_NAME__: JSON.stringify(process.env.VITE_BRAND_NAME || ''), __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: { resolve: {
alias: { alias: {