Merge remote-tracking branch 'origin/main' into pdf-to-image-direct-image

This commit is contained in:
Sebastian Espei
2025-12-19 02:08:06 +01:00
245 changed files with 33321 additions and 15984 deletions

View File

@@ -18,7 +18,7 @@ export async function ensureCpdfLoaded(): Promise<void> {
}
const script = document.createElement('script');
script.src = '/coherentpdf.browser.min.js';
script.src = import.meta.env.BASE_URL + 'coherentpdf.browser.min.js';
script.onload = () => {
cpdfLoaded = true;
resolve();

View File

@@ -14,10 +14,10 @@ export function applyFullWidthMode(enabled: boolean) {
const pageUploaders = document.querySelectorAll('#tool-uploader');
pageUploaders.forEach((uploader) => {
if (enabled) {
uploader.classList.remove('max-w-2xl', 'max-w-5xl');
uploader.classList.remove('max-w-2xl', 'max-w-4xl', 'max-w-5xl');
} else {
// Restore original max-width if not already present
if (!uploader.classList.contains('max-w-2xl') && !uploader.classList.contains('max-w-5xl')) {
if (!uploader.classList.contains('max-w-2xl') && !uploader.classList.contains('max-w-4xl') && !uploader.classList.contains('max-w-5xl')) {
uploader.classList.add('max-w-2xl');
}
}

View File

@@ -69,7 +69,7 @@ export const formatBytes = (bytes: any, decimals = 1) => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
};
export const downloadFile = (blob: any, filename: any) => {
export const downloadFile = (blob: Blob, filename: string): void => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
@@ -89,12 +89,12 @@ export const readFileAsArrayBuffer = (file: any) => {
});
};
export function parsePageRanges(rangeString: any, totalPages: any) {
export function parsePageRanges(rangeString: string, totalPages: number): number[] {
if (!rangeString || rangeString.trim() === '') {
return Array.from({ length: totalPages }, (_, i) => i);
}
const indices = new Set();
const indices = new Set<number>();
const parts = rangeString.split(',');
for (const part of parts) {
@@ -128,10 +128,11 @@ export function parsePageRanges(rangeString: any, totalPages: any) {
}
}
// @ts-expect-error TS(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
return Array.from(indices).sort((a, b) => a - b);
}
/**
* Formats an ISO 8601 date string (e.g., "2008-02-21T17:15:56-08:00")
* into a localized, human-readable string.
@@ -167,7 +168,7 @@ export async function initializeQpdf() {
showLoader('Initializing PDF engine...');
try {
qpdfInstance = await createModule({
locateFile: () => '/qpdf.wasm',
locateFile: () => import.meta.env.BASE_URL + 'qpdf.wasm',
});
} catch (error) {
console.error('Failed to initialize qpdf-wasm:', error);
@@ -279,6 +280,25 @@ export function getPDFDocument(src: any) {
// This is required for PDF.js v5+ to load OpenJPEG for certain images
return pdfjsLib.getDocument({
...params,
wasmUrl: '/pdfjs-viewer/wasm/',
wasmUrl: import.meta.env.BASE_URL + 'pdfjs-viewer/wasm/',
});
}
/**
* Returns a sanitized PDF filename.
*
* The provided filename is processed as follows:
* - Removes a trailing `.pdf` file extension (case-insensitive)
* - Trims leading and trailing whitespace
* - Truncates the name to a maximum of 80 characters
*
* @param filename The original filename (including extension)
* @returns The sanitized filename without the `.pdf` extension, limited to 80 characters
*/
export function getCleanPdfFilename(filename: string): string {
let clean = filename.replace(/\.pdf$/i, '').trim();
if (clean.length > 80) {
clean = clean.slice(0, 80);
}
return clean;
}

View File

@@ -0,0 +1,23 @@
// Rotation state management for PDF pages
const rotationState: number[] = [];
export function getRotationState(): readonly number[] {
return rotationState;
}
export function updateRotationState(pageIndex: number, rotation: number) {
if (pageIndex >= 0 && pageIndex < rotationState.length) {
rotationState[pageIndex] = rotation;
}
}
export function resetRotationState() {
rotationState.length = 0;
}
export function initializeRotationState(pageCount: number) {
rotationState.length = 0;
for (let i = 0; i < pageCount; i++) {
rotationState.push(0);
}
}

View File

@@ -1,27 +1,45 @@
import { APP_VERSION } from '../../version.js';
import { createLanguageSwitcher } from '../i18n/language-switcher.js';
// Handle simple mode footer replacement for tool pages
if (__SIMPLE_MODE__) {
const footer = document.querySelector('footer');
if (footer) {
footer.style.display = 'none';
const footer = document.querySelector('footer');
if (footer && !document.querySelector('[data-simple-footer]')) {
footer.style.display = 'none';
const simpleFooter = document.createElement('footer');
simpleFooter.className = 'mt-16 border-t-2 border-gray-700 py-8';
simpleFooter.innerHTML = `
const simpleFooter = document.createElement('footer');
simpleFooter.className = 'mt-16 border-t-2 border-gray-700 py-8';
simpleFooter.setAttribute('data-simple-footer', 'true');
simpleFooter.innerHTML = `
<div class="container mx-auto px-4">
<div class="flex items-center mb-4">
<img src="../../images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8 mr-2">
<span class="text-white font-bold text-lg">BentoPDF</span>
<div class="flex items-center justify-between flex-wrap gap-4">
<div>
<div class="flex items-center mb-2">
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8 mr-2">
<span class="text-white font-bold text-lg">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm">
&copy; 2025 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
Version <span id="app-version-simple">${APP_VERSION}</span>
</p>
</div>
<div id="simple-mode-lang-switcher" class="flex-shrink-0"></div>
</div>
<p class="text-gray-400 text-sm">
&copy; 2025 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
Version <span id="app-version-simple">${APP_VERSION}</span>
</p>
</div>
`;
document.body.appendChild(simpleFooter);
document.body.appendChild(simpleFooter);
const langContainer = simpleFooter.querySelector('#simple-mode-lang-switcher');
if (langContainer) {
const switcher = createLanguageSwitcher();
const dropdown = switcher.querySelector('div[role="menu"]');
if (dropdown) {
dropdown.classList.remove('mt-2');
dropdown.classList.add('bottom-full', 'mb-2');
}
langContainer.appendChild(switcher);
}
}
}