Refactor and enhance type safety across various modules
- Updated function parameters and return types in `page-preview.ts`, `pdf-decrypt.ts`, and `pymupdf-loader.ts` for improved type safety. - Introduced type definitions for `CpdfInstance`, `PyMuPDFInstance`, and other related types to ensure better type checking. - Enhanced error handling in `sanitize.ts` by creating a utility function for error messages. - Removed unnecessary type assertions and improved type inference in `editor.ts`, `serialization.ts`, and `tools.test.ts`. - Added type definitions for markdown-it plugins to improve compatibility and type safety. - Enforced stricter TypeScript settings by enabling `noImplicitAny` in `tsconfig.json`. - Cleaned up test files by refining type assertions and ensuring consistency in type usage.
This commit is contained in:
@@ -3,6 +3,7 @@ import type {
|
||||
AddPageLabelsState,
|
||||
LabelRule,
|
||||
PageLabelStyleName,
|
||||
CpdfInstance,
|
||||
} from '@/types';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
@@ -20,30 +21,6 @@ import {
|
||||
} from '../utils/page-labels.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
type AddPageLabelsCpdf = {
|
||||
setSlow?: () => void;
|
||||
fromMemory(data: Uint8Array, userpw: string): CoherentPdf;
|
||||
removePageLabels(pdf: CoherentPdf): void;
|
||||
parsePagespec(pdf: CoherentPdf, pagespec: string): CpdfPageRange;
|
||||
all(pdf: CoherentPdf): CpdfPageRange;
|
||||
addPageLabels(
|
||||
pdf: CoherentPdf,
|
||||
style: CpdfLabelStyle,
|
||||
prefix: string,
|
||||
offset: number,
|
||||
range: CpdfPageRange,
|
||||
progress: boolean
|
||||
): void;
|
||||
toMemory(pdf: CoherentPdf, linearize: boolean, makeId: boolean): Uint8Array;
|
||||
deletePdf(pdf: CoherentPdf): void;
|
||||
decimalArabic: CpdfLabelStyle;
|
||||
lowercaseRoman: CpdfLabelStyle;
|
||||
uppercaseRoman: CpdfLabelStyle;
|
||||
lowercaseLetters: CpdfLabelStyle;
|
||||
uppercaseLetters: CpdfLabelStyle;
|
||||
noLabelPrefixOnly?: CpdfLabelStyle;
|
||||
};
|
||||
|
||||
let labelRuleCounter = 0;
|
||||
|
||||
const translate = (
|
||||
@@ -464,8 +441,8 @@ async function addPageLabels() {
|
||||
) as HTMLInputElement | null
|
||||
)?.checked ?? true;
|
||||
|
||||
let cpdf: AddPageLabelsCpdf | null = null;
|
||||
let pdf: CoherentPdf | null = null;
|
||||
let cpdf: CpdfInstance | null = null;
|
||||
let pdf: unknown = null;
|
||||
|
||||
try {
|
||||
cpdf = await getCpdf();
|
||||
@@ -482,7 +459,7 @@ async function addPageLabels() {
|
||||
const rule = pageState.rules[index];
|
||||
const trimmedRange = rule.pageRange.trim();
|
||||
|
||||
let range: CpdfPageRange;
|
||||
let range: unknown;
|
||||
try {
|
||||
range = trimmedRange
|
||||
? cpdf.parsePagespec(pdf, trimmedRange)
|
||||
|
||||
@@ -9,7 +9,6 @@ import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
|
||||
let selectedFile: File | null = null;
|
||||
let viewerIframe: HTMLIFrameElement | null = null;
|
||||
let viewerReady = false;
|
||||
let currentBlobUrl: string | null = null;
|
||||
|
||||
const pdfInput = document.getElementById('pdfFile') as HTMLInputElement;
|
||||
@@ -47,7 +46,7 @@ function resetState() {
|
||||
viewerContainer.removeChild(viewerIframe);
|
||||
}
|
||||
viewerIframe = null;
|
||||
viewerReady = false;
|
||||
|
||||
if (viewerCard) viewerCard.classList.add('hidden');
|
||||
if (saveStampedBtn) saveStampedBtn.classList.add('hidden');
|
||||
|
||||
@@ -157,7 +156,6 @@ async function loadPdfInViewer(file: File) {
|
||||
currentBlobUrl = null;
|
||||
}
|
||||
viewerIframe = null;
|
||||
viewerReady = false;
|
||||
|
||||
// Calculate and apply dynamic height
|
||||
await adjustViewerHeight(file);
|
||||
@@ -169,7 +167,7 @@ async function loadPdfInViewer(file: File) {
|
||||
try {
|
||||
const existingPrefsRaw = localStorage.getItem('pdfjs.preferences');
|
||||
const existingPrefs = existingPrefsRaw ? JSON.parse(existingPrefsRaw) : {};
|
||||
delete (existingPrefs as any).annotationEditorMode;
|
||||
delete (existingPrefs as Record<string, unknown>).annotationEditorMode;
|
||||
const newPrefs = {
|
||||
...existingPrefs,
|
||||
enablePermissions: false,
|
||||
@@ -204,7 +202,14 @@ async function loadPdfInViewer(file: File) {
|
||||
|
||||
function setupAnnotationViewer(iframe: HTMLIFrameElement) {
|
||||
try {
|
||||
const win = iframe.contentWindow as any;
|
||||
const win = iframe.contentWindow as
|
||||
| (Window & {
|
||||
PDFViewerApplication?: {
|
||||
initializedPromise?: Promise<void>;
|
||||
eventBus?: { _on?: (event: string, callback: () => void) => void };
|
||||
};
|
||||
})
|
||||
| null;
|
||||
const doc = win?.document as Document | null;
|
||||
if (!win || !doc) return;
|
||||
|
||||
@@ -238,8 +243,6 @@ function setupAnnotationViewer(iframe: HTMLIFrameElement) {
|
||||
if (root) {
|
||||
root.classList.add('PdfjsAnnotationExtension_Comment_hidden');
|
||||
}
|
||||
|
||||
viewerReady = true;
|
||||
} catch (e) {
|
||||
console.error(
|
||||
'Failed to initialize annotation viewer for Add Stamps:',
|
||||
@@ -304,8 +307,14 @@ if (saveStampedBtn) {
|
||||
}
|
||||
|
||||
try {
|
||||
const win = viewerIframe.contentWindow as any;
|
||||
const extensionInstance = win?.pdfjsAnnotationExtensionInstance as any;
|
||||
const win = viewerIframe.contentWindow as
|
||||
| (Window & {
|
||||
pdfjsAnnotationExtensionInstance?: {
|
||||
exportPdf?: () => Promise<void>;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
const extensionInstance = win?.pdfjsAnnotationExtensionInstance;
|
||||
|
||||
if (
|
||||
extensionInstance &&
|
||||
|
||||
@@ -1013,9 +1013,12 @@ async function applyWatermark() {
|
||||
'watermarked.pdf'
|
||||
);
|
||||
showAlert('Success', 'Watermark added successfully!', 'success');
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(e);
|
||||
showAlert('Error', e.message || 'Could not add the watermark.');
|
||||
showAlert(
|
||||
'Error',
|
||||
e instanceof Error ? e.message : 'Could not add the watermark.'
|
||||
);
|
||||
} finally {
|
||||
hideLoader();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import '../../css/bookmark.css';
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
|
||||
import {
|
||||
truncateFilename,
|
||||
getPDFDocument,
|
||||
formatBytes,
|
||||
downloadFile,
|
||||
escapeHtml,
|
||||
@@ -46,7 +45,6 @@ let isPickingDestination = false;
|
||||
let currentPickingCallback: DestinationCallback | null = null;
|
||||
let destinationMarker: HTMLDivElement | null = null;
|
||||
let savedModalOverlay: HTMLDivElement | null = null;
|
||||
let savedModal: HTMLDivElement | null = null;
|
||||
let currentViewport: PageViewport | null = null;
|
||||
let currentZoom = 1.0;
|
||||
const fileInput = document.getElementById(
|
||||
@@ -463,7 +461,6 @@ class="w-full px-2 py-1 border border-gray-300 rounded text-sm text-gray-900" />
|
||||
if (pickDestBtn) {
|
||||
pickDestBtn.addEventListener('click', () => {
|
||||
savedModalOverlay = overlay;
|
||||
savedModal = modal;
|
||||
overlay.style.display = 'none';
|
||||
|
||||
startDestinationPicking((page: number, pdfX: number, pdfY: number) => {
|
||||
@@ -699,7 +696,6 @@ function cancelDestinationPicking(): void {
|
||||
if (savedModalOverlay) {
|
||||
savedModalOverlay.style.display = '';
|
||||
savedModalOverlay = null;
|
||||
savedModal = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1317,7 +1313,7 @@ jsonInput?.addEventListener('change', async (e: Event) => {
|
||||
'JSON Loaded',
|
||||
'Loaded bookmarks from JSON. Now upload your PDF.'
|
||||
);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
await showAlertModal('Error', 'Invalid JSON format');
|
||||
}
|
||||
});
|
||||
@@ -2018,7 +2014,7 @@ jsonImportHidden?.addEventListener('change', async (e: Event) => {
|
||||
saveState();
|
||||
renderBookmarkTree();
|
||||
await showAlertModal('Success', 'Bookmarks imported from JSON!');
|
||||
} catch (err) {
|
||||
} catch {
|
||||
await showAlertModal('Error', 'Invalid JSON format');
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import type { PyMuPDFInstance } from '@/types';
|
||||
import JSZip from 'jszip';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
|
||||
@@ -176,8 +175,8 @@ async function convertCbzToPdf(file: File): Promise<Blob> {
|
||||
}
|
||||
|
||||
async function convertCbrToPdf(file: File): Promise<Blob> {
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
return await (pymupdf as any).convertToPdf(file, { filetype: 'cbz' });
|
||||
const pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
|
||||
return await pymupdf.convertToPdf(file, { filetype: 'cbz' });
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -314,12 +313,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
readFileAsArrayBuffer,
|
||||
} from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { ChangePermissionsState } from '@/types';
|
||||
import { ChangePermissionsState, QpdfInstanceExtended } from '@/types';
|
||||
|
||||
const pageState: ChangePermissionsState = {
|
||||
file: null,
|
||||
@@ -114,7 +114,7 @@ async function changePermissions() {
|
||||
|
||||
const inputPath = '/input.pdf';
|
||||
const outputPath = '/output.pdf';
|
||||
let qpdf: any;
|
||||
let qpdf: QpdfInstanceExtended;
|
||||
|
||||
const loaderModal = document.getElementById('loader-modal');
|
||||
const loaderText = document.getElementById('loader-text');
|
||||
@@ -187,10 +187,10 @@ async function changePermissions() {
|
||||
args.push('--', outputPath);
|
||||
try {
|
||||
qpdf.callMain(args);
|
||||
} catch (qpdfError: any) {
|
||||
} catch (qpdfError: unknown) {
|
||||
console.error('qpdf execution error:', qpdfError);
|
||||
|
||||
const errorMsg = qpdfError.message || '';
|
||||
const errorMsg = qpdfError instanceof Error ? qpdfError.message : '';
|
||||
|
||||
if (
|
||||
errorMsg.includes('invalid password') ||
|
||||
@@ -219,7 +219,9 @@ async function changePermissions() {
|
||||
throw new Error('Processing resulted in an empty file.');
|
||||
}
|
||||
|
||||
const blob = new Blob([outputFile], { type: 'application/pdf' });
|
||||
const blob = new Blob([new Uint8Array(outputFile)], {
|
||||
type: 'application/pdf',
|
||||
});
|
||||
downloadFile(blob, `permissions-changed-${pageState.file.name}`);
|
||||
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
@@ -233,16 +235,17 @@ async function changePermissions() {
|
||||
showAlert('Success', successMessage, 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Error during PDF permission change:', error);
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
|
||||
if (error.message === 'INVALID_PASSWORD') {
|
||||
const errorMessage = error instanceof Error ? error.message : '';
|
||||
if (errorMessage === 'INVALID_PASSWORD') {
|
||||
showAlert(
|
||||
'Incorrect Password',
|
||||
'The current password you entered is incorrect. Please try again.'
|
||||
);
|
||||
} else if (error.message === 'PASSWORD_REQUIRED') {
|
||||
} else if (errorMessage === 'PASSWORD_REQUIRED') {
|
||||
showAlert(
|
||||
'Password Required',
|
||||
'This PDF is password-protected. Please enter the current password to proceed.'
|
||||
@@ -250,7 +253,7 @@ async function changePermissions() {
|
||||
} else {
|
||||
showAlert(
|
||||
'Processing Failed',
|
||||
`An error occurred: ${error.message || 'The PDF might be corrupted or password protected.'}`
|
||||
`An error occurred: ${errorMessage || 'The PDF might be corrupted or password protected.'}`
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -85,7 +85,10 @@ async function performCondenseCompression(
|
||||
scrub: {
|
||||
metadata: customSettings?.removeMetadata ?? preset.scrub.metadata,
|
||||
thumbnails: customSettings?.removeThumbnails ?? preset.scrub.thumbnails,
|
||||
xmlMetadata: (preset.scrub as any).xmlMetadata ?? false,
|
||||
xmlMetadata:
|
||||
'xmlMetadata' in preset.scrub
|
||||
? (preset.scrub as { xmlMetadata: boolean }).xmlMetadata
|
||||
: false,
|
||||
},
|
||||
subsetFonts: customSettings?.subsetFonts ?? preset.subsetFonts,
|
||||
save: {
|
||||
@@ -99,8 +102,8 @@ async function performCondenseCompression(
|
||||
try {
|
||||
const result = await pymupdf.compressPdf(fileBlob, options);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
const errorMessage = error?.message || String(error);
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
if (
|
||||
errorMessage.includes('PatternType') ||
|
||||
errorMessage.includes('pattern')
|
||||
@@ -432,7 +435,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
usedMethod = 'Condense';
|
||||
|
||||
// Check if fallback was used
|
||||
if ((result as any).usedFallback) {
|
||||
if ((result as { usedFallback?: boolean }).usedFallback) {
|
||||
usedMethod +=
|
||||
' (without image optimization due to unsupported patterns)';
|
||||
}
|
||||
@@ -551,12 +554,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
console.error('[CompressPDF] Error:', e);
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during compression. Error: ${e.message}`
|
||||
`An error occurred during compression. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import Cropper from 'cropperjs';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { CropperState } from '@/types';
|
||||
import { CropperState, CropPercentages } from '@/types';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
@@ -167,7 +167,11 @@ async function displayPageAsImage(num: number) {
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
tempCanvas.width = viewport.width;
|
||||
tempCanvas.height = viewport.height;
|
||||
await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;
|
||||
await page.render({
|
||||
canvas: null,
|
||||
canvasContext: tempCtx,
|
||||
viewport: viewport,
|
||||
}).promise;
|
||||
|
||||
if (cropperState.cropper) cropperState.cropper.destroy();
|
||||
|
||||
@@ -251,7 +255,7 @@ async function performCrop() {
|
||||
document.getElementById('apply-to-all-toggle') as HTMLInputElement
|
||||
)?.checked;
|
||||
|
||||
let finalCropData: Record<number, any> = {};
|
||||
let finalCropData: Record<number, CropPercentages> = {};
|
||||
|
||||
if (isApplyToAll) {
|
||||
const currentCrop = cropperState.pageCrops[cropperState.currentPageNum];
|
||||
@@ -286,7 +290,7 @@ async function performCrop() {
|
||||
|
||||
const fileName = isDestructive ? 'flattened_crop.pdf' : 'standard_crop.pdf';
|
||||
downloadFile(
|
||||
new Blob([finalPdfBytes], { type: 'application/pdf' }),
|
||||
new Blob([new Uint8Array(finalPdfBytes)], { type: 'application/pdf' }),
|
||||
fileName
|
||||
);
|
||||
showAlert(
|
||||
@@ -304,7 +308,7 @@ async function performCrop() {
|
||||
}
|
||||
|
||||
async function performMetadataCrop(
|
||||
cropData: Record<number, any>
|
||||
cropData: Record<number, CropPercentages>
|
||||
): Promise<Uint8Array> {
|
||||
const pdfToModify = await loadPdfDocument(cropperState.originalPdfBytes!);
|
||||
|
||||
@@ -344,7 +348,7 @@ async function performMetadataCrop(
|
||||
}
|
||||
|
||||
async function performFlatteningCrop(
|
||||
cropData: Record<number, any>
|
||||
cropData: Record<number, CropPercentages>
|
||||
): Promise<Uint8Array> {
|
||||
const newPdfDoc = await PDFLibDocument.create();
|
||||
const sourcePdfDocForCopying = await loadPdfDocument(
|
||||
@@ -364,7 +368,11 @@ async function performFlatteningCrop(
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
tempCanvas.width = viewport.width;
|
||||
tempCanvas.height = viewport.height;
|
||||
await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;
|
||||
await page.render({
|
||||
canvas: null,
|
||||
canvasContext: tempCtx,
|
||||
viewport: viewport,
|
||||
}).promise;
|
||||
|
||||
const finalCanvas = document.createElement('canvas');
|
||||
const finalCtx = finalCanvas.getContext('2d');
|
||||
|
||||
@@ -16,7 +16,17 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
).toString();
|
||||
|
||||
// --- Global State for the Cropper Tool ---
|
||||
const cropperState = {
|
||||
import type { CropPercentages } from '@/types';
|
||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
||||
|
||||
const cropperState: {
|
||||
pdfDoc: PDFDocumentProxy | null;
|
||||
currentPageNum: number;
|
||||
cropper: Cropper | null;
|
||||
originalPdfBytes: Uint8Array | null;
|
||||
cropperImageElement: HTMLImageElement | null;
|
||||
pageCrops: Record<number, CropPercentages>;
|
||||
} = {
|
||||
pdfDoc: null,
|
||||
currentPageNum: 1,
|
||||
cropper: null,
|
||||
@@ -46,7 +56,8 @@ function saveCurrentCrop() {
|
||||
* Renders a PDF page to the Cropper UI as an image.
|
||||
* @param {number} num The page number to render.
|
||||
*/
|
||||
async function displayPageAsImage(num: any) {
|
||||
|
||||
async function displayPageAsImage(num: number) {
|
||||
showLoader(`Rendering Page ${num}...`);
|
||||
|
||||
try {
|
||||
@@ -57,7 +68,11 @@ async function displayPageAsImage(num: any) {
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
tempCanvas.width = viewport.width;
|
||||
tempCanvas.height = viewport.height;
|
||||
await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;
|
||||
await page.render({
|
||||
canvas: null,
|
||||
canvasContext: tempCtx,
|
||||
viewport: viewport,
|
||||
}).promise;
|
||||
|
||||
if (cropperState.cropper) {
|
||||
cropperState.cropper.destroy();
|
||||
@@ -107,7 +122,7 @@ async function displayPageAsImage(num: any) {
|
||||
* Handles page navigation.
|
||||
* @param {number} offset -1 for previous, 1 for next.
|
||||
*/
|
||||
async function changePage(offset: any) {
|
||||
async function changePage(offset: number) {
|
||||
// Save the current page's crop before changing
|
||||
saveCurrentCrop();
|
||||
|
||||
@@ -137,7 +152,13 @@ function enableControls() {
|
||||
/**
|
||||
* Performs a non-destructive crop by updating the page's crop box.
|
||||
*/
|
||||
async function performMetadataCrop(pdfToModify: any, cropData: any) {
|
||||
async function performMetadataCrop(
|
||||
pdfToModify: PDFLibDocument,
|
||||
cropData: Record<
|
||||
string,
|
||||
{ x: number; y: number; width: number; height: number }
|
||||
>
|
||||
) {
|
||||
for (const pageNum in cropData) {
|
||||
const pdfJsPage = await cropperState.pdfDoc.getPage(Number(pageNum));
|
||||
const viewport = pdfJsPage.getViewport({ scale: 1 });
|
||||
@@ -183,7 +204,12 @@ async function performMetadataCrop(pdfToModify: any, cropData: any) {
|
||||
/**
|
||||
* Performs a destructive crop by flattening the selected area to an image.
|
||||
*/
|
||||
async function performFlatteningCrop(cropData: any) {
|
||||
async function performFlatteningCrop(
|
||||
cropData: Record<
|
||||
string,
|
||||
{ x: number; y: number; width: number; height: number }
|
||||
>
|
||||
) {
|
||||
const newPdfDoc = await PDFLibDocument.create();
|
||||
|
||||
// Load the original PDF with pdf-lib to copy un-cropped pages from
|
||||
@@ -204,7 +230,11 @@ async function performFlatteningCrop(cropData: any) {
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
tempCanvas.width = viewport.width;
|
||||
tempCanvas.height = viewport.height;
|
||||
await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;
|
||||
await page.render({
|
||||
canvas: null,
|
||||
canvasContext: tempCtx,
|
||||
viewport: viewport,
|
||||
}).promise;
|
||||
|
||||
const finalCanvas = document.createElement('canvas');
|
||||
const finalCtx = finalCanvas.getContext('2d');
|
||||
@@ -264,7 +294,7 @@ export async function setupCropperTool() {
|
||||
cropperState.pageCrops = {};
|
||||
|
||||
const arrayBuffer = await readFileAsArrayBuffer(state.files[0]);
|
||||
cropperState.originalPdfBytes = arrayBuffer;
|
||||
cropperState.originalPdfBytes = new Uint8Array(arrayBuffer as ArrayBuffer);
|
||||
const arrayBufferForPdfJs = (arrayBuffer as ArrayBuffer).slice(0);
|
||||
const loadingTask = getPDFDocument({ data: arrayBufferForPdfJs });
|
||||
|
||||
@@ -295,7 +325,7 @@ export async function setupCropperTool() {
|
||||
document.getElementById('apply-to-all-toggle') as HTMLInputElement
|
||||
).checked;
|
||||
|
||||
let finalCropData = {};
|
||||
let finalCropData: Record<number, CropPercentages> = {};
|
||||
if (isApplyToAll) {
|
||||
const currentCrop = cropperState.pageCrops[cropperState.currentPageNum];
|
||||
if (!currentCrop) {
|
||||
@@ -308,10 +338,13 @@ export async function setupCropperTool() {
|
||||
}
|
||||
} else {
|
||||
// If not applying to all, only process pages with saved crops
|
||||
finalCropData = Object.keys(cropperState.pageCrops).reduce((obj, key) => {
|
||||
obj[key] = cropperState.pageCrops[key];
|
||||
return obj;
|
||||
});
|
||||
finalCropData = Object.keys(cropperState.pageCrops).reduce(
|
||||
(obj, key) => {
|
||||
obj[Number(key)] = cropperState.pageCrops[Number(key)];
|
||||
return obj;
|
||||
},
|
||||
{} as Record<number, CropPercentages>
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(finalCropData).length === 0) {
|
||||
@@ -341,7 +374,7 @@ export async function setupCropperTool() {
|
||||
? 'flattened_crop.pdf'
|
||||
: 'standard_crop.pdf';
|
||||
downloadFile(
|
||||
new Blob([finalPdfBytes], { type: 'application/pdf' }),
|
||||
new Blob([new Uint8Array(finalPdfBytes)], { type: 'application/pdf' }),
|
||||
fileName
|
||||
);
|
||||
showAlert('Success', 'Crop complete! Your download has started.');
|
||||
|
||||
@@ -1,231 +1,255 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
formatBytes,
|
||||
} from '../utils/helpers.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
state.files = [];
|
||||
state.files = [];
|
||||
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const convertOptions = document.getElementById('convert-options');
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const fileControls = document.getElementById('file-controls');
|
||||
const addMoreBtn = document.getElementById('add-more-btn');
|
||||
const clearFilesBtn = document.getElementById('clear-files-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const convertOptions = document.getElementById('convert-options');
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const fileControls = document.getElementById('file-controls');
|
||||
const addMoreBtn = document.getElementById('add-more-btn');
|
||||
const clearFilesBtn = document.getElementById('clear-files-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
});
|
||||
}
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
});
|
||||
}
|
||||
|
||||
const updateUI = async () => {
|
||||
if (!convertOptions) return;
|
||||
const updateUI = async () => {
|
||||
if (!convertOptions) return;
|
||||
|
||||
if (state.files.length > 0) {
|
||||
if (fileDisplayArea) {
|
||||
fileDisplayArea.innerHTML = '';
|
||||
if (state.files.length > 0) {
|
||||
if (fileDisplayArea) {
|
||||
fileDisplayArea.innerHTML = '';
|
||||
|
||||
for (let index = 0; index < state.files.length; index++) {
|
||||
const file = state.files[index];
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
for (let index = 0; index < state.files.length; index++) {
|
||||
const file = state.files[index];
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className =
|
||||
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = file.name;
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className =
|
||||
'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = file.name;
|
||||
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(file.size);
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(file.size);
|
||||
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = () => {
|
||||
state.files = state.files.filter((_, i) => i !== index);
|
||||
updateUI();
|
||||
};
|
||||
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
}
|
||||
|
||||
createIcons({ icons });
|
||||
}
|
||||
if (fileControls) fileControls.classList.remove('hidden');
|
||||
convertOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
if (fileControls) fileControls.classList.add('hidden');
|
||||
convertOptions.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const resetState = () => {
|
||||
state.files = [];
|
||||
state.pdfDoc = null;
|
||||
updateUI();
|
||||
};
|
||||
|
||||
const convertToPdf = async () => {
|
||||
try {
|
||||
console.log('[CSV2PDF] Starting conversion...');
|
||||
console.log('[CSV2PDF] Number of files:', state.files.length);
|
||||
|
||||
if (state.files.length === 0) {
|
||||
showAlert('No Files', 'Please select at least one CSV file.');
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
const { convertCsvToPdf } = await import('../utils/csv-to-pdf.js');
|
||||
|
||||
if (state.files.length === 1) {
|
||||
const originalFile = state.files[0];
|
||||
console.log('[CSV2PDF] Converting single file:', originalFile.name, 'Size:', originalFile.size, 'bytes');
|
||||
|
||||
const pdfBlob = await convertCsvToPdf(originalFile, {
|
||||
onProgress: (percent, message) => {
|
||||
console.log(`[CSV2PDF] Progress: ${percent}% - ${message}`);
|
||||
showLoader(message, percent);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[CSV2PDF] Conversion complete! PDF size:', pdfBlob.size, 'bytes');
|
||||
|
||||
const fileName = originalFile.name.replace(/\.csv$/i, '') + '.pdf';
|
||||
downloadFile(pdfBlob, fileName);
|
||||
console.log('[CSV2PDF] File downloaded:', fileName);
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${originalFile.name} to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
} else {
|
||||
console.log('[CSV2PDF] Converting multiple files:', state.files.length);
|
||||
showLoader('Preparing conversion...');
|
||||
const JSZip = (await import('jszip')).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
for (let i = 0; i < state.files.length; i++) {
|
||||
const file = state.files[i];
|
||||
console.log(`[CSV2PDF] Converting file ${i + 1}/${state.files.length}:`, file.name);
|
||||
|
||||
const pdfBlob = await convertCsvToPdf(file, {
|
||||
onProgress: (percent, message) => {
|
||||
const overallPercent = ((i / state.files.length) * 100) + (percent / state.files.length);
|
||||
showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`, overallPercent);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`[CSV2PDF] Converted ${file.name}, PDF size:`, pdfBlob.size);
|
||||
|
||||
const baseName = file.name.replace(/\.csv$/i, '');
|
||||
const pdfBuffer = await pdfBlob.arrayBuffer();
|
||||
zip.file(`${baseName}.pdf`, pdfBuffer);
|
||||
}
|
||||
|
||||
console.log('[CSV2PDF] Generating ZIP file...');
|
||||
showLoader('Creating ZIP archive...');
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
console.log('[CSV2PDF] ZIP size:', zipBlob.size);
|
||||
|
||||
downloadFile(zipBlob, 'csv-converted.zip');
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${state.files.length} CSV file(s) to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('[CSV2PDF] ERROR:', e);
|
||||
console.error('[CSV2PDF] Error stack:', e.stack);
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelect = (files: FileList | null) => {
|
||||
if (files && files.length > 0) {
|
||||
state.files = [...state.files, ...Array.from(files)];
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className =
|
||||
'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = () => {
|
||||
state.files = state.files.filter((_, i) => i !== index);
|
||||
updateUI();
|
||||
};
|
||||
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
}
|
||||
};
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const csvFiles = Array.from(files).filter(f => f.name.toLowerCase().endsWith('.csv') || f.type === 'text/csv');
|
||||
if (csvFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
csvFiles.forEach(f => dataTransfer.items.add(f));
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clear value on click to allow re-selecting the same file
|
||||
fileInput.addEventListener('click', () => {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (addMoreBtn) {
|
||||
addMoreBtn.addEventListener('click', () => {
|
||||
fileInput.click();
|
||||
});
|
||||
}
|
||||
|
||||
if (clearFilesBtn) {
|
||||
clearFilesBtn.addEventListener('click', () => {
|
||||
resetState();
|
||||
});
|
||||
}
|
||||
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', convertToPdf);
|
||||
createIcons({ icons });
|
||||
}
|
||||
if (fileControls) fileControls.classList.remove('hidden');
|
||||
convertOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
if (fileControls) fileControls.classList.add('hidden');
|
||||
convertOptions.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const resetState = () => {
|
||||
state.files = [];
|
||||
state.pdfDoc = null;
|
||||
updateUI();
|
||||
};
|
||||
|
||||
const convertToPdf = async () => {
|
||||
try {
|
||||
console.log('[CSV2PDF] Starting conversion...');
|
||||
console.log('[CSV2PDF] Number of files:', state.files.length);
|
||||
|
||||
if (state.files.length === 0) {
|
||||
showAlert('No Files', 'Please select at least one CSV file.');
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
const { convertCsvToPdf } = await import('../utils/csv-to-pdf.js');
|
||||
|
||||
if (state.files.length === 1) {
|
||||
const originalFile = state.files[0];
|
||||
console.log(
|
||||
'[CSV2PDF] Converting single file:',
|
||||
originalFile.name,
|
||||
'Size:',
|
||||
originalFile.size,
|
||||
'bytes'
|
||||
);
|
||||
|
||||
const pdfBlob = await convertCsvToPdf(originalFile, {
|
||||
onProgress: (percent, message) => {
|
||||
console.log(`[CSV2PDF] Progress: ${percent}% - ${message}`);
|
||||
showLoader(message, percent);
|
||||
},
|
||||
});
|
||||
|
||||
console.log(
|
||||
'[CSV2PDF] Conversion complete! PDF size:',
|
||||
pdfBlob.size,
|
||||
'bytes'
|
||||
);
|
||||
|
||||
const fileName = originalFile.name.replace(/\.csv$/i, '') + '.pdf';
|
||||
downloadFile(pdfBlob, fileName);
|
||||
console.log('[CSV2PDF] File downloaded:', fileName);
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${originalFile.name} to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
} else {
|
||||
console.log('[CSV2PDF] Converting multiple files:', state.files.length);
|
||||
showLoader('Preparing conversion...');
|
||||
const JSZip = (await import('jszip')).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
for (let i = 0; i < state.files.length; i++) {
|
||||
const file = state.files[i];
|
||||
console.log(
|
||||
`[CSV2PDF] Converting file ${i + 1}/${state.files.length}:`,
|
||||
file.name
|
||||
);
|
||||
|
||||
const pdfBlob = await convertCsvToPdf(file, {
|
||||
onProgress: (percent) => {
|
||||
const overallPercent =
|
||||
(i / state.files.length) * 100 + percent / state.files.length;
|
||||
showLoader(
|
||||
`Converting ${i + 1}/${state.files.length}: ${file.name}...`,
|
||||
overallPercent
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[CSV2PDF] Converted ${file.name}, PDF size:`,
|
||||
pdfBlob.size
|
||||
);
|
||||
|
||||
const baseName = file.name.replace(/\.csv$/i, '');
|
||||
const pdfBuffer = await pdfBlob.arrayBuffer();
|
||||
zip.file(`${baseName}.pdf`, pdfBuffer);
|
||||
}
|
||||
|
||||
console.log('[CSV2PDF] Generating ZIP file...');
|
||||
showLoader('Creating ZIP archive...');
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
console.log('[CSV2PDF] ZIP size:', zipBlob.size);
|
||||
|
||||
downloadFile(zipBlob, 'csv-converted.zip');
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${state.files.length} CSV file(s) to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
console.error('[CSV2PDF] ERROR:', e);
|
||||
console.error(
|
||||
'[CSV2PDF] Error stack:',
|
||||
e instanceof Error ? e.stack : ''
|
||||
);
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelect = (files: FileList | null) => {
|
||||
if (files && files.length > 0) {
|
||||
state.files = [...state.files, ...Array.from(files)];
|
||||
updateUI();
|
||||
}
|
||||
};
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const csvFiles = Array.from(files).filter(
|
||||
(f) => f.name.toLowerCase().endsWith('.csv') || f.type === 'text/csv'
|
||||
);
|
||||
if (csvFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
csvFiles.forEach((f) => dataTransfer.items.add(f));
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clear value on click to allow re-selecting the same file
|
||||
fileInput.addEventListener('click', () => {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (addMoreBtn) {
|
||||
addMoreBtn.addEventListener('click', () => {
|
||||
fileInput.click();
|
||||
});
|
||||
}
|
||||
|
||||
if (clearFilesBtn) {
|
||||
clearFilesBtn.addEventListener('click', () => {
|
||||
resetState();
|
||||
});
|
||||
}
|
||||
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', convertToPdf);
|
||||
}
|
||||
|
||||
updateUI();
|
||||
});
|
||||
|
||||
@@ -170,7 +170,7 @@ async function decryptPdf() {
|
||||
|
||||
zip.file(`unlocked-${file.name}`, decryptedBytes, { binary: true });
|
||||
successCount++;
|
||||
} catch (fileError: any) {
|
||||
} catch (fileError: unknown) {
|
||||
errorCount++;
|
||||
console.error(`Failed to decrypt ${file.name}:`, fileError);
|
||||
}
|
||||
@@ -194,15 +194,16 @@ async function decryptPdf() {
|
||||
resetState();
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Error during PDF decryption:', error);
|
||||
|
||||
if (error.message === 'INVALID_PASSWORD') {
|
||||
const errorMessage = error instanceof Error ? error.message : '';
|
||||
if (errorMessage === 'INVALID_PASSWORD') {
|
||||
showAlert(
|
||||
'Incorrect Password',
|
||||
'The password you entered is incorrect. Please try again.'
|
||||
);
|
||||
} else if (error.message?.includes('password')) {
|
||||
} else if (errorMessage.includes('password')) {
|
||||
showAlert(
|
||||
'Password Error',
|
||||
'Unable to decrypt the PDF with the provided password.'
|
||||
@@ -210,7 +211,7 @@ async function decryptPdf() {
|
||||
} else {
|
||||
showAlert(
|
||||
'Decryption Failed',
|
||||
`An error occurred: ${error.message || 'The password you entered is wrong or the file is corrupted.'}`
|
||||
`An error occurred: ${errorMessage || 'The password you entered is wrong or the file is corrupted.'}`
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -164,7 +164,7 @@ async function renderThumbnails() {
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
await page.render({ canvasContext: ctx, viewport }).promise;
|
||||
await page.render({ canvas: null, canvasContext: ctx, viewport }).promise;
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'relative cursor-pointer group';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import type { PyMuPDFInstance } from '@/types';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { downloadFile } from '../utils/helpers';
|
||||
import { isWasmAvailable } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
|
||||
interface DeskewResult {
|
||||
totalPages: number;
|
||||
@@ -13,11 +14,11 @@ interface DeskewResult {
|
||||
}
|
||||
|
||||
let selectedFiles: File[] = [];
|
||||
let pymupdf: any = null;
|
||||
let pymupdf: PyMuPDFInstance | null = null;
|
||||
|
||||
async function initPyMuPDF(): Promise<any> {
|
||||
async function initPyMuPDF(): Promise<PyMuPDFInstance> {
|
||||
if (!pymupdf) {
|
||||
pymupdf = await loadPyMuPDF();
|
||||
pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
|
||||
}
|
||||
return pymupdf;
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ async function handleCertFile(file: File): Promise<void> {
|
||||
certStatus.className = 'text-xs text-green-400';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
const certStatus = getElement<HTMLDivElement>('cert-status');
|
||||
if (certStatus) {
|
||||
certStatus.textContent = 'Failed to parse PEM file';
|
||||
|
||||
@@ -16,7 +16,9 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
import.meta.url
|
||||
).toString();
|
||||
|
||||
const duplicateOrganizeState = {
|
||||
const duplicateOrganizeState: {
|
||||
sortableInstances: { pageGrid?: Sortable };
|
||||
} = {
|
||||
sortableInstances: {},
|
||||
};
|
||||
|
||||
@@ -24,13 +26,10 @@ function initializePageGridSortable() {
|
||||
const grid = document.getElementById('page-grid');
|
||||
if (!grid) return;
|
||||
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'pageGrid' does not exist on type '{}'.
|
||||
if (duplicateOrganizeState.sortableInstances.pageGrid) {
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'pageGrid' does not exist on type '{}'.
|
||||
duplicateOrganizeState.sortableInstances.pageGrid.destroy();
|
||||
}
|
||||
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'pageGrid' does not exist on type '{}'.
|
||||
duplicateOrganizeState.sortableInstances.pageGrid = Sortable.create(grid, {
|
||||
animation: 150,
|
||||
ghostClass: 'sortable-ghost',
|
||||
@@ -38,10 +37,10 @@ function initializePageGridSortable() {
|
||||
dragClass: 'sortable-drag',
|
||||
filter: '.duplicate-btn, .delete-btn',
|
||||
preventOnFilter: true,
|
||||
onStart: function (evt: any) {
|
||||
onStart: function (evt: Sortable.SortableEvent) {
|
||||
evt.item.style.opacity = '0.5';
|
||||
},
|
||||
onEnd: function (evt: any) {
|
||||
onEnd: function (evt: Sortable.SortableEvent) {
|
||||
evt.item.style.opacity = '1';
|
||||
},
|
||||
});
|
||||
@@ -51,7 +50,7 @@ function initializePageGridSortable() {
|
||||
* Attaches event listeners for duplicate and delete to a page thumbnail element.
|
||||
* @param {HTMLElement} element The thumbnail element to attach listeners to.
|
||||
*/
|
||||
function attachEventListeners(element: any) {
|
||||
function attachEventListeners(element: HTMLElement) {
|
||||
// Re-number all visible page labels
|
||||
const renumberPages = () => {
|
||||
const grid = document.getElementById('page-grid');
|
||||
@@ -65,16 +64,16 @@ function attachEventListeners(element: any) {
|
||||
// Duplicate button listener
|
||||
element
|
||||
.querySelector('.duplicate-btn')
|
||||
.addEventListener('click', (e: any) => {
|
||||
.addEventListener('click', (e: Event) => {
|
||||
e.stopPropagation();
|
||||
const clone = element.cloneNode(true);
|
||||
const clone = element.cloneNode(true) as HTMLElement;
|
||||
element.after(clone);
|
||||
attachEventListeners(clone);
|
||||
renumberPages();
|
||||
initializePageGridSortable();
|
||||
});
|
||||
|
||||
element.querySelector('.delete-btn').addEventListener('click', (e: any) => {
|
||||
element.querySelector('.delete-btn').addEventListener('click', (e: Event) => {
|
||||
e.stopPropagation();
|
||||
if (document.getElementById('page-grid').children.length > 1) {
|
||||
element.remove();
|
||||
@@ -218,7 +217,7 @@ export async function processAndSave() {
|
||||
}
|
||||
|
||||
const copiedPages = await newPdfDoc.copyPages(state.pdfDoc, finalIndices);
|
||||
copiedPages.forEach((page: any) => newPdfDoc.addPage(page));
|
||||
copiedPages.forEach((page) => newPdfDoc.addPage(page));
|
||||
|
||||
const newPdfBytes = await newPdfDoc.save();
|
||||
downloadFile(
|
||||
|
||||
@@ -44,7 +44,11 @@ worker.onmessage = function (e) {
|
||||
const data = e.data;
|
||||
|
||||
if (data.status === 'success' && data.attachments !== undefined) {
|
||||
pageState.allAttachments = data.attachments.map(function (att: any) {
|
||||
pageState.allAttachments = data.attachments.map(function (att: {
|
||||
data: ArrayBuffer;
|
||||
filename: string;
|
||||
description: string;
|
||||
}) {
|
||||
return {
|
||||
...att,
|
||||
data: new Uint8Array(att.data),
|
||||
|
||||
@@ -11,8 +11,11 @@ const embedPdfWasmUrl = new URL(
|
||||
import.meta.url
|
||||
).href;
|
||||
|
||||
let viewerInstance: any = null;
|
||||
let docManagerPlugin: any = null;
|
||||
import type { EmbedPdfContainer } from 'embedpdf-snippet';
|
||||
import type { DocManagerPlugin } from '@/types';
|
||||
|
||||
let viewerInstance: EmbedPdfContainer | null = null;
|
||||
let docManagerPlugin: DocManagerPlugin | null = null;
|
||||
let isViewerInitialized = false;
|
||||
const fileEntryMap = new Map<string, HTMLElement>();
|
||||
|
||||
@@ -148,33 +151,37 @@ async function handleFiles(files: FileList) {
|
||||
});
|
||||
|
||||
const registry = await viewerInstance.registry;
|
||||
docManagerPlugin = registry.getPlugin('document-manager').provides();
|
||||
docManagerPlugin = registry
|
||||
.getPlugin('document-manager')
|
||||
.provides() as unknown as DocManagerPlugin;
|
||||
|
||||
docManagerPlugin.onDocumentClosed((data: any) => {
|
||||
const docId = data?.id || data;
|
||||
docManagerPlugin.onDocumentClosed((data: { id?: string }) => {
|
||||
const docId = data?.id || '';
|
||||
removeFileEntry(docId);
|
||||
});
|
||||
|
||||
docManagerPlugin.onDocumentOpened((data: any) => {
|
||||
const docId = data?.id;
|
||||
const docKey = data?.name;
|
||||
if (!docId) return;
|
||||
const pendingEntry = fileDisplayArea.querySelector(
|
||||
`[data-pending-name="${CSS.escape(docKey)}"]`
|
||||
) as HTMLElement;
|
||||
if (pendingEntry) {
|
||||
pendingEntry.removeAttribute('data-pending-name');
|
||||
fileEntryMap.set(docId, pendingEntry);
|
||||
const removeBtn = pendingEntry.querySelector(
|
||||
'[data-remove-btn]'
|
||||
docManagerPlugin.onDocumentOpened(
|
||||
(data: { id?: string; name?: string }) => {
|
||||
const docId = data?.id;
|
||||
const docKey = data?.name;
|
||||
if (!docId) return;
|
||||
const pendingEntry = fileDisplayArea.querySelector(
|
||||
`[data-pending-name="${CSS.escape(docKey)}"]`
|
||||
) as HTMLElement;
|
||||
if (removeBtn) {
|
||||
removeBtn.onclick = () => {
|
||||
docManagerPlugin.closeDocument(docId);
|
||||
};
|
||||
if (pendingEntry) {
|
||||
pendingEntry.removeAttribute('data-pending-name');
|
||||
fileEntryMap.set(docId, pendingEntry);
|
||||
const removeBtn = pendingEntry.querySelector(
|
||||
'[data-remove-btn]'
|
||||
) as HTMLElement;
|
||||
if (removeBtn) {
|
||||
removeBtn.onclick = () => {
|
||||
docManagerPlugin.closeDocument(docId);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
addFileEntries(fileDisplayArea, decryptedFiles);
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { parseEmailFile, renderEmailToHtml } from './email-to-pdf.js';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
|
||||
const EXTENSIONS = ['.eml', '.msg'];
|
||||
const TOOL_NAME = 'Email';
|
||||
@@ -125,7 +123,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
pageSize,
|
||||
});
|
||||
|
||||
const pdfBlob = await (pymupdf as any).htmlToPdf(htmlContent, {
|
||||
const pdfBlob = await (
|
||||
pymupdf as unknown as {
|
||||
htmlToPdf: (html: string, options: unknown) => Promise<Blob>;
|
||||
}
|
||||
).htmlToPdf(htmlContent, {
|
||||
pageSize,
|
||||
margins: { top: 50, right: 50, bottom: 50, left: 50 },
|
||||
attachments: email.attachments
|
||||
@@ -166,7 +168,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
pageSize,
|
||||
});
|
||||
|
||||
const pdfBlob = await (pymupdf as any).htmlToPdf(htmlContent, {
|
||||
const pdfBlob = await (
|
||||
pymupdf as unknown as {
|
||||
htmlToPdf: (html: string, options: unknown) => Promise<Blob>;
|
||||
}
|
||||
).htmlToPdf(htmlContent, {
|
||||
pageSize,
|
||||
margins: { top: 50, right: 50, bottom: 50, left: 50 },
|
||||
attachments: email.attachments
|
||||
@@ -179,7 +185,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const baseName = file.name.replace(/\.[^.]+$/, '');
|
||||
const pdfBuffer = await pdfBlob.arrayBuffer();
|
||||
zip.file(`${baseName}.pdf`, pdfBuffer);
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(`Failed to convert ${file.name}:`, e);
|
||||
}
|
||||
}
|
||||
@@ -196,12 +202,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,7 +42,14 @@ export async function parseEmlFile(file: File): Promise<ParsedEmail> {
|
||||
.filter(Boolean);
|
||||
|
||||
// Helper to map parsing result to EmailAttachment
|
||||
const mapAttachment = (att: any): EmailAttachment => {
|
||||
interface RawAttachment {
|
||||
content?: string | ArrayBuffer | Uint8Array;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
contentId?: string;
|
||||
}
|
||||
|
||||
const mapAttachment = (att: RawAttachment): EmailAttachment => {
|
||||
let content: Uint8Array | undefined;
|
||||
let size = 0;
|
||||
if (att.content) {
|
||||
@@ -67,7 +74,9 @@ export async function parseEmlFile(file: File): Promise<ParsedEmail> {
|
||||
|
||||
const attachments: EmailAttachment[] = [
|
||||
...(email.attachments || []).map(mapAttachment),
|
||||
...((email as any).inline || []).map(mapAttachment),
|
||||
...((email as { inline?: RawAttachment[] }).inline || []).map(
|
||||
mapAttachment
|
||||
),
|
||||
];
|
||||
|
||||
// Preserve original date string from headers
|
||||
@@ -138,8 +147,16 @@ export async function parseMsgFile(file: File): Promise<ParsedEmail> {
|
||||
}
|
||||
}
|
||||
|
||||
interface MsgAttachment {
|
||||
fileName?: string;
|
||||
name?: string;
|
||||
content?: ArrayLike<number>;
|
||||
mimeType?: string;
|
||||
pidContentId?: string;
|
||||
}
|
||||
|
||||
const attachments: EmailAttachment[] = (msgData.attachments || []).map(
|
||||
(att: any) => ({
|
||||
(att: MsgAttachment) => ({
|
||||
filename: att.fileName || att.name || 'unnamed',
|
||||
size: att.content?.length || 0,
|
||||
contentType: att.mimeType || 'application/octet-stream',
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
readFileAsArrayBuffer,
|
||||
} from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { EncryptPdfState } from '@/types';
|
||||
import { EncryptPdfState, QpdfInstanceExtended } from '@/types';
|
||||
|
||||
const pageState: EncryptPdfState = {
|
||||
file: null,
|
||||
@@ -114,7 +114,7 @@ async function encryptPdf() {
|
||||
|
||||
const inputPath = '/input.pdf';
|
||||
const outputPath = '/output.pdf';
|
||||
let qpdf: any;
|
||||
let qpdf: QpdfInstanceExtended;
|
||||
|
||||
const loaderModal = document.getElementById('loader-modal');
|
||||
const loaderText = document.getElementById('loader-text');
|
||||
@@ -154,10 +154,11 @@ async function encryptPdf() {
|
||||
|
||||
try {
|
||||
qpdf.callMain(args);
|
||||
} catch (qpdfError: any) {
|
||||
} catch (qpdfError: unknown) {
|
||||
console.error('qpdf execution error:', qpdfError);
|
||||
throw new Error(
|
||||
'Encryption failed: ' + (qpdfError.message || 'Unknown error'),
|
||||
'Encryption failed: ' +
|
||||
(qpdfError instanceof Error ? qpdfError.message : 'Unknown error'),
|
||||
{ cause: qpdfError }
|
||||
);
|
||||
}
|
||||
@@ -169,7 +170,9 @@ async function encryptPdf() {
|
||||
throw new Error('Encryption resulted in an empty file.');
|
||||
}
|
||||
|
||||
const blob = new Blob([outputFile], { type: 'application/pdf' });
|
||||
const blob = new Blob([new Uint8Array(outputFile)], {
|
||||
type: 'application/pdf',
|
||||
});
|
||||
downloadFile(blob, `encrypted-${pageState.file.name}`);
|
||||
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
@@ -183,12 +186,12 @@ async function encryptPdf() {
|
||||
showAlert('Success', successMessage, 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Error during PDF encryption:', error);
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
showAlert(
|
||||
'Encryption Failed',
|
||||
`An error occurred: ${error.message || 'The PDF might be corrupted.'}`
|
||||
`An error occurred: ${error instanceof Error ? error.message : 'The PDF might be corrupted.'}`
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
|
||||
@@ -2,9 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
|
||||
const FILETYPE = 'epub';
|
||||
const EXTENSIONS = ['.epub'];
|
||||
@@ -145,12 +143,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,216 +1,226 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
formatBytes,
|
||||
} from '../utils/helpers.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
|
||||
import {
|
||||
getLibreOfficeConverter,
|
||||
type LoadProgress,
|
||||
} from '../utils/libreoffice-loader.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
state.files = [];
|
||||
state.files = [];
|
||||
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const convertOptions = document.getElementById('convert-options');
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const fileControls = document.getElementById('file-controls');
|
||||
const addMoreBtn = document.getElementById('add-more-btn');
|
||||
const clearFilesBtn = document.getElementById('clear-files-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const convertOptions = document.getElementById('convert-options');
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const fileControls = document.getElementById('file-controls');
|
||||
const addMoreBtn = document.getElementById('add-more-btn');
|
||||
const clearFilesBtn = document.getElementById('clear-files-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
});
|
||||
}
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
});
|
||||
}
|
||||
|
||||
const updateUI = async () => {
|
||||
if (!convertOptions) return;
|
||||
const updateUI = async () => {
|
||||
if (!convertOptions) return;
|
||||
|
||||
if (state.files.length > 0) {
|
||||
if (fileDisplayArea) {
|
||||
fileDisplayArea.innerHTML = '';
|
||||
if (state.files.length > 0) {
|
||||
if (fileDisplayArea) {
|
||||
fileDisplayArea.innerHTML = '';
|
||||
|
||||
for (let index = 0; index < state.files.length; index++) {
|
||||
const file = state.files[index];
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
for (let index = 0; index < state.files.length; index++) {
|
||||
const file = state.files[index];
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className =
|
||||
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = file.name;
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className =
|
||||
'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = file.name;
|
||||
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(file.size);
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(file.size);
|
||||
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = () => {
|
||||
state.files = state.files.filter((_, i) => i !== index);
|
||||
updateUI();
|
||||
};
|
||||
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
}
|
||||
|
||||
createIcons({ icons });
|
||||
}
|
||||
if (fileControls) fileControls.classList.remove('hidden');
|
||||
convertOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
if (fileControls) fileControls.classList.add('hidden');
|
||||
convertOptions.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const resetState = () => {
|
||||
state.files = [];
|
||||
state.pdfDoc = null;
|
||||
updateUI();
|
||||
};
|
||||
|
||||
const convertToPdf = async () => {
|
||||
try {
|
||||
if (state.files.length === 0) {
|
||||
showAlert('No Files', 'Please select at least one Excel file.');
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
|
||||
// Initialize LibreOffice if not already done
|
||||
await converter.initialize((progress: LoadProgress) => {
|
||||
showLoader(progress.message, progress.percent);
|
||||
});
|
||||
|
||||
if (state.files.length === 1) {
|
||||
const originalFile = state.files[0];
|
||||
|
||||
showLoader('Processing...');
|
||||
|
||||
const pdfBlob = await converter.convertToPdf(originalFile);
|
||||
|
||||
const fileName = originalFile.name.replace(/\.(xls|xlsx|ods|csv)$/i, '') + '.pdf';
|
||||
|
||||
downloadFile(pdfBlob, fileName);
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${originalFile.name} to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
} else {
|
||||
showLoader('Processing...');
|
||||
const JSZip = (await import('jszip')).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
for (let i = 0; i < state.files.length; i++) {
|
||||
const file = state.files[i];
|
||||
showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`);
|
||||
|
||||
const pdfBlob = await converter.convertToPdf(file);
|
||||
|
||||
const baseName = file.name.replace(/\.(xls|xlsx|ods|csv)$/i, '');
|
||||
const pdfBuffer = await pdfBlob.arrayBuffer();
|
||||
zip.file(`${baseName}.pdf`, pdfBuffer);
|
||||
}
|
||||
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
|
||||
downloadFile(zipBlob, 'excel-converted.zip');
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${state.files.length} Excel file(s) to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelect = (files: FileList | null) => {
|
||||
if (files && files.length > 0) {
|
||||
state.files = [...state.files, ...Array.from(files)];
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className =
|
||||
'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = () => {
|
||||
state.files = state.files.filter((_, i) => i !== index);
|
||||
updateUI();
|
||||
};
|
||||
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
}
|
||||
};
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const excelFiles = Array.from(files).filter(f => {
|
||||
const name = f.name.toLowerCase();
|
||||
return name.endsWith('.xls') || name.endsWith('.xlsx') || name.endsWith('.ods') || name.endsWith('.csv');
|
||||
});
|
||||
if (excelFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
excelFiles.forEach(f => dataTransfer.items.add(f));
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clear value on click to allow re-selecting the same file
|
||||
fileInput.addEventListener('click', () => {
|
||||
fileInput.value = '';
|
||||
});
|
||||
createIcons({ icons });
|
||||
}
|
||||
if (fileControls) fileControls.classList.remove('hidden');
|
||||
convertOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
if (fileControls) fileControls.classList.add('hidden');
|
||||
convertOptions.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
if (addMoreBtn) {
|
||||
addMoreBtn.addEventListener('click', () => {
|
||||
fileInput.click();
|
||||
const resetState = () => {
|
||||
state.files = [];
|
||||
state.pdfDoc = null;
|
||||
updateUI();
|
||||
};
|
||||
|
||||
const convertToPdf = async () => {
|
||||
try {
|
||||
if (state.files.length === 0) {
|
||||
showAlert('No Files', 'Please select at least one Excel file.');
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
|
||||
// Initialize LibreOffice if not already done
|
||||
await converter.initialize((progress: LoadProgress) => {
|
||||
showLoader(progress.message, progress.percent);
|
||||
});
|
||||
|
||||
if (state.files.length === 1) {
|
||||
const originalFile = state.files[0];
|
||||
|
||||
showLoader('Processing...');
|
||||
|
||||
const pdfBlob = await converter.convertToPdf(originalFile);
|
||||
|
||||
const fileName =
|
||||
originalFile.name.replace(/\.(xls|xlsx|ods|csv)$/i, '') + '.pdf';
|
||||
|
||||
downloadFile(pdfBlob, fileName);
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${originalFile.name} to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
} else {
|
||||
showLoader('Processing...');
|
||||
const JSZip = (await import('jszip')).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
for (let i = 0; i < state.files.length; i++) {
|
||||
const file = state.files[i];
|
||||
showLoader(
|
||||
`Converting ${i + 1}/${state.files.length}: ${file.name}...`
|
||||
);
|
||||
|
||||
const pdfBlob = await converter.convertToPdf(file);
|
||||
|
||||
const baseName = file.name.replace(/\.(xls|xlsx|ods|csv)$/i, '');
|
||||
const pdfBuffer = await pdfBlob.arrayBuffer();
|
||||
zip.file(`${baseName}.pdf`, pdfBuffer);
|
||||
}
|
||||
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
|
||||
downloadFile(zipBlob, 'excel-converted.zip');
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${state.files.length} Excel file(s) to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelect = (files: FileList | null) => {
|
||||
if (files && files.length > 0) {
|
||||
state.files = [...state.files, ...Array.from(files)];
|
||||
updateUI();
|
||||
}
|
||||
};
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const excelFiles = Array.from(files).filter((f) => {
|
||||
const name = f.name.toLowerCase();
|
||||
return (
|
||||
name.endsWith('.xls') ||
|
||||
name.endsWith('.xlsx') ||
|
||||
name.endsWith('.ods') ||
|
||||
name.endsWith('.csv')
|
||||
);
|
||||
});
|
||||
}
|
||||
if (excelFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
excelFiles.forEach((f) => dataTransfer.items.add(f));
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (clearFilesBtn) {
|
||||
clearFilesBtn.addEventListener('click', () => {
|
||||
resetState();
|
||||
});
|
||||
}
|
||||
// Clear value on click to allow re-selecting the same file
|
||||
fileInput.addEventListener('click', () => {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', convertToPdf);
|
||||
}
|
||||
if (addMoreBtn) {
|
||||
addMoreBtn.addEventListener('click', () => {
|
||||
fileInput.click();
|
||||
});
|
||||
}
|
||||
|
||||
if (clearFilesBtn) {
|
||||
clearFilesBtn.addEventListener('click', () => {
|
||||
resetState();
|
||||
});
|
||||
}
|
||||
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', convertToPdf);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,9 +8,7 @@ import {
|
||||
} from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
|
||||
interface ExtractedImage {
|
||||
@@ -88,7 +86,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const arrayBuffer = await readFileAsArrayBuffer(file);
|
||||
const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise;
|
||||
metaSpan.textContent = `${formatBytes(file.size)} • ${pdfDoc.numPages} pages`;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`;
|
||||
}
|
||||
}
|
||||
@@ -118,7 +116,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!imagesGrid || !imagesContainer) return;
|
||||
imagesGrid.innerHTML = '';
|
||||
|
||||
extractedImages.forEach((img, index) => {
|
||||
extractedImages.forEach((img) => {
|
||||
const blob = new Blob([new Uint8Array(img.data)]);
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
@@ -212,11 +210,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
'success'
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during extraction. Error: ${e.message}`
|
||||
`An error occurred during extraction. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
interface ExtractState {
|
||||
file: File | null;
|
||||
pdfDoc: any;
|
||||
pdfDoc: PDFDocument | null;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import JSZip from 'jszip';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
let file: File | null = null;
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
|
||||
const FILETYPE = 'fb2';
|
||||
const EXTENSIONS = ['.fb2'];
|
||||
@@ -141,12 +139,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ type PdfViewerWindow = Window & {
|
||||
};
|
||||
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
|
||||
import { downloadFile, hexToRgb, getPDFDocument } from '../utils/helpers.js';
|
||||
import { downloadFile, hexToRgb } from '../utils/helpers.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
@@ -319,15 +319,15 @@ toolItems.forEach((item) => {
|
||||
let touchStartY = 0;
|
||||
let isTouchDragging = false;
|
||||
|
||||
item.addEventListener('touchstart', (e) => {
|
||||
item.addEventListener('touchstart', (e: TouchEvent) => {
|
||||
const touch = e.touches[0];
|
||||
touchStartX = touch.clientX;
|
||||
touchStartY = touch.clientY;
|
||||
isTouchDragging = false;
|
||||
});
|
||||
|
||||
item.addEventListener('touchmove', (e) => {
|
||||
e.preventDefault(); // Prevent scrolling while dragging
|
||||
item.addEventListener('touchmove', (e: TouchEvent) => {
|
||||
e.preventDefault();
|
||||
const touch = e.touches[0];
|
||||
const moveX = Math.abs(touch.clientX - touchStartX);
|
||||
const moveY = Math.abs(touch.clientY - touchStartY);
|
||||
@@ -338,7 +338,7 @@ toolItems.forEach((item) => {
|
||||
}
|
||||
});
|
||||
|
||||
item.addEventListener('touchend', (e) => {
|
||||
item.addEventListener('touchend', (e: TouchEvent) => {
|
||||
e.preventDefault();
|
||||
if (!isTouchDragging) {
|
||||
// It was a tap, treat as click
|
||||
@@ -2131,8 +2131,7 @@ downloadBtn.addEventListener('click', async () => {
|
||||
nameCount.set(field.name, count + 1);
|
||||
|
||||
if (existingFieldNames.has(field.name)) {
|
||||
if (field.type === 'radio' && existingRadioGroups.has(field.name)) {
|
||||
} else {
|
||||
if (!(field.type === 'radio' && existingRadioGroups.has(field.name))) {
|
||||
conflictsWithPdf.push(field.name);
|
||||
}
|
||||
}
|
||||
@@ -2337,7 +2336,7 @@ downloadBtn.addEventListener('click', async () => {
|
||||
const existingField = form.getFieldMaybe(groupName);
|
||||
|
||||
if (existingField) {
|
||||
radioGroup = existingField;
|
||||
radioGroup = existingField as PDFRadioGroup;
|
||||
radioGroups.set(groupName, radioGroup);
|
||||
console.log(`Using existing radio group from PDF: ${groupName}`);
|
||||
} else {
|
||||
@@ -2788,11 +2787,11 @@ function getPageDimensions(size: string): { width: number; height: number } {
|
||||
case 'a3':
|
||||
dimensions = PageSizes.A3;
|
||||
break;
|
||||
case 'custom':
|
||||
// Get custom dimensions from inputs
|
||||
case 'custom': {
|
||||
const width = parseInt(customWidth.value) || 612;
|
||||
const height = parseInt(customHeight.value) || 792;
|
||||
return { width, height };
|
||||
}
|
||||
default:
|
||||
dimensions = PageSizes.Letter;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import type { PyMuPDFInstance } from '@/types';
|
||||
import heic2any from 'heic2any';
|
||||
import {
|
||||
getSelectedQuality,
|
||||
@@ -16,7 +15,7 @@ const SUPPORTED_FORMATS_DISPLAY =
|
||||
'JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP';
|
||||
|
||||
let files: File[] = [];
|
||||
let pymupdf: any = null;
|
||||
let pymupdf: PyMuPDFInstance | null = null;
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initializePage);
|
||||
@@ -177,9 +176,9 @@ function updateUI() {
|
||||
}
|
||||
}
|
||||
|
||||
async function ensurePyMuPDF(): Promise<any> {
|
||||
async function ensurePyMuPDF(): Promise<PyMuPDFInstance> {
|
||||
if (!pymupdf) {
|
||||
pymupdf = await loadPyMuPDF();
|
||||
pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
|
||||
}
|
||||
return pymupdf;
|
||||
}
|
||||
@@ -274,7 +273,7 @@ async function convertToPdf() {
|
||||
const processed = await preprocessFile(file);
|
||||
const compressed = await compressImageFile(processed, quality);
|
||||
processedFiles.push(compressed);
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.warn(error);
|
||||
throw error;
|
||||
}
|
||||
@@ -291,11 +290,11 @@ async function convertToPdf() {
|
||||
showAlert('Success', 'PDF created successfully!', 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error('[ImageToPDF]', e);
|
||||
showAlert(
|
||||
'Conversion Error',
|
||||
e.message || 'Failed to convert images to PDF.'
|
||||
e instanceof Error ? e.message : 'Failed to convert images to PDF.'
|
||||
);
|
||||
} finally {
|
||||
hideLoader();
|
||||
|
||||
@@ -1,81 +1,18 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import { pdfToMarkdown } from './pdf-to-markdown.js';
|
||||
import { repairPdf } from './repair-pdf.js';
|
||||
|
||||
|
||||
// import { mdToPdf } from './md-to-pdf.js';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import { processAndSave } from './duplicate-organize.js';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import { wordToPdf } from './word-to-pdf.js';
|
||||
|
||||
import { setupCropperTool } from './cropper.js';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const toolLogic = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const toolLogic: Record<
|
||||
string,
|
||||
| {
|
||||
process?: (...args: unknown[]) => Promise<unknown>;
|
||||
setup?: (...args: unknown[]) => Promise<unknown>;
|
||||
}
|
||||
| ((...args: unknown[]) => unknown)
|
||||
> = {
|
||||
'duplicate-organize': { process: processAndSave },
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cropper: { setup: setupCropperTool },
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createIcons, icons } from 'lucide';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import type { PyMuPDFInstance } from '@/types';
|
||||
import {
|
||||
getSelectedQuality,
|
||||
compressImageFile,
|
||||
@@ -11,7 +12,7 @@ const SUPPORTED_FORMATS = '.jpg,.jpeg,.jp2,.jpx';
|
||||
const SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/jpg', 'image/jp2'];
|
||||
|
||||
let files: File[] = [];
|
||||
let pymupdf: any = null;
|
||||
let pymupdf: PyMuPDFInstance | null = null;
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initializePage);
|
||||
@@ -168,9 +169,9 @@ function updateUI() {
|
||||
}
|
||||
}
|
||||
|
||||
async function ensurePyMuPDF(): Promise<any> {
|
||||
async function ensurePyMuPDF(): Promise<PyMuPDFInstance> {
|
||||
if (!pymupdf) {
|
||||
pymupdf = await loadPyMuPDF();
|
||||
pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
|
||||
}
|
||||
return pymupdf;
|
||||
}
|
||||
@@ -200,11 +201,11 @@ async function convertToPdf() {
|
||||
showAlert('Success', 'PDF created successfully!', 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error('[JpgToPdf]', e);
|
||||
showAlert(
|
||||
'Conversion Error',
|
||||
e.message || 'Failed to convert images to PDF.'
|
||||
e instanceof Error ? e.message : 'Failed to convert images to PDF.'
|
||||
);
|
||||
} finally {
|
||||
hideLoader();
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import JSZip from 'jszip';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
import { LinearizePdfState } from '@/types';
|
||||
import { LinearizePdfState, QpdfInstanceExtended } from '@/types';
|
||||
|
||||
const pageState: LinearizePdfState = {
|
||||
files: [],
|
||||
@@ -111,7 +111,7 @@ async function linearizePdf() {
|
||||
|
||||
const zip = new JSZip();
|
||||
const usedNames = new Set<string>();
|
||||
let qpdf: any;
|
||||
let qpdf: QpdfInstanceExtended;
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
@@ -150,7 +150,7 @@ async function linearizePdf() {
|
||||
);
|
||||
zip.file(zipEntryName, outputFile, { binary: true });
|
||||
successCount++;
|
||||
} catch (fileError: any) {
|
||||
} catch (fileError: unknown) {
|
||||
errorCount++;
|
||||
console.error(`Failed to linearize ${file.name}:`, fileError);
|
||||
} finally {
|
||||
@@ -187,11 +187,11 @@ async function linearizePdf() {
|
||||
showAlert('Processing Complete', alertMessage, 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Linearization process error:', error);
|
||||
showAlert(
|
||||
'Linearization Failed',
|
||||
`An error occurred: ${error.message || 'Unknown error'}.`
|
||||
`An error occurred: ${error instanceof Error ? error.message : 'Unknown error'}.`
|
||||
);
|
||||
} finally {
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
|
||||
@@ -16,15 +16,15 @@ import {
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import Sortable from 'sortablejs';
|
||||
import type { MergeJob, MergeFile, MergeMessage, MergeResponse } from '@/types';
|
||||
|
||||
// @ts-ignore
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
import.meta.url
|
||||
).toString();
|
||||
|
||||
interface MergeState {
|
||||
pdfDocs: Record<string, any>;
|
||||
pdfDocs: Record<string, pdfjsLib.PDFDocumentProxy>;
|
||||
pdfBytes: Record<string, ArrayBuffer>;
|
||||
activeMode: 'file' | 'page';
|
||||
sortableInstances: {
|
||||
@@ -66,10 +66,10 @@ function initializeFileListSortable() {
|
||||
ghostClass: 'sortable-ghost',
|
||||
chosenClass: 'sortable-chosen',
|
||||
dragClass: 'sortable-drag',
|
||||
onStart: function (evt: any) {
|
||||
onStart: function (evt: Sortable.SortableEvent) {
|
||||
evt.item.style.opacity = '0.5';
|
||||
},
|
||||
onEnd: function (evt: any) {
|
||||
onEnd: function (evt: Sortable.SortableEvent) {
|
||||
evt.item.style.opacity = '1';
|
||||
},
|
||||
});
|
||||
@@ -88,10 +88,10 @@ function initializePageThumbnailsSortable() {
|
||||
ghostClass: 'sortable-ghost',
|
||||
chosenClass: 'sortable-chosen',
|
||||
dragClass: 'sortable-drag',
|
||||
onStart: function (evt: any) {
|
||||
onStart: function (evt: Sortable.SortableEvent) {
|
||||
evt.item.style.opacity = '0.5';
|
||||
},
|
||||
onEnd: function (evt: any) {
|
||||
onEnd: function (evt: Sortable.SortableEvent) {
|
||||
evt.item.style.opacity = '1';
|
||||
},
|
||||
});
|
||||
@@ -201,7 +201,7 @@ async function renderPageMergeThumbnails() {
|
||||
batchSize: 8,
|
||||
useLazyLoading: true,
|
||||
lazyLoadMargin: '300px',
|
||||
onProgress: (current, total) => {
|
||||
onProgress: () => {
|
||||
currentPageNumber++;
|
||||
showLoader(`Rendering page previews...`);
|
||||
},
|
||||
@@ -288,9 +288,7 @@ export async function merge() {
|
||||
|
||||
showLoader('Merging PDFs...');
|
||||
try {
|
||||
// @ts-ignore
|
||||
const jobs: MergeJob[] = [];
|
||||
// @ts-ignore
|
||||
const filesToMerge: MergeFile[] = [];
|
||||
const uniqueFileNames = new Set<string>();
|
||||
|
||||
@@ -387,7 +385,6 @@ export async function merge() {
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const message: MergeMessage = {
|
||||
command: 'merge',
|
||||
files: filesToMerge,
|
||||
@@ -400,7 +397,6 @@ export async function merge() {
|
||||
filesToMerge.map((f) => f.data)
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
mergeWorker.onmessage = (e: MessageEvent<MergeResponse>) => {
|
||||
hideLoader();
|
||||
if (e.data.status === 'success') {
|
||||
|
||||
@@ -2,9 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
|
||||
const FILETYPE = 'mobi';
|
||||
const EXTENSIONS = ['.mobi'];
|
||||
@@ -141,12 +139,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,215 +1,223 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
formatBytes,
|
||||
} from '../utils/helpers.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
|
||||
import {
|
||||
getLibreOfficeConverter,
|
||||
type LoadProgress,
|
||||
} from '../utils/libreoffice-loader.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
state.files = [];
|
||||
state.files = [];
|
||||
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const convertOptions = document.getElementById('convert-options');
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const fileControls = document.getElementById('file-controls');
|
||||
const addMoreBtn = document.getElementById('add-more-btn');
|
||||
const clearFilesBtn = document.getElementById('clear-files-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const convertOptions = document.getElementById('convert-options');
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const fileControls = document.getElementById('file-controls');
|
||||
const addMoreBtn = document.getElementById('add-more-btn');
|
||||
const clearFilesBtn = document.getElementById('clear-files-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
});
|
||||
}
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
});
|
||||
}
|
||||
|
||||
const updateUI = async () => {
|
||||
if (!convertOptions) return;
|
||||
const updateUI = async () => {
|
||||
if (!convertOptions) return;
|
||||
|
||||
if (state.files.length > 0) {
|
||||
if (fileDisplayArea) {
|
||||
fileDisplayArea.innerHTML = '';
|
||||
if (state.files.length > 0) {
|
||||
if (fileDisplayArea) {
|
||||
fileDisplayArea.innerHTML = '';
|
||||
|
||||
for (let index = 0; index < state.files.length; index++) {
|
||||
const file = state.files[index];
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
for (let index = 0; index < state.files.length; index++) {
|
||||
const file = state.files[index];
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className =
|
||||
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = file.name;
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className =
|
||||
'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = file.name;
|
||||
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(file.size);
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(file.size);
|
||||
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = () => {
|
||||
state.files = state.files.filter((_, i) => i !== index);
|
||||
updateUI();
|
||||
};
|
||||
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
}
|
||||
|
||||
createIcons({ icons });
|
||||
}
|
||||
if (fileControls) fileControls.classList.remove('hidden');
|
||||
convertOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
if (fileControls) fileControls.classList.add('hidden');
|
||||
convertOptions.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const resetState = () => {
|
||||
state.files = [];
|
||||
state.pdfDoc = null;
|
||||
updateUI();
|
||||
};
|
||||
|
||||
const convertToPdf = async () => {
|
||||
try {
|
||||
if (state.files.length === 0) {
|
||||
showAlert('No Files', 'Please select at least one ODT file.');
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
|
||||
// Initialize LibreOffice if not already done
|
||||
await converter.initialize((progress: LoadProgress) => {
|
||||
showLoader(progress.message, progress.percent);
|
||||
});
|
||||
|
||||
if (state.files.length === 1) {
|
||||
const originalFile = state.files[0];
|
||||
|
||||
showLoader('Processing...');
|
||||
|
||||
const pdfBlob = await converter.convertToPdf(originalFile);
|
||||
|
||||
const fileName = originalFile.name.replace(/\.odt$/i, '') + '.pdf';
|
||||
|
||||
downloadFile(pdfBlob, fileName);
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${originalFile.name} to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
} else {
|
||||
showLoader('Processing...');
|
||||
const JSZip = (await import('jszip')).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
for (let i = 0; i < state.files.length; i++) {
|
||||
const file = state.files[i];
|
||||
showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`);
|
||||
|
||||
const pdfBlob = await converter.convertToPdf(file);
|
||||
|
||||
const baseName = file.name.replace(/\.odt$/i, '');
|
||||
const pdfBuffer = await pdfBlob.arrayBuffer();
|
||||
zip.file(`${baseName}.pdf`, pdfBuffer);
|
||||
}
|
||||
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
|
||||
downloadFile(zipBlob, 'odt-converted.zip');
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${state.files.length} ODT file(s) to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelect = (files: FileList | null) => {
|
||||
if (files && files.length > 0) {
|
||||
state.files = [...state.files, ...Array.from(files)];
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className =
|
||||
'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = () => {
|
||||
state.files = state.files.filter((_, i) => i !== index);
|
||||
updateUI();
|
||||
};
|
||||
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
}
|
||||
};
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const odtFiles = Array.from(files).filter(f => f.name.toLowerCase().endsWith('.odt') || f.type === 'application/vnd.oasis.opendocument.text');
|
||||
if (odtFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
odtFiles.forEach(f => dataTransfer.items.add(f));
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clear value on click to allow re-selecting the same file
|
||||
fileInput.addEventListener('click', () => {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (addMoreBtn) {
|
||||
addMoreBtn.addEventListener('click', () => {
|
||||
fileInput.click();
|
||||
});
|
||||
}
|
||||
|
||||
if (clearFilesBtn) {
|
||||
clearFilesBtn.addEventListener('click', () => {
|
||||
resetState();
|
||||
});
|
||||
}
|
||||
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', convertToPdf);
|
||||
createIcons({ icons });
|
||||
}
|
||||
if (fileControls) fileControls.classList.remove('hidden');
|
||||
convertOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
if (fileControls) fileControls.classList.add('hidden');
|
||||
convertOptions.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const resetState = () => {
|
||||
state.files = [];
|
||||
state.pdfDoc = null;
|
||||
updateUI();
|
||||
};
|
||||
|
||||
const convertToPdf = async () => {
|
||||
try {
|
||||
if (state.files.length === 0) {
|
||||
showAlert('No Files', 'Please select at least one ODT file.');
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
|
||||
// Initialize LibreOffice if not already done
|
||||
await converter.initialize((progress: LoadProgress) => {
|
||||
showLoader(progress.message, progress.percent);
|
||||
});
|
||||
|
||||
if (state.files.length === 1) {
|
||||
const originalFile = state.files[0];
|
||||
|
||||
showLoader('Processing...');
|
||||
|
||||
const pdfBlob = await converter.convertToPdf(originalFile);
|
||||
|
||||
const fileName = originalFile.name.replace(/\.odt$/i, '') + '.pdf';
|
||||
|
||||
downloadFile(pdfBlob, fileName);
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${originalFile.name} to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
} else {
|
||||
showLoader('Processing...');
|
||||
const JSZip = (await import('jszip')).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
for (let i = 0; i < state.files.length; i++) {
|
||||
const file = state.files[i];
|
||||
showLoader(
|
||||
`Converting ${i + 1}/${state.files.length}: ${file.name}...`
|
||||
);
|
||||
|
||||
const pdfBlob = await converter.convertToPdf(file);
|
||||
|
||||
const baseName = file.name.replace(/\.odt$/i, '');
|
||||
const pdfBuffer = await pdfBlob.arrayBuffer();
|
||||
zip.file(`${baseName}.pdf`, pdfBuffer);
|
||||
}
|
||||
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
|
||||
downloadFile(zipBlob, 'odt-converted.zip');
|
||||
|
||||
hideLoader();
|
||||
|
||||
showAlert(
|
||||
'Conversion Complete',
|
||||
`Successfully converted ${state.files.length} ODT file(s) to PDF.`,
|
||||
'success',
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelect = (files: FileList | null) => {
|
||||
if (files && files.length > 0) {
|
||||
state.files = [...state.files, ...Array.from(files)];
|
||||
updateUI();
|
||||
}
|
||||
};
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const odtFiles = Array.from(files).filter(
|
||||
(f) =>
|
||||
f.name.toLowerCase().endsWith('.odt') ||
|
||||
f.type === 'application/vnd.oasis.opendocument.text'
|
||||
);
|
||||
if (odtFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
odtFiles.forEach((f) => dataTransfer.items.add(f));
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clear value on click to allow re-selecting the same file
|
||||
fileInput.addEventListener('click', () => {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (addMoreBtn) {
|
||||
addMoreBtn.addEventListener('click', () => {
|
||||
fileInput.click();
|
||||
});
|
||||
}
|
||||
|
||||
if (clearFilesBtn) {
|
||||
clearFilesBtn.addEventListener('click', () => {
|
||||
resetState();
|
||||
});
|
||||
}
|
||||
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', convertToPdf);
|
||||
}
|
||||
|
||||
updateUI();
|
||||
});
|
||||
|
||||
@@ -15,10 +15,10 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
|
||||
interface OrganizeState {
|
||||
file: File | null;
|
||||
pdfDoc: any;
|
||||
pdfJsDoc: any;
|
||||
pdfDoc: PDFDocument | null;
|
||||
pdfJsDoc: pdfjsLib.PDFDocumentProxy | null;
|
||||
totalPages: number;
|
||||
sortableInstance: any;
|
||||
sortableInstance: Sortable | null;
|
||||
}
|
||||
|
||||
const organizeState: OrganizeState = {
|
||||
@@ -282,7 +282,7 @@ async function renderThumbnails() {
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
await page.render({ canvasContext: ctx, viewport }).promise;
|
||||
await page.render({ canvas: null, canvasContext: ctx, viewport }).promise;
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className =
|
||||
|
||||
@@ -150,12 +150,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e);
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -226,9 +226,9 @@ async function generatePreview() {
|
||||
const ctx = offscreen.getContext('2d')!;
|
||||
|
||||
await page.render({
|
||||
canvasContext: ctx as any,
|
||||
canvasContext: ctx as unknown as CanvasRenderingContext2D,
|
||||
viewport: viewport,
|
||||
canvas: offscreen as any,
|
||||
canvas: offscreen as unknown as HTMLCanvasElement,
|
||||
}).promise;
|
||||
|
||||
const bitmap = await createImageBitmap(offscreen);
|
||||
|
||||
@@ -7,9 +7,7 @@ import {
|
||||
getPDFDocument,
|
||||
} from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
|
||||
interface LayerData {
|
||||
@@ -24,7 +22,21 @@ interface LayerData {
|
||||
}
|
||||
|
||||
let currentFile: File | null = null;
|
||||
let currentDoc: any = null;
|
||||
interface PyMuPDFDocument {
|
||||
getLayerConfig: () => LayerData[];
|
||||
addLayer: (name: string) => { number: number; xref: number };
|
||||
setLayerConfig: (layers: LayerData[]) => void;
|
||||
setLayerVisibility: (xref: number, visible: boolean) => void;
|
||||
deleteOCG: (xref: number) => void;
|
||||
addOCGWithParent: (
|
||||
name: string,
|
||||
parentXref: number
|
||||
) => { number: number; xref: number };
|
||||
addOCG: (name: string) => { number: number; xref: number };
|
||||
save: () => Uint8Array;
|
||||
}
|
||||
|
||||
let currentDoc: PyMuPDFDocument | null = null;
|
||||
const layersMap = new Map<number, LayerData>();
|
||||
let nextDisplayOrder = 0;
|
||||
|
||||
@@ -274,7 +286,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!childName || !childName.trim()) return;
|
||||
|
||||
try {
|
||||
const childXref = currentDoc.addOCGWithParent(
|
||||
const childResult = currentDoc.addOCGWithParent(
|
||||
childName.trim(),
|
||||
parentXref
|
||||
);
|
||||
@@ -285,9 +297,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
layersMap.set(childXref, {
|
||||
number: childXref,
|
||||
xref: childXref,
|
||||
layersMap.set(childResult.xref, {
|
||||
number: childResult.number,
|
||||
xref: childResult.xref,
|
||||
text: childName.trim(),
|
||||
on: true,
|
||||
locked: false,
|
||||
@@ -316,16 +328,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
|
||||
showLoader(`Loading layers from ${currentFile.name}...`);
|
||||
currentDoc = await pymupdf.open(currentFile);
|
||||
currentDoc = await (
|
||||
pymupdf as { open: (file: File) => Promise<PyMuPDFDocument> }
|
||||
).open(currentFile);
|
||||
|
||||
showLoader('Reading layer configuration...');
|
||||
const existingLayers = currentDoc.getLayerConfig();
|
||||
|
||||
// Reset and populate layers map
|
||||
layersMap.clear();
|
||||
nextDisplayOrder = 0;
|
||||
|
||||
existingLayers.forEach((layer: any) => {
|
||||
existingLayers.forEach((layer: LayerData) => {
|
||||
layersMap.set(layer.number, {
|
||||
number: layer.number,
|
||||
xref: layer.xref ?? layer.number,
|
||||
@@ -350,9 +363,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
renderLayers();
|
||||
setupLayerHandlers();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
hideLoader();
|
||||
showAlert('Error', error.message || 'Failed to load PDF layers');
|
||||
showAlert(
|
||||
'Error',
|
||||
error instanceof Error ? error.message : 'Failed to load PDF layers'
|
||||
);
|
||||
console.error('Layers error:', error);
|
||||
}
|
||||
};
|
||||
@@ -373,13 +389,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const xref = currentDoc.addOCG(name);
|
||||
const layerResult = currentDoc.addOCG(name);
|
||||
newLayerInput.value = '';
|
||||
|
||||
const newDisplayOrder = nextDisplayOrder++;
|
||||
layersMap.set(xref, {
|
||||
number: xref,
|
||||
xref: xref,
|
||||
layersMap.set(layerResult.xref, {
|
||||
number: layerResult.number,
|
||||
xref: layerResult.xref,
|
||||
text: name,
|
||||
on: true,
|
||||
locked: false,
|
||||
@@ -389,8 +405,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
renderLayers();
|
||||
} catch (err: any) {
|
||||
showAlert('Error', 'Failed to add layer: ' + err.message);
|
||||
} catch (err: unknown) {
|
||||
showAlert(
|
||||
'Error',
|
||||
'Failed to add layer: ' +
|
||||
(err instanceof Error ? err.message : String(err))
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -409,9 +429,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
hideLoader();
|
||||
resetState();
|
||||
showAlert('Success', 'PDF with layer changes saved!', 'success');
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
hideLoader();
|
||||
showAlert('Error', 'Failed to save PDF: ' + err.message);
|
||||
showAlert(
|
||||
'Error',
|
||||
'Failed to save PDF: ' +
|
||||
(err instanceof Error ? err.message : String(err))
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
renderPagesProgressively,
|
||||
cleanupLazyRendering,
|
||||
renderPageToCanvas,
|
||||
createPlaceholder,
|
||||
} from '../utils/render-utils';
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
|
||||
import { repairPdfFile } from './repair-pdf.js';
|
||||
@@ -841,7 +840,7 @@ function deletePage(index: number) {
|
||||
|
||||
async function insertPdfAfter(index: number) {
|
||||
document.getElementById('insert-pdf-input')?.click();
|
||||
(window as any).__insertAfterIndex = index;
|
||||
(window as unknown as Record<string, unknown>).__insertAfterIndex = index;
|
||||
}
|
||||
|
||||
async function handleInsertPdf(e: Event) {
|
||||
@@ -849,7 +848,8 @@ async function handleInsertPdf(e: Event) {
|
||||
const file = input.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const insertAfterIndex = (window as any).__insertAfterIndex;
|
||||
const insertAfterIndex = (window as unknown as Record<string, unknown>)
|
||||
.__insertAfterIndex as number | undefined;
|
||||
if (insertAfterIndex === undefined) return;
|
||||
|
||||
try {
|
||||
@@ -974,7 +974,7 @@ function addBlankPage() {
|
||||
rotation: 0,
|
||||
visualRotation: 0,
|
||||
canvas,
|
||||
pdfDoc: null as any,
|
||||
pdfDoc: null!,
|
||||
originalPageIndex: -1,
|
||||
fileName: '', // Blank page has no file
|
||||
};
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import JSZip from 'jszip';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
let file: File | null = null;
|
||||
|
||||
|
||||
@@ -8,9 +8,7 @@ import {
|
||||
} from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
|
||||
@@ -73,7 +71,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const arrayBuffer = await readFileAsArrayBuffer(file);
|
||||
const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise;
|
||||
metaSpan.textContent = `${formatBytes(file.size)} • ${pdfDoc.numPages} pages`;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`;
|
||||
}
|
||||
}
|
||||
@@ -160,11 +158,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import * as XLSX from 'xlsx';
|
||||
let file: File | null = null;
|
||||
|
||||
@@ -8,9 +8,7 @@ import {
|
||||
} from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
|
||||
@@ -76,7 +74,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const arrayBuffer = await readFileAsArrayBuffer(file);
|
||||
const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise;
|
||||
metaSpan.textContent = `${formatBytes(file.size)} • ${pdfDoc.numPages} pages`;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`;
|
||||
}
|
||||
}
|
||||
@@ -162,11 +160,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
getPDFDocument,
|
||||
} from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
|
||||
export async function pdfToMarkdown() {
|
||||
@@ -14,7 +18,9 @@ export async function pdfToMarkdown() {
|
||||
const page = await pdf.getPage(i);
|
||||
const content = await page.getTextContent();
|
||||
// This is a simple text extraction. For more advanced formatting, more complex logic is needed.
|
||||
const text = content.items.map((item: any) => item.str).join(' ');
|
||||
const text = content.items
|
||||
.map((item) => ('str' in item ? item.str : ''))
|
||||
.join(' ');
|
||||
markdown += text + '\n\n'; // Add double newline for paragraph breaks between pages
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { convertFileToPdfA, type PdfALevel } from '../utils/ghostscript-loader';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import type { PyMuPDFInstance } from '@/types';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
@@ -134,7 +135,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
|
||||
// Rasterize PDF to images and back to PDF (300 DPI for quality)
|
||||
const flattenedBlob = await (pymupdf as any).rasterizePdf(
|
||||
const flattenedBlob = await (pymupdf as PyMuPDFInstance).rasterizePdf(
|
||||
originalFile,
|
||||
{
|
||||
dpi: 300,
|
||||
@@ -205,11 +206,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,12 +2,12 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import JSZip from 'jszip';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import type { PyMuPDFInstance } from '@/types';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
|
||||
let pymupdf: any = null;
|
||||
let pymupdf: PyMuPDFInstance | null = null;
|
||||
let files: File[] = [];
|
||||
|
||||
const updateUI = () => {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import type { PyMuPDFInstance } from '@/types';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
|
||||
let files: File[] = [];
|
||||
let pymupdf: any = null;
|
||||
let pymupdf: PyMuPDFInstance | null = null;
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initializePage);
|
||||
@@ -159,9 +158,9 @@ function updateUI() {
|
||||
}
|
||||
}
|
||||
|
||||
async function ensurePyMuPDF(): Promise<any> {
|
||||
async function ensurePyMuPDF(): Promise<PyMuPDFInstance> {
|
||||
if (!pymupdf) {
|
||||
pymupdf = await loadPyMuPDF();
|
||||
pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
|
||||
}
|
||||
return pymupdf;
|
||||
}
|
||||
@@ -230,12 +229,12 @@ async function extractText() {
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error('[PDFToText]', e);
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Extraction Error',
|
||||
e.message || 'Failed to extract text from PDF.'
|
||||
e instanceof Error ? e.message : 'Failed to extract text from PDF.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,8 +913,15 @@ function showNodeSettings(node: BaseWorkflowNode) {
|
||||
content.appendChild(divider);
|
||||
}
|
||||
|
||||
interface FileInputNode extends BaseWorkflowNode {
|
||||
hasFile(): boolean;
|
||||
getFilenames(): string[];
|
||||
removeFile(index: number): void;
|
||||
addFiles(files: File[]): Promise<void>;
|
||||
}
|
||||
|
||||
const fileInputConfigs: {
|
||||
cls: any;
|
||||
cls: new (...args: unknown[]) => FileInputNode;
|
||||
label: string;
|
||||
accept: string;
|
||||
btnLabel: string;
|
||||
@@ -1534,7 +1541,7 @@ function showNodeSettings(node: BaseWorkflowNode) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const [dropdownKey, mapping] of Object.entries(conditionalVisibility)) {
|
||||
for (const [dropdownKey] of Object.entries(conditionalVisibility)) {
|
||||
const ctrl = controlEntries.find(([k]) => k === dropdownKey)?.[1] as
|
||||
| { value?: unknown }
|
||||
| undefined;
|
||||
|
||||
@@ -160,9 +160,13 @@ function updateUI() {
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeImageAsJpeg(imageBytes: any) {
|
||||
function sanitizeImageAsJpeg(imageBytes: Uint8Array | ArrayBuffer) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const blob = new Blob([imageBytes]);
|
||||
const blob = new Blob([
|
||||
imageBytes instanceof Uint8Array
|
||||
? new Uint8Array(imageBytes)
|
||||
: imageBytes,
|
||||
]);
|
||||
const imageUrl = URL.createObjectURL(blob);
|
||||
const img = new Image();
|
||||
|
||||
@@ -248,9 +252,9 @@ async function convertToPdf() {
|
||||
showAlert('Success', 'PDF created successfully!', 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(e);
|
||||
showAlert('Conversion Error', e.message);
|
||||
showAlert('Conversion Error', e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
hideLoader();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
formatBytes,
|
||||
} from '../utils/helpers.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import {
|
||||
@@ -159,11 +155,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import type { PyMuPDFInstance } from '@/types';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
|
||||
@@ -117,7 +118,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const file = state.files[0];
|
||||
showLoader(`Extracting ${file.name} for AI...`);
|
||||
|
||||
const llamaDocs = await (pymupdf as any).pdfToLlamaIndex(file);
|
||||
const llamaDocs = await (pymupdf as PyMuPDFInstance).pdfToLlamaIndex(
|
||||
file
|
||||
);
|
||||
const outName = file.name.replace(/\.pdf$/i, '') + '_llm.json';
|
||||
const jsonContent = JSON.stringify(llamaDocs, null, 2);
|
||||
downloadFile(
|
||||
@@ -144,7 +147,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
`Extracting ${file.name} for AI (${completed + 1}/${total})...`
|
||||
);
|
||||
|
||||
const llamaDocs = await (pymupdf as any).pdfToLlamaIndex(file);
|
||||
const llamaDocs = await (
|
||||
pymupdf as PyMuPDFInstance
|
||||
).pdfToLlamaIndex(file);
|
||||
const outName = file.name.replace(/\.pdf$/i, '') + '_llm.json';
|
||||
const jsonContent = JSON.stringify(llamaDocs, null, 2);
|
||||
const zipEntryName = deduplicateFileName(outName, usedNames);
|
||||
@@ -180,11 +185,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during extraction. Error: ${e.message}`
|
||||
`An error occurred during extraction. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,18 +2,17 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import type { PyMuPDFInstance } from '@/types';
|
||||
|
||||
const ACCEPTED_EXTENSIONS = ['.psd'];
|
||||
const FILETYPE_NAME = 'PSD';
|
||||
|
||||
let pymupdf: any = null;
|
||||
let pymupdf: PyMuPDFInstance | null = null;
|
||||
|
||||
async function ensurePyMuPDF(): Promise<any> {
|
||||
async function ensurePyMuPDF(): Promise<PyMuPDFInstance> {
|
||||
if (!pymupdf) {
|
||||
pymupdf = await loadPyMuPDF();
|
||||
pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
|
||||
}
|
||||
return pymupdf;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import {
|
||||
} from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import type { PyMuPDFInstance } from '@/types';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
@@ -136,12 +136,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const file = state.files[0];
|
||||
showLoader(`Rasterizing ${file.name}...`);
|
||||
|
||||
const rasterizedBlob = await (pymupdf as any).rasterizePdf(file, {
|
||||
dpi,
|
||||
format,
|
||||
grayscale,
|
||||
quality: 95,
|
||||
});
|
||||
const rasterizedBlob = await (pymupdf as PyMuPDFInstance).rasterizePdf(
|
||||
file,
|
||||
{
|
||||
dpi,
|
||||
format,
|
||||
grayscale,
|
||||
quality: 95,
|
||||
}
|
||||
);
|
||||
|
||||
const outName = file.name.replace(/\.pdf$/i, '') + '_rasterized.pdf';
|
||||
downloadFile(rasterizedBlob, outName);
|
||||
@@ -165,7 +168,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
`Rasterizing ${file.name} (${completed + 1}/${total})...`
|
||||
);
|
||||
|
||||
const rasterizedBlob = await (pymupdf as any).rasterizePdf(file, {
|
||||
const rasterizedBlob = await (
|
||||
pymupdf as PyMuPDFInstance
|
||||
).rasterizePdf(file, {
|
||||
dpi,
|
||||
format,
|
||||
grayscale,
|
||||
@@ -210,11 +215,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during rasterization. Error: ${e.message}`
|
||||
`An error occurred during rasterization. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,16 +2,18 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
|
||||
import type { RedactionRect } from '@/types';
|
||||
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'PDFLib' does not exist on type 'Window &... Remove this comment to see the full error message
|
||||
const { rgb } = window.PDFLib;
|
||||
|
||||
export async function redact(redactions: any, canvasScale: any) {
|
||||
export async function redact(redactions: RedactionRect[], canvasScale: number) {
|
||||
showLoader('Applying redactions...');
|
||||
try {
|
||||
const pdfPages = state.pdfDoc.getPages();
|
||||
const conversionScale = 1 / canvasScale;
|
||||
|
||||
redactions.forEach((r: any) => {
|
||||
redactions.forEach((r: RedactionRect) => {
|
||||
const page = pdfPages[r.pageIndex];
|
||||
const { height: pageHeight } = page.getSize();
|
||||
|
||||
@@ -32,7 +34,7 @@ export async function redact(redactions: any, canvasScale: any) {
|
||||
|
||||
const redactedBytes = await state.pdfDoc.save();
|
||||
downloadFile(
|
||||
new Blob([redactedBytes], { type: 'application/pdf' }),
|
||||
new Blob([new Uint8Array(redactedBytes)], { type: 'application/pdf' }),
|
||||
'redacted.pdf'
|
||||
);
|
||||
} catch (e) {
|
||||
|
||||
@@ -36,7 +36,7 @@ function hideLoader() {
|
||||
function showAlert(
|
||||
title: string,
|
||||
msg: string,
|
||||
type = 'error',
|
||||
_type = 'error',
|
||||
cb?: () => void
|
||||
) {
|
||||
const modal = document.getElementById('alert-modal');
|
||||
@@ -138,7 +138,7 @@ async function handleFileUpload(file: File) {
|
||||
}
|
||||
|
||||
async function isPageBlank(
|
||||
page: any,
|
||||
page: pdfjsLib.PDFPageProxy,
|
||||
maxNonWhitePercent = 0.5
|
||||
): Promise<boolean> {
|
||||
const viewport = page.getViewport({ scale: 0.5 });
|
||||
@@ -149,7 +149,7 @@ async function isPageBlank(
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
|
||||
await page.render({ canvasContext: ctx, viewport }).promise;
|
||||
await page.render({ canvas: null, canvasContext: ctx, viewport }).promise;
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const data = imageData.data;
|
||||
@@ -165,7 +165,7 @@ async function isPageBlank(
|
||||
return nonWhitePercent <= maxNonWhitePercent;
|
||||
}
|
||||
|
||||
async function generateThumbnail(page: any): Promise<string> {
|
||||
async function generateThumbnail(page: pdfjsLib.PDFPageProxy): Promise<string> {
|
||||
const viewport = page.getViewport({ scale: 1 });
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
@@ -174,7 +174,7 @@ async function generateThumbnail(page: any): Promise<string> {
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
|
||||
await page.render({ canvasContext: ctx, viewport }).promise;
|
||||
await page.render({ canvas: null, canvasContext: ctx, viewport }).promise;
|
||||
return canvas.toDataURL('image/jpeg', 0.7);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
readFileAsArrayBuffer,
|
||||
} from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { RemoveRestrictionsState } from '@/types';
|
||||
import { RemoveRestrictionsState, QpdfInstanceExtended } from '@/types';
|
||||
|
||||
const pageState: RemoveRestrictionsState = {
|
||||
file: null,
|
||||
@@ -98,7 +98,7 @@ async function removeRestrictions() {
|
||||
|
||||
const inputPath = '/input.pdf';
|
||||
const outputPath = '/output.pdf';
|
||||
let qpdf: any;
|
||||
let qpdf: QpdfInstanceExtended;
|
||||
|
||||
const loaderModal = document.getElementById('loader-modal');
|
||||
const loaderText = document.getElementById('loader-text');
|
||||
@@ -127,12 +127,10 @@ async function removeRestrictions() {
|
||||
|
||||
try {
|
||||
qpdf.callMain(args);
|
||||
} catch (qpdfError: any) {
|
||||
} catch (qpdfError: unknown) {
|
||||
console.error('qpdf execution error:', qpdfError);
|
||||
if (
|
||||
qpdfError.message?.includes('password') ||
|
||||
qpdfError.message?.includes('encrypt')
|
||||
) {
|
||||
const qpdfMsg = qpdfError instanceof Error ? qpdfError.message : '';
|
||||
if (qpdfMsg.includes('password') || qpdfMsg.includes('encrypt')) {
|
||||
throw new Error(
|
||||
'Failed to remove restrictions. The PDF may require the correct owner password.',
|
||||
{ cause: qpdfError }
|
||||
@@ -140,8 +138,7 @@ async function removeRestrictions() {
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Failed to remove restrictions: ' +
|
||||
(qpdfError.message || 'Unknown error'),
|
||||
'Failed to remove restrictions: ' + (qpdfMsg || 'Unknown error'),
|
||||
{ cause: qpdfError }
|
||||
);
|
||||
}
|
||||
@@ -153,7 +150,9 @@ async function removeRestrictions() {
|
||||
throw new Error('Operation resulted in an empty file.');
|
||||
}
|
||||
|
||||
const blob = new Blob([outputFile], { type: 'application/pdf' });
|
||||
const blob = new Blob([new Uint8Array(outputFile)], {
|
||||
type: 'application/pdf',
|
||||
});
|
||||
downloadFile(blob, `unrestricted-${pageState.file.name}`);
|
||||
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
@@ -166,12 +165,12 @@ async function removeRestrictions() {
|
||||
resetState();
|
||||
}
|
||||
);
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Error during restriction removal:', error);
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
showAlert(
|
||||
'Operation Failed',
|
||||
`An error occurred: ${error.message || 'The PDF might be corrupted or password-protected.'}`
|
||||
`An error occurred: ${error instanceof Error ? error.message : 'The PDF might be corrupted or password-protected.'}`
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
|
||||
@@ -8,11 +8,12 @@ import { state } from '../state.js';
|
||||
import JSZip from 'jszip';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
import type { QpdfInstanceExtended } from '@/types';
|
||||
|
||||
export async function repairPdfFile(file: File): Promise<Uint8Array | null> {
|
||||
const inputPath = '/input.pdf';
|
||||
const outputPath = '/repaired_form.pdf';
|
||||
let qpdf: any;
|
||||
let qpdf: QpdfInstanceExtended;
|
||||
|
||||
try {
|
||||
qpdf = await initializeQpdf();
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
formatBytes,
|
||||
} from '../utils/helpers.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import {
|
||||
@@ -158,11 +154,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,192 +1,202 @@
|
||||
import { categories } from '../config/tools.js';
|
||||
import type { ToolEntry } from '@/types';
|
||||
|
||||
export class ShortcutsManager {
|
||||
private static STORAGE_KEY = 'bentopdf_shortcuts';
|
||||
private static shortcuts: Map<string, string> = new Map();
|
||||
private static defaultShortcuts: Map<string, string> = new Map();
|
||||
private static STORAGE_KEY = 'bentopdf_shortcuts';
|
||||
private static shortcuts: Map<string, string> = new Map();
|
||||
private static defaultShortcuts: Map<string, string> = new Map();
|
||||
|
||||
private static getToolId(tool: any): string {
|
||||
if (tool.id) return tool.id;
|
||||
if (tool.href) {
|
||||
// Extract filename without extension from href
|
||||
const match = tool.href.match(/\/([^/]+)\.html$/);
|
||||
return match ? match[1] : tool.href;
|
||||
}
|
||||
return 'unknown';
|
||||
private static getToolId(tool: ToolEntry): string {
|
||||
if (tool.id) return tool.id;
|
||||
if (tool.href) {
|
||||
// Extract filename without extension from href
|
||||
const match = tool.href.match(/\/([^/]+)\.html$/);
|
||||
return match ? match[1] : tool.href;
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
static init() {
|
||||
this.loadDefaults();
|
||||
this.loadFromStorage();
|
||||
this.setupGlobalListener();
|
||||
}
|
||||
static init() {
|
||||
this.loadDefaults();
|
||||
this.loadFromStorage();
|
||||
this.setupGlobalListener();
|
||||
}
|
||||
|
||||
private static loadDefaults() {
|
||||
this.defaultShortcuts.set('merge', 'mod+shift+m');
|
||||
this.defaultShortcuts.set('split', 'mod+shift+s');
|
||||
this.defaultShortcuts.set('compress', 'mod+shift+c');
|
||||
}
|
||||
private static loadDefaults() {
|
||||
this.defaultShortcuts.set('merge', 'mod+shift+m');
|
||||
this.defaultShortcuts.set('split', 'mod+shift+s');
|
||||
this.defaultShortcuts.set('compress', 'mod+shift+c');
|
||||
}
|
||||
|
||||
private static loadFromStorage() {
|
||||
const stored = localStorage.getItem(this.STORAGE_KEY);
|
||||
if (stored) {
|
||||
try {
|
||||
const parsed = JSON.parse(stored);
|
||||
this.shortcuts = new Map(Object.entries(parsed));
|
||||
private static loadFromStorage() {
|
||||
const stored = localStorage.getItem(this.STORAGE_KEY);
|
||||
if (stored) {
|
||||
try {
|
||||
const parsed = JSON.parse(stored);
|
||||
this.shortcuts = new Map(Object.entries(parsed));
|
||||
|
||||
const allTools = categories.flatMap(c => c.tools);
|
||||
const validToolIds = allTools.map(t => this.getToolId(t));
|
||||
for (const [toolId, _] of this.shortcuts.entries()) {
|
||||
if (!validToolIds.includes(toolId)) {
|
||||
this.shortcuts.delete(toolId);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.shortcuts.size !== Object.keys(parsed).length) {
|
||||
this.save();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse shortcuts from local storage', e);
|
||||
this.shortcuts = new Map(this.defaultShortcuts);
|
||||
}
|
||||
} else {
|
||||
this.shortcuts = new Map(this.defaultShortcuts);
|
||||
}
|
||||
}
|
||||
|
||||
static save() {
|
||||
const obj = Object.fromEntries(this.shortcuts);
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(obj));
|
||||
}
|
||||
|
||||
static reset() {
|
||||
this.shortcuts = new Map(this.defaultShortcuts);
|
||||
this.save();
|
||||
// Dispatch event to update UI if needed
|
||||
window.dispatchEvent(new CustomEvent('shortcuts-updated'));
|
||||
}
|
||||
|
||||
static getShortcut(toolId: string): string | undefined {
|
||||
return this.shortcuts.get(toolId);
|
||||
}
|
||||
|
||||
static findToolByShortcut(key: string): string | undefined {
|
||||
for (const [id, k] of this.shortcuts.entries()) {
|
||||
if (k === key) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static setShortcut(toolId: string, key: string) {
|
||||
if (key) {
|
||||
this.shortcuts.set(toolId, key);
|
||||
} else {
|
||||
const allTools = categories.flatMap((c) => c.tools);
|
||||
const validToolIds = allTools.map((t) => this.getToolId(t));
|
||||
for (const [toolId] of this.shortcuts.entries()) {
|
||||
if (!validToolIds.includes(toolId)) {
|
||||
this.shortcuts.delete(toolId);
|
||||
}
|
||||
}
|
||||
this.save();
|
||||
window.dispatchEvent(new CustomEvent('shortcuts-updated'));
|
||||
}
|
||||
|
||||
static getAllShortcuts(): Map<string, string> {
|
||||
return this.shortcuts;
|
||||
}
|
||||
|
||||
static exportSettings() {
|
||||
// Create a map with all tools, defaulting to empty string if not set
|
||||
const exportObj: Record<string, string> = {};
|
||||
|
||||
const allTools = categories.flatMap(c => c.tools);
|
||||
|
||||
allTools.forEach(tool => {
|
||||
const toolId = this.getToolId(tool);
|
||||
exportObj[toolId] = this.shortcuts.get(toolId) || '';
|
||||
});
|
||||
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj, null, 2));
|
||||
const downloadAnchorNode = document.createElement('a');
|
||||
downloadAnchorNode.setAttribute("href", dataStr);
|
||||
downloadAnchorNode.setAttribute("download", "bentopdf_shortcuts.json");
|
||||
document.body.appendChild(downloadAnchorNode);
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
}
|
||||
|
||||
static importSettings(jsonString: string): boolean {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonString);
|
||||
for (const key in parsed) {
|
||||
if (typeof parsed[key] !== 'string') {
|
||||
throw new Error('Invalid shortcut format');
|
||||
}
|
||||
}
|
||||
this.shortcuts = new Map(Object.entries(parsed));
|
||||
this.save();
|
||||
window.dispatchEvent(new CustomEvent('shortcuts-updated'));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Import failed', e);
|
||||
return false;
|
||||
if (this.shortcuts.size !== Object.keys(parsed).length) {
|
||||
this.save();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse shortcuts from local storage', e);
|
||||
this.shortcuts = new Map(this.defaultShortcuts);
|
||||
}
|
||||
} else {
|
||||
this.shortcuts = new Map(this.defaultShortcuts);
|
||||
}
|
||||
}
|
||||
|
||||
private static setupGlobalListener() {
|
||||
window.addEventListener('keydown', (e) => {
|
||||
// Ignore if typing in an input or textarea
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
||||
return;
|
||||
}
|
||||
static save() {
|
||||
const obj = Object.fromEntries(this.shortcuts);
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(obj));
|
||||
}
|
||||
|
||||
// Build key string
|
||||
const keys: string[] = [];
|
||||
const isMac = navigator.userAgent.toUpperCase().includes('MAC');
|
||||
static reset() {
|
||||
this.shortcuts = new Map(this.defaultShortcuts);
|
||||
this.save();
|
||||
// Dispatch event to update UI if needed
|
||||
window.dispatchEvent(new CustomEvent('shortcuts-updated'));
|
||||
}
|
||||
|
||||
// On Mac: metaKey = Command, ctrlKey = Control
|
||||
// On Windows/Linux: metaKey is rare, ctrlKey = Ctrl
|
||||
if (isMac) {
|
||||
if (e.metaKey) keys.push('mod'); // Command on Mac
|
||||
if (e.ctrlKey) keys.push('ctrl'); // Control on Mac (separate from Command)
|
||||
} else {
|
||||
if (e.ctrlKey || e.metaKey) keys.push('mod'); // Ctrl on Windows/Linux
|
||||
}
|
||||
if (e.altKey) keys.push('alt');
|
||||
if (e.shiftKey) keys.push('shift');
|
||||
static getShortcut(toolId: string): string | undefined {
|
||||
return this.shortcuts.get(toolId);
|
||||
}
|
||||
|
||||
let key = e.key.toLowerCase();
|
||||
|
||||
if (e.altKey && e.code) {
|
||||
if (e.code.startsWith('Key')) {
|
||||
key = e.code.slice(3).toLowerCase();
|
||||
} else if (e.code.startsWith('Digit')) {
|
||||
key = e.code.slice(5);
|
||||
}
|
||||
}
|
||||
|
||||
if (!['control', 'shift', 'alt', 'meta'].includes(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
const combo = keys.join('+');
|
||||
|
||||
for (const [toolId, shortcut] of this.shortcuts.entries()) {
|
||||
if (shortcut === combo) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Find the tool in categories
|
||||
const allTools = categories.flatMap(c => c.tools);
|
||||
const tool = allTools.find(t => this.getToolId(t) === toolId);
|
||||
|
||||
if (tool && (tool as any).href) {
|
||||
// All tools now use href - navigate to the page
|
||||
const href = (tool as any).href;
|
||||
window.location.href = href.startsWith('/') ? href : `/${href}`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, { capture: true });
|
||||
static findToolByShortcut(key: string): string | undefined {
|
||||
for (const [id, k] of this.shortcuts.entries()) {
|
||||
if (k === key) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static setShortcut(toolId: string, key: string) {
|
||||
if (key) {
|
||||
this.shortcuts.set(toolId, key);
|
||||
} else {
|
||||
this.shortcuts.delete(toolId);
|
||||
}
|
||||
this.save();
|
||||
window.dispatchEvent(new CustomEvent('shortcuts-updated'));
|
||||
}
|
||||
|
||||
static getAllShortcuts(): Map<string, string> {
|
||||
return this.shortcuts;
|
||||
}
|
||||
|
||||
static exportSettings() {
|
||||
// Create a map with all tools, defaulting to empty string if not set
|
||||
const exportObj: Record<string, string> = {};
|
||||
|
||||
const allTools = categories.flatMap((c) => c.tools);
|
||||
|
||||
allTools.forEach((tool) => {
|
||||
const toolId = this.getToolId(tool);
|
||||
exportObj[toolId] = this.shortcuts.get(toolId) || '';
|
||||
});
|
||||
|
||||
const dataStr =
|
||||
'data:text/json;charset=utf-8,' +
|
||||
encodeURIComponent(JSON.stringify(exportObj, null, 2));
|
||||
const downloadAnchorNode = document.createElement('a');
|
||||
downloadAnchorNode.setAttribute('href', dataStr);
|
||||
downloadAnchorNode.setAttribute('download', 'bentopdf_shortcuts.json');
|
||||
document.body.appendChild(downloadAnchorNode);
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
}
|
||||
|
||||
static importSettings(jsonString: string): boolean {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonString);
|
||||
for (const key in parsed) {
|
||||
if (typeof parsed[key] !== 'string') {
|
||||
throw new Error('Invalid shortcut format');
|
||||
}
|
||||
}
|
||||
this.shortcuts = new Map(Object.entries(parsed));
|
||||
this.save();
|
||||
window.dispatchEvent(new CustomEvent('shortcuts-updated'));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Import failed', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static setupGlobalListener() {
|
||||
window.addEventListener(
|
||||
'keydown',
|
||||
(e) => {
|
||||
// Ignore if typing in an input or textarea
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.isContentEditable
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build key string
|
||||
const keys: string[] = [];
|
||||
const isMac = navigator.userAgent.toUpperCase().includes('MAC');
|
||||
|
||||
// On Mac: metaKey = Command, ctrlKey = Control
|
||||
// On Windows/Linux: metaKey is rare, ctrlKey = Ctrl
|
||||
if (isMac) {
|
||||
if (e.metaKey) keys.push('mod'); // Command on Mac
|
||||
if (e.ctrlKey) keys.push('ctrl'); // Control on Mac (separate from Command)
|
||||
} else {
|
||||
if (e.ctrlKey || e.metaKey) keys.push('mod'); // Ctrl on Windows/Linux
|
||||
}
|
||||
if (e.altKey) keys.push('alt');
|
||||
if (e.shiftKey) keys.push('shift');
|
||||
|
||||
let key = e.key.toLowerCase();
|
||||
|
||||
if (e.altKey && e.code) {
|
||||
if (e.code.startsWith('Key')) {
|
||||
key = e.code.slice(3).toLowerCase();
|
||||
} else if (e.code.startsWith('Digit')) {
|
||||
key = e.code.slice(5);
|
||||
}
|
||||
}
|
||||
|
||||
if (!['control', 'shift', 'alt', 'meta'].includes(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
const combo = keys.join('+');
|
||||
|
||||
for (const [toolId, shortcut] of this.shortcuts.entries()) {
|
||||
if (shortcut === combo) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Find the tool in categories
|
||||
const allTools = categories.flatMap((c) => c.tools);
|
||||
const tool = allTools.find((t) => this.getToolId(t) === toolId);
|
||||
|
||||
if (tool && tool.href) {
|
||||
const href = tool.href;
|
||||
window.location.href = href.startsWith('/') ? href : `/${href}`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ capture: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import JSZip from 'jszip';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
// @ts-ignore
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
import.meta.url
|
||||
@@ -178,7 +177,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
imgContainer.append(img, pageNumDiv);
|
||||
wrapper.appendChild(imgContainer);
|
||||
|
||||
const handleSelection = (e: any) => {
|
||||
const handleSelection = (e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -286,7 +285,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let indicesToExtract: number[] = [];
|
||||
|
||||
switch (splitMode) {
|
||||
case 'range':
|
||||
case 'range': {
|
||||
const pageRangeInput = (
|
||||
document.getElementById('page-range') as HTMLInputElement
|
||||
).value;
|
||||
@@ -331,7 +330,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const group = rangeGroups[i];
|
||||
const newPdf = await PDFLibDocument.create();
|
||||
const copiedPages = await newPdf.copyPages(state.pdfDoc, group);
|
||||
copiedPages.forEach((page: any) => newPdf.addPage(page));
|
||||
copiedPages.forEach((page) => newPdf.addPage(page));
|
||||
const pdfBytes = await newPdf.save();
|
||||
|
||||
const minPage = Math.min(...group) + 1;
|
||||
@@ -357,8 +356,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'even-odd':
|
||||
case 'even-odd': {
|
||||
const choiceElement = document.querySelector(
|
||||
'input[name="even-odd-choice"]:checked'
|
||||
) as HTMLInputElement;
|
||||
@@ -371,6 +371,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (choice === 'odd' && (i + 1) % 2 !== 0) indicesToExtract.push(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'all':
|
||||
indicesToExtract = Array.from({ length: totalPages }, (_, i) => i);
|
||||
break;
|
||||
@@ -379,8 +380,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.page-thumbnail-wrapper.selected')
|
||||
).map((el) => parseInt((el as HTMLElement).dataset.pageIndex || '0'));
|
||||
break;
|
||||
case 'bookmarks':
|
||||
// Check if CPDF is configured
|
||||
case 'bookmarks': {
|
||||
if (!isCpdfAvailable()) {
|
||||
showWasmRequiredDialog('cpdf');
|
||||
hideLoader();
|
||||
@@ -404,7 +404,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
if (bookmarkLevel === 'all' || level === parseInt(bookmarkLevel)) {
|
||||
if (page > 1 && !splitPages.includes(page - 1)) {
|
||||
splitPages.push(page - 1); // Convert to 0-based index
|
||||
splitPages.push(page - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,7 +434,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
state.pdfDoc,
|
||||
pageIndices
|
||||
);
|
||||
copiedPages.forEach((page: any) => newPdf.addPage(page));
|
||||
copiedPages.forEach((page) => newPdf.addPage(page));
|
||||
const pdfBytes2 = await newPdf.save();
|
||||
zip.file(`split-${i + 1}.pdf`, pdfBytes2);
|
||||
}
|
||||
@@ -446,8 +446,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
resetState();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
case 'n-times':
|
||||
case 'n-times': {
|
||||
const nValue = parseInt(
|
||||
(document.getElementById('split-n-value') as HTMLInputElement)
|
||||
?.value || '5'
|
||||
@@ -470,7 +471,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
state.pdfDoc,
|
||||
pageIndices
|
||||
);
|
||||
copiedPages.forEach((page: any) => newPdf.addPage(page));
|
||||
copiedPages.forEach((page) => newPdf.addPage(page));
|
||||
const pdfBytes3 = await newPdf.save();
|
||||
zip2.file(`split-${i + 1}.pdf`, pdfBytes3);
|
||||
}
|
||||
@@ -482,6 +483,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
resetState();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueIndices = [...new Set(indicesToExtract)];
|
||||
@@ -506,8 +508,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
]);
|
||||
newPdf.addPage(copiedPage);
|
||||
const pdfBytes = await newPdf.save();
|
||||
// @ts-ignore
|
||||
zip.file(`page-${index + 1}.pdf`, pdfBytes);
|
||||
zip.file(`page-${index + 1}.pdf`, new Uint8Array(pdfBytes));
|
||||
}
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
downloadFile(zipBlob, 'split-pages.zip');
|
||||
@@ -517,7 +518,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
state.pdfDoc,
|
||||
uniqueIndices as number[]
|
||||
);
|
||||
copiedPages.forEach((page: any) => newPdf.addPage(page));
|
||||
copiedPages.forEach((page) => newPdf.addPage(page));
|
||||
const pdfBytes = await newPdf.save();
|
||||
downloadFile(
|
||||
new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }),
|
||||
@@ -532,11 +533,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
showAlert('Success', 'PDF split successfully!', 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(e);
|
||||
showAlert(
|
||||
'Error',
|
||||
e.message || 'Failed to split PDF. Please check your selection.'
|
||||
e instanceof Error
|
||||
? e.message
|
||||
: 'Failed to split PDF. Please check your selection.'
|
||||
);
|
||||
} finally {
|
||||
hideLoader();
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
formatBytes,
|
||||
} from '../utils/helpers.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import {
|
||||
getSelectedQuality,
|
||||
@@ -261,9 +257,9 @@ async function convertToPdf() {
|
||||
showAlert('Success', 'PDF created successfully!', 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(e);
|
||||
showAlert('Conversion Error', e.message);
|
||||
showAlert('Conversion Error', e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
hideLoader();
|
||||
}
|
||||
|
||||
@@ -36,15 +36,6 @@ const backToToolsBtn = document.getElementById(
|
||||
'back-to-tools'
|
||||
) as HTMLButtonElement;
|
||||
|
||||
interface GenerateTOCMessage {
|
||||
command: 'generate-toc';
|
||||
pdfData: ArrayBuffer;
|
||||
title: string;
|
||||
fontSize: number;
|
||||
fontFamily: number;
|
||||
addBookmark: boolean;
|
||||
}
|
||||
|
||||
interface TOCSuccessResponse {
|
||||
status: 'success';
|
||||
pdfBytes: ArrayBuffer;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
|
||||
let files: File[] = [];
|
||||
let currentMode: 'upload' | 'text' = 'upload';
|
||||
@@ -140,9 +138,12 @@ async function convert() {
|
||||
resetState();
|
||||
}
|
||||
);
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error('[TxtToPDF] Error:', e);
|
||||
showAlert('Error', `Failed to convert text to PDF. ${e.message || ''}`);
|
||||
showAlert(
|
||||
'Error',
|
||||
`Failed to convert text to PDF. ${e instanceof Error ? e.message : ''}`
|
||||
);
|
||||
} finally {
|
||||
hideLoader();
|
||||
}
|
||||
|
||||
@@ -100,7 +100,13 @@ export function validateSignature(
|
||||
Array.from(signature.contents)
|
||||
);
|
||||
const asn1 = forge.asn1.fromDer(binaryString);
|
||||
const p7 = forge.pkcs7.messageFromAsn1(asn1) as any;
|
||||
const p7 = forge.pkcs7.messageFromAsn1(
|
||||
asn1
|
||||
) as forge.pkcs7.PkcsSignedData & {
|
||||
rawCapture?: {
|
||||
authenticatedAttributes?: Array<{ type: string; value: Date }>;
|
||||
};
|
||||
};
|
||||
|
||||
if (!p7.certificates || p7.certificates.length === 0) {
|
||||
result.errorMessage = 'No certificates found in signature';
|
||||
@@ -166,10 +172,8 @@ export function validateSignature(
|
||||
} else {
|
||||
// Try to extract from authenticated attributes
|
||||
try {
|
||||
const signedData = p7 as any;
|
||||
if (signedData.rawCapture?.authenticatedAttributes) {
|
||||
// Look for signing time attribute
|
||||
for (const attr of signedData.rawCapture.authenticatedAttributes) {
|
||||
if (p7.rawCapture?.authenticatedAttributes) {
|
||||
for (const attr of p7.rawCapture.authenticatedAttributes) {
|
||||
if (attr.type === forge.pki.oids.signingTime) {
|
||||
result.signatureDate = attr.value;
|
||||
break;
|
||||
@@ -185,7 +189,7 @@ export function validateSignature(
|
||||
}
|
||||
|
||||
if (signature.byteRange && signature.byteRange.length === 4) {
|
||||
const [start1, len1, start2, len2] = signature.byteRange;
|
||||
const [, len1, start2, len2] = signature.byteRange;
|
||||
const totalCovered = len1 + len2;
|
||||
const expectedEnd = start2 + len2;
|
||||
|
||||
|
||||
@@ -160,9 +160,13 @@ function updateUI() {
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeImageAsJpeg(imageBytes: any) {
|
||||
function sanitizeImageAsJpeg(imageBytes: Uint8Array | ArrayBuffer) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const blob = new Blob([imageBytes]);
|
||||
const blob = new Blob([
|
||||
imageBytes instanceof Uint8Array
|
||||
? new Uint8Array(imageBytes)
|
||||
: imageBytes,
|
||||
]);
|
||||
const imageUrl = URL.createObjectURL(blob);
|
||||
const img = new Image();
|
||||
|
||||
@@ -248,9 +252,9 @@ async function convertToPdf() {
|
||||
showAlert('Success', 'PDF created successfully!', 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(e);
|
||||
showAlert('Conversion Error', e.message);
|
||||
showAlert('Conversion Error', e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
hideLoader();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
formatBytes,
|
||||
} from '../utils/helpers.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import {
|
||||
@@ -187,13 +183,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error('[Word2PDF] ERROR:', e);
|
||||
console.error('[Word2PDF] Error stack:', e.stack);
|
||||
console.error(
|
||||
'[Word2PDF] Error stack:',
|
||||
e instanceof Error ? e.stack : ''
|
||||
);
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,13 +15,18 @@ export async function wordToPdf() {
|
||||
try {
|
||||
const mammothOptions = {
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'mammoth'.
|
||||
convertImage: mammoth.images.inline((element: any) => {
|
||||
return element.read('base64').then((imageBuffer: any) => {
|
||||
return {
|
||||
src: `data:${element.contentType};base64,${imageBuffer}`,
|
||||
};
|
||||
});
|
||||
}),
|
||||
convertImage: mammoth.images.inline(
|
||||
(element: {
|
||||
read: (encoding: string) => Promise<string>;
|
||||
contentType: string;
|
||||
}) => {
|
||||
return element.read('base64').then((imageBuffer: string) => {
|
||||
return {
|
||||
src: `data:${element.contentType};base64,${imageBuffer}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
),
|
||||
};
|
||||
const arrayBuffer = await readFileAsArrayBuffer(file);
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'mammoth'.
|
||||
@@ -77,7 +82,18 @@ export async function wordToPdf() {
|
||||
});
|
||||
|
||||
await doc.html(previewContent, {
|
||||
callback: function (doc: any) {
|
||||
callback: function (doc: {
|
||||
internal: { pageSize: { getHeight: () => number } };
|
||||
link: (
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
opts: { url: string }
|
||||
) => void;
|
||||
save: (name: string) => void;
|
||||
setPage: (page: number) => void;
|
||||
}) {
|
||||
const links = previewContent.querySelectorAll('a');
|
||||
const pageHeight = doc.internal.pageSize.getHeight();
|
||||
const containerRect = previewContent.getBoundingClientRect(); // Get container's position
|
||||
|
||||
@@ -150,12 +150,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e);
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -150,12 +150,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
hideLoader();
|
||||
console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e);
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,9 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
|
||||
import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
|
||||
const FILETYPE = 'xps';
|
||||
@@ -147,12 +145,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
() => resetState()
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Error',
|
||||
`An error occurred during conversion. Error: ${e.message}`
|
||||
`An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user