import { resetState } from './state.js'; import { formatBytes } from './utils/helpers.js'; import { tesseractLanguages } from './config/tesseract-languages.js'; import { icons, createIcons } from "lucide"; import Sortable from 'sortablejs'; // Centralizing DOM element selection export const dom = { gridView: document.getElementById('grid-view'), toolGrid: document.getElementById('tool-grid'), toolInterface: document.getElementById('tool-interface'), toolContent: document.getElementById('tool-content'), backToGridBtn: document.getElementById('back-to-grid'), loaderModal: document.getElementById('loader-modal'), loaderText: document.getElementById('loader-text'), alertModal: document.getElementById('alert-modal'), alertTitle: document.getElementById('alert-title'), alertMessage: document.getElementById('alert-message'), alertOkBtn: document.getElementById('alert-ok'), heroSection: document.getElementById('hero-section'), featuresSection: document.getElementById('features-section'), toolsHeader: document.getElementById('tools-header'), dividers: document.querySelectorAll('.section-divider'), hideSections: document.querySelectorAll('.hide-section'), }; export const showLoader = (text = 'Processing...') => { dom.loaderText.textContent = text; dom.loaderModal.classList.remove('hidden'); }; export const hideLoader = () => dom.loaderModal.classList.add('hidden'); export const showAlert = (title: any, message: any) => { dom.alertTitle.textContent = title; dom.alertMessage.textContent = message; dom.alertModal.classList.remove('hidden'); }; export const hideAlert = () => dom.alertModal.classList.add('hidden'); export const switchView = (view: any) => { if (view === 'grid') { dom.gridView.classList.remove('hidden'); dom.toolInterface.classList.add('hidden'); // show hero and features and header dom.heroSection.classList.remove('hidden'); dom.featuresSection.classList.remove('hidden'); dom.toolsHeader.classList.remove('hidden'); // show dividers dom.dividers.forEach(divider => { divider.classList.remove('hidden'); }); // show hideSections dom.hideSections.forEach(section => { section.classList.remove('hidden'); }); resetState(); } else { dom.gridView.classList.add('hidden'); dom.toolInterface.classList.remove('hidden'); dom.featuresSection.classList.add('hidden'); dom.heroSection.classList.add('hidden'); dom.toolsHeader.classList.add('hidden'); dom.dividers.forEach(divider => { divider.classList.add('hidden'); }); dom.hideSections.forEach(section => { section.classList.add('hidden'); }); } }; const thumbnailState = { sortableInstances: {} }; function initializeOrganizeSortable(containerId: any) { const container = document.getElementById(containerId); if (!container) return; if (thumbnailState.sortableInstances[containerId]) { thumbnailState.sortableInstances[containerId].destroy(); } thumbnailState.sortableInstances[containerId] = Sortable.create(container, { animation: 150, ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', dragClass: 'sortable-drag', filter: '.delete-page-btn', preventOnFilter: true, onStart: function(evt: any) { evt.item.style.opacity = '0.5'; }, onEnd: function(evt: any) { evt.item.style.opacity = '1'; } }); } /** * Renders page thumbnails for tools like 'Organize' and 'Rotate'. * @param {string} toolId The ID of the active tool. * @param {object} pdfDoc The loaded pdf-lib document instance. */ export const renderPageThumbnails = async (toolId: any, pdfDoc: any) => { const containerId = toolId === 'organize' ? 'page-organizer' : 'page-rotator'; const container = document.getElementById(containerId); if (!container) return; container.innerHTML = ''; showLoader('Rendering page previews...'); const pdfData = await pdfDoc.save(); // @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'. const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const viewport = page.getViewport({ scale: 0.5 }); const canvas = document.createElement('canvas'); canvas.height = viewport.height; canvas.width = viewport.width; const context = canvas.getContext('2d'); await page.render({ canvasContext: context, viewport: viewport }).promise; const wrapper = document.createElement('div'); wrapper.className = 'page-thumbnail relative group'; // @ts-expect-error TS(2322) FIXME: Type 'number' is not assignable to type 'string'. wrapper.dataset.pageIndex = i - 1; const imgContainer = document.createElement('div'); imgContainer.className = 'w-full h-36 bg-gray-900 rounded-lg flex items-center justify-center overflow-hidden border-2 border-gray-600'; const img = document.createElement('img'); img.src = canvas.toDataURL(); img.className = 'max-w-full max-h-full object-contain'; imgContainer.appendChild(img); if (toolId === 'organize') { wrapper.className = 'page-thumbnail relative group'; wrapper.appendChild(imgContainer); const pageNumSpan = document.createElement('span'); pageNumSpan.className = 'absolute top-1 left-1 bg-gray-900 bg-opacity-75 text-white text-xs rounded-full px-2 py-1'; pageNumSpan.textContent = i.toString(); const deleteBtn = document.createElement('button'); deleteBtn.className = 'delete-page-btn absolute top-1 right-1 bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center'; deleteBtn.innerHTML = '×'; deleteBtn.addEventListener('click', (e) => { (e.currentTarget as HTMLElement).parentElement.remove(); initializeOrganizeSortable(containerId); }); wrapper.append(pageNumSpan, deleteBtn); } else if (toolId === 'rotate') { wrapper.className = 'page-rotator-item flex flex-col items-center gap-2'; wrapper.dataset.rotation = '0'; img.classList.add('transition-transform', 'duration-300'); wrapper.appendChild(imgContainer); const controlsDiv = document.createElement('div'); controlsDiv.className = 'flex items-center justify-center gap-3 w-full'; const pageNumSpan = document.createElement('span'); pageNumSpan.className = 'font-medium text-sm text-white'; pageNumSpan.textContent = i.toString(); const rotateBtn = document.createElement('button'); rotateBtn.className = 'rotate-btn btn bg-gray-700 hover:bg-gray-600 p-2 rounded-full'; rotateBtn.title = 'Rotate 90°'; rotateBtn.innerHTML = ''; rotateBtn.addEventListener('click', (e) => { e.stopPropagation(); const card = (e.currentTarget as HTMLElement).closest('.page-rotator-item') as HTMLElement; const imgEl = card.querySelector('img'); let currentRotation = parseInt(card.dataset.rotation); currentRotation = (currentRotation + 90) % 360; card.dataset.rotation = currentRotation.toString(); imgEl.style.transform = `rotate(${currentRotation}deg)`; }); controlsDiv.append(pageNumSpan, rotateBtn); wrapper.appendChild(controlsDiv); } container.appendChild(wrapper); createIcons({icons}); } if (toolId === 'organize') { initializeOrganizeSortable(containerId); } hideLoader(); }; /** * Renders a list of uploaded files in the specified container. * @param {HTMLElement} container The DOM element to render the list into. * @param {File[]} files The array of file objects. */ export const renderFileDisplay = (container: any, files: any) => { container.textContent = ''; if (files.length > 0) { files.forEach((file: any) => { const fileDiv = document.createElement('div'); fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; const nameSpan = document.createElement('span'); nameSpan.className = 'truncate font-medium text-gray-200'; nameSpan.textContent = file.name; const sizeSpan = document.createElement('span'); sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400'; sizeSpan.textContent = formatBytes(file.size); fileDiv.append(nameSpan, sizeSpan); container.appendChild(fileDiv); }); } }; const createFileInputHTML = (options = {}) => { // @ts-expect-error TS(2339) FIXME: Property 'multiple' does not exist on type '{}'. const multiple = options.multiple ? 'multiple' : ''; // @ts-expect-error TS(2339) FIXME: Property 'accept' does not exist on type '{}'. const acceptedFiles = options.accept || 'application/pdf'; // @ts-expect-error TS(2339) FIXME: Property 'showControls' does not exist on type '{}... Remove this comment to see the full error message const showControls = options.showControls || false; // NEW: Add this parameter return `

Click to select a file or drag and drop

${multiple ? 'PDFs or Images' : 'A single PDF file'}

Your files never leave your device.

${showControls ? ` ` : ''} `; }; export const toolTemplates = { merge: () => `

Merge PDFs

Combine whole files, or select specific pages to merge into a new document.

${createFileInputHTML({ multiple: true, showControls: true })} `, split: () => `

Split PDF

Extract pages from a PDF using various methods.

${createFileInputHTML()}
`, encrypt: () => `

Encrypt PDF

Upload a PDF to create a new, password-protected version.

${createFileInputHTML()}
`, decrypt: () => `

Decrypt PDF

Upload an encrypted PDF and provide its password to create an unlocked version.

${createFileInputHTML()}
`, organize: () => `

Organize PDF

Reorder, rotate, or delete pages. Drag and drop pages to reorder them.

${createFileInputHTML()}
`, rotate: () => `

Rotate PDF

Rotate all or specific pages in a PDF document.

${createFileInputHTML()}
`, 'add-page-numbers': () => `

Add Page Numbers

Add customizable page numbers to your PDF file.

${createFileInputHTML()}
`, 'pdf-to-jpg': () => `

PDF to JPG

Convert each page of a PDF file into a high-quality JPG image.

${createFileInputHTML()}
`, 'jpg-to-pdf': () => `

JPG to PDF

Convert one or more JPG images into a single PDF file.

${createFileInputHTML({ multiple: true, accept: 'image/jpeg', showControls: true })}
`, 'scan-to-pdf': () => `

Scan to PDF

Use your device's camera to scan documents and save them as a PDF. On desktop, this will open a file picker.

${createFileInputHTML({ accept: 'image/*' })}
`, crop: () => `

Crop PDF

Click and drag to select a crop area on any page. You can set different crop areas for each page.

${createFileInputHTML()} `, compress: () => `

Compress PDF

Reduce file size by choosing the compression method that best suits your document.

${createFileInputHTML()}
`, 'pdf-to-greyscale': () => `

PDF to Greyscale

Convert all pages of a PDF to greyscale. This is done by rendering each page, applying a filter, and rebuilding the PDF.

${createFileInputHTML()}
`, 'pdf-to-zip': () => `

Combine PDFs into ZIP

Select multiple PDF files to download them together in a single ZIP archive.

${createFileInputHTML({ multiple: true, showControls: true })}
`, 'edit-metadata': () => `

Edit PDF Metadata

Modify the core metadata fields of your PDF. Leave a field blank to clear it.

Important Note: This tool pdf-lib library, which may update the Producer, CreationDate, and ModDate fields due to its default behavior on upload. To accurately view a file's final metadata after editing, or just normal viewing, please use our View Metadata tool.
${createFileInputHTML()} `, 'remove-metadata': () => `

Remove PDF Metadata

Completely remove identifying metadata from your PDF.

${createFileInputHTML()}
`, flatten: () => `

Flatten PDF

Make PDF forms and annotations non-editable by flattening them.

${createFileInputHTML()}
`, 'pdf-to-png': () => `

PDF to PNG

Convert each page of a PDF file into a high-quality PNG image.

${createFileInputHTML()}
`, 'png-to-pdf': () => `

PNG to PDF

Convert one or more PNG images into a single PDF file.

${createFileInputHTML({ multiple: true, accept: 'image/png', showControls: true })}
`, 'pdf-to-webp': () => `

PDF to WebP

Convert each page of a PDF file into a modern WebP image.

${createFileInputHTML()}
`, 'webp-to-pdf': () => `

WebP to PDF

Convert one or more WebP images into a single PDF file.

${createFileInputHTML({ multiple: true, accept: 'image/webp', showControls: true })}
`, edit: () => `

PDF Studio

An all-in-one PDF workspace where you can annotate, draw, highlight, redact, add comments and shapes, take screenshots, and view PDFs.

${createFileInputHTML()}
`, 'delete-pages': () => `

Delete Pages

Remove specific pages or ranges of pages from your PDF file.

${createFileInputHTML()}
`, 'add-blank-page': () => `

Add Blank Page

Insert a new blank page at a specific position in your document.

${createFileInputHTML()}
`, 'extract-pages': () => `

Extract Pages

Extract specific pages from a PDF into separate files. Your files will download in a ZIP archive.

${createFileInputHTML()}
`, 'add-watermark': () => `

Add Watermark

Apply a text or image watermark to every page of your PDF document.

${createFileInputHTML()}
`, 'add-header-footer': () => `

Add Header & Footer

Add custom text to the top and bottom margins of every page.

${createFileInputHTML()}
`, 'image-to-pdf': () => `

Image to PDF Converter

Combine multiple images into a single PDF. Drag and drop to reorder.

${createFileInputHTML({ multiple: true, accept: 'image/jpeg,image/png,image/webp', showControls: true })} `, 'change-permissions': () => `

Change PDF Permissions

Unlock a PDF and re-save it with new passwords and permissions.

${createFileInputHTML()}
`, 'pdf-to-markdown': () => `

PDF to Markdown

Convert a PDF's text content into a structured Markdown file.

${createFileInputHTML({ accept: '.pdf' })}
`, 'txt-to-pdf': () => `

Text to PDF

Type or paste your text below and convert it to a PDF with custom formatting.

`, 'invert-colors': () => `

Invert PDF Colors

Convert your PDF to a "dark mode" by inverting its colors. This works best on simple text and image documents.

${createFileInputHTML()}
`, 'view-metadata': () => `

View PDF Metadata

Upload a PDF to view its internal properties, such as Title, Author, and Creation Date.

${createFileInputHTML()}
`, 'reverse-pages': () => `

Reverse PDF Pages

Flip the order of all pages in your document, making the last page the first.

${createFileInputHTML()}
`, 'md-to-pdf': () => `

Markdown to PDF

Write in Markdown, select your formatting options, and get a high-quality, multi-page PDF.
Note: Images linked from the web (e.g., https://...) require an internet connection to be rendered.

`, 'svg-to-pdf': () => `

SVG to PDF

Convert one or more SVG vector images into a single PDF file.

${createFileInputHTML({ multiple: true, accept: 'image/svg+xml', showControls: true })}
`, 'bmp-to-pdf': () => `

BMP to PDF

Convert one or more BMP images into a single PDF file.

${createFileInputHTML({ multiple: true, accept: 'image/bmp', showControls: true })}
`, 'heic-to-pdf': () => `

HEIC to PDF

Convert one or more HEIC (High Efficiency) images from your iPhone or camera into a single PDF file.

${createFileInputHTML({ multiple: true, accept: '.heic,.heif', showControls: true })}
`, 'tiff-to-pdf': () => `

TIFF to PDF

Convert one or more single or multi-page TIFF images into a single PDF file.

${createFileInputHTML({ multiple: true, accept: 'image/tiff', showControls: true })}
`, 'pdf-to-bmp': () => `

PDF to BMP

Convert each page of a PDF file into a BMP image. Your files will be downloaded in a ZIP archive.

${createFileInputHTML()}
`, 'pdf-to-tiff': () => `

PDF to TIFF

Convert each page of a PDF file into a high-quality TIFF image. Your files will be downloaded in a ZIP archive.

${createFileInputHTML()}
`, 'split-in-half': () => `

Split Pages in Half

Choose a method to divide every page of your document into two separate pages.

${createFileInputHTML()}
`, 'page-dimensions': () => `

Analyze Page Dimensions

Upload a PDF to see the precise dimensions, standard size, and orientation of every page.

${createFileInputHTML()}
`, 'n-up': () => `

N-Up Page Arrangement

Combine multiple pages from your PDF onto a single sheet. This is great for creating booklets or proof sheets.

${createFileInputHTML()}
`, 'duplicate-organize': () => `

Page Manager

Drag pages to reorder them. Use the icon to duplicate a page or the icon to delete it.

${createFileInputHTML()}
`, 'combine-single-page': () => `

Combine to a Single Page

Stitch all pages of your PDF together vertically to create one continuous, scrollable page.

${createFileInputHTML()}
`, 'fix-dimensions': () => `

Standardize Page Dimensions

Convert all pages in your PDF to a uniform size. Choose a standard format or define a custom dimension.

${createFileInputHTML()}
`, 'change-background-color': () => `

Change Background Color

Select a new background color for every page of your PDF.

${createFileInputHTML()}
`, 'change-text-color': () => `

Change Text Color

Change the color of dark text in your PDF. This process converts pages to images, so text will not be selectable in the final file.

${createFileInputHTML()}
`, 'compare-pdfs': () => `

Compare PDFs

Upload two files to visually compare them using either an overlay or a side-by-side view.

Upload Original PDF

Upload Revised PDF

`, 'ocr-pdf': () => `

OCR PDF

Convert scanned PDFs into searchable documents. Select one or more languages present in your file for the best results.

${createFileInputHTML()}
`, 'word-to-pdf': () => `

Word to PDF Converter

Upload a .docx file to convert it into a high-quality PDF with selectable text. Complex layouts may not be perfectly preserved.

Click to select a file or drag and drop

A single .docx file

`, 'sign-pdf': () => `

Sign PDF

Create your signature, select it, then click on the document to place. You can drag to move placed signatures.

${createFileInputHTML()} `, 'remove-annotations': () => `

Remove Annotations

Select the types of annotations to remove from all pages or a specific range.

${createFileInputHTML()}
`, cropper: () => `

PDF Cropper

Upload a PDF to visually crop one or more pages. This tool offers a live preview and two distinct cropping modes.

${createFileInputHTML()}
`, 'form-filler': () => `

PDF Form Filler

Upload a PDF to fill in existing form fields. The PDF view on the right will update as you type.

${createFileInputHTML()}
`, posterize: () => `

Posterize PDF

Split pages into multiple smaller sheets to print as a poster. Navigate the preview and see the grid update based on your settings.

${createFileInputHTML()}
`, };