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:
alam00000
2026-03-31 17:59:49 +05:30
parent a1fc2fc3c6
commit 9d0b68e18c
114 changed files with 2577 additions and 1868 deletions

View File

@@ -1,47 +1,63 @@
import globals from "globals"; import globals from 'globals';
import pluginJs from "@eslint/js"; import pluginJs from '@eslint/js';
import tseslint from "typescript-eslint"; import tseslint from 'typescript-eslint';
import eslintConfigPrettier from "eslint-config-prettier"; import eslintConfigPrettier from 'eslint-config-prettier';
export default [ export default [
{ files: ["**/*.{js,mjs,cjs,ts}"] }, { files: ['**/*.{js,mjs,cjs,ts}'] },
{ ignores: ["dist/**", "coverage/**", "node_modules/**", "**/.vitepress/cache/**", "public/**/*.min.js"] }, {
ignores: [
'dist/**',
'coverage/**',
'node_modules/**',
'**/.vitepress/cache/**',
'public/**/*.min.js',
],
},
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } }, { languageOptions: { globals: { ...globals.browser, ...globals.node } } },
pluginJs.configs.recommended, pluginJs.configs.recommended,
...tseslint.configs.recommended, ...tseslint.configs.recommended,
eslintConfigPrettier, eslintConfigPrettier,
{ {
rules: { rules: {
"@typescript-eslint/no-explicit-any": "warn", '@typescript-eslint/no-explicit-any': 'warn',
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], '@typescript-eslint/no-unused-vars': [
"@typescript-eslint/no-unused-expressions": "warn", 'warn',
"no-redeclare": "warn", {
"no-constant-condition": "warn", argsIgnorePattern: '^_',
"no-ex-assign": "warn", varsIgnorePattern: '^_',
"no-empty": "warn", caughtErrorsIgnorePattern: '^_',
"no-case-declarations": "warn", destructuredArrayIgnorePattern: '^_',
"@typescript-eslint/no-this-alias": "warn", },
"no-func-assign": "warn", ],
"no-fallthrough": "warn", '@typescript-eslint/no-unused-expressions': 'warn',
"no-cond-assign": "warn", 'no-redeclare': 'warn',
"no-irregular-whitespace": "warn", 'no-constant-condition': 'warn',
"no-prototype-builtins": "warn", 'no-ex-assign': 'warn',
"no-undef": "warn", 'no-empty': 'warn',
"no-useless-escape": "warn", 'no-case-declarations': 'warn',
"no-unsafe-finally": "warn", '@typescript-eslint/no-this-alias': 'warn',
"no-self-assign": "warn", 'no-func-assign': 'warn',
"no-control-regex": "warn", 'no-fallthrough': 'warn',
"@typescript-eslint/no-require-imports": "warn", 'no-cond-assign': 'warn',
"getter-return": "warn", 'no-irregular-whitespace': 'warn',
"no-constant-binary-expression": "warn", 'no-prototype-builtins': 'warn',
"@typescript-eslint/ban-ts-comment": "warn", 'no-undef': 'warn',
"no-unused-private-class-members": "warn", 'no-useless-escape': 'warn',
"no-unreachable": "warn", 'no-unsafe-finally': 'warn',
"no-setter-return": "warn", 'no-self-assign': 'warn',
"no-useless-catch": "warn", 'no-control-regex': 'warn',
"valid-typeof": "warn", '@typescript-eslint/no-require-imports': 'warn',
"no-sparse-arrays": "warn", 'getter-return': 'warn',
"no-misleading-character-class": "warn" 'no-constant-binary-expression': 'warn',
} '@typescript-eslint/ban-ts-comment': 'warn',
} 'no-unused-private-class-members': 'warn',
'no-unreachable': 'warn',
'no-setter-return': 'warn',
'no-useless-catch': 'warn',
'valid-typeof': 'warn',
'no-sparse-arrays': 'warn',
'no-misleading-character-class': 'warn',
},
},
]; ];

View File

@@ -4,25 +4,34 @@ import { state } from './state.js';
import { toolLogic } from './logic/index.js'; import { toolLogic } from './logic/index.js';
import { icons, createIcons } from 'lucide'; import { icons, createIcons } from 'lucide';
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';
import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist';
import type { CropBox } from '@/types';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url
).toString();
const editorState: { const editorState: {
pdf: any; pdf: PDFDocumentProxy | null;
canvas: any; canvas: HTMLCanvasElement | null;
context: any; context: CanvasRenderingContext2D | null;
container: any; container: HTMLElement | null;
currentPageNum: number; currentPageNum: number;
pageRendering: boolean; pageRendering: boolean;
pageNumPending: number | null; pageNumPending: number | null;
scale: number | 'fit'; scale: number | 'fit';
pageSnapshot: any; pageSnapshot: ImageData | null;
isDrawing: boolean; isDrawing: boolean;
startX: number; startX: number;
startY: number; startY: number;
cropBoxes: Record<number, any>; cropBoxes: Record<number, CropBox>;
lastInteractionRect: { x: number; y: number; width: number; height: number } | null; lastInteractionRect: {
x: number;
y: number;
width: number;
height: number;
} | null;
} = { } = {
pdf: null, pdf: null,
canvas: null, canvas: null,
@@ -37,15 +46,15 @@ const editorState: {
startX: 0, startX: 0,
startY: 0, startY: 0,
cropBoxes: {}, cropBoxes: {},
lastInteractionRect: null, // Used to store the rectangle from the last move event lastInteractionRect: null,
}; };
/** /**
* Calculates the best scale to fit the page within the container. * Calculates the best scale to fit the page within the container.
* @param {PDFPageProxy} page - The PDF.js page object. * @param {PDFPageProxy} page - The PDF.js page object.
*/ */
function calculateFitScale(page: any) { function calculateFitScale(page: PDFPageProxy) {
const containerWidth = editorState.container.clientWidth; const containerWidth = editorState.container!.clientWidth;
const viewport = page.getViewport({ scale: 1.0 }); const viewport = page.getViewport({ scale: 1.0 });
return containerWidth / viewport.width; return containerWidth / viewport.width;
} }
@@ -54,33 +63,34 @@ function calculateFitScale(page: any) {
* Renders a specific page of the PDF onto the canvas. * Renders a specific page of the PDF onto the canvas.
* @param {number} num The page number to render. * @param {number} num The page number to render.
*/ */
async function renderPage(num: any) { async function renderPage(num: number) {
editorState.pageRendering = true; editorState.pageRendering = true;
showLoader(`Loading page ${num}...`); showLoader(`Loading page ${num}...`);
try { try {
const page = await editorState.pdf.getPage(num); const page = await editorState.pdf!.getPage(num);
if (editorState.scale === 'fit') { if (editorState.scale === 'fit') {
editorState.scale = calculateFitScale(page); editorState.scale = calculateFitScale(page);
} }
const viewport = page.getViewport({ scale: editorState.scale }); const viewport = page.getViewport({ scale: editorState.scale as number });
editorState.canvas.height = viewport.height; editorState.canvas!.height = viewport.height;
editorState.canvas.width = viewport.width; editorState.canvas!.width = viewport.width;
const renderContext = { const renderContext = {
canvasContext: editorState.context, canvas: editorState.canvas,
canvasContext: editorState.context!,
viewport: viewport, viewport: viewport,
}; };
await page.render(renderContext).promise; await page.render(renderContext).promise;
editorState.pageSnapshot = editorState.context.getImageData( editorState.pageSnapshot = editorState.context!.getImageData(
0, 0,
0, 0,
editorState.canvas.width, editorState.canvas!.width,
editorState.canvas.height editorState.canvas!.height
); );
redrawShapes(); redrawShapes();
} catch (error) { } catch (error) {
@@ -90,12 +100,11 @@ async function renderPage(num: any) {
editorState.pageRendering = false; editorState.pageRendering = false;
hideLoader(); hideLoader();
document.getElementById('current-page-display').textContent = num; document.getElementById('current-page-display')!.textContent = String(num);
// @ts-expect-error TS(2339) FIXME: Property 'disabled' does not exist on type 'HTMLEl... Remove this comment to see the full error message (document.getElementById('prev-page') as HTMLButtonElement).disabled =
document.getElementById('prev-page').disabled = num <= 1; num <= 1;
// @ts-expect-error TS(2339) FIXME: Property 'disabled' does not exist on type 'HTMLEl... Remove this comment to see the full error message (document.getElementById('next-page') as HTMLButtonElement).disabled =
document.getElementById('next-page').disabled = num >= editorState.pdf!.numPages;
num >= editorState.pdf.numPages;
if (editorState.pageNumPending !== null) { if (editorState.pageNumPending !== null) {
const pendingPage = editorState.pageNumPending; const pendingPage = editorState.pageNumPending;
@@ -105,7 +114,7 @@ async function renderPage(num: any) {
} }
} }
function queueRenderPage(num: any) { function queueRenderPage(num: number) {
if (editorState.pageRendering) { if (editorState.pageRendering) {
editorState.pageNumPending = num; editorState.pageNumPending = num;
} else { } else {
@@ -116,36 +125,37 @@ function queueRenderPage(num: any) {
function redrawShapes() { function redrawShapes() {
if (editorState.pageSnapshot) { if (editorState.pageSnapshot) {
editorState.context.putImageData(editorState.pageSnapshot, 0, 0); editorState.context!.putImageData(editorState.pageSnapshot, 0, 0);
} }
const currentCropBox = editorState.cropBoxes[editorState.currentPageNum - 1]; const currentCropBox = editorState.cropBoxes[editorState.currentPageNum - 1];
if (currentCropBox) { if (currentCropBox) {
editorState.context.strokeStyle = 'rgba(79, 70, 229, 0.9)'; editorState.context!.strokeStyle = 'rgba(79, 70, 229, 0.9)';
editorState.context.lineWidth = 2; editorState.context!.lineWidth = 2;
editorState.context.setLineDash([8, 4]); editorState.context!.setLineDash([8, 4]);
editorState.context.strokeRect( editorState.context!.strokeRect(
currentCropBox.x, currentCropBox.x,
currentCropBox.y, currentCropBox.y,
currentCropBox.width, currentCropBox.width,
currentCropBox.height currentCropBox.height
); );
editorState.context.setLineDash([]); editorState.context!.setLineDash([]);
} }
} }
function getEventCoordinates(e: any) { function getEventCoordinates(e: MouseEvent | TouchEvent) {
const rect = editorState.canvas.getBoundingClientRect(); const rect = editorState.canvas!.getBoundingClientRect();
const touch = e.touches ? e.touches[0] : e; const touch =
const scaleX = editorState.canvas.width / rect.width; 'touches' in e && e.touches.length > 0 ? e.touches[0] : (e as MouseEvent);
const scaleY = editorState.canvas.height / rect.height; const scaleX = editorState.canvas!.width / rect.width;
const scaleY = editorState.canvas!.height / rect.height;
return { return {
x: (touch.clientX - rect.left) * scaleX, x: (touch.clientX - rect.left) * scaleX,
y: (touch.clientY - rect.top) * scaleY, y: (touch.clientY - rect.top) * scaleY,
}; };
} }
function handleInteractionStart(e: any) { function handleInteractionStart(e: MouseEvent | TouchEvent) {
e.preventDefault(); e.preventDefault();
const coords = getEventCoordinates(e); const coords = getEventCoordinates(e);
editorState.isDrawing = true; editorState.isDrawing = true;
@@ -153,7 +163,7 @@ function handleInteractionStart(e: any) {
editorState.startY = coords.y; editorState.startY = coords.y;
} }
function handleInteractionMove(e: any) { function handleInteractionMove(e: MouseEvent | TouchEvent) {
if (!editorState.isDrawing) return; if (!editorState.isDrawing) return;
e.preventDefault(); e.preventDefault();
@@ -165,13 +175,12 @@ function handleInteractionMove(e: any) {
const width = Math.abs(editorState.startX - coords.x); const width = Math.abs(editorState.startX - coords.x);
const height = Math.abs(editorState.startY - coords.y); const height = Math.abs(editorState.startY - coords.y);
editorState.context.strokeStyle = 'rgba(79, 70, 229, 0.9)'; editorState.context!.strokeStyle = 'rgba(79, 70, 229, 0.9)';
editorState.context.lineWidth = 2; editorState.context!.lineWidth = 2;
editorState.context.setLineDash([8, 4]); editorState.context!.setLineDash([8, 4]);
editorState.context.strokeRect(x, y, width, height); editorState.context!.strokeRect(x, y, width, height);
editorState.context.setLineDash([]); editorState.context!.setLineDash([]);
// Store the last valid rectangle drawn during the move event
editorState.lastInteractionRect = { x, y, width, height }; editorState.lastInteractionRect = { x, y, width, height };
} }
@@ -192,12 +201,14 @@ function handleInteractionEnd() {
scale: editorState.scale, scale: editorState.scale,
}; };
editorState.lastInteractionRect = null; // Reset for the next drawing action editorState.lastInteractionRect = null;
redrawShapes(); redrawShapes();
} }
export async function setupCanvasEditor(toolId: any) { export async function setupCanvasEditor(toolId: string) {
editorState.canvas = document.getElementById('canvas-editor'); editorState.canvas = document.getElementById(
'canvas-editor'
) as HTMLCanvasElement | null;
if (!editorState.canvas) return; if (!editorState.canvas) return;
editorState.container = document.getElementById('canvas-container'); editorState.container = document.getElementById('canvas-container');
editorState.context = editorState.canvas.getContext('2d'); editorState.context = editorState.canvas.getContext('2d');
@@ -210,7 +221,7 @@ export async function setupCanvasEditor(toolId: any) {
editorState.currentPageNum = 1; editorState.currentPageNum = 1;
editorState.scale = 'fit'; editorState.scale = 'fit';
pageNav.textContent = ''; pageNav!.textContent = '';
const prevButton = document.createElement('button'); const prevButton = document.createElement('button');
prevButton.id = 'prev-page'; prevButton.id = 'prev-page';
@@ -237,22 +248,21 @@ export async function setupCanvasEditor(toolId: any) {
'btn p-2 rounded-full bg-gray-700 hover:bg-gray-600 disabled:opacity-50'; 'btn p-2 rounded-full bg-gray-700 hover:bg-gray-600 disabled:opacity-50';
nextButton.innerHTML = '<i data-lucide="chevron-right"></i>'; nextButton.innerHTML = '<i data-lucide="chevron-right"></i>';
pageNav.append(prevButton, pageInfo, nextButton); pageNav!.append(prevButton, pageInfo, nextButton);
createIcons({ icons }); createIcons({ icons });
document.getElementById('prev-page').addEventListener('click', () => { document.getElementById('prev-page')!.addEventListener('click', () => {
if (editorState.currentPageNum > 1) if (editorState.currentPageNum > 1)
queueRenderPage(editorState.currentPageNum - 1); queueRenderPage(editorState.currentPageNum - 1);
}); });
document.getElementById('next-page').addEventListener('click', () => { document.getElementById('next-page')!.addEventListener('click', () => {
if (editorState.currentPageNum < editorState.pdf.numPages) if (editorState.currentPageNum < editorState.pdf!.numPages)
queueRenderPage(editorState.currentPageNum + 1); queueRenderPage(editorState.currentPageNum + 1);
}); });
// To prevent stacking multiple listeners, we replace the canvas element with a clone const newCanvas = editorState.canvas.cloneNode(true) as HTMLCanvasElement;
const newCanvas = editorState.canvas.cloneNode(true); editorState.canvas.parentNode!.replaceChild(newCanvas, editorState.canvas);
editorState.canvas.parentNode.replaceChild(newCanvas, editorState.canvas);
editorState.canvas = newCanvas; editorState.canvas = newCanvas;
editorState.context = newCanvas.getContext('2d'); editorState.context = newCanvas.getContext('2d');
@@ -272,33 +282,36 @@ export async function setupCanvasEditor(toolId: any) {
editorState.canvas.addEventListener('touchend', handleInteractionEnd); editorState.canvas.addEventListener('touchend', handleInteractionEnd);
if (toolId === 'crop') { if (toolId === 'crop') {
document.getElementById('zoom-in-btn').onclick = () => { (document.getElementById('zoom-in-btn') as HTMLElement).onclick = () => {
if (typeof editorState.scale === 'number') { if (typeof editorState.scale === 'number') {
editorState.scale += 0.25; editorState.scale += 0.25;
} }
renderPage(editorState.currentPageNum); renderPage(editorState.currentPageNum);
}; };
document.getElementById('zoom-out-btn').onclick = () => { (document.getElementById('zoom-out-btn') as HTMLElement).onclick = () => {
if (typeof editorState.scale === 'number' && editorState.scale > 0.25) { if (typeof editorState.scale === 'number' && editorState.scale > 0.25) {
editorState.scale -= 0.25; editorState.scale -= 0.25;
renderPage(editorState.currentPageNum); renderPage(editorState.currentPageNum);
} }
}; };
document.getElementById('fit-page-btn').onclick = async () => { (document.getElementById('fit-page-btn') as HTMLElement).onclick =
const page = await editorState.pdf.getPage(editorState.currentPageNum); async () => {
const page = await editorState.pdf!.getPage(editorState.currentPageNum);
editorState.scale = calculateFitScale(page); editorState.scale = calculateFitScale(page);
renderPage(editorState.currentPageNum); renderPage(editorState.currentPageNum);
}; };
document.getElementById('clear-crop-btn').onclick = () => { (document.getElementById('clear-crop-btn') as HTMLElement).onclick = () => {
delete editorState.cropBoxes[editorState.currentPageNum - 1]; delete editorState.cropBoxes[editorState.currentPageNum - 1];
redrawShapes(); redrawShapes();
}; };
document.getElementById('clear-all-crops-btn').onclick = () => { (document.getElementById('clear-all-crops-btn') as HTMLElement).onclick =
() => {
editorState.cropBoxes = {}; editorState.cropBoxes = {};
redrawShapes(); redrawShapes();
}; };
document.getElementById('process-btn').onclick = async () => { (document.getElementById('process-btn') as HTMLElement).onclick =
async () => {
if (Object.keys(editorState.cropBoxes).length === 0) { if (Object.keys(editorState.cropBoxes).length === 0) {
showAlert( showAlert(
'No Area Selected', 'No Area Selected',
@@ -306,9 +319,11 @@ export async function setupCanvasEditor(toolId: any) {
); );
return; return;
} }
const success = await toolLogic['crop-pdf'].process( const cropLogic = toolLogic['crop-pdf'];
editorState.cropBoxes const success =
); typeof cropLogic === 'object' && cropLogic.process
? await cropLogic.process(editorState.cropBoxes)
: false;
if (success) { if (success) {
showAlert( showAlert(
'Success!', 'Success!',

View File

@@ -4,7 +4,6 @@ import type {
CompareTextItem, CompareTextItem,
ComparePageSignature, ComparePageSignature,
ComparePagePair, ComparePagePair,
ComparePageResult,
CompareChangeSummary, CompareChangeSummary,
CompareTextChange, CompareTextChange,
} from '../types.ts'; } from '../types.ts';
@@ -44,8 +43,6 @@ interface ErrorResult {
message: string; message: string;
} }
type WorkerResult = DiffResult | PairResult | ErrorResult;
self.onmessage = function (e: MessageEvent<WorkerMessage>) { self.onmessage = function (e: MessageEvent<WorkerMessage>) {
const msg = e.data; const msg = e.data;
try { try {

View File

@@ -58,13 +58,13 @@ async function handleSinglePdfUpload(toolId: string, file: File) {
const processBtn = document.getElementById('process-btn'); const processBtn = document.getElementById('process-btn');
if (processBtn) { if (processBtn) {
const logic = toolLogic[toolId]; const logic = toolLogic[toolId];
if (logic && logic.process) { if (logic && typeof logic === 'object' && logic.process) {
processBtn.onclick = logic.process; processBtn.onclick = logic.process;
} }
} }
const logic = toolLogic[toolId]; const logic = toolLogic[toolId];
if (logic && logic.setup) { if (logic && typeof logic === 'object' && logic.setup) {
await logic.setup(); await logic.setup();
} }
return; return;
@@ -102,8 +102,12 @@ async function handleSinglePdfUpload(toolId: string, file: File) {
const logic = toolLogic[toolId]; const logic = toolLogic[toolId];
if (logic) { if (logic) {
const func = const func =
typeof logic.process === 'function' ? logic.process : logic; typeof logic === 'object' && typeof logic.process === 'function'
processBtn.onclick = func; ? logic.process
: typeof logic === 'function'
? logic
: null;
if (func) processBtn.onclick = func;
} }
} }
@@ -281,13 +285,17 @@ async function handleSinglePdfUpload(toolId: string, file: File) {
const infoSection = createSection('Info Dictionary'); const infoSection = createSection('Info Dictionary');
if (info && Object.keys(info).length > 0) { if (info && Object.keys(info).length > 0) {
for (const key in info) { for (const key in info) {
const value = info[key]; const value = (info as Record<string, unknown>)[key];
let displayValue; let displayValue: string;
if (value === null || typeof value === 'undefined') { if (value === null || typeof value === 'undefined') {
displayValue = '- Not Set -'; displayValue = '- Not Set -';
} else if (typeof value === 'object' && value.name) { } else if (
displayValue = value.name; typeof value === 'object' &&
'name' in (value as object) &&
(value as Record<string, unknown>).name
) {
displayValue = String((value as Record<string, unknown>).name);
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
try { try {
displayValue = JSON.stringify(value); displayValue = JSON.stringify(value);
@@ -524,7 +532,8 @@ async function handleSinglePdfUpload(toolId: string, file: File) {
} }
if (toolId === 'page-dimensions') { if (toolId === 'page-dimensions') {
toolLogic['page-dimensions'](); const pageDimLogic = toolLogic['page-dimensions'];
if (typeof pageDimLogic === 'function') pageDimLogic();
} }
if (toolId === 'pdf-to-jpg') { if (toolId === 'pdf-to-jpg') {
@@ -569,8 +578,13 @@ async function handleSinglePdfUpload(toolId: string, file: File) {
} }
} }
if (toolLogic[toolId] && typeof toolLogic[toolId].setup === 'function') { const setupLogic = toolLogic[toolId];
toolLogic[toolId].setup(); if (
setupLogic &&
typeof setupLogic === 'object' &&
typeof setupLogic.setup === 'function'
) {
setupLogic.setup();
} }
} catch (e) { } catch (e) {
hideLoader(); hideLoader();
@@ -656,13 +670,19 @@ async function handleMultiFileUpload(toolId: string) {
(processBtn as HTMLButtonElement).disabled = false; (processBtn as HTMLButtonElement).disabled = false;
const logic = toolLogic[toolId]; const logic = toolLogic[toolId];
if (logic) { if (logic) {
const func = typeof logic.process === 'function' ? logic.process : logic; const func =
processBtn.onclick = func; typeof logic === 'object' && typeof logic.process === 'function'
? logic.process
: typeof logic === 'function'
? logic
: null;
if (func) processBtn.onclick = func;
} }
} }
if (toolId === 'alternate-merge') { if (toolId === 'alternate-merge') {
toolLogic['alternate-merge'].setup(); const altMerge = toolLogic['alternate-merge'];
if (typeof altMerge === 'object' && altMerge.setup) altMerge.setup();
} else if (toolId === 'image-to-pdf') { } else if (toolId === 'image-to-pdf') {
const imageList = document.getElementById('image-list'); const imageList = document.getElementById('image-list');
@@ -869,9 +889,8 @@ export function setupFileInputHandler(toolId: string) {
processBtn.onclick = () => { processBtn.onclick = () => {
const logic = toolLogic[toolId]; const logic = toolLogic[toolId];
if (logic) { if (logic) {
const func = if (typeof logic === 'function') logic();
typeof logic.process === 'function' ? logic.process : logic; else if (logic.process) logic.process();
func();
} }
}; };
} }
@@ -893,9 +912,8 @@ export function setupFileInputHandler(toolId: string) {
processBtn.onclick = () => { processBtn.onclick = () => {
const logic = toolLogic[toolId]; const logic = toolLogic[toolId];
if (logic) { if (logic) {
const func = if (typeof logic === 'function') logic();
typeof logic.process === 'function' ? logic.process : logic; else if (logic.process) logic.process();
func();
} }
}; };
} }

View File

@@ -3,6 +3,7 @@ import type {
AddPageLabelsState, AddPageLabelsState,
LabelRule, LabelRule,
PageLabelStyleName, PageLabelStyleName,
CpdfInstance,
} from '@/types'; } from '@/types';
import { showAlert, showLoader, hideLoader } from '../ui.js'; import { showAlert, showLoader, hideLoader } from '../ui.js';
import { t } from '../i18n/index.js'; import { t } from '../i18n/index.js';
@@ -20,30 +21,6 @@ import {
} from '../utils/page-labels.js'; } from '../utils/page-labels.js';
import { loadPdfDocument } from '../utils/load-pdf-document.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; let labelRuleCounter = 0;
const translate = ( const translate = (
@@ -464,8 +441,8 @@ async function addPageLabels() {
) as HTMLInputElement | null ) as HTMLInputElement | null
)?.checked ?? true; )?.checked ?? true;
let cpdf: AddPageLabelsCpdf | null = null; let cpdf: CpdfInstance | null = null;
let pdf: CoherentPdf | null = null; let pdf: unknown = null;
try { try {
cpdf = await getCpdf(); cpdf = await getCpdf();
@@ -482,7 +459,7 @@ async function addPageLabels() {
const rule = pageState.rules[index]; const rule = pageState.rules[index];
const trimmedRange = rule.pageRange.trim(); const trimmedRange = rule.pageRange.trim();
let range: CpdfPageRange; let range: unknown;
try { try {
range = trimmedRange range = trimmedRange
? cpdf.parsePagespec(pdf, trimmedRange) ? cpdf.parsePagespec(pdf, trimmedRange)

View File

@@ -9,7 +9,6 @@ import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
let selectedFile: File | null = null; let selectedFile: File | null = null;
let viewerIframe: HTMLIFrameElement | null = null; let viewerIframe: HTMLIFrameElement | null = null;
let viewerReady = false;
let currentBlobUrl: string | null = null; let currentBlobUrl: string | null = null;
const pdfInput = document.getElementById('pdfFile') as HTMLInputElement; const pdfInput = document.getElementById('pdfFile') as HTMLInputElement;
@@ -47,7 +46,7 @@ function resetState() {
viewerContainer.removeChild(viewerIframe); viewerContainer.removeChild(viewerIframe);
} }
viewerIframe = null; viewerIframe = null;
viewerReady = false;
if (viewerCard) viewerCard.classList.add('hidden'); if (viewerCard) viewerCard.classList.add('hidden');
if (saveStampedBtn) saveStampedBtn.classList.add('hidden'); if (saveStampedBtn) saveStampedBtn.classList.add('hidden');
@@ -157,7 +156,6 @@ async function loadPdfInViewer(file: File) {
currentBlobUrl = null; currentBlobUrl = null;
} }
viewerIframe = null; viewerIframe = null;
viewerReady = false;
// Calculate and apply dynamic height // Calculate and apply dynamic height
await adjustViewerHeight(file); await adjustViewerHeight(file);
@@ -169,7 +167,7 @@ async function loadPdfInViewer(file: File) {
try { try {
const existingPrefsRaw = localStorage.getItem('pdfjs.preferences'); const existingPrefsRaw = localStorage.getItem('pdfjs.preferences');
const existingPrefs = existingPrefsRaw ? JSON.parse(existingPrefsRaw) : {}; const existingPrefs = existingPrefsRaw ? JSON.parse(existingPrefsRaw) : {};
delete (existingPrefs as any).annotationEditorMode; delete (existingPrefs as Record<string, unknown>).annotationEditorMode;
const newPrefs = { const newPrefs = {
...existingPrefs, ...existingPrefs,
enablePermissions: false, enablePermissions: false,
@@ -204,7 +202,14 @@ async function loadPdfInViewer(file: File) {
function setupAnnotationViewer(iframe: HTMLIFrameElement) { function setupAnnotationViewer(iframe: HTMLIFrameElement) {
try { 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; const doc = win?.document as Document | null;
if (!win || !doc) return; if (!win || !doc) return;
@@ -238,8 +243,6 @@ function setupAnnotationViewer(iframe: HTMLIFrameElement) {
if (root) { if (root) {
root.classList.add('PdfjsAnnotationExtension_Comment_hidden'); root.classList.add('PdfjsAnnotationExtension_Comment_hidden');
} }
viewerReady = true;
} catch (e) { } catch (e) {
console.error( console.error(
'Failed to initialize annotation viewer for Add Stamps:', 'Failed to initialize annotation viewer for Add Stamps:',
@@ -304,8 +307,14 @@ if (saveStampedBtn) {
} }
try { try {
const win = viewerIframe.contentWindow as any; const win = viewerIframe.contentWindow as
const extensionInstance = win?.pdfjsAnnotationExtensionInstance as any; | (Window & {
pdfjsAnnotationExtensionInstance?: {
exportPdf?: () => Promise<void>;
};
})
| null;
const extensionInstance = win?.pdfjsAnnotationExtensionInstance;
if ( if (
extensionInstance && extensionInstance &&

View File

@@ -1013,9 +1013,12 @@ async function applyWatermark() {
'watermarked.pdf' 'watermarked.pdf'
); );
showAlert('Success', 'Watermark added successfully!', 'success'); showAlert('Success', 'Watermark added successfully!', 'success');
} catch (e: any) { } catch (e: unknown) {
console.error(e); 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 { } finally {
hideLoader(); hideLoader();
} }

View File

@@ -7,7 +7,6 @@ import '../../css/bookmark.css';
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'; import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
import { import {
truncateFilename, truncateFilename,
getPDFDocument,
formatBytes, formatBytes,
downloadFile, downloadFile,
escapeHtml, escapeHtml,
@@ -46,7 +45,6 @@ let isPickingDestination = false;
let currentPickingCallback: DestinationCallback | null = null; let currentPickingCallback: DestinationCallback | null = null;
let destinationMarker: HTMLDivElement | null = null; let destinationMarker: HTMLDivElement | null = null;
let savedModalOverlay: HTMLDivElement | null = null; let savedModalOverlay: HTMLDivElement | null = null;
let savedModal: HTMLDivElement | null = null;
let currentViewport: PageViewport | null = null; let currentViewport: PageViewport | null = null;
let currentZoom = 1.0; let currentZoom = 1.0;
const fileInput = document.getElementById( 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) { if (pickDestBtn) {
pickDestBtn.addEventListener('click', () => { pickDestBtn.addEventListener('click', () => {
savedModalOverlay = overlay; savedModalOverlay = overlay;
savedModal = modal;
overlay.style.display = 'none'; overlay.style.display = 'none';
startDestinationPicking((page: number, pdfX: number, pdfY: number) => { startDestinationPicking((page: number, pdfX: number, pdfY: number) => {
@@ -699,7 +696,6 @@ function cancelDestinationPicking(): void {
if (savedModalOverlay) { if (savedModalOverlay) {
savedModalOverlay.style.display = ''; savedModalOverlay.style.display = '';
savedModalOverlay = null; savedModalOverlay = null;
savedModal = null;
} }
} }
@@ -1317,7 +1313,7 @@ jsonInput?.addEventListener('change', async (e: Event) => {
'JSON Loaded', 'JSON Loaded',
'Loaded bookmarks from JSON. Now upload your PDF.' 'Loaded bookmarks from JSON. Now upload your PDF.'
); );
} catch (err) { } catch {
await showAlertModal('Error', 'Invalid JSON format'); await showAlertModal('Error', 'Invalid JSON format');
} }
}); });
@@ -2018,7 +2014,7 @@ jsonImportHidden?.addEventListener('change', async (e: Event) => {
saveState(); saveState();
renderBookmarkTree(); renderBookmarkTree();
await showAlertModal('Success', 'Bookmarks imported from JSON!'); await showAlertModal('Success', 'Bookmarks imported from JSON!');
} catch (err) { } catch {
await showAlertModal('Error', 'Invalid JSON format'); await showAlertModal('Error', 'Invalid JSON format');
} }

View File

@@ -2,9 +2,8 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; import type { PyMuPDFInstance } from '@/types';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import JSZip from 'jszip'; import JSZip from 'jszip';
import { PDFDocument } from 'pdf-lib'; import { PDFDocument } from 'pdf-lib';
@@ -176,8 +175,8 @@ async function convertCbzToPdf(file: File): Promise<Blob> {
} }
async function convertCbrToPdf(file: File): Promise<Blob> { async function convertCbrToPdf(file: File): Promise<Blob> {
const pymupdf = await loadPyMuPDF(); const pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
return await (pymupdf as any).convertToPdf(file, { filetype: 'cbz' }); return await pymupdf.convertToPdf(file, { filetype: 'cbz' });
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
@@ -314,12 +313,12 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e); console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -6,7 +6,7 @@ import {
readFileAsArrayBuffer, readFileAsArrayBuffer,
} from '../utils/helpers.js'; } from '../utils/helpers.js';
import { icons, createIcons } from 'lucide'; import { icons, createIcons } from 'lucide';
import { ChangePermissionsState } from '@/types'; import { ChangePermissionsState, QpdfInstanceExtended } from '@/types';
const pageState: ChangePermissionsState = { const pageState: ChangePermissionsState = {
file: null, file: null,
@@ -114,7 +114,7 @@ async function changePermissions() {
const inputPath = '/input.pdf'; const inputPath = '/input.pdf';
const outputPath = '/output.pdf'; const outputPath = '/output.pdf';
let qpdf: any; let qpdf: QpdfInstanceExtended;
const loaderModal = document.getElementById('loader-modal'); const loaderModal = document.getElementById('loader-modal');
const loaderText = document.getElementById('loader-text'); const loaderText = document.getElementById('loader-text');
@@ -187,10 +187,10 @@ async function changePermissions() {
args.push('--', outputPath); args.push('--', outputPath);
try { try {
qpdf.callMain(args); qpdf.callMain(args);
} catch (qpdfError: any) { } catch (qpdfError: unknown) {
console.error('qpdf execution error:', qpdfError); console.error('qpdf execution error:', qpdfError);
const errorMsg = qpdfError.message || ''; const errorMsg = qpdfError instanceof Error ? qpdfError.message : '';
if ( if (
errorMsg.includes('invalid password') || errorMsg.includes('invalid password') ||
@@ -219,7 +219,9 @@ async function changePermissions() {
throw new Error('Processing resulted in an empty file.'); 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}`); downloadFile(blob, `permissions-changed-${pageState.file.name}`);
if (loaderModal) loaderModal.classList.add('hidden'); if (loaderModal) loaderModal.classList.add('hidden');
@@ -233,16 +235,17 @@ async function changePermissions() {
showAlert('Success', successMessage, 'success', () => { showAlert('Success', successMessage, 'success', () => {
resetState(); resetState();
}); });
} catch (error: any) { } catch (error: unknown) {
console.error('Error during PDF permission change:', error); console.error('Error during PDF permission change:', error);
if (loaderModal) loaderModal.classList.add('hidden'); if (loaderModal) loaderModal.classList.add('hidden');
if (error.message === 'INVALID_PASSWORD') { const errorMessage = error instanceof Error ? error.message : '';
if (errorMessage === 'INVALID_PASSWORD') {
showAlert( showAlert(
'Incorrect Password', 'Incorrect Password',
'The current password you entered is incorrect. Please try again.' 'The current password you entered is incorrect. Please try again.'
); );
} else if (error.message === 'PASSWORD_REQUIRED') { } else if (errorMessage === 'PASSWORD_REQUIRED') {
showAlert( showAlert(
'Password Required', 'Password Required',
'This PDF is password-protected. Please enter the current password to proceed.' 'This PDF is password-protected. Please enter the current password to proceed.'
@@ -250,7 +253,7 @@ async function changePermissions() {
} else { } else {
showAlert( showAlert(
'Processing Failed', '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 { } finally {

View File

@@ -85,7 +85,10 @@ async function performCondenseCompression(
scrub: { scrub: {
metadata: customSettings?.removeMetadata ?? preset.scrub.metadata, metadata: customSettings?.removeMetadata ?? preset.scrub.metadata,
thumbnails: customSettings?.removeThumbnails ?? preset.scrub.thumbnails, 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, subsetFonts: customSettings?.subsetFonts ?? preset.subsetFonts,
save: { save: {
@@ -99,8 +102,8 @@ async function performCondenseCompression(
try { try {
const result = await pymupdf.compressPdf(fileBlob, options); const result = await pymupdf.compressPdf(fileBlob, options);
return result; return result;
} catch (error: any) { } catch (error: unknown) {
const errorMessage = error?.message || String(error); const errorMessage = error instanceof Error ? error.message : String(error);
if ( if (
errorMessage.includes('PatternType') || errorMessage.includes('PatternType') ||
errorMessage.includes('pattern') errorMessage.includes('pattern')
@@ -432,7 +435,7 @@ document.addEventListener('DOMContentLoaded', () => {
usedMethod = 'Condense'; usedMethod = 'Condense';
// Check if fallback was used // Check if fallback was used
if ((result as any).usedFallback) { if ((result as { usedFallback?: boolean }).usedFallback) {
usedMethod += usedMethod +=
' (without image optimization due to unsupported patterns)'; ' (without image optimization due to unsupported patterns)';
} }
@@ -551,12 +554,12 @@ document.addEventListener('DOMContentLoaded', () => {
); );
} }
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
console.error('[CompressPDF] Error:', e); console.error('[CompressPDF] Error:', e);
showAlert( showAlert(
'Error', 'Error',
`An error occurred during compression. Error: ${e.message}` `An error occurred during compression. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -5,7 +5,7 @@ import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
import Cropper from 'cropperjs'; import Cropper from 'cropperjs';
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';
import { PDFDocument as PDFLibDocument } from 'pdf-lib'; import { PDFDocument as PDFLibDocument } from 'pdf-lib';
import { CropperState } from '@/types'; import { CropperState, CropPercentages } from '@/types';
import { loadPdfDocument } from '../utils/load-pdf-document.js'; import { loadPdfDocument } from '../utils/load-pdf-document.js';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
@@ -167,7 +167,11 @@ async function displayPageAsImage(num: number) {
const tempCtx = tempCanvas.getContext('2d'); const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = viewport.width; tempCanvas.width = viewport.width;
tempCanvas.height = viewport.height; 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(); if (cropperState.cropper) cropperState.cropper.destroy();
@@ -251,7 +255,7 @@ async function performCrop() {
document.getElementById('apply-to-all-toggle') as HTMLInputElement document.getElementById('apply-to-all-toggle') as HTMLInputElement
)?.checked; )?.checked;
let finalCropData: Record<number, any> = {}; let finalCropData: Record<number, CropPercentages> = {};
if (isApplyToAll) { if (isApplyToAll) {
const currentCrop = cropperState.pageCrops[cropperState.currentPageNum]; const currentCrop = cropperState.pageCrops[cropperState.currentPageNum];
@@ -286,7 +290,7 @@ async function performCrop() {
const fileName = isDestructive ? 'flattened_crop.pdf' : 'standard_crop.pdf'; const fileName = isDestructive ? 'flattened_crop.pdf' : 'standard_crop.pdf';
downloadFile( downloadFile(
new Blob([finalPdfBytes], { type: 'application/pdf' }), new Blob([new Uint8Array(finalPdfBytes)], { type: 'application/pdf' }),
fileName fileName
); );
showAlert( showAlert(
@@ -304,7 +308,7 @@ async function performCrop() {
} }
async function performMetadataCrop( async function performMetadataCrop(
cropData: Record<number, any> cropData: Record<number, CropPercentages>
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const pdfToModify = await loadPdfDocument(cropperState.originalPdfBytes!); const pdfToModify = await loadPdfDocument(cropperState.originalPdfBytes!);
@@ -344,7 +348,7 @@ async function performMetadataCrop(
} }
async function performFlatteningCrop( async function performFlatteningCrop(
cropData: Record<number, any> cropData: Record<number, CropPercentages>
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const newPdfDoc = await PDFLibDocument.create(); const newPdfDoc = await PDFLibDocument.create();
const sourcePdfDocForCopying = await loadPdfDocument( const sourcePdfDocForCopying = await loadPdfDocument(
@@ -364,7 +368,11 @@ async function performFlatteningCrop(
const tempCtx = tempCanvas.getContext('2d'); const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = viewport.width; tempCanvas.width = viewport.width;
tempCanvas.height = viewport.height; 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 finalCanvas = document.createElement('canvas');
const finalCtx = finalCanvas.getContext('2d'); const finalCtx = finalCanvas.getContext('2d');

View File

@@ -16,7 +16,17 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
).toString(); ).toString();
// --- Global State for the Cropper Tool --- // --- 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, pdfDoc: null,
currentPageNum: 1, currentPageNum: 1,
cropper: null, cropper: null,
@@ -46,7 +56,8 @@ function saveCurrentCrop() {
* Renders a PDF page to the Cropper UI as an image. * Renders a PDF page to the Cropper UI as an image.
* @param {number} num The page number to render. * @param {number} num The page number to render.
*/ */
async function displayPageAsImage(num: any) {
async function displayPageAsImage(num: number) {
showLoader(`Rendering Page ${num}...`); showLoader(`Rendering Page ${num}...`);
try { try {
@@ -57,7 +68,11 @@ async function displayPageAsImage(num: any) {
const tempCtx = tempCanvas.getContext('2d'); const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = viewport.width; tempCanvas.width = viewport.width;
tempCanvas.height = viewport.height; 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) { if (cropperState.cropper) {
cropperState.cropper.destroy(); cropperState.cropper.destroy();
@@ -107,7 +122,7 @@ async function displayPageAsImage(num: any) {
* Handles page navigation. * Handles page navigation.
* @param {number} offset -1 for previous, 1 for next. * @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 // Save the current page's crop before changing
saveCurrentCrop(); saveCurrentCrop();
@@ -137,7 +152,13 @@ function enableControls() {
/** /**
* Performs a non-destructive crop by updating the page's crop box. * 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) { for (const pageNum in cropData) {
const pdfJsPage = await cropperState.pdfDoc.getPage(Number(pageNum)); const pdfJsPage = await cropperState.pdfDoc.getPage(Number(pageNum));
const viewport = pdfJsPage.getViewport({ scale: 1 }); 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. * 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(); const newPdfDoc = await PDFLibDocument.create();
// Load the original PDF with pdf-lib to copy un-cropped pages from // 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'); const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = viewport.width; tempCanvas.width = viewport.width;
tempCanvas.height = viewport.height; 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 finalCanvas = document.createElement('canvas');
const finalCtx = finalCanvas.getContext('2d'); const finalCtx = finalCanvas.getContext('2d');
@@ -264,7 +294,7 @@ export async function setupCropperTool() {
cropperState.pageCrops = {}; cropperState.pageCrops = {};
const arrayBuffer = await readFileAsArrayBuffer(state.files[0]); 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 arrayBufferForPdfJs = (arrayBuffer as ArrayBuffer).slice(0);
const loadingTask = getPDFDocument({ data: arrayBufferForPdfJs }); const loadingTask = getPDFDocument({ data: arrayBufferForPdfJs });
@@ -295,7 +325,7 @@ export async function setupCropperTool() {
document.getElementById('apply-to-all-toggle') as HTMLInputElement document.getElementById('apply-to-all-toggle') as HTMLInputElement
).checked; ).checked;
let finalCropData = {}; let finalCropData: Record<number, CropPercentages> = {};
if (isApplyToAll) { if (isApplyToAll) {
const currentCrop = cropperState.pageCrops[cropperState.currentPageNum]; const currentCrop = cropperState.pageCrops[cropperState.currentPageNum];
if (!currentCrop) { if (!currentCrop) {
@@ -308,10 +338,13 @@ export async function setupCropperTool() {
} }
} else { } else {
// If not applying to all, only process pages with saved crops // If not applying to all, only process pages with saved crops
finalCropData = Object.keys(cropperState.pageCrops).reduce((obj, key) => { finalCropData = Object.keys(cropperState.pageCrops).reduce(
obj[key] = cropperState.pageCrops[key]; (obj, key) => {
obj[Number(key)] = cropperState.pageCrops[Number(key)];
return obj; return obj;
}); },
{} as Record<number, CropPercentages>
);
} }
if (Object.keys(finalCropData).length === 0) { if (Object.keys(finalCropData).length === 0) {
@@ -341,7 +374,7 @@ export async function setupCropperTool() {
? 'flattened_crop.pdf' ? 'flattened_crop.pdf'
: 'standard_crop.pdf'; : 'standard_crop.pdf';
downloadFile( downloadFile(
new Blob([finalPdfBytes], { type: 'application/pdf' }), new Blob([new Uint8Array(finalPdfBytes)], { type: 'application/pdf' }),
fileName fileName
); );
showAlert('Success', 'Crop complete! Your download has started.'); showAlert('Success', 'Crop complete! Your download has started.');

View File

@@ -1,9 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { import { downloadFile, formatBytes } from '../utils/helpers.js';
downloadFile,
readFileAsArrayBuffer,
formatBytes,
} from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
@@ -36,13 +32,15 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) { for (let index = 0; index < state.files.length; index++) {
const file = state.files[index]; const file = state.files[index];
const fileDiv = document.createElement('div'); const fileDiv = document.createElement('div');
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; fileDiv.className =
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
const infoContainer = document.createElement('div'); const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden'; infoContainer.className = 'flex flex-col overflow-hidden';
const nameSpan = document.createElement('div'); const nameSpan = document.createElement('div');
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; nameSpan.className =
'truncate font-medium text-gray-200 text-sm mb-1';
nameSpan.textContent = file.name; nameSpan.textContent = file.name;
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
@@ -52,7 +50,8 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan); infoContainer.append(nameSpan, metaSpan);
const removeBtn = document.createElement('button'); const removeBtn = document.createElement('button');
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; 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.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
removeBtn.onclick = () => { removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index); state.files = state.files.filter((_, i) => i !== index);
@@ -95,16 +94,26 @@ document.addEventListener('DOMContentLoaded', () => {
if (state.files.length === 1) { if (state.files.length === 1) {
const originalFile = state.files[0]; const originalFile = state.files[0];
console.log('[CSV2PDF] Converting single file:', originalFile.name, 'Size:', originalFile.size, 'bytes'); console.log(
'[CSV2PDF] Converting single file:',
originalFile.name,
'Size:',
originalFile.size,
'bytes'
);
const pdfBlob = await convertCsvToPdf(originalFile, { const pdfBlob = await convertCsvToPdf(originalFile, {
onProgress: (percent, message) => { onProgress: (percent, message) => {
console.log(`[CSV2PDF] Progress: ${percent}% - ${message}`); console.log(`[CSV2PDF] Progress: ${percent}% - ${message}`);
showLoader(message, percent); showLoader(message, percent);
} },
}); });
console.log('[CSV2PDF] Conversion complete! PDF size:', pdfBlob.size, 'bytes'); console.log(
'[CSV2PDF] Conversion complete! PDF size:',
pdfBlob.size,
'bytes'
);
const fileName = originalFile.name.replace(/\.csv$/i, '') + '.pdf'; const fileName = originalFile.name.replace(/\.csv$/i, '') + '.pdf';
downloadFile(pdfBlob, fileName); downloadFile(pdfBlob, fileName);
@@ -126,16 +135,26 @@ document.addEventListener('DOMContentLoaded', () => {
for (let i = 0; i < state.files.length; i++) { for (let i = 0; i < state.files.length; i++) {
const file = state.files[i]; const file = state.files[i];
console.log(`[CSV2PDF] Converting file ${i + 1}/${state.files.length}:`, file.name); console.log(
`[CSV2PDF] Converting file ${i + 1}/${state.files.length}:`,
file.name
);
const pdfBlob = await convertCsvToPdf(file, { const pdfBlob = await convertCsvToPdf(file, {
onProgress: (percent, message) => { onProgress: (percent) => {
const overallPercent = ((i / state.files.length) * 100) + (percent / state.files.length); const overallPercent =
showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`, 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); console.log(
`[CSV2PDF] Converted ${file.name}, PDF size:`,
pdfBlob.size
);
const baseName = file.name.replace(/\.csv$/i, ''); const baseName = file.name.replace(/\.csv$/i, '');
const pdfBuffer = await pdfBlob.arrayBuffer(); const pdfBuffer = await pdfBlob.arrayBuffer();
@@ -158,13 +177,16 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
console.error('[CSV2PDF] ERROR:', e); console.error('[CSV2PDF] ERROR:', e);
console.error('[CSV2PDF] Error stack:', e.stack); console.error(
'[CSV2PDF] Error stack:',
e instanceof Error ? e.stack : ''
);
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };
@@ -196,10 +218,12 @@ document.addEventListener('DOMContentLoaded', () => {
dropZone.classList.remove('bg-gray-700'); dropZone.classList.remove('bg-gray-700');
const files = e.dataTransfer?.files; const files = e.dataTransfer?.files;
if (files && files.length > 0) { if (files && files.length > 0) {
const csvFiles = Array.from(files).filter(f => f.name.toLowerCase().endsWith('.csv') || f.type === 'text/csv'); const csvFiles = Array.from(files).filter(
(f) => f.name.toLowerCase().endsWith('.csv') || f.type === 'text/csv'
);
if (csvFiles.length > 0) { if (csvFiles.length > 0) {
const dataTransfer = new DataTransfer(); const dataTransfer = new DataTransfer();
csvFiles.forEach(f => dataTransfer.items.add(f)); csvFiles.forEach((f) => dataTransfer.items.add(f));
handleFileSelect(dataTransfer.files); handleFileSelect(dataTransfer.files);
} }
} }

View File

@@ -170,7 +170,7 @@ async function decryptPdf() {
zip.file(`unlocked-${file.name}`, decryptedBytes, { binary: true }); zip.file(`unlocked-${file.name}`, decryptedBytes, { binary: true });
successCount++; successCount++;
} catch (fileError: any) { } catch (fileError: unknown) {
errorCount++; errorCount++;
console.error(`Failed to decrypt ${file.name}:`, fileError); console.error(`Failed to decrypt ${file.name}:`, fileError);
} }
@@ -194,15 +194,16 @@ async function decryptPdf() {
resetState(); resetState();
}); });
} }
} catch (error: any) { } catch (error: unknown) {
console.error('Error during PDF decryption:', error); console.error('Error during PDF decryption:', error);
if (error.message === 'INVALID_PASSWORD') { const errorMessage = error instanceof Error ? error.message : '';
if (errorMessage === 'INVALID_PASSWORD') {
showAlert( showAlert(
'Incorrect Password', 'Incorrect Password',
'The password you entered is incorrect. Please try again.' 'The password you entered is incorrect. Please try again.'
); );
} else if (error.message?.includes('password')) { } else if (errorMessage.includes('password')) {
showAlert( showAlert(
'Password Error', 'Password Error',
'Unable to decrypt the PDF with the provided password.' 'Unable to decrypt the PDF with the provided password.'
@@ -210,7 +211,7 @@ async function decryptPdf() {
} else { } else {
showAlert( showAlert(
'Decryption Failed', '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 { } finally {

View File

@@ -164,7 +164,7 @@ async function renderThumbnails() {
canvas.width = viewport.width; canvas.width = viewport.width;
canvas.height = viewport.height; canvas.height = viewport.height;
const ctx = canvas.getContext('2d'); 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'); const wrapper = document.createElement('div');
wrapper.className = 'relative cursor-pointer group'; wrapper.className = 'relative cursor-pointer group';

View File

@@ -1,9 +1,10 @@
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; import type { PyMuPDFInstance } from '@/types';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import { batchDecryptIfNeeded } from '../utils/password-prompt.js'; import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { downloadFile } from '../utils/helpers'; import { downloadFile } from '../utils/helpers';
import { isWasmAvailable } from '../config/wasm-cdn-config.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
interface DeskewResult { interface DeskewResult {
totalPages: number; totalPages: number;
@@ -13,11 +14,11 @@ interface DeskewResult {
} }
let selectedFiles: File[] = []; let selectedFiles: File[] = [];
let pymupdf: any = null; let pymupdf: PyMuPDFInstance | null = null;
async function initPyMuPDF(): Promise<any> { async function initPyMuPDF(): Promise<PyMuPDFInstance> {
if (!pymupdf) { if (!pymupdf) {
pymupdf = await loadPyMuPDF(); pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
} }
return pymupdf; return pymupdf;
} }

View File

@@ -400,7 +400,7 @@ async function handleCertFile(file: File): Promise<void> {
certStatus.className = 'text-xs text-green-400'; certStatus.className = 'text-xs text-green-400';
} }
} }
} catch (error) { } catch {
const certStatus = getElement<HTMLDivElement>('cert-status'); const certStatus = getElement<HTMLDivElement>('cert-status');
if (certStatus) { if (certStatus) {
certStatus.textContent = 'Failed to parse PEM file'; certStatus.textContent = 'Failed to parse PEM file';

View File

@@ -16,7 +16,9 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
import.meta.url import.meta.url
).toString(); ).toString();
const duplicateOrganizeState = { const duplicateOrganizeState: {
sortableInstances: { pageGrid?: Sortable };
} = {
sortableInstances: {}, sortableInstances: {},
}; };
@@ -24,13 +26,10 @@ function initializePageGridSortable() {
const grid = document.getElementById('page-grid'); const grid = document.getElementById('page-grid');
if (!grid) return; if (!grid) return;
// @ts-expect-error TS(2339) FIXME: Property 'pageGrid' does not exist on type '{}'.
if (duplicateOrganizeState.sortableInstances.pageGrid) { if (duplicateOrganizeState.sortableInstances.pageGrid) {
// @ts-expect-error TS(2339) FIXME: Property 'pageGrid' does not exist on type '{}'.
duplicateOrganizeState.sortableInstances.pageGrid.destroy(); duplicateOrganizeState.sortableInstances.pageGrid.destroy();
} }
// @ts-expect-error TS(2339) FIXME: Property 'pageGrid' does not exist on type '{}'.
duplicateOrganizeState.sortableInstances.pageGrid = Sortable.create(grid, { duplicateOrganizeState.sortableInstances.pageGrid = Sortable.create(grid, {
animation: 150, animation: 150,
ghostClass: 'sortable-ghost', ghostClass: 'sortable-ghost',
@@ -38,10 +37,10 @@ function initializePageGridSortable() {
dragClass: 'sortable-drag', dragClass: 'sortable-drag',
filter: '.duplicate-btn, .delete-btn', filter: '.duplicate-btn, .delete-btn',
preventOnFilter: true, preventOnFilter: true,
onStart: function (evt: any) { onStart: function (evt: Sortable.SortableEvent) {
evt.item.style.opacity = '0.5'; evt.item.style.opacity = '0.5';
}, },
onEnd: function (evt: any) { onEnd: function (evt: Sortable.SortableEvent) {
evt.item.style.opacity = '1'; evt.item.style.opacity = '1';
}, },
}); });
@@ -51,7 +50,7 @@ function initializePageGridSortable() {
* Attaches event listeners for duplicate and delete to a page thumbnail element. * Attaches event listeners for duplicate and delete to a page thumbnail element.
* @param {HTMLElement} element The thumbnail element to attach listeners to. * @param {HTMLElement} element The thumbnail element to attach listeners to.
*/ */
function attachEventListeners(element: any) { function attachEventListeners(element: HTMLElement) {
// Re-number all visible page labels // Re-number all visible page labels
const renumberPages = () => { const renumberPages = () => {
const grid = document.getElementById('page-grid'); const grid = document.getElementById('page-grid');
@@ -65,16 +64,16 @@ function attachEventListeners(element: any) {
// Duplicate button listener // Duplicate button listener
element element
.querySelector('.duplicate-btn') .querySelector('.duplicate-btn')
.addEventListener('click', (e: any) => { .addEventListener('click', (e: Event) => {
e.stopPropagation(); e.stopPropagation();
const clone = element.cloneNode(true); const clone = element.cloneNode(true) as HTMLElement;
element.after(clone); element.after(clone);
attachEventListeners(clone); attachEventListeners(clone);
renumberPages(); renumberPages();
initializePageGridSortable(); initializePageGridSortable();
}); });
element.querySelector('.delete-btn').addEventListener('click', (e: any) => { element.querySelector('.delete-btn').addEventListener('click', (e: Event) => {
e.stopPropagation(); e.stopPropagation();
if (document.getElementById('page-grid').children.length > 1) { if (document.getElementById('page-grid').children.length > 1) {
element.remove(); element.remove();
@@ -218,7 +217,7 @@ export async function processAndSave() {
} }
const copiedPages = await newPdfDoc.copyPages(state.pdfDoc, finalIndices); 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(); const newPdfBytes = await newPdfDoc.save();
downloadFile( downloadFile(

View File

@@ -44,7 +44,11 @@ worker.onmessage = function (e) {
const data = e.data; const data = e.data;
if (data.status === 'success' && data.attachments !== undefined) { 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 { return {
...att, ...att,
data: new Uint8Array(att.data), data: new Uint8Array(att.data),

View File

@@ -11,8 +11,11 @@ const embedPdfWasmUrl = new URL(
import.meta.url import.meta.url
).href; ).href;
let viewerInstance: any = null; import type { EmbedPdfContainer } from 'embedpdf-snippet';
let docManagerPlugin: any = null; import type { DocManagerPlugin } from '@/types';
let viewerInstance: EmbedPdfContainer | null = null;
let docManagerPlugin: DocManagerPlugin | null = null;
let isViewerInitialized = false; let isViewerInitialized = false;
const fileEntryMap = new Map<string, HTMLElement>(); const fileEntryMap = new Map<string, HTMLElement>();
@@ -148,14 +151,17 @@ async function handleFiles(files: FileList) {
}); });
const registry = await viewerInstance.registry; 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) => { docManagerPlugin.onDocumentClosed((data: { id?: string }) => {
const docId = data?.id || data; const docId = data?.id || '';
removeFileEntry(docId); removeFileEntry(docId);
}); });
docManagerPlugin.onDocumentOpened((data: any) => { docManagerPlugin.onDocumentOpened(
(data: { id?: string; name?: string }) => {
const docId = data?.id; const docId = data?.id;
const docKey = data?.name; const docKey = data?.name;
if (!docId) return; if (!docId) return;
@@ -174,7 +180,8 @@ async function handleFiles(files: FileList) {
}; };
} }
} }
}); }
);
addFileEntries(fileDisplayArea, decryptedFiles); addFileEntries(fileDisplayArea, decryptedFiles);

View File

@@ -3,9 +3,7 @@ import { downloadFile, formatBytes } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { parseEmailFile, renderEmailToHtml } from './email-to-pdf.js'; import { parseEmailFile, renderEmailToHtml } from './email-to-pdf.js';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
const EXTENSIONS = ['.eml', '.msg']; const EXTENSIONS = ['.eml', '.msg'];
const TOOL_NAME = 'Email'; const TOOL_NAME = 'Email';
@@ -125,7 +123,11 @@ document.addEventListener('DOMContentLoaded', () => {
pageSize, 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, pageSize,
margins: { top: 50, right: 50, bottom: 50, left: 50 }, margins: { top: 50, right: 50, bottom: 50, left: 50 },
attachments: email.attachments attachments: email.attachments
@@ -166,7 +168,11 @@ document.addEventListener('DOMContentLoaded', () => {
pageSize, 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, pageSize,
margins: { top: 50, right: 50, bottom: 50, left: 50 }, margins: { top: 50, right: 50, bottom: 50, left: 50 },
attachments: email.attachments attachments: email.attachments
@@ -179,7 +185,7 @@ document.addEventListener('DOMContentLoaded', () => {
const baseName = file.name.replace(/\.[^.]+$/, ''); const baseName = file.name.replace(/\.[^.]+$/, '');
const pdfBuffer = await pdfBlob.arrayBuffer(); const pdfBuffer = await pdfBlob.arrayBuffer();
zip.file(`${baseName}.pdf`, pdfBuffer); zip.file(`${baseName}.pdf`, pdfBuffer);
} catch (e: any) { } catch (e: unknown) {
console.error(`Failed to convert ${file.name}:`, e); console.error(`Failed to convert ${file.name}:`, e);
} }
} }
@@ -196,12 +202,12 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e); console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -42,7 +42,14 @@ export async function parseEmlFile(file: File): Promise<ParsedEmail> {
.filter(Boolean); .filter(Boolean);
// Helper to map parsing result to EmailAttachment // 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 content: Uint8Array | undefined;
let size = 0; let size = 0;
if (att.content) { if (att.content) {
@@ -67,7 +74,9 @@ export async function parseEmlFile(file: File): Promise<ParsedEmail> {
const attachments: EmailAttachment[] = [ const attachments: EmailAttachment[] = [
...(email.attachments || []).map(mapAttachment), ...(email.attachments || []).map(mapAttachment),
...((email as any).inline || []).map(mapAttachment), ...((email as { inline?: RawAttachment[] }).inline || []).map(
mapAttachment
),
]; ];
// Preserve original date string from headers // 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( const attachments: EmailAttachment[] = (msgData.attachments || []).map(
(att: any) => ({ (att: MsgAttachment) => ({
filename: att.fileName || att.name || 'unnamed', filename: att.fileName || att.name || 'unnamed',
size: att.content?.length || 0, size: att.content?.length || 0,
contentType: att.mimeType || 'application/octet-stream', contentType: att.mimeType || 'application/octet-stream',

View File

@@ -6,7 +6,7 @@ import {
readFileAsArrayBuffer, readFileAsArrayBuffer,
} from '../utils/helpers.js'; } from '../utils/helpers.js';
import { icons, createIcons } from 'lucide'; import { icons, createIcons } from 'lucide';
import { EncryptPdfState } from '@/types'; import { EncryptPdfState, QpdfInstanceExtended } from '@/types';
const pageState: EncryptPdfState = { const pageState: EncryptPdfState = {
file: null, file: null,
@@ -114,7 +114,7 @@ async function encryptPdf() {
const inputPath = '/input.pdf'; const inputPath = '/input.pdf';
const outputPath = '/output.pdf'; const outputPath = '/output.pdf';
let qpdf: any; let qpdf: QpdfInstanceExtended;
const loaderModal = document.getElementById('loader-modal'); const loaderModal = document.getElementById('loader-modal');
const loaderText = document.getElementById('loader-text'); const loaderText = document.getElementById('loader-text');
@@ -154,10 +154,11 @@ async function encryptPdf() {
try { try {
qpdf.callMain(args); qpdf.callMain(args);
} catch (qpdfError: any) { } catch (qpdfError: unknown) {
console.error('qpdf execution error:', qpdfError); console.error('qpdf execution error:', qpdfError);
throw new Error( throw new Error(
'Encryption failed: ' + (qpdfError.message || 'Unknown error'), 'Encryption failed: ' +
(qpdfError instanceof Error ? qpdfError.message : 'Unknown error'),
{ cause: qpdfError } { cause: qpdfError }
); );
} }
@@ -169,7 +170,9 @@ async function encryptPdf() {
throw new Error('Encryption resulted in an empty file.'); 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}`); downloadFile(blob, `encrypted-${pageState.file.name}`);
if (loaderModal) loaderModal.classList.add('hidden'); if (loaderModal) loaderModal.classList.add('hidden');
@@ -183,12 +186,12 @@ async function encryptPdf() {
showAlert('Success', successMessage, 'success', () => { showAlert('Success', successMessage, 'success', () => {
resetState(); resetState();
}); });
} catch (error: any) { } catch (error: unknown) {
console.error('Error during PDF encryption:', error); console.error('Error during PDF encryption:', error);
if (loaderModal) loaderModal.classList.add('hidden'); if (loaderModal) loaderModal.classList.add('hidden');
showAlert( showAlert(
'Encryption Failed', '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 { } finally {
try { try {

View File

@@ -2,9 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
const FILETYPE = 'epub'; const FILETYPE = 'epub';
const EXTENSIONS = ['.epub']; const EXTENSIONS = ['.epub'];
@@ -145,12 +143,12 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e); console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -1,12 +1,11 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { import { downloadFile, formatBytes } from '../utils/helpers.js';
downloadFile,
readFileAsArrayBuffer,
formatBytes,
} from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; 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', () => { document.addEventListener('DOMContentLoaded', () => {
state.files = []; state.files = [];
@@ -37,13 +36,15 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) { for (let index = 0; index < state.files.length; index++) {
const file = state.files[index]; const file = state.files[index];
const fileDiv = document.createElement('div'); const fileDiv = document.createElement('div');
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; fileDiv.className =
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
const infoContainer = document.createElement('div'); const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden'; infoContainer.className = 'flex flex-col overflow-hidden';
const nameSpan = document.createElement('div'); const nameSpan = document.createElement('div');
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; nameSpan.className =
'truncate font-medium text-gray-200 text-sm mb-1';
nameSpan.textContent = file.name; nameSpan.textContent = file.name;
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
@@ -53,7 +54,8 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan); infoContainer.append(nameSpan, metaSpan);
const removeBtn = document.createElement('button'); const removeBtn = document.createElement('button');
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; 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.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
removeBtn.onclick = () => { removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index); state.files = state.files.filter((_, i) => i !== index);
@@ -103,7 +105,8 @@ document.addEventListener('DOMContentLoaded', () => {
const pdfBlob = await converter.convertToPdf(originalFile); const pdfBlob = await converter.convertToPdf(originalFile);
const fileName = originalFile.name.replace(/\.(xls|xlsx|ods|csv)$/i, '') + '.pdf'; const fileName =
originalFile.name.replace(/\.(xls|xlsx|ods|csv)$/i, '') + '.pdf';
downloadFile(pdfBlob, fileName); downloadFile(pdfBlob, fileName);
@@ -122,7 +125,9 @@ document.addEventListener('DOMContentLoaded', () => {
for (let i = 0; i < state.files.length; i++) { for (let i = 0; i < state.files.length; i++) {
const file = state.files[i]; const file = state.files[i];
showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); showLoader(
`Converting ${i + 1}/${state.files.length}: ${file.name}...`
);
const pdfBlob = await converter.convertToPdf(file); const pdfBlob = await converter.convertToPdf(file);
@@ -144,11 +149,11 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };
@@ -180,13 +185,18 @@ document.addEventListener('DOMContentLoaded', () => {
dropZone.classList.remove('bg-gray-700'); dropZone.classList.remove('bg-gray-700');
const files = e.dataTransfer?.files; const files = e.dataTransfer?.files;
if (files && files.length > 0) { if (files && files.length > 0) {
const excelFiles = Array.from(files).filter(f => { const excelFiles = Array.from(files).filter((f) => {
const name = f.name.toLowerCase(); const name = f.name.toLowerCase();
return name.endsWith('.xls') || name.endsWith('.xlsx') || name.endsWith('.ods') || name.endsWith('.csv'); return (
name.endsWith('.xls') ||
name.endsWith('.xlsx') ||
name.endsWith('.ods') ||
name.endsWith('.csv')
);
}); });
if (excelFiles.length > 0) { if (excelFiles.length > 0) {
const dataTransfer = new DataTransfer(); const dataTransfer = new DataTransfer();
excelFiles.forEach(f => dataTransfer.items.add(f)); excelFiles.forEach((f) => dataTransfer.items.add(f));
handleFileSelect(dataTransfer.files); handleFileSelect(dataTransfer.files);
} }
} }

View File

@@ -8,9 +8,7 @@ import {
} from '../utils/helpers.js'; } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import { batchDecryptIfNeeded } from '../utils/password-prompt.js'; import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
interface ExtractedImage { interface ExtractedImage {
@@ -88,7 +86,7 @@ document.addEventListener('DOMContentLoaded', () => {
const arrayBuffer = await readFileAsArrayBuffer(file); const arrayBuffer = await readFileAsArrayBuffer(file);
const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise;
metaSpan.textContent = `${formatBytes(file.size)}${pdfDoc.numPages} pages`; metaSpan.textContent = `${formatBytes(file.size)}${pdfDoc.numPages} pages`;
} catch (error) { } catch {
metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`; metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`;
} }
} }
@@ -118,7 +116,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (!imagesGrid || !imagesContainer) return; if (!imagesGrid || !imagesContainer) return;
imagesGrid.innerHTML = ''; imagesGrid.innerHTML = '';
extractedImages.forEach((img, index) => { extractedImages.forEach((img) => {
const blob = new Blob([new Uint8Array(img.data)]); const blob = new Blob([new Uint8Array(img.data)]);
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
@@ -212,11 +210,11 @@ document.addEventListener('DOMContentLoaded', () => {
'success' 'success'
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during extraction. Error: ${e.message}` `An error occurred during extraction. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -12,7 +12,7 @@ import { loadPdfDocument } from '../utils/load-pdf-document.js';
interface ExtractState { interface ExtractState {
file: File | null; file: File | null;
pdfDoc: any; pdfDoc: PDFDocument | null;
totalPages: number; totalPages: number;
} }

View File

@@ -2,9 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import JSZip from 'jszip'; import JSZip from 'jszip';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js'; import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
let file: File | null = null; let file: File | null = null;

View File

@@ -2,9 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
const FILETYPE = 'fb2'; const FILETYPE = 'fb2';
const EXTENSIONS = ['.fb2']; const EXTENSIONS = ['.fb2'];
@@ -141,12 +139,12 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e); console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -28,7 +28,7 @@ type PdfViewerWindow = Window & {
}; };
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'; 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 { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';
@@ -319,15 +319,15 @@ toolItems.forEach((item) => {
let touchStartY = 0; let touchStartY = 0;
let isTouchDragging = false; let isTouchDragging = false;
item.addEventListener('touchstart', (e) => { item.addEventListener('touchstart', (e: TouchEvent) => {
const touch = e.touches[0]; const touch = e.touches[0];
touchStartX = touch.clientX; touchStartX = touch.clientX;
touchStartY = touch.clientY; touchStartY = touch.clientY;
isTouchDragging = false; isTouchDragging = false;
}); });
item.addEventListener('touchmove', (e) => { item.addEventListener('touchmove', (e: TouchEvent) => {
e.preventDefault(); // Prevent scrolling while dragging e.preventDefault();
const touch = e.touches[0]; const touch = e.touches[0];
const moveX = Math.abs(touch.clientX - touchStartX); const moveX = Math.abs(touch.clientX - touchStartX);
const moveY = Math.abs(touch.clientY - touchStartY); 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(); e.preventDefault();
if (!isTouchDragging) { if (!isTouchDragging) {
// It was a tap, treat as click // It was a tap, treat as click
@@ -2131,8 +2131,7 @@ downloadBtn.addEventListener('click', async () => {
nameCount.set(field.name, count + 1); nameCount.set(field.name, count + 1);
if (existingFieldNames.has(field.name)) { if (existingFieldNames.has(field.name)) {
if (field.type === 'radio' && existingRadioGroups.has(field.name)) { if (!(field.type === 'radio' && existingRadioGroups.has(field.name))) {
} else {
conflictsWithPdf.push(field.name); conflictsWithPdf.push(field.name);
} }
} }
@@ -2337,7 +2336,7 @@ downloadBtn.addEventListener('click', async () => {
const existingField = form.getFieldMaybe(groupName); const existingField = form.getFieldMaybe(groupName);
if (existingField) { if (existingField) {
radioGroup = existingField; radioGroup = existingField as PDFRadioGroup;
radioGroups.set(groupName, radioGroup); radioGroups.set(groupName, radioGroup);
console.log(`Using existing radio group from PDF: ${groupName}`); console.log(`Using existing radio group from PDF: ${groupName}`);
} else { } else {
@@ -2788,11 +2787,11 @@ function getPageDimensions(size: string): { width: number; height: number } {
case 'a3': case 'a3':
dimensions = PageSizes.A3; dimensions = PageSizes.A3;
break; break;
case 'custom': case 'custom': {
// Get custom dimensions from inputs
const width = parseInt(customWidth.value) || 612; const width = parseInt(customWidth.value) || 612;
const height = parseInt(customHeight.value) || 792; const height = parseInt(customHeight.value) || 792;
return { width, height }; return { width, height };
}
default: default:
dimensions = PageSizes.Letter; dimensions = PageSizes.Letter;
} }

View File

@@ -1,9 +1,8 @@
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { showAlert, showLoader, hideLoader } from '../ui.js'; import { showAlert, showLoader, hideLoader } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; import type { PyMuPDFInstance } from '@/types';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import heic2any from 'heic2any'; import heic2any from 'heic2any';
import { import {
getSelectedQuality, 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'; 'JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP';
let files: File[] = []; let files: File[] = [];
let pymupdf: any = null; let pymupdf: PyMuPDFInstance | null = null;
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializePage); document.addEventListener('DOMContentLoaded', initializePage);
@@ -177,9 +176,9 @@ function updateUI() {
} }
} }
async function ensurePyMuPDF(): Promise<any> { async function ensurePyMuPDF(): Promise<PyMuPDFInstance> {
if (!pymupdf) { if (!pymupdf) {
pymupdf = await loadPyMuPDF(); pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
} }
return pymupdf; return pymupdf;
} }
@@ -274,7 +273,7 @@ async function convertToPdf() {
const processed = await preprocessFile(file); const processed = await preprocessFile(file);
const compressed = await compressImageFile(processed, quality); const compressed = await compressImageFile(processed, quality);
processedFiles.push(compressed); processedFiles.push(compressed);
} catch (error: any) { } catch (error: unknown) {
console.warn(error); console.warn(error);
throw error; throw error;
} }
@@ -291,11 +290,11 @@ async function convertToPdf() {
showAlert('Success', 'PDF created successfully!', 'success', () => { showAlert('Success', 'PDF created successfully!', 'success', () => {
resetState(); resetState();
}); });
} catch (e: any) { } catch (e: unknown) {
console.error('[ImageToPDF]', e); console.error('[ImageToPDF]', e);
showAlert( showAlert(
'Conversion Error', 'Conversion Error',
e.message || 'Failed to convert images to PDF.' e instanceof Error ? e.message : 'Failed to convert images to PDF.'
); );
} finally { } finally {
hideLoader(); hideLoader();

View File

@@ -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 { mdToPdf } from './md-to-pdf.js';
import { processAndSave } from './duplicate-organize.js'; import { processAndSave } from './duplicate-organize.js';
import { wordToPdf } from './word-to-pdf.js';
import { setupCropperTool } from './cropper.js'; import { setupCropperTool } from './cropper.js';
export const toolLogic: Record<
string,
| {
process?: (...args: unknown[]) => Promise<unknown>;
setup?: (...args: unknown[]) => Promise<unknown>;
}
| ((...args: unknown[]) => unknown)
> = {
export const toolLogic = {
'duplicate-organize': { process: processAndSave }, 'duplicate-organize': { process: processAndSave },
cropper: { setup: setupCropperTool }, cropper: { setup: setupCropperTool },
}; };

View File

@@ -2,6 +2,7 @@ import { createIcons, icons } from 'lucide';
import { showAlert, showLoader, hideLoader } from '../ui.js'; import { showAlert, showLoader, hideLoader } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { loadPyMuPDF } from '../utils/pymupdf-loader.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import type { PyMuPDFInstance } from '@/types';
import { import {
getSelectedQuality, getSelectedQuality,
compressImageFile, compressImageFile,
@@ -11,7 +12,7 @@ const SUPPORTED_FORMATS = '.jpg,.jpeg,.jp2,.jpx';
const SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/jpg', 'image/jp2']; const SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/jpg', 'image/jp2'];
let files: File[] = []; let files: File[] = [];
let pymupdf: any = null; let pymupdf: PyMuPDFInstance | null = null;
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializePage); document.addEventListener('DOMContentLoaded', initializePage);
@@ -168,9 +169,9 @@ function updateUI() {
} }
} }
async function ensurePyMuPDF(): Promise<any> { async function ensurePyMuPDF(): Promise<PyMuPDFInstance> {
if (!pymupdf) { if (!pymupdf) {
pymupdf = await loadPyMuPDF(); pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
} }
return pymupdf; return pymupdf;
} }
@@ -200,11 +201,11 @@ async function convertToPdf() {
showAlert('Success', 'PDF created successfully!', 'success', () => { showAlert('Success', 'PDF created successfully!', 'success', () => {
resetState(); resetState();
}); });
} catch (e: any) { } catch (e: unknown) {
console.error('[JpgToPdf]', e); console.error('[JpgToPdf]', e);
showAlert( showAlert(
'Conversion Error', 'Conversion Error',
e.message || 'Failed to convert images to PDF.' e instanceof Error ? e.message : 'Failed to convert images to PDF.'
); );
} finally { } finally {
hideLoader(); hideLoader();

View File

@@ -8,7 +8,7 @@ import {
import { icons, createIcons } from 'lucide'; import { icons, createIcons } from 'lucide';
import JSZip from 'jszip'; import JSZip from 'jszip';
import { deduplicateFileName } from '../utils/deduplicate-filename.js'; import { deduplicateFileName } from '../utils/deduplicate-filename.js';
import { LinearizePdfState } from '@/types'; import { LinearizePdfState, QpdfInstanceExtended } from '@/types';
const pageState: LinearizePdfState = { const pageState: LinearizePdfState = {
files: [], files: [],
@@ -111,7 +111,7 @@ async function linearizePdf() {
const zip = new JSZip(); const zip = new JSZip();
const usedNames = new Set<string>(); const usedNames = new Set<string>();
let qpdf: any; let qpdf: QpdfInstanceExtended;
let successCount = 0; let successCount = 0;
let errorCount = 0; let errorCount = 0;
@@ -150,7 +150,7 @@ async function linearizePdf() {
); );
zip.file(zipEntryName, outputFile, { binary: true }); zip.file(zipEntryName, outputFile, { binary: true });
successCount++; successCount++;
} catch (fileError: any) { } catch (fileError: unknown) {
errorCount++; errorCount++;
console.error(`Failed to linearize ${file.name}:`, fileError); console.error(`Failed to linearize ${file.name}:`, fileError);
} finally { } finally {
@@ -187,11 +187,11 @@ async function linearizePdf() {
showAlert('Processing Complete', alertMessage, 'success', () => { showAlert('Processing Complete', alertMessage, 'success', () => {
resetState(); resetState();
}); });
} catch (error: any) { } catch (error: unknown) {
console.error('Linearization process error:', error); console.error('Linearization process error:', error);
showAlert( showAlert(
'Linearization Failed', 'Linearization Failed',
`An error occurred: ${error.message || 'Unknown error'}.` `An error occurred: ${error instanceof Error ? error.message : 'Unknown error'}.`
); );
} finally { } finally {
if (loaderModal) loaderModal.classList.add('hidden'); if (loaderModal) loaderModal.classList.add('hidden');

View File

@@ -16,15 +16,15 @@ import {
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import type { MergeJob, MergeFile, MergeMessage, MergeResponse } from '@/types';
// @ts-ignore
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs', 'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url import.meta.url
).toString(); ).toString();
interface MergeState { interface MergeState {
pdfDocs: Record<string, any>; pdfDocs: Record<string, pdfjsLib.PDFDocumentProxy>;
pdfBytes: Record<string, ArrayBuffer>; pdfBytes: Record<string, ArrayBuffer>;
activeMode: 'file' | 'page'; activeMode: 'file' | 'page';
sortableInstances: { sortableInstances: {
@@ -66,10 +66,10 @@ function initializeFileListSortable() {
ghostClass: 'sortable-ghost', ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen', chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag', dragClass: 'sortable-drag',
onStart: function (evt: any) { onStart: function (evt: Sortable.SortableEvent) {
evt.item.style.opacity = '0.5'; evt.item.style.opacity = '0.5';
}, },
onEnd: function (evt: any) { onEnd: function (evt: Sortable.SortableEvent) {
evt.item.style.opacity = '1'; evt.item.style.opacity = '1';
}, },
}); });
@@ -88,10 +88,10 @@ function initializePageThumbnailsSortable() {
ghostClass: 'sortable-ghost', ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen', chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag', dragClass: 'sortable-drag',
onStart: function (evt: any) { onStart: function (evt: Sortable.SortableEvent) {
evt.item.style.opacity = '0.5'; evt.item.style.opacity = '0.5';
}, },
onEnd: function (evt: any) { onEnd: function (evt: Sortable.SortableEvent) {
evt.item.style.opacity = '1'; evt.item.style.opacity = '1';
}, },
}); });
@@ -201,7 +201,7 @@ async function renderPageMergeThumbnails() {
batchSize: 8, batchSize: 8,
useLazyLoading: true, useLazyLoading: true,
lazyLoadMargin: '300px', lazyLoadMargin: '300px',
onProgress: (current, total) => { onProgress: () => {
currentPageNumber++; currentPageNumber++;
showLoader(`Rendering page previews...`); showLoader(`Rendering page previews...`);
}, },
@@ -288,9 +288,7 @@ export async function merge() {
showLoader('Merging PDFs...'); showLoader('Merging PDFs...');
try { try {
// @ts-ignore
const jobs: MergeJob[] = []; const jobs: MergeJob[] = [];
// @ts-ignore
const filesToMerge: MergeFile[] = []; const filesToMerge: MergeFile[] = [];
const uniqueFileNames = new Set<string>(); const uniqueFileNames = new Set<string>();
@@ -387,7 +385,6 @@ export async function merge() {
} }
} }
// @ts-ignore
const message: MergeMessage = { const message: MergeMessage = {
command: 'merge', command: 'merge',
files: filesToMerge, files: filesToMerge,
@@ -400,7 +397,6 @@ export async function merge() {
filesToMerge.map((f) => f.data) filesToMerge.map((f) => f.data)
); );
// @ts-ignore
mergeWorker.onmessage = (e: MessageEvent<MergeResponse>) => { mergeWorker.onmessage = (e: MessageEvent<MergeResponse>) => {
hideLoader(); hideLoader();
if (e.data.status === 'success') { if (e.data.status === 'success') {

View File

@@ -2,9 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
const FILETYPE = 'mobi'; const FILETYPE = 'mobi';
const EXTENSIONS = ['.mobi']; const EXTENSIONS = ['.mobi'];
@@ -141,12 +139,12 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e); console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -1,12 +1,11 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { import { downloadFile, formatBytes } from '../utils/helpers.js';
downloadFile,
readFileAsArrayBuffer,
formatBytes,
} from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; 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', () => { document.addEventListener('DOMContentLoaded', () => {
state.files = []; state.files = [];
@@ -37,13 +36,15 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) { for (let index = 0; index < state.files.length; index++) {
const file = state.files[index]; const file = state.files[index];
const fileDiv = document.createElement('div'); const fileDiv = document.createElement('div');
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; fileDiv.className =
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
const infoContainer = document.createElement('div'); const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden'; infoContainer.className = 'flex flex-col overflow-hidden';
const nameSpan = document.createElement('div'); const nameSpan = document.createElement('div');
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; nameSpan.className =
'truncate font-medium text-gray-200 text-sm mb-1';
nameSpan.textContent = file.name; nameSpan.textContent = file.name;
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
@@ -53,7 +54,8 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan); infoContainer.append(nameSpan, metaSpan);
const removeBtn = document.createElement('button'); const removeBtn = document.createElement('button');
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; 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.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
removeBtn.onclick = () => { removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index); state.files = state.files.filter((_, i) => i !== index);
@@ -122,7 +124,9 @@ document.addEventListener('DOMContentLoaded', () => {
for (let i = 0; i < state.files.length; i++) { for (let i = 0; i < state.files.length; i++) {
const file = state.files[i]; const file = state.files[i];
showLoader(`Converting ${i + 1}/${state.files.length}: ${file.name}...`); showLoader(
`Converting ${i + 1}/${state.files.length}: ${file.name}...`
);
const pdfBlob = await converter.convertToPdf(file); const pdfBlob = await converter.convertToPdf(file);
@@ -144,11 +148,11 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };
@@ -180,10 +184,14 @@ document.addEventListener('DOMContentLoaded', () => {
dropZone.classList.remove('bg-gray-700'); dropZone.classList.remove('bg-gray-700');
const files = e.dataTransfer?.files; const files = e.dataTransfer?.files;
if (files && files.length > 0) { if (files && files.length > 0) {
const odtFiles = Array.from(files).filter(f => f.name.toLowerCase().endsWith('.odt') || f.type === 'application/vnd.oasis.opendocument.text'); const odtFiles = Array.from(files).filter(
(f) =>
f.name.toLowerCase().endsWith('.odt') ||
f.type === 'application/vnd.oasis.opendocument.text'
);
if (odtFiles.length > 0) { if (odtFiles.length > 0) {
const dataTransfer = new DataTransfer(); const dataTransfer = new DataTransfer();
odtFiles.forEach(f => dataTransfer.items.add(f)); odtFiles.forEach((f) => dataTransfer.items.add(f));
handleFileSelect(dataTransfer.files); handleFileSelect(dataTransfer.files);
} }
} }

View File

@@ -15,10 +15,10 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
interface OrganizeState { interface OrganizeState {
file: File | null; file: File | null;
pdfDoc: any; pdfDoc: PDFDocument | null;
pdfJsDoc: any; pdfJsDoc: pdfjsLib.PDFDocumentProxy | null;
totalPages: number; totalPages: number;
sortableInstance: any; sortableInstance: Sortable | null;
} }
const organizeState: OrganizeState = { const organizeState: OrganizeState = {
@@ -282,7 +282,7 @@ async function renderThumbnails() {
canvas.width = viewport.width; canvas.width = viewport.width;
canvas.height = viewport.height; canvas.height = viewport.height;
const ctx = canvas.getContext('2d'); 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'); const wrapper = document.createElement('div');
wrapper.className = wrapper.className =

View File

@@ -150,12 +150,12 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e); console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e);
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -226,9 +226,9 @@ async function generatePreview() {
const ctx = offscreen.getContext('2d')!; const ctx = offscreen.getContext('2d')!;
await page.render({ await page.render({
canvasContext: ctx as any, canvasContext: ctx as unknown as CanvasRenderingContext2D,
viewport: viewport, viewport: viewport,
canvas: offscreen as any, canvas: offscreen as unknown as HTMLCanvasElement,
}).promise; }).promise;
const bitmap = await createImageBitmap(offscreen); const bitmap = await createImageBitmap(offscreen);

View File

@@ -7,9 +7,7 @@ import {
getPDFDocument, getPDFDocument,
} from '../utils/helpers.js'; } from '../utils/helpers.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js'; import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
interface LayerData { interface LayerData {
@@ -24,7 +22,21 @@ interface LayerData {
} }
let currentFile: File | null = null; 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>(); const layersMap = new Map<number, LayerData>();
let nextDisplayOrder = 0; let nextDisplayOrder = 0;
@@ -274,7 +286,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (!childName || !childName.trim()) return; if (!childName || !childName.trim()) return;
try { try {
const childXref = currentDoc.addOCGWithParent( const childResult = currentDoc.addOCGWithParent(
childName.trim(), childName.trim(),
parentXref parentXref
); );
@@ -285,9 +297,9 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
layersMap.set(childXref, { layersMap.set(childResult.xref, {
number: childXref, number: childResult.number,
xref: childXref, xref: childResult.xref,
text: childName.trim(), text: childName.trim(),
on: true, on: true,
locked: false, locked: false,
@@ -316,16 +328,17 @@ document.addEventListener('DOMContentLoaded', () => {
const pymupdf = await loadPyMuPDF(); const pymupdf = await loadPyMuPDF();
showLoader(`Loading layers from ${currentFile.name}...`); 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...'); showLoader('Reading layer configuration...');
const existingLayers = currentDoc.getLayerConfig(); const existingLayers = currentDoc.getLayerConfig();
// Reset and populate layers map
layersMap.clear(); layersMap.clear();
nextDisplayOrder = 0; nextDisplayOrder = 0;
existingLayers.forEach((layer: any) => { existingLayers.forEach((layer: LayerData) => {
layersMap.set(layer.number, { layersMap.set(layer.number, {
number: layer.number, number: layer.number,
xref: layer.xref ?? layer.number, xref: layer.xref ?? layer.number,
@@ -350,9 +363,12 @@ document.addEventListener('DOMContentLoaded', () => {
renderLayers(); renderLayers();
setupLayerHandlers(); setupLayerHandlers();
} catch (error: any) { } catch (error: unknown) {
hideLoader(); 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); console.error('Layers error:', error);
} }
}; };
@@ -373,13 +389,13 @@ document.addEventListener('DOMContentLoaded', () => {
} }
try { try {
const xref = currentDoc.addOCG(name); const layerResult = currentDoc.addOCG(name);
newLayerInput.value = ''; newLayerInput.value = '';
const newDisplayOrder = nextDisplayOrder++; const newDisplayOrder = nextDisplayOrder++;
layersMap.set(xref, { layersMap.set(layerResult.xref, {
number: xref, number: layerResult.number,
xref: xref, xref: layerResult.xref,
text: name, text: name,
on: true, on: true,
locked: false, locked: false,
@@ -389,8 +405,12 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
renderLayers(); renderLayers();
} catch (err: any) { } catch (err: unknown) {
showAlert('Error', 'Failed to add layer: ' + err.message); showAlert(
'Error',
'Failed to add layer: ' +
(err instanceof Error ? err.message : String(err))
);
} }
}; };
} }
@@ -409,9 +429,13 @@ document.addEventListener('DOMContentLoaded', () => {
hideLoader(); hideLoader();
resetState(); resetState();
showAlert('Success', 'PDF with layer changes saved!', 'success'); showAlert('Success', 'PDF with layer changes saved!', 'success');
} catch (err: any) { } catch (err: unknown) {
hideLoader(); hideLoader();
showAlert('Error', 'Failed to save PDF: ' + err.message); showAlert(
'Error',
'Failed to save PDF: ' +
(err instanceof Error ? err.message : String(err))
);
} }
}; };
} }

View File

@@ -9,7 +9,6 @@ import {
renderPagesProgressively, renderPagesProgressively,
cleanupLazyRendering, cleanupLazyRendering,
renderPageToCanvas, renderPageToCanvas,
createPlaceholder,
} from '../utils/render-utils'; } from '../utils/render-utils';
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'; import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
import { repairPdfFile } from './repair-pdf.js'; import { repairPdfFile } from './repair-pdf.js';
@@ -841,7 +840,7 @@ function deletePage(index: number) {
async function insertPdfAfter(index: number) { async function insertPdfAfter(index: number) {
document.getElementById('insert-pdf-input')?.click(); 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) { async function handleInsertPdf(e: Event) {
@@ -849,7 +848,8 @@ async function handleInsertPdf(e: Event) {
const file = input.files?.[0]; const file = input.files?.[0];
if (!file) return; 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; if (insertAfterIndex === undefined) return;
try { try {
@@ -974,7 +974,7 @@ function addBlankPage() {
rotation: 0, rotation: 0,
visualRotation: 0, visualRotation: 0,
canvas, canvas,
pdfDoc: null as any, pdfDoc: null!,
originalPageIndex: -1, originalPageIndex: -1,
fileName: '', // Blank page has no file fileName: '', // Blank page has no file
}; };

View File

@@ -1,10 +1,7 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import JSZip from 'jszip'; import { loadPyMuPDF } from '../utils/pymupdf-loader.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 { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js'; import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
let file: File | null = null; let file: File | null = null;

View File

@@ -8,9 +8,7 @@ import {
} from '../utils/helpers.js'; } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import { batchDecryptIfNeeded } from '../utils/password-prompt.js'; import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
import { deduplicateFileName } from '../utils/deduplicate-filename.js'; import { deduplicateFileName } from '../utils/deduplicate-filename.js';
@@ -73,7 +71,7 @@ document.addEventListener('DOMContentLoaded', () => {
const arrayBuffer = await readFileAsArrayBuffer(file); const arrayBuffer = await readFileAsArrayBuffer(file);
const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise;
metaSpan.textContent = `${formatBytes(file.size)}${pdfDoc.numPages} pages`; metaSpan.textContent = `${formatBytes(file.size)}${pdfDoc.numPages} pages`;
} catch (error) { } catch {
metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`; metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`;
} }
} }
@@ -160,11 +158,11 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -1,9 +1,7 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js'; import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
let file: File | null = null; let file: File | null = null;

View File

@@ -8,9 +8,7 @@ import {
} from '../utils/helpers.js'; } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import { batchDecryptIfNeeded } from '../utils/password-prompt.js'; import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
import { deduplicateFileName } from '../utils/deduplicate-filename.js'; import { deduplicateFileName } from '../utils/deduplicate-filename.js';
@@ -76,7 +74,7 @@ document.addEventListener('DOMContentLoaded', () => {
const arrayBuffer = await readFileAsArrayBuffer(file); const arrayBuffer = await readFileAsArrayBuffer(file);
const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise;
metaSpan.textContent = `${formatBytes(file.size)}${pdfDoc.numPages} pages`; metaSpan.textContent = `${formatBytes(file.size)}${pdfDoc.numPages} pages`;
} catch (error) { } catch {
metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`; metaSpan.textContent = `${formatBytes(file.size)} • Could not load page count`;
} }
} }
@@ -162,11 +160,11 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -1,5 +1,9 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; 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'; import { state } from '../state.js';
export async function pdfToMarkdown() { export async function pdfToMarkdown() {
@@ -14,7 +18,9 @@ export async function pdfToMarkdown() {
const page = await pdf.getPage(i); const page = await pdf.getPage(i);
const content = await page.getTextContent(); const content = await page.getTextContent();
// This is a simple text extraction. For more advanced formatting, more complex logic is needed. // 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 markdown += text + '\n\n'; // Add double newline for paragraph breaks between pages
} }

View File

@@ -10,6 +10,7 @@ import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { convertFileToPdfA, type PdfALevel } from '../utils/ghostscript-loader'; import { convertFileToPdfA, type PdfALevel } from '../utils/ghostscript-loader';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js'; import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import type { PyMuPDFInstance } from '@/types';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { batchDecryptIfNeeded } from '../utils/password-prompt.js'; import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
import { deduplicateFileName } from '../utils/deduplicate-filename.js'; import { deduplicateFileName } from '../utils/deduplicate-filename.js';
@@ -134,7 +135,7 @@ document.addEventListener('DOMContentLoaded', () => {
const pymupdf = await loadPyMuPDF(); const pymupdf = await loadPyMuPDF();
// Rasterize PDF to images and back to PDF (300 DPI for quality) // 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, originalFile,
{ {
dpi: 300, dpi: 300,
@@ -205,11 +206,11 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -2,12 +2,12 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import JSZip from 'jszip'; 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, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import type { PyMuPDFInstance } from '@/types';
import { batchDecryptIfNeeded } from '../utils/password-prompt.js'; 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[] = []; let files: File[] = [];
const updateUI = () => { const updateUI = () => {

View File

@@ -1,14 +1,13 @@
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { showAlert, showLoader, hideLoader } from '../ui.js'; import { showAlert, showLoader, hideLoader } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; import type { PyMuPDFInstance } from '@/types';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import { batchDecryptIfNeeded } from '../utils/password-prompt.js'; import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
import { deduplicateFileName } from '../utils/deduplicate-filename.js'; import { deduplicateFileName } from '../utils/deduplicate-filename.js';
let files: File[] = []; let files: File[] = [];
let pymupdf: any = null; let pymupdf: PyMuPDFInstance | null = null;
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializePage); document.addEventListener('DOMContentLoaded', initializePage);
@@ -159,9 +158,9 @@ function updateUI() {
} }
} }
async function ensurePyMuPDF(): Promise<any> { async function ensurePyMuPDF(): Promise<PyMuPDFInstance> {
if (!pymupdf) { if (!pymupdf) {
pymupdf = await loadPyMuPDF(); pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
} }
return pymupdf; return pymupdf;
} }
@@ -230,12 +229,12 @@ async function extractText() {
} }
); );
} }
} catch (e: any) { } catch (e: unknown) {
console.error('[PDFToText]', e); console.error('[PDFToText]', e);
hideLoader(); hideLoader();
showAlert( showAlert(
'Extraction Error', 'Extraction Error',
e.message || 'Failed to extract text from PDF.' e instanceof Error ? e.message : 'Failed to extract text from PDF.'
); );
} }
} }

View File

@@ -913,8 +913,15 @@ function showNodeSettings(node: BaseWorkflowNode) {
content.appendChild(divider); content.appendChild(divider);
} }
interface FileInputNode extends BaseWorkflowNode {
hasFile(): boolean;
getFilenames(): string[];
removeFile(index: number): void;
addFiles(files: File[]): Promise<void>;
}
const fileInputConfigs: { const fileInputConfigs: {
cls: any; cls: new (...args: unknown[]) => FileInputNode;
label: string; label: string;
accept: string; accept: string;
btnLabel: 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 const ctrl = controlEntries.find(([k]) => k === dropdownKey)?.[1] as
| { value?: unknown } | { value?: unknown }
| undefined; | undefined;

View File

@@ -160,9 +160,13 @@ function updateUI() {
} }
} }
function sanitizeImageAsJpeg(imageBytes: any) { function sanitizeImageAsJpeg(imageBytes: Uint8Array | ArrayBuffer) {
return new Promise((resolve, reject) => { 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 imageUrl = URL.createObjectURL(blob);
const img = new Image(); const img = new Image();
@@ -248,9 +252,9 @@ async function convertToPdf() {
showAlert('Success', 'PDF created successfully!', 'success', () => { showAlert('Success', 'PDF created successfully!', 'success', () => {
resetState(); resetState();
}); });
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
showAlert('Conversion Error', e.message); showAlert('Conversion Error', e instanceof Error ? e.message : String(e));
} finally { } finally {
hideLoader(); hideLoader();
} }

View File

@@ -1,9 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { import { downloadFile, formatBytes } from '../utils/helpers.js';
downloadFile,
readFileAsArrayBuffer,
formatBytes,
} from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { import {
@@ -159,11 +155,11 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -9,6 +9,7 @@ import {
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { loadPyMuPDF } 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 { batchDecryptIfNeeded } from '../utils/password-prompt.js';
import { deduplicateFileName } from '../utils/deduplicate-filename.js'; import { deduplicateFileName } from '../utils/deduplicate-filename.js';
@@ -117,7 +118,9 @@ document.addEventListener('DOMContentLoaded', () => {
const file = state.files[0]; const file = state.files[0];
showLoader(`Extracting ${file.name} for AI...`); 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 outName = file.name.replace(/\.pdf$/i, '') + '_llm.json';
const jsonContent = JSON.stringify(llamaDocs, null, 2); const jsonContent = JSON.stringify(llamaDocs, null, 2);
downloadFile( downloadFile(
@@ -144,7 +147,9 @@ document.addEventListener('DOMContentLoaded', () => {
`Extracting ${file.name} for AI (${completed + 1}/${total})...` `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 outName = file.name.replace(/\.pdf$/i, '') + '_llm.json';
const jsonContent = JSON.stringify(llamaDocs, null, 2); const jsonContent = JSON.stringify(llamaDocs, null, 2);
const zipEntryName = deduplicateFileName(outName, usedNames); const zipEntryName = deduplicateFileName(outName, usedNames);
@@ -180,11 +185,11 @@ document.addEventListener('DOMContentLoaded', () => {
); );
} }
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during extraction. Error: ${e.message}` `An error occurred during extraction. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -2,18 +2,17 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js'; import type { PyMuPDFInstance } from '@/types';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
const ACCEPTED_EXTENSIONS = ['.psd']; const ACCEPTED_EXTENSIONS = ['.psd'];
const FILETYPE_NAME = '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) { if (!pymupdf) {
pymupdf = await loadPyMuPDF(); pymupdf = (await loadPyMuPDF()) as PyMuPDFInstance;
} }
return pymupdf; return pymupdf;
} }

View File

@@ -8,11 +8,11 @@ import {
} from '../utils/helpers.js'; } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; 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, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import type { PyMuPDFInstance } from '@/types';
import { batchDecryptIfNeeded } from '../utils/password-prompt.js'; import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
import { deduplicateFileName } from '../utils/deduplicate-filename.js'; import { deduplicateFileName } from '../utils/deduplicate-filename.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const fileInput = document.getElementById('file-input') as HTMLInputElement; const fileInput = document.getElementById('file-input') as HTMLInputElement;
@@ -136,12 +136,15 @@ document.addEventListener('DOMContentLoaded', () => {
const file = state.files[0]; const file = state.files[0];
showLoader(`Rasterizing ${file.name}...`); showLoader(`Rasterizing ${file.name}...`);
const rasterizedBlob = await (pymupdf as any).rasterizePdf(file, { const rasterizedBlob = await (pymupdf as PyMuPDFInstance).rasterizePdf(
file,
{
dpi, dpi,
format, format,
grayscale, grayscale,
quality: 95, quality: 95,
}); }
);
const outName = file.name.replace(/\.pdf$/i, '') + '_rasterized.pdf'; const outName = file.name.replace(/\.pdf$/i, '') + '_rasterized.pdf';
downloadFile(rasterizedBlob, outName); downloadFile(rasterizedBlob, outName);
@@ -165,7 +168,9 @@ document.addEventListener('DOMContentLoaded', () => {
`Rasterizing ${file.name} (${completed + 1}/${total})...` `Rasterizing ${file.name} (${completed + 1}/${total})...`
); );
const rasterizedBlob = await (pymupdf as any).rasterizePdf(file, { const rasterizedBlob = await (
pymupdf as PyMuPDFInstance
).rasterizePdf(file, {
dpi, dpi,
format, format,
grayscale, grayscale,
@@ -210,11 +215,11 @@ document.addEventListener('DOMContentLoaded', () => {
); );
} }
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during rasterization. Error: ${e.message}` `An error occurred during rasterization. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -2,16 +2,18 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile } from '../utils/helpers.js'; import { downloadFile } from '../utils/helpers.js';
import { state } from '../state.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 // @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; const { rgb } = window.PDFLib;
export async function redact(redactions: any, canvasScale: any) { export async function redact(redactions: RedactionRect[], canvasScale: number) {
showLoader('Applying redactions...'); showLoader('Applying redactions...');
try { try {
const pdfPages = state.pdfDoc.getPages(); const pdfPages = state.pdfDoc.getPages();
const conversionScale = 1 / canvasScale; const conversionScale = 1 / canvasScale;
redactions.forEach((r: any) => { redactions.forEach((r: RedactionRect) => {
const page = pdfPages[r.pageIndex]; const page = pdfPages[r.pageIndex];
const { height: pageHeight } = page.getSize(); const { height: pageHeight } = page.getSize();
@@ -32,7 +34,7 @@ export async function redact(redactions: any, canvasScale: any) {
const redactedBytes = await state.pdfDoc.save(); const redactedBytes = await state.pdfDoc.save();
downloadFile( downloadFile(
new Blob([redactedBytes], { type: 'application/pdf' }), new Blob([new Uint8Array(redactedBytes)], { type: 'application/pdf' }),
'redacted.pdf' 'redacted.pdf'
); );
} catch (e) { } catch (e) {

View File

@@ -36,7 +36,7 @@ function hideLoader() {
function showAlert( function showAlert(
title: string, title: string,
msg: string, msg: string,
type = 'error', _type = 'error',
cb?: () => void cb?: () => void
) { ) {
const modal = document.getElementById('alert-modal'); const modal = document.getElementById('alert-modal');
@@ -138,7 +138,7 @@ async function handleFileUpload(file: File) {
} }
async function isPageBlank( async function isPageBlank(
page: any, page: pdfjsLib.PDFPageProxy,
maxNonWhitePercent = 0.5 maxNonWhitePercent = 0.5
): Promise<boolean> { ): Promise<boolean> {
const viewport = page.getViewport({ scale: 0.5 }); const viewport = page.getViewport({ scale: 0.5 });
@@ -149,7 +149,7 @@ async function isPageBlank(
canvas.width = viewport.width; canvas.width = viewport.width;
canvas.height = viewport.height; 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 imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data; const data = imageData.data;
@@ -165,7 +165,7 @@ async function isPageBlank(
return nonWhitePercent <= maxNonWhitePercent; 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 viewport = page.getViewport({ scale: 1 });
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
@@ -174,7 +174,7 @@ async function generateThumbnail(page: any): Promise<string> {
canvas.width = viewport.width; canvas.width = viewport.width;
canvas.height = viewport.height; 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); return canvas.toDataURL('image/jpeg', 0.7);
} }

View File

@@ -6,7 +6,7 @@ import {
readFileAsArrayBuffer, readFileAsArrayBuffer,
} from '../utils/helpers.js'; } from '../utils/helpers.js';
import { icons, createIcons } from 'lucide'; import { icons, createIcons } from 'lucide';
import { RemoveRestrictionsState } from '@/types'; import { RemoveRestrictionsState, QpdfInstanceExtended } from '@/types';
const pageState: RemoveRestrictionsState = { const pageState: RemoveRestrictionsState = {
file: null, file: null,
@@ -98,7 +98,7 @@ async function removeRestrictions() {
const inputPath = '/input.pdf'; const inputPath = '/input.pdf';
const outputPath = '/output.pdf'; const outputPath = '/output.pdf';
let qpdf: any; let qpdf: QpdfInstanceExtended;
const loaderModal = document.getElementById('loader-modal'); const loaderModal = document.getElementById('loader-modal');
const loaderText = document.getElementById('loader-text'); const loaderText = document.getElementById('loader-text');
@@ -127,12 +127,10 @@ async function removeRestrictions() {
try { try {
qpdf.callMain(args); qpdf.callMain(args);
} catch (qpdfError: any) { } catch (qpdfError: unknown) {
console.error('qpdf execution error:', qpdfError); console.error('qpdf execution error:', qpdfError);
if ( const qpdfMsg = qpdfError instanceof Error ? qpdfError.message : '';
qpdfError.message?.includes('password') || if (qpdfMsg.includes('password') || qpdfMsg.includes('encrypt')) {
qpdfError.message?.includes('encrypt')
) {
throw new Error( throw new Error(
'Failed to remove restrictions. The PDF may require the correct owner password.', 'Failed to remove restrictions. The PDF may require the correct owner password.',
{ cause: qpdfError } { cause: qpdfError }
@@ -140,8 +138,7 @@ async function removeRestrictions() {
} }
throw new Error( throw new Error(
'Failed to remove restrictions: ' + 'Failed to remove restrictions: ' + (qpdfMsg || 'Unknown error'),
(qpdfError.message || 'Unknown error'),
{ cause: qpdfError } { cause: qpdfError }
); );
} }
@@ -153,7 +150,9 @@ async function removeRestrictions() {
throw new Error('Operation resulted in an empty file.'); 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}`); downloadFile(blob, `unrestricted-${pageState.file.name}`);
if (loaderModal) loaderModal.classList.add('hidden'); if (loaderModal) loaderModal.classList.add('hidden');
@@ -166,12 +165,12 @@ async function removeRestrictions() {
resetState(); resetState();
} }
); );
} catch (error: any) { } catch (error: unknown) {
console.error('Error during restriction removal:', error); console.error('Error during restriction removal:', error);
if (loaderModal) loaderModal.classList.add('hidden'); if (loaderModal) loaderModal.classList.add('hidden');
showAlert( showAlert(
'Operation Failed', '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 { } finally {
try { try {

View File

@@ -8,11 +8,12 @@ import { state } from '../state.js';
import JSZip from 'jszip'; import JSZip from 'jszip';
import { deduplicateFileName } from '../utils/deduplicate-filename.js'; import { deduplicateFileName } from '../utils/deduplicate-filename.js';
import { batchDecryptIfNeeded } from '../utils/password-prompt.js'; import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
import type { QpdfInstanceExtended } from '@/types';
export async function repairPdfFile(file: File): Promise<Uint8Array | null> { export async function repairPdfFile(file: File): Promise<Uint8Array | null> {
const inputPath = '/input.pdf'; const inputPath = '/input.pdf';
const outputPath = '/repaired_form.pdf'; const outputPath = '/repaired_form.pdf';
let qpdf: any; let qpdf: QpdfInstanceExtended;
try { try {
qpdf = await initializeQpdf(); qpdf = await initializeQpdf();

View File

@@ -1,9 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { import { downloadFile, formatBytes } from '../utils/helpers.js';
downloadFile,
readFileAsArrayBuffer,
formatBytes,
} from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { import {
@@ -158,11 +154,11 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -1,11 +1,12 @@
import { categories } from '../config/tools.js'; import { categories } from '../config/tools.js';
import type { ToolEntry } from '@/types';
export class ShortcutsManager { export class ShortcutsManager {
private static STORAGE_KEY = 'bentopdf_shortcuts'; private static STORAGE_KEY = 'bentopdf_shortcuts';
private static shortcuts: Map<string, string> = new Map(); private static shortcuts: Map<string, string> = new Map();
private static defaultShortcuts: Map<string, string> = new Map(); private static defaultShortcuts: Map<string, string> = new Map();
private static getToolId(tool: any): string { private static getToolId(tool: ToolEntry): string {
if (tool.id) return tool.id; if (tool.id) return tool.id;
if (tool.href) { if (tool.href) {
// Extract filename without extension from href // Extract filename without extension from href
@@ -34,9 +35,9 @@ export class ShortcutsManager {
const parsed = JSON.parse(stored); const parsed = JSON.parse(stored);
this.shortcuts = new Map(Object.entries(parsed)); this.shortcuts = new Map(Object.entries(parsed));
const allTools = categories.flatMap(c => c.tools); const allTools = categories.flatMap((c) => c.tools);
const validToolIds = allTools.map(t => this.getToolId(t)); const validToolIds = allTools.map((t) => this.getToolId(t));
for (const [toolId, _] of this.shortcuts.entries()) { for (const [toolId] of this.shortcuts.entries()) {
if (!validToolIds.includes(toolId)) { if (!validToolIds.includes(toolId)) {
this.shortcuts.delete(toolId); this.shortcuts.delete(toolId);
} }
@@ -97,17 +98,19 @@ export class ShortcutsManager {
// Create a map with all tools, defaulting to empty string if not set // Create a map with all tools, defaulting to empty string if not set
const exportObj: Record<string, string> = {}; const exportObj: Record<string, string> = {};
const allTools = categories.flatMap(c => c.tools); const allTools = categories.flatMap((c) => c.tools);
allTools.forEach(tool => { allTools.forEach((tool) => {
const toolId = this.getToolId(tool); const toolId = this.getToolId(tool);
exportObj[toolId] = this.shortcuts.get(toolId) || ''; exportObj[toolId] = this.shortcuts.get(toolId) || '';
}); });
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj, null, 2)); const dataStr =
'data:text/json;charset=utf-8,' +
encodeURIComponent(JSON.stringify(exportObj, null, 2));
const downloadAnchorNode = document.createElement('a'); const downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute('href', dataStr);
downloadAnchorNode.setAttribute("download", "bentopdf_shortcuts.json"); downloadAnchorNode.setAttribute('download', 'bentopdf_shortcuts.json');
document.body.appendChild(downloadAnchorNode); document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click(); downloadAnchorNode.click();
downloadAnchorNode.remove(); downloadAnchorNode.remove();
@@ -132,10 +135,16 @@ export class ShortcutsManager {
} }
private static setupGlobalListener() { private static setupGlobalListener() {
window.addEventListener('keydown', (e) => { window.addEventListener(
'keydown',
(e) => {
// Ignore if typing in an input or textarea // Ignore if typing in an input or textarea
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) { if (
target.tagName === 'INPUT' ||
target.tagName === 'TEXTAREA' ||
target.isContentEditable
) {
return; return;
} }
@@ -176,17 +185,18 @@ export class ShortcutsManager {
e.stopPropagation(); e.stopPropagation();
// Find the tool in categories // Find the tool in categories
const allTools = categories.flatMap(c => c.tools); const allTools = categories.flatMap((c) => c.tools);
const tool = allTools.find(t => this.getToolId(t) === toolId); const tool = allTools.find((t) => this.getToolId(t) === toolId);
if (tool && (tool as any).href) { if (tool && tool.href) {
// All tools now use href - navigate to the page const href = tool.href;
const href = (tool as any).href;
window.location.href = href.startsWith('/') ? href : `/${href}`; window.location.href = href.startsWith('/') ? href : `/${href}`;
} }
return; return;
} }
} }
}, { capture: true }); },
{ capture: true }
);
} }
} }

View File

@@ -16,7 +16,6 @@ import JSZip from 'jszip';
import { PDFDocument as PDFLibDocument } from 'pdf-lib'; import { PDFDocument as PDFLibDocument } from 'pdf-lib';
import { loadPdfDocument } from '../utils/load-pdf-document.js'; import { loadPdfDocument } from '../utils/load-pdf-document.js';
// @ts-ignore
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs', 'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url import.meta.url
@@ -178,7 +177,7 @@ document.addEventListener('DOMContentLoaded', () => {
imgContainer.append(img, pageNumDiv); imgContainer.append(img, pageNumDiv);
wrapper.appendChild(imgContainer); wrapper.appendChild(imgContainer);
const handleSelection = (e: any) => { const handleSelection = (e: Event) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -286,7 +285,7 @@ document.addEventListener('DOMContentLoaded', () => {
let indicesToExtract: number[] = []; let indicesToExtract: number[] = [];
switch (splitMode) { switch (splitMode) {
case 'range': case 'range': {
const pageRangeInput = ( const pageRangeInput = (
document.getElementById('page-range') as HTMLInputElement document.getElementById('page-range') as HTMLInputElement
).value; ).value;
@@ -331,7 +330,7 @@ document.addEventListener('DOMContentLoaded', () => {
const group = rangeGroups[i]; const group = rangeGroups[i];
const newPdf = await PDFLibDocument.create(); const newPdf = await PDFLibDocument.create();
const copiedPages = await newPdf.copyPages(state.pdfDoc, group); 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 pdfBytes = await newPdf.save();
const minPage = Math.min(...group) + 1; const minPage = Math.min(...group) + 1;
@@ -357,8 +356,9 @@ document.addEventListener('DOMContentLoaded', () => {
return; return;
} }
break; break;
}
case 'even-odd': case 'even-odd': {
const choiceElement = document.querySelector( const choiceElement = document.querySelector(
'input[name="even-odd-choice"]:checked' 'input[name="even-odd-choice"]:checked'
) as HTMLInputElement; ) as HTMLInputElement;
@@ -371,6 +371,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (choice === 'odd' && (i + 1) % 2 !== 0) indicesToExtract.push(i); if (choice === 'odd' && (i + 1) % 2 !== 0) indicesToExtract.push(i);
} }
break; break;
}
case 'all': case 'all':
indicesToExtract = Array.from({ length: totalPages }, (_, i) => i); indicesToExtract = Array.from({ length: totalPages }, (_, i) => i);
break; break;
@@ -379,8 +380,7 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.page-thumbnail-wrapper.selected') document.querySelectorAll('.page-thumbnail-wrapper.selected')
).map((el) => parseInt((el as HTMLElement).dataset.pageIndex || '0')); ).map((el) => parseInt((el as HTMLElement).dataset.pageIndex || '0'));
break; break;
case 'bookmarks': case 'bookmarks': {
// Check if CPDF is configured
if (!isCpdfAvailable()) { if (!isCpdfAvailable()) {
showWasmRequiredDialog('cpdf'); showWasmRequiredDialog('cpdf');
hideLoader(); hideLoader();
@@ -404,7 +404,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (bookmarkLevel === 'all' || level === parseInt(bookmarkLevel)) { if (bookmarkLevel === 'all' || level === parseInt(bookmarkLevel)) {
if (page > 1 && !splitPages.includes(page - 1)) { 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, state.pdfDoc,
pageIndices pageIndices
); );
copiedPages.forEach((page: any) => newPdf.addPage(page)); copiedPages.forEach((page) => newPdf.addPage(page));
const pdfBytes2 = await newPdf.save(); const pdfBytes2 = await newPdf.save();
zip.file(`split-${i + 1}.pdf`, pdfBytes2); zip.file(`split-${i + 1}.pdf`, pdfBytes2);
} }
@@ -446,8 +446,9 @@ document.addEventListener('DOMContentLoaded', () => {
resetState(); resetState();
}); });
return; return;
}
case 'n-times': case 'n-times': {
const nValue = parseInt( const nValue = parseInt(
(document.getElementById('split-n-value') as HTMLInputElement) (document.getElementById('split-n-value') as HTMLInputElement)
?.value || '5' ?.value || '5'
@@ -470,7 +471,7 @@ document.addEventListener('DOMContentLoaded', () => {
state.pdfDoc, state.pdfDoc,
pageIndices pageIndices
); );
copiedPages.forEach((page: any) => newPdf.addPage(page)); copiedPages.forEach((page) => newPdf.addPage(page));
const pdfBytes3 = await newPdf.save(); const pdfBytes3 = await newPdf.save();
zip2.file(`split-${i + 1}.pdf`, pdfBytes3); zip2.file(`split-${i + 1}.pdf`, pdfBytes3);
} }
@@ -483,6 +484,7 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
return; return;
} }
}
const uniqueIndices = [...new Set(indicesToExtract)]; const uniqueIndices = [...new Set(indicesToExtract)];
if ( if (
@@ -506,8 +508,7 @@ document.addEventListener('DOMContentLoaded', () => {
]); ]);
newPdf.addPage(copiedPage); newPdf.addPage(copiedPage);
const pdfBytes = await newPdf.save(); const pdfBytes = await newPdf.save();
// @ts-ignore zip.file(`page-${index + 1}.pdf`, new Uint8Array(pdfBytes));
zip.file(`page-${index + 1}.pdf`, pdfBytes);
} }
const zipBlob = await zip.generateAsync({ type: 'blob' }); const zipBlob = await zip.generateAsync({ type: 'blob' });
downloadFile(zipBlob, 'split-pages.zip'); downloadFile(zipBlob, 'split-pages.zip');
@@ -517,7 +518,7 @@ document.addEventListener('DOMContentLoaded', () => {
state.pdfDoc, state.pdfDoc,
uniqueIndices as number[] uniqueIndices as number[]
); );
copiedPages.forEach((page: any) => newPdf.addPage(page)); copiedPages.forEach((page) => newPdf.addPage(page));
const pdfBytes = await newPdf.save(); const pdfBytes = await newPdf.save();
downloadFile( downloadFile(
new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }), new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }),
@@ -532,11 +533,13 @@ document.addEventListener('DOMContentLoaded', () => {
showAlert('Success', 'PDF split successfully!', 'success', () => { showAlert('Success', 'PDF split successfully!', 'success', () => {
resetState(); resetState();
}); });
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
showAlert( showAlert(
'Error', '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 { } finally {
hideLoader(); hideLoader();

View File

@@ -1,10 +1,6 @@
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { showAlert, showLoader, hideLoader } from '../ui.js'; import { showAlert, showLoader, hideLoader } from '../ui.js';
import { import { downloadFile, formatBytes } from '../utils/helpers.js';
downloadFile,
readFileAsArrayBuffer,
formatBytes,
} from '../utils/helpers.js';
import { PDFDocument as PDFLibDocument } from 'pdf-lib'; import { PDFDocument as PDFLibDocument } from 'pdf-lib';
import { import {
getSelectedQuality, getSelectedQuality,
@@ -261,9 +257,9 @@ async function convertToPdf() {
showAlert('Success', 'PDF created successfully!', 'success', () => { showAlert('Success', 'PDF created successfully!', 'success', () => {
resetState(); resetState();
}); });
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
showAlert('Conversion Error', e.message); showAlert('Conversion Error', e instanceof Error ? e.message : String(e));
} finally { } finally {
hideLoader(); hideLoader();
} }

View File

@@ -36,15 +36,6 @@ const backToToolsBtn = document.getElementById(
'back-to-tools' 'back-to-tools'
) as HTMLButtonElement; ) as HTMLButtonElement;
interface GenerateTOCMessage {
command: 'generate-toc';
pdfData: ArrayBuffer;
title: string;
fontSize: number;
fontFamily: number;
addBookmark: boolean;
}
interface TOCSuccessResponse { interface TOCSuccessResponse {
status: 'success'; status: 'success';
pdfBytes: ArrayBuffer; pdfBytes: ArrayBuffer;

View File

@@ -1,9 +1,7 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
let files: File[] = []; let files: File[] = [];
let currentMode: 'upload' | 'text' = 'upload'; let currentMode: 'upload' | 'text' = 'upload';
@@ -140,9 +138,12 @@ async function convert() {
resetState(); resetState();
} }
); );
} catch (e: any) { } catch (e: unknown) {
console.error('[TxtToPDF] Error:', e); 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 { } finally {
hideLoader(); hideLoader();
} }

View File

@@ -100,7 +100,13 @@ export function validateSignature(
Array.from(signature.contents) Array.from(signature.contents)
); );
const asn1 = forge.asn1.fromDer(binaryString); 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) { if (!p7.certificates || p7.certificates.length === 0) {
result.errorMessage = 'No certificates found in signature'; result.errorMessage = 'No certificates found in signature';
@@ -166,10 +172,8 @@ export function validateSignature(
} else { } else {
// Try to extract from authenticated attributes // Try to extract from authenticated attributes
try { try {
const signedData = p7 as any; if (p7.rawCapture?.authenticatedAttributes) {
if (signedData.rawCapture?.authenticatedAttributes) { for (const attr of p7.rawCapture.authenticatedAttributes) {
// Look for signing time attribute
for (const attr of signedData.rawCapture.authenticatedAttributes) {
if (attr.type === forge.pki.oids.signingTime) { if (attr.type === forge.pki.oids.signingTime) {
result.signatureDate = attr.value; result.signatureDate = attr.value;
break; break;
@@ -185,7 +189,7 @@ export function validateSignature(
} }
if (signature.byteRange && signature.byteRange.length === 4) { 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 totalCovered = len1 + len2;
const expectedEnd = start2 + len2; const expectedEnd = start2 + len2;

View File

@@ -160,9 +160,13 @@ function updateUI() {
} }
} }
function sanitizeImageAsJpeg(imageBytes: any) { function sanitizeImageAsJpeg(imageBytes: Uint8Array | ArrayBuffer) {
return new Promise((resolve, reject) => { 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 imageUrl = URL.createObjectURL(blob);
const img = new Image(); const img = new Image();
@@ -248,9 +252,9 @@ async function convertToPdf() {
showAlert('Success', 'PDF created successfully!', 'success', () => { showAlert('Success', 'PDF created successfully!', 'success', () => {
resetState(); resetState();
}); });
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
showAlert('Conversion Error', e.message); showAlert('Conversion Error', e instanceof Error ? e.message : String(e));
} finally { } finally {
hideLoader(); hideLoader();
} }

View File

@@ -1,9 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { import { downloadFile, formatBytes } from '../utils/helpers.js';
downloadFile,
readFileAsArrayBuffer,
formatBytes,
} from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { import {
@@ -187,13 +183,16 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
console.error('[Word2PDF] ERROR:', e); console.error('[Word2PDF] ERROR:', e);
console.error('[Word2PDF] Error stack:', e.stack); console.error(
'[Word2PDF] Error stack:',
e instanceof Error ? e.stack : ''
);
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -15,13 +15,18 @@ export async function wordToPdf() {
try { try {
const mammothOptions = { const mammothOptions = {
// @ts-expect-error TS(2304) FIXME: Cannot find name 'mammoth'. // @ts-expect-error TS(2304) FIXME: Cannot find name 'mammoth'.
convertImage: mammoth.images.inline((element: any) => { convertImage: mammoth.images.inline(
return element.read('base64').then((imageBuffer: any) => { (element: {
read: (encoding: string) => Promise<string>;
contentType: string;
}) => {
return element.read('base64').then((imageBuffer: string) => {
return { return {
src: `data:${element.contentType};base64,${imageBuffer}`, src: `data:${element.contentType};base64,${imageBuffer}`,
}; };
}); });
}), }
),
}; };
const arrayBuffer = await readFileAsArrayBuffer(file); const arrayBuffer = await readFileAsArrayBuffer(file);
// @ts-expect-error TS(2304) FIXME: Cannot find name 'mammoth'. // @ts-expect-error TS(2304) FIXME: Cannot find name 'mammoth'.
@@ -77,7 +82,18 @@ export async function wordToPdf() {
}); });
await doc.html(previewContent, { 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 links = previewContent.querySelectorAll('a');
const pageHeight = doc.internal.pageSize.getHeight(); const pageHeight = doc.internal.pageSize.getHeight();
const containerRect = previewContent.getBoundingClientRect(); // Get container's position const containerRect = previewContent.getBoundingClientRect(); // Get container's position

View File

@@ -150,12 +150,12 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e); console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e);
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -150,12 +150,12 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
hideLoader(); hideLoader();
console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e); console.error(`[${FILETYPE_NAME}ToPDF] Error:`, e);
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -2,9 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js'; import { downloadFile, formatBytes } from '../utils/helpers.js';
import { state } from '../state.js'; import { state } from '../state.js';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js'; import { loadPyMuPDF } from '../utils/pymupdf-loader.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { loadPyMuPDF, isPyMuPDFAvailable } from '../utils/pymupdf-loader.js';
import { deduplicateFileName } from '../utils/deduplicate-filename.js'; import { deduplicateFileName } from '../utils/deduplicate-filename.js';
const FILETYPE = 'xps'; const FILETYPE = 'xps';
@@ -147,12 +145,12 @@ document.addEventListener('DOMContentLoaded', () => {
() => resetState() () => resetState()
); );
} }
} catch (e: any) { } catch (e: unknown) {
console.error(`[${TOOL_NAME}2PDF] ERROR:`, e); console.error(`[${TOOL_NAME}2PDF] ERROR:`, e);
hideLoader(); hideLoader();
showAlert( showAlert(
'Error', 'Error',
`An error occurred during conversion. Error: ${e.message}` `An error occurred during conversion. Error: ${e instanceof Error ? e.message : String(e)}`
); );
} }
}; };

View File

@@ -6,13 +6,11 @@ import '@phosphor-icons/web/regular';
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';
import '../css/styles.css'; import '../css/styles.css';
import { formatShortcutDisplay, formatStars } from './utils/helpers.js'; import { formatShortcutDisplay, formatStars } from './utils/helpers.js';
import { APP_VERSION } from '../version.js';
import { import {
initI18n, initI18n,
applyTranslations, applyTranslations,
rewriteLinks, rewriteLinks,
injectLanguageSwitcher, injectLanguageSwitcher,
createLanguageSwitcher,
t, t,
} from './i18n/index.js'; } from './i18n/index.js';
import { import {
@@ -501,7 +499,7 @@ const init = async () => {
} }
}); });
dom.toolGrid.addEventListener('click', (e) => { dom.toolGrid.addEventListener('click', () => {
// All tools now use href and navigate directly - no modal handling needed // All tools now use href and navigate directly - no modal handling needed
}); });
} }
@@ -889,7 +887,7 @@ const init = async () => {
}); });
} }
function getToolId(tool: any): string { function getToolId(tool: { id?: string; href?: string }): string {
if (tool.id) return tool.id; if (tool.id) return tool.id;
if (tool.href) { if (tool.href) {
const match = tool.href.match(/\/([^/]+)\.html$/); const match = tool.href.match(/\/([^/]+)\.html$/);

View File

@@ -1,4 +1,12 @@
export const state = { import type { PDFDocument } from 'pdf-lib';
export const state: {
activeTool: string | null;
files: File[];
pdfDoc: PDFDocument | null;
pdfPages: unknown[];
currentPdfUrl: string | null;
} = {
activeTool: null, activeTool: null,
files: [], files: [],
pdfDoc: null, pdfDoc: null,

View File

@@ -1,5 +1,7 @@
import type { PDFDocumentProxy } from 'pdfjs-dist';
export interface AlternateMergeState { export interface AlternateMergeState {
files: File[]; files: File[];
pdfBytes: Map<string, ArrayBuffer>; pdfBytes: Map<string, ArrayBuffer>;
pdfDocs: Map<string, any>; pdfDocs: Map<string, PDFDocumentProxy>;
} }

View File

@@ -0,0 +1,7 @@
export interface CropBox {
x: number;
y: number;
width: number;
height: number;
scale: number | 'fit';
}

View File

@@ -1,8 +1,18 @@
import type { PDFDocumentProxy } from 'pdfjs-dist';
import Cropper from 'cropperjs';
export interface CropPercentages {
x: number;
y: number;
width: number;
height: number;
}
export interface CropperState { export interface CropperState {
pdfDoc: any; pdfDoc: PDFDocumentProxy | null;
currentPageNum: number; currentPageNum: number;
cropper: any; cropper: Cropper | null;
originalPdfBytes: ArrayBuffer | null; originalPdfBytes: ArrayBuffer | null;
pageCrops: Record<number, any>; pageCrops: Record<number, CropPercentages>;
file: File | null; file: File | null;
} }

View File

@@ -1,7 +1,10 @@
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
import type { PDFDocumentProxy } from 'pdfjs-dist';
export interface DeletePagesState { export interface DeletePagesState {
file: File | null; file: File | null;
pdfDoc: any; pdfDoc: PDFLibDocument | null;
pdfJsDoc: any; pdfJsDoc: PDFDocumentProxy | null;
totalPages: number; totalPages: number;
pagesToDelete: Set<number>; pagesToDelete: Set<number>;
} }

View File

@@ -0,0 +1,15 @@
export interface DocManagerPlugin {
onDocumentClosed: (
callback: (data: { id?: string } | string) => void
) => void;
onDocumentOpened: (
callback: (data: { id?: string; name?: string }) => void
) => void;
openDocumentBuffer: (opts: {
buffer: ArrayBuffer;
name?: string;
autoActivate?: boolean;
}) => void;
closeDocument: (id: string) => void;
saveAsCopy: (id: string) => Promise<Uint8Array>;
}

View File

@@ -56,3 +56,12 @@ export * from './pdf-to-tiff-type.ts';
export * from './pdf-to-cbz-type.ts'; export * from './pdf-to-cbz-type.ts';
export * from './password-prompt-type.ts'; export * from './password-prompt-type.ts';
export * from './config-types.ts'; export * from './config-types.ts';
export * from './utils-types.ts';
export * from './merge-worker-type.ts';
export * from './canvas-editor-type.ts';
export * from './edit-pdf-type.ts';
export * from './redact-type.ts';
export * from './shortcuts-type.ts';
export * from './ui-type.ts';
export * from './markdown-editor-type.ts';
export * from './sanitize-type.ts';

View File

@@ -0,0 +1,28 @@
export interface MarkdownItOptions {
html: boolean;
breaks: boolean;
linkify: boolean;
typographer: boolean;
highlight?: (str: string, lang: string) => string;
}
export interface MarkdownItToken {
attrSet(name: string, value: string): void;
[key: string]: unknown;
}
export interface MarkdownItRendererSelf {
renderToken(
tokens: MarkdownItToken[],
idx: number,
options: MarkdownItOptions
): string;
}
export type MarkdownItRenderRule = (
tokens: MarkdownItToken[],
idx: number,
options: MarkdownItOptions,
env: unknown,
self: MarkdownItRendererSelf
) => string;

View File

@@ -1,5 +1,7 @@
import type { PDFDocumentProxy } from 'pdfjs-dist';
export interface MergeState { export interface MergeState {
files: File[]; files: File[];
pdfBytes: Map<string, ArrayBuffer>; pdfBytes: Map<string, ArrayBuffer>;
pdfDocs: Map<string, any>; pdfDocs: Map<string, PDFDocumentProxy>;
} }

View File

@@ -0,0 +1,32 @@
export interface MergeJob {
fileName: string;
rangeType: 'all' | 'specific' | 'single' | 'range';
rangeString?: string;
pageIndex?: number;
startPage?: number;
endPage?: number;
}
export interface MergeFile {
name: string;
data: ArrayBuffer;
}
export interface MergeMessage {
command: 'merge';
files: MergeFile[];
jobs: MergeJob[];
cpdfUrl?: string;
}
export interface MergeSuccessResponse {
status: 'success';
pdfBytes: ArrayBuffer;
}
export interface MergeErrorResponse {
status: 'error';
message: string;
}
export type MergeResponse = MergeSuccessResponse | MergeErrorResponse;

View File

@@ -0,0 +1,7 @@
export interface RedactionRect {
pageIndex: number;
canvasX: number;
canvasY: number;
canvasWidth: number;
canvasHeight: number;
}

View File

@@ -0,0 +1,8 @@
import type { PDFDict } from 'pdf-lib';
export interface PDFDocumentInternal {
getInfoDict(): PDFDict;
javaScripts?: unknown[];
embeddedFiles?: unknown[];
fonts?: unknown[];
}

View File

@@ -0,0 +1,5 @@
export interface ToolEntry {
id?: string;
href?: string;
name?: string;
}

5
src/js/types/ui-type.ts Normal file
View File

@@ -0,0 +1,5 @@
export interface FileInputOptions {
multiple?: boolean;
accept?: string;
showControls?: boolean;
}

235
src/js/types/utils-types.ts Normal file
View File

@@ -0,0 +1,235 @@
import type { PDFName, PDFObject } from 'pdf-lib';
export interface PDFDictLike {
keys(): PDFName[];
values(): PDFObject[];
entries(): [PDFName, PDFObject][];
set(key: PDFName, value: PDFObject): void;
get(key: PDFName, preservePDFNull?: boolean): PDFObject | undefined;
has(key: PDFName): boolean;
delete(key: PDFName): boolean;
lookup(key: PDFName): PDFObject | undefined;
asArray?(): PDFObject[];
}
export interface WindowWithCoherentPdf {
coherentpdf?: unknown;
}
export interface WindowWithLucide {
lucide?: {
createIcons(): void;
};
}
export interface WindowWithI18next {
i18next?: {
t(key: string): string;
};
}
export interface GlobalScopeWithGhostscript {
loadGS?: (config: { baseUrl: string }) => Promise<GhostscriptDynamicInstance>;
GhostscriptWASM?: new (url: string) => GhostscriptDynamicInstance;
}
export interface GhostscriptDynamicInstance {
convertToPDFA?(pdfBuffer: ArrayBuffer, profile: string): Promise<ArrayBuffer>;
fontToOutline?(pdfBuffer: ArrayBuffer): Promise<ArrayBuffer>;
init?(): Promise<void>;
}
export interface PyMuPDFCompressOptions {
images: {
enabled: boolean;
quality: number;
dpiTarget: number;
dpiThreshold: number;
convertToGray: boolean;
};
scrub: {
metadata: boolean;
thumbnails: boolean;
xmlMetadata?: boolean;
};
subsetFonts: boolean;
save: {
garbage: 4;
deflate: boolean;
clean: boolean;
useObjstms: boolean;
};
}
export interface PyMuPDFExtractTextOptions {
format?: string;
pages?: number[];
}
export interface PyMuPDFRasterizeOptions {
dpi?: number;
format?: string;
pages?: number[];
grayscale?: boolean;
quality?: number;
}
export interface PyMuPDFDocument {
pageCount: number;
pages: (() => PyMuPDFPage[]) & PyMuPDFPage[];
needsPass: boolean;
isEncrypted: boolean;
authenticate(password: string): boolean;
getPage(index: number): PyMuPDFPage;
save(): Uint8Array;
close(): void;
getLayerConfig?(): unknown[];
addLayer?(name: string): { number: number; xref: number };
setLayerConfig?(layers: unknown[]): 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 };
applyRedactions?(): void;
searchFor?(text: string, pageNum: number): unknown[];
}
export interface PyMuPDFPage {
getText(format?: string): string;
getImages(): Array<{ data: Uint8Array; ext: string; xref: number }>;
extractTables?(): unknown[];
findTables(): Array<{
rows: (string | null)[][];
markdown: string;
rowCount: number;
colCount: number;
}>;
extractImage(xref: number): { data: Uint8Array; ext: string } | null;
toSvg?(): string;
toPixmap?(options?: { dpi?: number }): {
toBlob(format: string, quality?: number): Blob;
};
addRedactAnnot?(rect: unknown): void;
searchFor(text: string): unknown[];
addRedaction(rect: unknown, text: string, fill: unknown): void;
applyRedactions(): void;
}
export interface PyMuPDFTextToPdfOptions {
fontSize?: number;
pageSize?: string;
fontName?: string;
textColor?: string;
margins?: number;
}
export interface PyMuPDFDeskewOptions {
threshold?: number;
dpi?: number;
}
export interface PyMuPDFInstance {
load(): Promise<void>;
compressPdf(
file: Blob,
options: PyMuPDFCompressOptions
): Promise<{ blob: Blob; compressedSize: number; usedFallback?: boolean }>;
convertToPdf(
file: Blob | File,
ext: string | { filetype: string }
): Promise<Blob>;
extractText(file: Blob, options?: PyMuPDFExtractTextOptions): Promise<string>;
extractImages(file: Blob): Promise<Array<{ data: Uint8Array; ext: string }>>;
extractTables(file: Blob): Promise<unknown[]>;
toSvg(file: Blob, pageNum: number): Promise<string>;
renderPageToImage(file: Blob, pageNum: number, scale: number): Promise<Blob>;
getPageCount(file: Blob): Promise<number>;
rasterizePdf(
file: Blob | File,
options: PyMuPDFRasterizeOptions
): Promise<Blob>;
open(file: Blob | File, password?: string): Promise<PyMuPDFDocument>;
textToPdf(text: string, options?: PyMuPDFTextToPdfOptions): Promise<Blob>;
pdfToDocx(file: Blob | File): Promise<Blob>;
pdfToMarkdown(
file: Blob | File,
options?: { includeImages?: boolean }
): Promise<string>;
pdfToText(file: Blob | File): Promise<string>;
deskewPdf(
file: Blob,
options?: PyMuPDFDeskewOptions
): Promise<{
pdf: Blob;
result: {
totalPages: number;
correctedPages: number;
angles: number[];
corrected: boolean[];
};
}>;
imageToPdf(file: File, options?: { imageType?: string }): Promise<Blob>;
imagesToPdf(files: File[]): Promise<Blob>;
htmlToPdf(html: string, options: unknown): Promise<Blob>;
pdfToLlamaIndex(file: File): Promise<unknown>;
}
export type { QpdfInstance as QpdfWasmInstance } from '@neslinesli93/qpdf-wasm';
export interface QpdfInstanceExtended {
callMain: (args: string[]) => number;
FS: EmscriptenFSExtended;
}
export interface EmscriptenFSExtended {
mkdir: (path: string) => void;
mount: (
type: unknown,
opts: { blobs: { name: string; data: Blob }[] },
mountPoint: string
) => void;
unmount: (mountPoint: string) => void;
writeFile: (
path: string,
data: Uint8Array | string,
opts?: { encoding?: string }
) => void;
readFile: (path: string, opts?: { encoding?: string }) => Uint8Array;
unlink: (path: string) => void;
analyzePath: (path: string) => { exists: boolean };
}
export interface CpdfInstance {
setSlow: () => void;
fromMemory: (data: Uint8Array, userpw: string) => unknown;
isEncrypted: (pdf: unknown) => boolean;
decryptPdf: (pdf: unknown, password: string) => void;
decryptPdfOwner: (pdf: unknown, password: string) => void;
toMemory: (pdf: unknown, linearize: boolean, makeId: boolean) => Uint8Array;
deletePdf: (pdf: unknown) => void;
startGetBookmarkInfo: (pdf: unknown) => void;
numberBookmarks: () => number;
getBookmarkLevel: (index: number) => number;
getBookmarkPage: (pdf: unknown, index: number) => number;
endGetBookmarkInfo: () => void;
parsePagespec: (pdf: unknown, pagespec: string) => unknown;
removePageLabels: (pdf: unknown) => void;
all: (pdf: unknown) => unknown;
addPageLabels: (
pdf: unknown,
style: unknown,
prefix: string,
offset: number,
range: unknown,
progress: boolean
) => void;
decimalArabic: number;
lowercaseRoman: number;
uppercaseRoman: number;
lowercaseLetters: number;
uppercaseLetters: number;
noLabelPrefixOnly?: number;
}

View File

@@ -1,6 +1,5 @@
import { resetState } from './state.js'; import { resetState } from './state.js';
import { formatBytes, getPDFDocument } from './utils/helpers.js'; import { formatBytes, getPDFDocument } from './utils/helpers.js';
import { tesseractLanguages } from './config/tesseract-languages.js';
import { import {
renderPagesProgressively, renderPagesProgressively,
cleanupLazyRendering, cleanupLazyRendering,
@@ -14,6 +13,7 @@ import {
} from './utils/rotation-state.js'; } from './utils/rotation-state.js';
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';
import { t } from './i18n/i18n'; import { t } from './i18n/i18n';
import type { FileInputOptions } from '@/types';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs', 'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -112,8 +112,8 @@ export const hideLoader = () => {
}; };
export const showAlert = ( export const showAlert = (
title: any, title: string,
message: any, message: string,
type: string = 'error', type: string = 'error',
callback?: () => void callback?: () => void
) => { ) => {
@@ -137,7 +137,7 @@ export const hideAlert = () => {
if (dom.alertModal) dom.alertModal.classList.add('hidden'); if (dom.alertModal) dom.alertModal.classList.add('hidden');
}; };
export const switchView = (view: any) => { export const switchView = (view: string) => {
if (view === 'grid') { if (view === 'grid') {
dom.gridView.classList.remove('hidden'); dom.gridView.classList.remove('hidden');
dom.toolInterface.classList.add('hidden'); dom.toolInterface.classList.add('hidden');
@@ -170,11 +170,13 @@ export const switchView = (view: any) => {
} }
}; };
const thumbnailState = { const thumbnailState: {
sortableInstances: Record<string, Sortable>;
} = {
sortableInstances: {}, sortableInstances: {},
}; };
function initializeOrganizeSortable(containerId: any) { function initializeOrganizeSortable(containerId: string) {
const container = document.getElementById(containerId); const container = document.getElementById(containerId);
if (!container) return; if (!container) return;
@@ -189,10 +191,10 @@ function initializeOrganizeSortable(containerId: any) {
dragClass: 'sortable-drag', dragClass: 'sortable-drag',
filter: '.delete-page-btn', filter: '.delete-page-btn',
preventOnFilter: true, preventOnFilter: true,
onStart: function (evt: any) { onStart: function (evt: Sortable.SortableEvent) {
evt.item.style.opacity = '0.5'; evt.item.style.opacity = '0.5';
}, },
onEnd: function (evt: any) { onEnd: function (evt: Sortable.SortableEvent) {
evt.item.style.opacity = '1'; evt.item.style.opacity = '1';
}, },
}); });
@@ -203,7 +205,10 @@ function initializeOrganizeSortable(containerId: any) {
* @param {string} toolId The ID of the active tool. * @param {string} toolId The ID of the active tool.
* @param {object} pdfDoc The loaded pdf-lib document instance. * @param {object} pdfDoc The loaded pdf-lib document instance.
*/ */
export const renderPageThumbnails = async (toolId: any, pdfDoc: any) => { export const renderPageThumbnails = async (
toolId: string,
pdfDoc: { save: () => Promise<Uint8Array> }
) => {
const containerId = const containerId =
toolId === 'organize' toolId === 'organize'
? 'page-organizer' ? 'page-organizer'
@@ -229,8 +234,7 @@ export const renderPageThumbnails = async (toolId: any, pdfDoc: any) => {
// Function to create wrapper element for each page // Function to create wrapper element for each page
const createWrapper = (canvas: HTMLCanvasElement, pageNumber: number) => { const createWrapper = (canvas: HTMLCanvasElement, pageNumber: number) => {
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
// @ts-expect-error TS(2322) FIXME: Type 'number' is not assignable to type 'string'. wrapper.dataset.pageIndex = String(pageNumber - 1);
wrapper.dataset.pageIndex = pageNumber - 1;
const imgContainer = document.createElement('div'); const imgContainer = document.createElement('div');
imgContainer.className = 'relative'; imgContainer.className = 'relative';
@@ -452,10 +456,10 @@ export const renderPageThumbnails = async (toolId: any, pdfDoc: any) => {
* @param {HTMLElement} container The DOM element to render the list into. * @param {HTMLElement} container The DOM element to render the list into.
* @param {File[]} files The array of file objects. * @param {File[]} files The array of file objects.
*/ */
export const renderFileDisplay = (container: any, files: any) => { export const renderFileDisplay = (container: HTMLElement, files: File[]) => {
container.textContent = ''; container.textContent = '';
if (files.length > 0) { if (files.length > 0) {
files.forEach((file: any) => { files.forEach((file: File) => {
const fileDiv = document.createElement('div'); const fileDiv = document.createElement('div');
fileDiv.className = fileDiv.className =
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
@@ -474,13 +478,10 @@ export const renderFileDisplay = (container: any, files: any) => {
} }
}; };
const createFileInputHTML = (options = {}) => { const createFileInputHTML = (options: FileInputOptions = {}) => {
// @ts-expect-error TS(2339) FIXME: Property 'multiple' does not exist on type '{}'.
const multiple = options.multiple ? 'multiple' : ''; const multiple = options.multiple ? 'multiple' : '';
// @ts-expect-error TS(2339) FIXME: Property 'accept' does not exist on type '{}'.
const acceptedFiles = options.accept || 'application/pdf'; const acceptedFiles = options.accept || 'application/pdf';
// @ts-expect-error TS(2339) FIXME: Property 'showControls' does not exist on type '{}... Remove this comment to see the full error message const showControls = options.showControls || false;
const showControls = options.showControls || false; // NEW: Add this parameter
return ` return `
<div id="drop-zone" class="relative flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-900 hover:bg-gray-700 transition-colors duration-300"> <div id="drop-zone" class="relative flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-900 hover:bg-gray-700 transition-colors duration-300">

View File

@@ -69,7 +69,10 @@ export async function performCondenseCompression(
scrub: { scrub: {
metadata: customSettings?.removeMetadata ?? preset.scrub.metadata, metadata: customSettings?.removeMetadata ?? preset.scrub.metadata,
thumbnails: customSettings?.removeThumbnails ?? preset.scrub.thumbnails, thumbnails: customSettings?.removeThumbnails ?? preset.scrub.thumbnails,
xmlMetadata: (preset.scrub as any).xmlMetadata ?? false, xmlMetadata:
('xmlMetadata' in preset.scrub
? (preset.scrub as { xmlMetadata?: boolean }).xmlMetadata
: undefined) ?? false,
}, },
subsetFonts: customSettings?.subsetFonts ?? preset.subsetFonts, subsetFonts: customSettings?.subsetFonts ?? preset.subsetFonts,
save: { save: {
@@ -95,8 +98,11 @@ export async function performCondenseCompression(
try { try {
const result = await pymupdf.compressPdf(fileBlob, fallbackOptions); const result = await pymupdf.compressPdf(fileBlob, fallbackOptions);
return { ...result, usedFallback: true }; return { ...result, usedFallback: true };
} catch (fallbackError: any) { } catch (fallbackError: unknown) {
const msg = fallbackError?.message || String(fallbackError); const msg =
fallbackError instanceof Error
? fallbackError.message
: String(fallbackError);
throw new Error(`PDF compression failed: ${msg}`, { throw new Error(`PDF compression failed: ${msg}`, {
cause: fallbackError, cause: fallbackError,
}); });

View File

@@ -1,4 +1,5 @@
import { WasmProvider } from './wasm-provider'; import { WasmProvider } from './wasm-provider';
import type { WindowWithCoherentPdf, CpdfInstance } from '@/types';
let cpdfLoaded = false; let cpdfLoaded = false;
let cpdfLoadPromise: Promise<void> | null = null; let cpdfLoadPromise: Promise<void> | null = null;
@@ -31,7 +32,7 @@ export async function isCpdfLoaded(): Promise<void> {
} }
cpdfLoadPromise = new Promise((resolve, reject) => { cpdfLoadPromise = new Promise((resolve, reject) => {
if (typeof (window as any).coherentpdf !== 'undefined') { if (typeof (window as WindowWithCoherentPdf).coherentpdf !== 'undefined') {
cpdfLoaded = true; cpdfLoaded = true;
resolve(); resolve();
return; return;
@@ -53,7 +54,7 @@ export async function isCpdfLoaded(): Promise<void> {
return cpdfLoadPromise; return cpdfLoadPromise;
} }
export async function getCpdf(): Promise<any> { export async function getCpdf(): Promise<CpdfInstance> {
await isCpdfLoaded(); await isCpdfLoaded();
return (window as any).coherentpdf; return (window as WindowWithCoherentPdf).coherentpdf as CpdfInstance;
} }

View File

@@ -27,7 +27,9 @@ export async function loadRuntimeConfig(): Promise<void> {
(c): c is string => typeof c === 'string' (c): c is string => typeof c === 'string'
); );
} }
} catch {} } catch {
console.error('[LOAD_RUNTIME_CONFIG] Failed to load runtime configuration');
}
} }
export function isToolDisabled(toolId: string): boolean { export function isToolDisabled(toolId: string): boolean {

View File

@@ -1,7 +1,11 @@
import { WasmProvider } from './wasm-provider.js'; import { WasmProvider } from './wasm-provider.js';
import type {
GlobalScopeWithGhostscript,
GhostscriptDynamicInstance,
} from '@/types';
let cachedGS: any = null; let cachedGS: GhostscriptInterface | null = null;
let loadPromise: Promise<any> | null = null; let loadPromise: Promise<GhostscriptInterface> | null = null;
export interface GhostscriptInterface { export interface GhostscriptInterface {
convertToPDFA(pdfBuffer: ArrayBuffer, profile: string): Promise<ArrayBuffer>; convertToPDFA(pdfBuffer: ArrayBuffer, profile: string): Promise<ArrayBuffer>;
@@ -32,16 +36,20 @@ export async function loadGhostscript(): Promise<GhostscriptInterface> {
await loadScript(wrapperUrl); await loadScript(wrapperUrl);
const globalScope = const globalScope = (
typeof globalThis !== 'undefined' ? globalThis : window; typeof globalThis !== 'undefined' ? globalThis : window
) as typeof globalThis & GlobalScopeWithGhostscript;
if (typeof (globalScope as any).loadGS === 'function') { if (typeof globalScope.loadGS === 'function') {
cachedGS = await (globalScope as any).loadGS({ const instance = await globalScope.loadGS({
baseUrl: normalizedUrl, baseUrl: normalizedUrl,
}); });
} else if (typeof (globalScope as any).GhostscriptWASM === 'function') { cachedGS = instance as unknown as GhostscriptInterface;
cachedGS = new (globalScope as any).GhostscriptWASM(normalizedUrl); } else if (typeof globalScope.GhostscriptWASM === 'function') {
await cachedGS.init?.(); const instance: GhostscriptDynamicInstance =
new globalScope.GhostscriptWASM(normalizedUrl);
await instance.init?.();
cachedGS = instance as unknown as GhostscriptInterface;
} else { } else {
throw new Error( throw new Error(
'Ghostscript wrapper did not expose expected interface. Expected loadGS() or GhostscriptWASM class.' 'Ghostscript wrapper did not expose expected interface. Expected loadGS() or GhostscriptWASM class.'
@@ -49,10 +57,11 @@ export async function loadGhostscript(): Promise<GhostscriptInterface> {
} }
return cachedGS; return cachedGS;
} catch (error: any) { } catch (error: unknown) {
loadPromise = null; loadPromise = null;
const msg = error instanceof Error ? error.message : String(error);
throw new Error( throw new Error(
`Failed to load Ghostscript from ${normalizedUrl}: ${error.message}`, `Failed to load Ghostscript from ${normalizedUrl}: ${msg}`,
{ cause: error } { cause: error }
); );
} }

View File

@@ -1,8 +1,10 @@
import createModule from '@neslinesli93/qpdf-wasm'; import createModule from '@neslinesli93/qpdf-wasm';
import type { QpdfInstanceExtended } from '@/types';
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { createIcons } from 'lucide'; import { createIcons } from 'lucide';
import { state, resetState } from '../state.js'; import { state, resetState } from '../state.js';
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';
import type { DocumentInitParameters } from 'pdfjs-dist/types/src/display/api';
const STANDARD_SIZES = { const STANDARD_SIZES = {
A4: { width: 595.28, height: 841.89 }, A4: { width: 595.28, height: 841.89 },
@@ -13,7 +15,7 @@ const STANDARD_SIZES = {
A5: { width: 419.53, height: 595.28 }, A5: { width: 419.53, height: 595.28 },
}; };
export function getStandardPageName(width: any, height: any) { export function getStandardPageName(width: number, height: number) {
const tolerance = 1; // Allow for minor floating point variations const tolerance = 1; // Allow for minor floating point variations
for (const [name, size] of Object.entries(STANDARD_SIZES)) { for (const [name, size] of Object.entries(STANDARD_SIZES)) {
if ( if (
@@ -28,7 +30,7 @@ export function getStandardPageName(width: any, height: any) {
return 'Custom'; return 'Custom';
} }
export function convertPoints(points: any, unit: any) { export function convertPoints(points: number, unit: string) {
let result: number; let result: number;
switch (unit) { switch (unit) {
case 'in': case 'in':
@@ -59,7 +61,7 @@ export function hexToRgb(hex: string): { r: number; g: number; b: number } {
: { r: 0, g: 0, b: 0 }; : { r: 0, g: 0, b: 0 };
} }
export const formatBytes = (bytes: any, decimals = 1) => { export const formatBytes = (bytes: number, decimals = 1) => {
if (bytes === 0) return '0 Bytes'; if (bytes === 0) return '0 Bytes';
const k = 1024; const k = 1024;
const dm = decimals < 0 ? 0 : decimals; const dm = decimals < 0 ? 0 : decimals;
@@ -79,10 +81,12 @@ export const downloadFile = (blob: Blob, filename: string): void => {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}; };
export const readFileAsArrayBuffer = (file: any) => { export const readFileAsArrayBuffer = (
file: Blob
): Promise<ArrayBuffer | null> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => resolve(reader.result); reader.onload = () => resolve(reader.result as ArrayBuffer | null);
reader.onerror = (error) => reject(error); reader.onerror = (error) => reject(error);
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
}); });
@@ -139,7 +143,7 @@ export function parsePageRanges(
* @param {string} isoDateString - The ISO 8601 date string. * @param {string} isoDateString - The ISO 8601 date string.
* @returns {string} A localized date and time string, or the original string if parsing fails. * @returns {string} A localized date and time string, or the original string if parsing fails.
*/ */
export function formatIsoDate(isoDateString) { export function formatIsoDate(isoDateString: string) {
if (!isoDateString || typeof isoDateString !== 'string') { if (!isoDateString || typeof isoDateString !== 'string') {
return isoDateString; // Return original value if it's not a valid string return isoDateString; // Return original value if it's not a valid string
} }
@@ -156,20 +160,20 @@ export function formatIsoDate(isoDateString) {
} }
} }
let qpdfInstance: any = null; let qpdfInstance: QpdfInstanceExtended | null = null;
/** /**
* Initialize qpdf-wasm singleton. * Initialize qpdf-wasm singleton.
* Subsequent calls return the same instance. * Subsequent calls return the same instance.
*/ */
export async function initializeQpdf() { export async function initializeQpdf(): Promise<QpdfInstanceExtended> {
if (qpdfInstance) return qpdfInstance; if (qpdfInstance) return qpdfInstance;
showLoader('Initializing PDF engine...'); showLoader('Initializing PDF engine...');
try { try {
qpdfInstance = await createModule({ qpdfInstance = (await createModule({
locateFile: () => import.meta.env.BASE_URL + 'qpdf.wasm', locateFile: () => import.meta.env.BASE_URL + 'qpdf.wasm',
}); })) as unknown as QpdfInstanceExtended;
} catch (error) { } catch (error) {
console.error('Failed to initialize qpdf-wasm:', error); console.error('Failed to initialize qpdf-wasm:', error);
showAlert( showAlert(
@@ -267,24 +271,19 @@ export function resetAndReloadTool(preResetCallback?: () => void) {
* @param src The source to load (url string, typed array, or parameters object) * @param src The source to load (url string, typed array, or parameters object)
* @returns The PDF loading task * @returns The PDF loading task
*/ */
export function getPDFDocument(src: any) { export function getPDFDocument(
let params = src; src: string | Uint8Array | ArrayBuffer | DocumentInitParameters
) {
let params: DocumentInitParameters;
// Handle different input types similar to how getDocument handles them,
// but we ensure we have an object to attach wasmUrl to.
if (typeof src === 'string') { if (typeof src === 'string') {
params = { url: src }; params = { url: src };
} else if (src instanceof Uint8Array || src instanceof ArrayBuffer) { } else if (src instanceof Uint8Array || src instanceof ArrayBuffer) {
params = { data: src }; params = { data: src };
} else {
params = src;
} }
// Ensure params is an object
if (typeof params !== 'object' || params === null) {
params = {};
}
// Add wasmUrl pointing to our public/wasm directory
// This is required for PDF.js v5+ to load OpenJPEG for certain images
return pdfjsLib.getDocument({ return pdfjsLib.getDocument({
...params, ...params,
wasmUrl: import.meta.env.BASE_URL + 'pdfjs-viewer/wasm/', wasmUrl: import.meta.env.BASE_URL + 'pdfjs-viewer/wasm/',
@@ -413,7 +412,7 @@ export function formatRawDate(raw: string): string {
year, year,
hoursStr, hoursStr,
minsStr, minsStr,
secsStr, _secsStr,
timezone, timezone,
] = match; ] = match;
@@ -455,8 +454,8 @@ export function formatRawDate(raw: string): string {
return `${fullDay}, ${fullMonth} ${dom}, ${year} at ${hours}:${minsStr} ${ampm} (${formattedTz})`; return `${fullDay}, ${fullMonth} ${dom}, ${year} at ${hours}:${minsStr} ${ampm} (${formattedTz})`;
} }
} catch (e) { } catch {
// Fallback to raw string if parsing fails console.error('Error parsing date string:', raw);
} }
return raw; return raw;
} }

View File

@@ -6,6 +6,7 @@
*/ */
import { WorkerBrowserConverter } from '@matbee/libreoffice-converter/browser'; import { WorkerBrowserConverter } from '@matbee/libreoffice-converter/browser';
import type { InputFormat } from '@matbee/libreoffice-converter/browser';
const LIBREOFFICE_LOCAL_PATH = import.meta.env.BASE_URL + 'libreoffice-wasm/'; const LIBREOFFICE_LOCAL_PATH = import.meta.env.BASE_URL + 'libreoffice-wasm/';
@@ -35,17 +36,20 @@ export class LibreOfficeConverter {
if (this.initializing) { if (this.initializing) {
while (this.initializing) { while (this.initializing) {
await new Promise(r => setTimeout(r, 100)); await new Promise((r) => setTimeout(r, 100));
} }
return; return;
} }
this.initializing = true; this.initializing = true;
let progressCallback = onProgress; // Store original callback let progressCallback = onProgress; // Store original callback
try { try {
progressCallback?.({ phase: 'loading', percent: 0, message: 'Loading conversion engine...' }); progressCallback?.({
phase: 'loading',
percent: 0,
message: 'Loading conversion engine...',
});
this.converter = new WorkerBrowserConverter({ this.converter = new WorkerBrowserConverter({
sofficeJs: `${this.basePath}soffice.js`, sofficeJs: `${this.basePath}soffice.js`,
@@ -54,13 +58,17 @@ export class LibreOfficeConverter {
sofficeWorkerJs: `${this.basePath}soffice.worker.js`, sofficeWorkerJs: `${this.basePath}soffice.worker.js`,
browserWorkerJs: `${this.basePath}browser.worker.global.js`, browserWorkerJs: `${this.basePath}browser.worker.global.js`,
verbose: false, verbose: false,
onProgress: (info: { phase: string; percent: number; message: string }) => { onProgress: (info: {
phase: string;
percent: number;
message: string;
}) => {
if (progressCallback && !this.initialized) { if (progressCallback && !this.initialized) {
const simplifiedMessage = `Loading conversion engine (${Math.round(info.percent)}%)...`; const simplifiedMessage = `Loading conversion engine (${Math.round(info.percent)}%)...`;
progressCallback({ progressCallback({
phase: info.phase as LoadProgress['phase'], phase: info.phase as LoadProgress['phase'],
percent: info.percent, percent: info.percent,
message: simplifiedMessage message: simplifiedMessage,
}); });
} }
}, },
@@ -76,7 +84,11 @@ export class LibreOfficeConverter {
this.initialized = true; this.initialized = true;
// Call completion message // Call completion message
progressCallback?.({ phase: 'ready', percent: 100, message: 'Conversion engine ready!' }); progressCallback?.({
phase: 'ready',
percent: 100,
message: 'Conversion engine ready!',
});
// Null out the callback to prevent any late-firing progress updates // Null out the callback to prevent any late-firing progress updates
progressCallback = undefined; progressCallback = undefined;
@@ -95,7 +107,9 @@ export class LibreOfficeConverter {
} }
console.log(`[LibreOffice] Converting ${file.name} to PDF...`); console.log(`[LibreOffice] Converting ${file.name} to PDF...`);
console.log(`[LibreOffice] File type: ${file.type}, Size: ${file.size} bytes`); console.log(
`[LibreOffice] File type: ${file.type}, Size: ${file.size} bytes`
);
try { try {
console.log(`[LibreOffice] Reading file as ArrayBuffer...`); console.log(`[LibreOffice] Reading file as ArrayBuffer...`);
@@ -110,13 +124,19 @@ export class LibreOfficeConverter {
const ext = file.name.split('.').pop()?.toLowerCase() || ''; const ext = file.name.split('.').pop()?.toLowerCase() || '';
console.log(`[LibreOffice] Detected format from extension: ${ext}`); console.log(`[LibreOffice] Detected format from extension: ${ext}`);
const result = await this.converter.convert(uint8Array, { const result = await this.converter.convert(
uint8Array,
{
outputFormat: 'pdf', outputFormat: 'pdf',
inputFormat: ext as any, // Explicitly specify format for CSV import filters inputFormat: ext as InputFormat,
}, file.name); },
file.name
);
const duration = Date.now() - startTime; const duration = Date.now() - startTime;
console.log(`[LibreOffice] Conversion complete! Duration: ${duration}ms, Size: ${result.data.length} bytes`); console.log(
`[LibreOffice] Conversion complete! Duration: ${duration}ms, Size: ${result.data.length} bytes`
);
// Create a copy to avoid SharedArrayBuffer type issues // Create a copy to avoid SharedArrayBuffer type issues
const data = new Uint8Array(result.data); const data = new Uint8Array(result.data);
@@ -125,7 +145,7 @@ export class LibreOfficeConverter {
console.error(`[LibreOffice] Conversion FAILED for ${file.name}:`, error); console.error(`[LibreOffice] Conversion FAILED for ${file.name}:`, error);
console.error(`[LibreOffice] Error details:`, { console.error(`[LibreOffice] Error details:`, {
message: error instanceof Error ? error.message : String(error), message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined stack: error instanceof Error ? error.stack : undefined,
}); });
throw error; throw error;
} }
@@ -152,7 +172,9 @@ export class LibreOfficeConverter {
} }
} }
export function getLibreOfficeConverter(basePath?: string): LibreOfficeConverter { export function getLibreOfficeConverter(
basePath?: string
): LibreOfficeConverter {
if (!converterInstance) { if (!converterInstance) {
converterInstance = new LibreOfficeConverter(basePath); converterInstance = new LibreOfficeConverter(basePath);
} }

View File

@@ -29,8 +29,14 @@ import taskLists from 'markdown-it-task-lists';
import anchor from 'markdown-it-anchor'; import anchor from 'markdown-it-anchor';
import tocDoneRight from 'markdown-it-toc-done-right'; import tocDoneRight from 'markdown-it-toc-done-right';
import { applyTranslations } from '../i18n/i18n'; import { applyTranslations } from '../i18n/i18n';
import type {
WindowWithLucide,
WindowWithI18next,
MarkdownItOptions,
MarkdownItToken,
MarkdownItRendererSelf,
MarkdownItRenderRule,
} from '@/types';
// Register highlight.js languages // Register highlight.js languages
hljs.registerLanguage('javascript', javascript); hljs.registerLanguage('javascript', javascript);
@@ -66,18 +72,7 @@ export interface MarkdownEditorOptions {
onBack?: () => void; onBack?: () => void;
} }
export interface MarkdownItOptions { export type { MarkdownItOptions } from '@/types';
/** Enable HTML tags in source */
html: boolean;
/** Convert '\n' in paragraphs into <br> */
breaks: boolean;
/** Autoconvert URL-like text to links */
linkify: boolean;
/** Enable some language-neutral replacement + quotes beautification */
typographer: boolean;
/** Highlight function for fenced code blocks */
highlight?: (str: string, lang: string) => string;
}
const DEFAULT_MARKDOWN = `# Welcome to BentoPDF Markdown Editor const DEFAULT_MARKDOWN = `# Welcome to BentoPDF Markdown Editor
@@ -277,7 +272,7 @@ export class MarkdownEditor {
html: true, html: true,
breaks: false, breaks: false,
linkify: true, linkify: true,
typographer: true typographer: true,
}; };
constructor(container: HTMLElement, options: MarkdownEditorOptions) { constructor(container: HTMLElement, options: MarkdownEditorOptions) {
@@ -303,22 +298,36 @@ export class MarkdownEditor {
startOnLoad: false, startOnLoad: false,
theme: 'default', theme: 'default',
securityLevel: 'loose', securityLevel: 'loose',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif' fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
}); });
this.mermaidInitialized = true; this.mermaidInitialized = true;
} }
} }
private configureLinkRenderer(): void { private configureLinkRenderer(): void {
// Override link renderer to add target="_blank" and rel="noopener" const existingRule = this.md.renderer.rules.link_open;
const defaultRender = this.md.renderer.rules.link_open || const defaultRender: MarkdownItRenderRule = existingRule
((tokens: any[], idx: number, options: any, _env: any, self: any) => self.renderToken(tokens, idx, options)); ? (existingRule as unknown as MarkdownItRenderRule)
: (
tokens: MarkdownItToken[],
idx: number,
options: MarkdownItOptions,
env: unknown,
self: MarkdownItRendererSelf
) => self.renderToken(tokens, idx, options);
this.md.renderer.rules.link_open = (tokens: any[], idx: number, options: any, env: any, self: any) => { this.md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
const token = tokens[idx]; const token = tokens[idx] as unknown as MarkdownItToken;
token.attrSet('target', '_blank'); token.attrSet('target', '_blank');
token.attrSet('rel', 'noopener noreferrer'); token.attrSet('rel', 'noopener noreferrer');
return defaultRender(tokens, idx, options, env, self); return defaultRender(
tokens as unknown as MarkdownItToken[],
idx,
options as unknown as MarkdownItOptions,
env as unknown,
self as unknown as MarkdownItRendererSelf
);
}; };
} }
@@ -417,12 +426,11 @@ export class MarkdownEditor {
this.applyI18n(); this.applyI18n();
// Initialize Lucide icons // Initialize Lucide icons
if (typeof (window as any).lucide !== 'undefined') { if (typeof (window as WindowWithLucide).lucide !== 'undefined') {
(window as any).lucide.createIcons(); (window as WindowWithLucide).lucide?.createIcons();
} }
} }
private setupEventListeners(): void { private setupEventListeners(): void {
// Editor input // Editor input
this.editor?.addEventListener('input', () => { this.editor?.addEventListener('input', () => {
@@ -441,9 +449,13 @@ export class MarkdownEditor {
this.editor?.addEventListener('scroll', () => { this.editor?.addEventListener('scroll', () => {
if (this.syncScroll && !this.isSyncing && this.editor && this.preview) { if (this.syncScroll && !this.isSyncing && this.editor && this.preview) {
this.isSyncing = true; this.isSyncing = true;
const scrollPercentage = this.editor.scrollTop / (this.editor.scrollHeight - this.editor.clientHeight); const scrollPercentage =
this.preview.scrollTop = scrollPercentage * (this.preview.scrollHeight - this.preview.clientHeight); this.editor.scrollTop /
setTimeout(() => this.isSyncing = false, 10); (this.editor.scrollHeight - this.editor.clientHeight);
this.preview.scrollTop =
scrollPercentage *
(this.preview.scrollHeight - this.preview.clientHeight);
setTimeout(() => (this.isSyncing = false), 10);
} }
}); });
@@ -451,9 +463,13 @@ export class MarkdownEditor {
this.preview?.addEventListener('scroll', () => { this.preview?.addEventListener('scroll', () => {
if (this.syncScroll && !this.isSyncing && this.editor && this.preview) { if (this.syncScroll && !this.isSyncing && this.editor && this.preview) {
this.isSyncing = true; this.isSyncing = true;
const scrollPercentage = this.preview.scrollTop / (this.preview.scrollHeight - this.preview.clientHeight); const scrollPercentage =
this.editor.scrollTop = scrollPercentage * (this.editor.scrollHeight - this.editor.clientHeight); this.preview.scrollTop /
setTimeout(() => this.isSyncing = false, 10); (this.preview.scrollHeight - this.preview.clientHeight);
this.editor.scrollTop =
scrollPercentage *
(this.editor.scrollHeight - this.editor.clientHeight);
setTimeout(() => (this.isSyncing = false), 10);
} }
}); });
@@ -474,7 +490,9 @@ export class MarkdownEditor {
}); });
// Settings modal close // Settings modal close
document.getElementById('mdCloseSettings')?.addEventListener('click', () => { document
.getElementById('mdCloseSettings')
?.addEventListener('click', () => {
const modal = document.getElementById('mdSettingsModal'); const modal = document.getElementById('mdSettingsModal');
if (modal) { if (modal) {
modal.style.display = 'none'; modal.style.display = 'none';
@@ -482,8 +500,14 @@ export class MarkdownEditor {
}); });
// Close modal on overlay click // Close modal on overlay click
document.getElementById('mdSettingsModal')?.addEventListener('click', (e) => { document
if ((e.target as HTMLElement).classList.contains('md-editor-modal-overlay')) { .getElementById('mdSettingsModal')
?.addEventListener('click', (e) => {
if (
(e.target as HTMLElement).classList.contains(
'md-editor-modal-overlay'
)
) {
const modal = document.getElementById('mdSettingsModal'); const modal = document.getElementById('mdSettingsModal');
if (modal) { if (modal) {
modal.style.display = 'none'; modal.style.display = 'none';
@@ -507,7 +531,9 @@ export class MarkdownEditor {
this.updateMarkdownIt(); this.updateMarkdownIt();
}); });
document.getElementById('mdOptTypographer')?.addEventListener('change', (e) => { document
.getElementById('mdOptTypographer')
?.addEventListener('change', (e) => {
this.mdOptions.typographer = (e.target as HTMLInputElement).checked; this.mdOptions.typographer = (e.target as HTMLInputElement).checked;
this.updateMarkdownIt(); this.updateMarkdownIt();
}); });
@@ -549,7 +575,8 @@ export class MarkdownEditor {
const start = this.editor!.selectionStart; const start = this.editor!.selectionStart;
const end = this.editor!.selectionEnd; const end = this.editor!.selectionEnd;
const value = this.editor!.value; const value = this.editor!.value;
this.editor!.value = value.substring(0, start) + ' ' + value.substring(end); this.editor!.value =
value.substring(0, start) + ' ' + value.substring(end);
this.editor!.selectionStart = this.editor!.selectionEnd = start + 2; this.editor!.selectionStart = this.editor!.selectionEnd = start + 2;
this.updatePreview(); this.updatePreview();
} }
@@ -563,18 +590,37 @@ export class MarkdownEditor {
// Update options based on preset // Update options based on preset
if (preset === 'commonmark') { if (preset === 'commonmark') {
this.mdOptions = { html: false, breaks: false, linkify: false, typographer: false }; this.mdOptions = {
html: false,
breaks: false,
linkify: false,
typographer: false,
};
} else if (preset === 'zero') { } else if (preset === 'zero') {
this.mdOptions = { html: false, breaks: false, linkify: false, typographer: false }; this.mdOptions = {
html: false,
breaks: false,
linkify: false,
typographer: false,
};
} else { } else {
this.mdOptions = { html: true, breaks: false, linkify: true, typographer: true }; this.mdOptions = {
html: true,
breaks: false,
linkify: true,
typographer: true,
};
} }
// Update UI checkboxes // Update UI checkboxes
(document.getElementById('mdOptHtml') as HTMLInputElement).checked = this.mdOptions.html; (document.getElementById('mdOptHtml') as HTMLInputElement).checked =
(document.getElementById('mdOptBreaks') as HTMLInputElement).checked = this.mdOptions.breaks; this.mdOptions.html;
(document.getElementById('mdOptLinkify') as HTMLInputElement).checked = this.mdOptions.linkify; (document.getElementById('mdOptBreaks') as HTMLInputElement).checked =
(document.getElementById('mdOptTypographer') as HTMLInputElement).checked = this.mdOptions.typographer; this.mdOptions.breaks;
(document.getElementById('mdOptLinkify') as HTMLInputElement).checked =
this.mdOptions.linkify;
(document.getElementById('mdOptTypographer') as HTMLInputElement).checked =
this.mdOptions.typographer;
this.updateMarkdownIt(); this.updateMarkdownIt();
} }
@@ -588,7 +634,6 @@ export class MarkdownEditor {
} }
} }
private createMarkdownIt(): MarkdownIt { private createMarkdownIt(): MarkdownIt {
// Use preset if commonmark or zero // Use preset if commonmark or zero
let md: MarkdownIt; let md: MarkdownIt;
@@ -604,13 +649,16 @@ export class MarkdownEditor {
highlight: (str: string, lang: string) => { highlight: (str: string, lang: string) => {
if (lang && hljs.getLanguage(lang)) { if (lang && hljs.getLanguage(lang)) {
try { try {
return hljs.highlight(str, { language: lang, ignoreIllegals: true }).value; return hljs.highlight(str, {
language: lang,
ignoreIllegals: true,
}).value;
} catch { } catch {
// Fall through to default // Fall through to default
} }
} }
return ''; // Use external default escaping return ''; // Use external default escaping
} },
}); });
} }
@@ -650,7 +698,9 @@ export class MarkdownEditor {
private async renderMermaidDiagrams(): Promise<void> { private async renderMermaidDiagrams(): Promise<void> {
if (!this.preview) return; if (!this.preview) return;
const mermaidBlocks = this.preview.querySelectorAll('pre > code.language-mermaid'); const mermaidBlocks = this.preview.querySelectorAll(
'pre > code.language-mermaid'
);
for (let i = 0; i < mermaidBlocks.length; i++) { for (let i = 0; i < mermaidBlocks.length; i++) {
const block = mermaidBlocks[i] as HTMLElement; const block = mermaidBlocks[i] as HTMLElement;
@@ -948,14 +998,16 @@ ${content}
applyTranslations(); applyTranslations();
// Special handling for select options (data-i18n on options doesn't work with applyTranslations) // Special handling for select options (data-i18n on options doesn't work with applyTranslations)
const presetSelect = document.getElementById('mdPreset') as HTMLSelectElement; const presetSelect = document.getElementById(
'mdPreset'
) as HTMLSelectElement;
if (presetSelect) { if (presetSelect) {
const options = presetSelect.querySelectorAll('option[data-i18n]'); const options = presetSelect.querySelectorAll('option[data-i18n]');
options.forEach((option) => { options.forEach((option) => {
const key = option.getAttribute('data-i18n'); const key = option.getAttribute('data-i18n');
if (key) { if (key) {
// Use i18next directly for option text // Use i18next directly for option text
const translated = (window as any).i18next?.t(key); const translated = (window as WindowWithI18next).i18next?.t(key);
if (translated && translated !== key) { if (translated && translated !== key) {
option.textContent = translated; option.textContent = translated;
} }

View File

@@ -154,7 +154,7 @@ document.addEventListener('keydown', handleKeydown);
export function initPagePreview( export function initPagePreview(
container: HTMLElement, container: HTMLElement,
pdfjsDoc: PDFDocumentProxy, pdfjsDoc: PDFDocumentProxy,
options: { pageAttr?: string } = {} _options: { pageAttr?: string } = {}
): void { ): void {
const totalPages = pdfjsDoc.numPages; const totalPages = pdfjsDoc.numPages;

View File

@@ -1,5 +1,6 @@
import { getCpdf, isCpdfAvailable } from './cpdf-helper'; import { getCpdf, isCpdfAvailable } from './cpdf-helper';
import { isPyMuPDFAvailable, loadPyMuPDF } from './pymupdf-loader'; import { isPyMuPDFAvailable, loadPyMuPDF } from './pymupdf-loader';
import type { CpdfInstance } from '@/types';
export type PdfDecryptEngine = 'cpdf' | 'pymupdf'; export type PdfDecryptEngine = 'cpdf' | 'pymupdf';
@@ -37,13 +38,13 @@ function copyBytes(bytes: Uint8Array): Uint8Array {
return Uint8Array.from(bytes); return Uint8Array.from(bytes);
} }
function cleanupCpdfDocument(cpdf: unknown, pdf: unknown): void { function cleanupCpdfDocument(cpdf: CpdfInstance, pdf: unknown): void {
if (!cpdf || !pdf || typeof cpdf !== 'object' || !('deletePdf' in cpdf)) { if (!cpdf || !pdf) {
return; return;
} }
try { try {
(cpdf as { deletePdf: (document: unknown) => void }).deletePdf(pdf); cpdf.deletePdf(pdf);
} catch (cleanupError) { } catch (cleanupError) {
console.warn( console.warn(
`${DECRYPT_LOG_PREFIX} Failed to cleanup CoherentPDF document: ${normalizeErrorMessage(cleanupError)}` `${DECRYPT_LOG_PREFIX} Failed to cleanup CoherentPDF document: ${normalizeErrorMessage(cleanupError)}`

Some files were not shown because too many files have changed in this diff Show More