diff --git a/Dockerfile b/Dockerfile index f42cda5..106eb15 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 \ diff --git a/docs/self-hosting/docker.md b/docs/self-hosting/docker.md index 2e3d2f4..e062a07 100644 --- a/docs/self-hosting/docker.md +++ b/docs/self-hosting/docker.md @@ -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. + +
+Full list of tool IDs + +**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` + +
+ +#### 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] diff --git a/docs/self-hosting/index.md b/docs/self-hosting/index.md index 3a79e1f..d1d1f79 100644 --- a/docs/self-hosting/index.md +++ b/docs/self-hosting/index.md @@ -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: diff --git a/docs/self-hosting/kubernetes.md b/docs/self-hosting/kubernetes.md index 183ad9e..354010b 100644 --- a/docs/self-hosting/kubernetes.md +++ b/docs/self-hosting/kubernetes.md @@ -4,11 +4,12 @@ Kubernetes may be overkill for a static site, but it can be a great fit if you a > [!IMPORTANT] > **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` -> +> > The official BentoPDF nginx images include these headers. In Kubernetes, **Ingress/Gateway controllers are also reverse proxies**, so ensure these headers are preserved (or add them at the edge). ## Prereqs @@ -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. diff --git a/public/locales/ar/common.json b/public/locales/ar/common.json index 133e022..693b1a7 100644 --- a/public/locales/ar/common.json +++ b/public/locales/ar/common.json @@ -361,5 +361,11 @@ "simpleMode": { "title": "أدوات PDF", "subtitle": "اختر أداة للبدء" + }, + "disabledTool": { + "title": "الأداة غير متاحة", + "heading": "هذه الأداة معطّلة", + "message": "هذه الأداة غير متوفرة في بيئة النشر الخاصة بك. تواصل مع المسؤول للحصول على مزيد من المعلومات.", + "backHome": "العودة إلى الرئيسية" } } diff --git a/public/locales/be/common.json b/public/locales/be/common.json index 3161429..67c94ee 100644 --- a/public/locales/be/common.json +++ b/public/locales/be/common.json @@ -361,5 +361,11 @@ "simpleMode": { "title": "Інструменты PDF", "subtitle": "Выберыце інструмент, каб пачаць" + }, + "disabledTool": { + "title": "Інструмент недаступны", + "heading": "Гэты інструмент адключаны", + "message": "Гэты інструмент недаступны ў вашым разгортванні. Звяжыцеся з адміністратарам для атрымання дадатковай інфармацыі.", + "backHome": "Вярнуцца на галоўную" } } diff --git a/public/locales/da/common.json b/public/locales/da/common.json index 6710815..68836e3 100644 --- a/public/locales/da/common.json +++ b/public/locales/da/common.json @@ -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" } } diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 3c342e8..b24e2da 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -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" } } diff --git a/public/locales/en/common.json b/public/locales/en/common.json index c66aebd..0d9b28f 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -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" } } diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 690ba7b..039d1ae 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -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" } } diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json index cbd7211..16aa178 100644 --- a/public/locales/fr/common.json +++ b/public/locales/fr/common.json @@ -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" } } diff --git a/public/locales/id/common.json b/public/locales/id/common.json index f178594..1956687 100644 --- a/public/locales/id/common.json +++ b/public/locales/id/common.json @@ -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" } } diff --git a/public/locales/it/common.json b/public/locales/it/common.json index e938a8a..c72f4d8 100644 --- a/public/locales/it/common.json +++ b/public/locales/it/common.json @@ -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" } } diff --git a/public/locales/ko/common.json b/public/locales/ko/common.json index bbe93bd..6beed76 100644 --- a/public/locales/ko/common.json +++ b/public/locales/ko/common.json @@ -361,5 +361,11 @@ "simpleMode": { "title": "PDF 도구", "subtitle": "사용할 도구를 선택하세요" + }, + "disabledTool": { + "title": "도구를 사용할 수 없음", + "heading": "이 도구는 비활성화되었습니다", + "message": "이 도구는 현재 배포 환경에서 사용할 수 없습니다. 자세한 내용은 관리자에게 문의하세요.", + "backHome": "홈으로 돌아가기" } } diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json index ba30d98..2dc6027 100644 --- a/public/locales/nl/common.json +++ b/public/locales/nl/common.json @@ -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" } } diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json index 632ebb9..0f4ac7d 100644 --- a/public/locales/pt/common.json +++ b/public/locales/pt/common.json @@ -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" } } diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 54e05f5..ef82a2b 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -357,5 +357,11 @@ "errorRendering": "Не удалось отрисовать миниатюры страниц", "error": "Ошибка", "failedToLoad": "Не удалось загрузить" + }, + "disabledTool": { + "title": "Инструмент недоступен", + "heading": "Этот инструмент отключён", + "message": "Этот инструмент недоступен в вашей конфигурации. Обратитесь к администратору для получения дополнительной информации.", + "backHome": "На главную" } } diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json index a63647d..a602899 100644 --- a/public/locales/sv/common.json +++ b/public/locales/sv/common.json @@ -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" } } diff --git a/public/locales/tr/common.json b/public/locales/tr/common.json index ca6fceb..3d32b95 100644 --- a/public/locales/tr/common.json +++ b/public/locales/tr/common.json @@ -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" } } diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json index 0a57f66..3ee1a28 100644 --- a/public/locales/vi/common.json +++ b/public/locales/vi/common.json @@ -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ủ" } } diff --git a/public/locales/zh-TW/common.json b/public/locales/zh-TW/common.json index 7b10dc4..6d372a8 100644 --- a/public/locales/zh-TW/common.json +++ b/public/locales/zh-TW/common.json @@ -361,5 +361,11 @@ "simpleMode": { "title": "PDF 工具", "subtitle": "選擇一個工具開始使用" + }, + "disabledTool": { + "title": "工具無法使用", + "heading": "此工具已被停用", + "message": "此工具在您的部署環境中無法使用。請聯絡管理員以取得更多資訊。", + "backHome": "返回首頁" } } diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index a3b76bd..8aa2583 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -361,5 +361,11 @@ "simpleMode": { "title": "PDF 工具", "subtitle": "选择一个工具开始使用" + }, + "disabledTool": { + "title": "工具不可用", + "heading": "此工具已被禁用", + "message": "此工具在您的部署环境中不可用。请联系管理员以获取更多信息。", + "backHome": "返回首页" } } diff --git a/src/js/logic/pdf-workflow-page.ts b/src/js/logic/pdf-workflow-page.ts index 7bac637..1a6e59c 100644 --- a/src/js/logic/pdf-workflow-page.ts +++ b/src/js/logic/pdf-workflow-page.ts @@ -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'); diff --git a/src/js/main.ts b/src/js/main.ts index a20e7de..50458f2 100644 --- a/src/js/main.ts +++ b/src/js/main.ts @@ -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 = ` +
+ +

${heading}

+

${message}

+ ${backHome} +
+ `; + 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); diff --git a/src/js/types/config-types.ts b/src/js/types/config-types.ts new file mode 100644 index 0000000..40c29c5 --- /dev/null +++ b/src/js/types/config-types.ts @@ -0,0 +1,3 @@ +export interface AppConfig { + disabledTools?: string[]; +} diff --git a/src/js/types/index.ts b/src/js/types/index.ts index b859335..acd03cd 100644 --- a/src/js/types/index.ts +++ b/src/js/types/index.ts @@ -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'; diff --git a/src/js/utils/disabled-tools.ts b/src/js/utils/disabled-tools.ts new file mode 100644 index 0000000..a70491d --- /dev/null +++ b/src/js/utils/disabled-tools.ts @@ -0,0 +1,43 @@ +import type { AppConfig } from '@/types'; + +const disabledToolsSet = new Set(__DISABLED_TOOLS__); +let runtimeConfigLoaded = false; + +export async function loadRuntimeConfig(): Promise { + 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); +} diff --git a/src/js/workflow/nodes/registry.ts b/src/js/workflow/nodes/registry.ts index 2fdfa67..10dd6a6 100644 --- a/src/js/workflow/nodes/registry.ts +++ b/src/js/workflow/nodes/registry.ts @@ -80,6 +80,7 @@ export interface NodeRegistryEntry { description: string; factory: () => BaseWorkflowNode; hidden?: boolean; + toolPageId?: string; } export const nodeRegistry: Record = { @@ -96,6 +97,7 @@ export const nodeRegistry: Record = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { icon: 'ph-palette', description: 'Change background color', factory: () => new BackgroundColorNode(), + toolPageId: 'background-color', }, WatermarkNode: { label: 'Watermark', @@ -397,6 +441,7 @@ export const nodeRegistry: Record = { 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 = { 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 = { 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 = { 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 = { icon: 'ph-eraser', description: 'Strip all annotations', factory: () => new RemoveAnnotationsNode(), + toolPageId: 'remove-annotations', }, CompressNode: { label: 'Compress', @@ -432,6 +481,7 @@ export const nodeRegistry: Record = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { icon: 'ph-wrench', description: 'Repair corrupted PDF', factory: () => new RepairNode(), + toolPageId: 'repair-pdf', }, EncryptNode: { label: 'Encrypt', @@ -481,6 +537,7 @@ export const nodeRegistry: Record = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { icon: 'ph-download-simple', description: 'Extract all images from PDF', factory: () => new ExtractImagesNode(), + toolPageId: 'extract-images', }, }; diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index aee6f2f..e2b983e 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -13,3 +13,4 @@ interface ImportMeta { } declare const __SIMPLE_MODE__: boolean; +declare const __DISABLED_TOOLS__: string[]; diff --git a/vite.config.ts b/vite.config.ts index f193aa7..1f8e3af 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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: {