2025-12-11 19:34:14 +05:30
|
|
|
import { tesseractLanguages } from '../config/tesseract-languages.js';
|
|
|
|
|
import { showAlert } from '../ui.js';
|
2026-02-08 17:05:40 +05:30
|
|
|
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
2026-03-26 12:11:12 +05:30
|
|
|
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
2025-12-11 19:34:14 +05:30
|
|
|
import { icons, createIcons } from 'lucide';
|
2026-02-08 17:05:40 +05:30
|
|
|
import { OcrState } from '@/types';
|
|
|
|
|
import { performOcr } from '../utils/ocr.js';
|
2026-03-14 15:50:30 +05:30
|
|
|
import {
|
|
|
|
|
getAvailableTesseractLanguageEntries,
|
|
|
|
|
resolveConfiguredTesseractAvailableLanguages,
|
|
|
|
|
UnsupportedOcrLanguageError,
|
|
|
|
|
} from '../utils/tesseract-language-availability.js';
|
2025-12-11 19:34:14 +05:30
|
|
|
|
|
|
|
|
const pageState: OcrState = {
|
2026-01-10 13:09:52 +05:30
|
|
|
file: null,
|
|
|
|
|
searchablePdfBytes: null,
|
2025-12-11 19:34:14 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const whitelistPresets: Record<string, string> = {
|
2026-01-10 13:09:52 +05:30
|
|
|
alphanumeric:
|
|
|
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,!?-\'"',
|
|
|
|
|
'numbers-currency': '0123456789$€£¥.,- ',
|
|
|
|
|
'letters-only': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ',
|
|
|
|
|
'numbers-only': '0123456789',
|
|
|
|
|
invoice: '0123456789$.,/-#: ',
|
|
|
|
|
forms:
|
|
|
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,()-_/@#:',
|
2025-12-11 19:34:14 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function updateProgress(status: string, progress: number) {
|
2026-01-10 13:09:52 +05:30
|
|
|
const progressBar = document.getElementById('progress-bar');
|
|
|
|
|
const progressStatus = document.getElementById('progress-status');
|
|
|
|
|
const progressLog = document.getElementById('progress-log');
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
if (!progressBar || !progressStatus || !progressLog) return;
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
progressStatus.textContent = status;
|
|
|
|
|
progressBar.style.width = `${Math.min(100, progress * 100)}%`;
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const logMessage = `Status: ${status}`;
|
|
|
|
|
progressLog.textContent += logMessage + '\n';
|
|
|
|
|
progressLog.scrollTop = progressLog.scrollHeight;
|
2025-12-11 19:34:14 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetState() {
|
2026-01-10 13:09:52 +05:30
|
|
|
pageState.file = null;
|
|
|
|
|
pageState.searchablePdfBytes = null;
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const fileDisplayArea = document.getElementById('file-display-area');
|
|
|
|
|
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const toolOptions = document.getElementById('tool-options');
|
|
|
|
|
if (toolOptions) toolOptions.classList.add('hidden');
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const ocrProgress = document.getElementById('ocr-progress');
|
|
|
|
|
if (ocrProgress) ocrProgress.classList.add('hidden');
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const ocrResults = document.getElementById('ocr-results');
|
|
|
|
|
if (ocrResults) ocrResults.classList.add('hidden');
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const progressLog = document.getElementById('progress-log');
|
|
|
|
|
if (progressLog) progressLog.textContent = '';
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const progressBar = document.getElementById('progress-bar');
|
|
|
|
|
if (progressBar) progressBar.style.width = '0%';
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
|
|
|
|
if (fileInput) fileInput.value = '';
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
// Reset selected languages
|
|
|
|
|
const langCheckboxes = document.querySelectorAll(
|
|
|
|
|
'.lang-checkbox'
|
|
|
|
|
) as NodeListOf<HTMLInputElement>;
|
|
|
|
|
langCheckboxes.forEach(function (cb) {
|
|
|
|
|
cb.checked = false;
|
|
|
|
|
});
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const selectedLangsDisplay = document.getElementById(
|
|
|
|
|
'selected-langs-display'
|
|
|
|
|
);
|
|
|
|
|
if (selectedLangsDisplay) selectedLangsDisplay.textContent = 'None';
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const processBtn = document.getElementById(
|
|
|
|
|
'process-btn'
|
|
|
|
|
) as HTMLButtonElement;
|
|
|
|
|
if (processBtn) processBtn.disabled = true;
|
2025-12-11 19:34:14 +05:30
|
|
|
}
|
|
|
|
|
|
2026-03-14 15:50:30 +05:30
|
|
|
function updateLanguageAvailabilityNotice() {
|
|
|
|
|
const notice = document.getElementById('lang-availability-note');
|
|
|
|
|
if (!notice) return;
|
|
|
|
|
|
|
|
|
|
const configuredLanguages = resolveConfiguredTesseractAvailableLanguages();
|
|
|
|
|
if (!configuredLanguages) {
|
|
|
|
|
notice.classList.add('hidden');
|
|
|
|
|
notice.textContent = '';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const availableEntries = getAvailableTesseractLanguageEntries();
|
|
|
|
|
if (availableEntries.length === 0) {
|
|
|
|
|
notice.classList.remove('hidden');
|
|
|
|
|
notice.textContent =
|
|
|
|
|
'This deployment does not expose any valid OCR languages. Rebuild it with VITE_TESSERACT_AVAILABLE_LANGUAGES set to valid Tesseract codes.';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const availableNames = availableEntries.map(([, name]) => name).join(', ');
|
|
|
|
|
notice.classList.remove('hidden');
|
|
|
|
|
notice.textContent = `This deployment bundles OCR for: ${availableNames}.`;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-11 19:34:14 +05:30
|
|
|
async function runOCR() {
|
2026-01-10 13:09:52 +05:30
|
|
|
const selectedLangs = Array.from(
|
|
|
|
|
document.querySelectorAll('.lang-checkbox:checked')
|
|
|
|
|
).map(function (cb) {
|
|
|
|
|
return (cb as HTMLInputElement).value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const scale = parseFloat(
|
|
|
|
|
(document.getElementById('ocr-resolution') as HTMLSelectElement).value
|
|
|
|
|
);
|
|
|
|
|
const binarize = (document.getElementById('ocr-binarize') as HTMLInputElement)
|
|
|
|
|
.checked;
|
2026-03-21 16:39:51 +05:30
|
|
|
const embedFullFonts = (
|
|
|
|
|
document.getElementById('ocr-embed-full-fonts') as HTMLInputElement
|
|
|
|
|
).checked;
|
2026-01-10 13:09:52 +05:30
|
|
|
const whitelist = (
|
|
|
|
|
document.getElementById('ocr-whitelist') as HTMLInputElement
|
|
|
|
|
).value;
|
|
|
|
|
|
|
|
|
|
if (selectedLangs.length === 0) {
|
|
|
|
|
showAlert(
|
|
|
|
|
'No Languages Selected',
|
|
|
|
|
'Please select at least one language for OCR.'
|
2025-12-11 19:34:14 +05:30
|
|
|
);
|
2026-01-10 13:09:52 +05:30
|
|
|
return;
|
|
|
|
|
}
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
if (!pageState.file) {
|
|
|
|
|
showAlert('No File', 'Please upload a PDF file first.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const langString = selectedLangs.join('+');
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const toolOptions = document.getElementById('tool-options');
|
|
|
|
|
const ocrProgress = document.getElementById('ocr-progress');
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
if (toolOptions) toolOptions.classList.add('hidden');
|
|
|
|
|
if (ocrProgress) ocrProgress.classList.remove('hidden');
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
try {
|
|
|
|
|
const arrayBuffer = await pageState.file.arrayBuffer();
|
|
|
|
|
|
2026-02-08 17:05:40 +05:30
|
|
|
const result = await performOcr(new Uint8Array(arrayBuffer), {
|
|
|
|
|
language: langString,
|
|
|
|
|
resolution: scale,
|
|
|
|
|
binarize,
|
|
|
|
|
whitelist,
|
2026-03-21 16:39:51 +05:30
|
|
|
embedFullFonts,
|
2026-02-08 17:05:40 +05:30
|
|
|
onProgress: updateProgress,
|
2026-01-10 13:09:52 +05:30
|
|
|
});
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-02-08 17:05:40 +05:30
|
|
|
pageState.searchablePdfBytes = result.pdfBytes;
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const ocrResults = document.getElementById('ocr-results');
|
|
|
|
|
if (ocrProgress) ocrProgress.classList.add('hidden');
|
|
|
|
|
if (ocrResults) ocrResults.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
createIcons({ icons });
|
|
|
|
|
|
|
|
|
|
const textOutput = document.getElementById(
|
|
|
|
|
'ocr-text-output'
|
|
|
|
|
) as HTMLTextAreaElement;
|
2026-02-08 17:05:40 +05:30
|
|
|
if (textOutput) textOutput.value = result.fullText.trim();
|
2026-01-10 13:09:52 +05:30
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
2026-03-14 15:50:30 +05:30
|
|
|
if (e instanceof UnsupportedOcrLanguageError) {
|
|
|
|
|
showAlert('OCR Language Not Available', e.message);
|
|
|
|
|
} else {
|
|
|
|
|
showAlert(
|
|
|
|
|
'OCR Error',
|
|
|
|
|
'An error occurred during the OCR process. The worker may have failed to load. Please try again.'
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-01-10 13:09:52 +05:30
|
|
|
if (toolOptions) toolOptions.classList.remove('hidden');
|
|
|
|
|
if (ocrProgress) ocrProgress.classList.add('hidden');
|
|
|
|
|
}
|
2025-12-11 19:34:14 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function updateUI() {
|
2026-01-10 13:09:52 +05:30
|
|
|
const fileDisplayArea = document.getElementById('file-display-area');
|
|
|
|
|
const toolOptions = document.getElementById('tool-options');
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
if (!fileDisplayArea) return;
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
fileDisplayArea.innerHTML = '';
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
if (pageState.file) {
|
|
|
|
|
const fileDiv = document.createElement('div');
|
|
|
|
|
fileDiv.className =
|
|
|
|
|
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const infoContainer = document.createElement('div');
|
|
|
|
|
infoContainer.className = 'flex flex-col overflow-hidden';
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const nameSpan = document.createElement('div');
|
|
|
|
|
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
|
|
|
|
nameSpan.textContent = pageState.file.name;
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const metaSpan = document.createElement('div');
|
|
|
|
|
metaSpan.className = 'text-xs text-gray-400';
|
|
|
|
|
metaSpan.textContent = formatBytes(pageState.file.size);
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
infoContainer.append(nameSpan, metaSpan);
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
const removeBtn = document.createElement('button');
|
|
|
|
|
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
|
|
|
|
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
|
|
|
|
removeBtn.onclick = function () {
|
|
|
|
|
resetState();
|
|
|
|
|
};
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
fileDiv.append(infoContainer, removeBtn);
|
|
|
|
|
fileDisplayArea.appendChild(fileDiv);
|
|
|
|
|
createIcons({ icons });
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
if (toolOptions) toolOptions.classList.remove('hidden');
|
|
|
|
|
} else {
|
|
|
|
|
if (toolOptions) toolOptions.classList.add('hidden');
|
|
|
|
|
}
|
2025-12-11 19:34:14 +05:30
|
|
|
}
|
|
|
|
|
|
2026-03-26 12:11:12 +05:30
|
|
|
async function handleFileSelect(files: FileList | null) {
|
2026-01-10 13:09:52 +05:30
|
|
|
if (files && files.length > 0) {
|
|
|
|
|
const file = files[0];
|
|
|
|
|
if (
|
|
|
|
|
file.type === 'application/pdf' ||
|
|
|
|
|
file.name.toLowerCase().endsWith('.pdf')
|
|
|
|
|
) {
|
2026-03-26 12:11:12 +05:30
|
|
|
const result = await loadPdfWithPasswordPrompt(file);
|
|
|
|
|
if (!result) return;
|
|
|
|
|
result.pdf.destroy();
|
|
|
|
|
pageState.file = result.file;
|
2026-01-10 13:09:52 +05:30
|
|
|
updateUI();
|
2025-12-11 19:34:14 +05:30
|
|
|
}
|
2026-01-10 13:09:52 +05:30
|
|
|
}
|
2025-12-11 19:34:14 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function populateLanguageList() {
|
2026-01-10 13:09:52 +05:30
|
|
|
const langList = document.getElementById('lang-list');
|
|
|
|
|
if (!langList) return;
|
|
|
|
|
|
|
|
|
|
langList.innerHTML = '';
|
|
|
|
|
|
2026-03-14 15:50:30 +05:30
|
|
|
const availableEntries = getAvailableTesseractLanguageEntries();
|
|
|
|
|
if (availableEntries.length === 0) {
|
|
|
|
|
const emptyState = document.createElement('p');
|
|
|
|
|
emptyState.className = 'text-sm text-yellow-300 p-2';
|
|
|
|
|
emptyState.textContent =
|
|
|
|
|
'No OCR languages are available in this deployment.';
|
|
|
|
|
langList.appendChild(emptyState);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
availableEntries.forEach(function ([code, name]) {
|
2026-01-10 13:09:52 +05:30
|
|
|
const label = document.createElement('label');
|
|
|
|
|
label.className =
|
|
|
|
|
'flex items-center gap-2 p-2 rounded-md hover:bg-gray-700 cursor-pointer';
|
2026-03-14 15:50:30 +05:30
|
|
|
label.dataset.search = `${name} ${code}`.toLowerCase();
|
2026-01-10 13:09:52 +05:30
|
|
|
|
|
|
|
|
const checkbox = document.createElement('input');
|
|
|
|
|
checkbox.type = 'checkbox';
|
|
|
|
|
checkbox.value = code;
|
|
|
|
|
checkbox.className =
|
|
|
|
|
'lang-checkbox w-4 h-4 rounded text-indigo-600 bg-gray-700 border-gray-600 focus:ring-indigo-500';
|
|
|
|
|
|
|
|
|
|
label.append(checkbox);
|
|
|
|
|
label.append(document.createTextNode(' ' + name));
|
|
|
|
|
langList.appendChild(label);
|
|
|
|
|
});
|
2025-12-11 19:34:14 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
2026-01-10 13:09:52 +05:30
|
|
|
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
|
|
|
|
const dropZone = document.getElementById('drop-zone');
|
|
|
|
|
const processBtn = document.getElementById(
|
|
|
|
|
'process-btn'
|
|
|
|
|
) as HTMLButtonElement;
|
|
|
|
|
const backBtn = document.getElementById('back-to-tools');
|
|
|
|
|
const langSearch = document.getElementById('lang-search') as HTMLInputElement;
|
|
|
|
|
const langList = document.getElementById('lang-list');
|
|
|
|
|
const selectedLangsDisplay = document.getElementById(
|
|
|
|
|
'selected-langs-display'
|
|
|
|
|
);
|
|
|
|
|
const presetSelect = document.getElementById(
|
|
|
|
|
'whitelist-preset'
|
|
|
|
|
) as HTMLSelectElement;
|
|
|
|
|
const whitelistInput = document.getElementById(
|
|
|
|
|
'ocr-whitelist'
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const copyBtn = document.getElementById('copy-text-btn');
|
|
|
|
|
const downloadTxtBtn = document.getElementById('download-txt-btn');
|
|
|
|
|
const downloadPdfBtn = document.getElementById('download-searchable-pdf');
|
|
|
|
|
|
|
|
|
|
populateLanguageList();
|
2026-03-14 15:50:30 +05:30
|
|
|
updateLanguageAvailabilityNotice();
|
2026-01-10 13:09:52 +05:30
|
|
|
|
|
|
|
|
if (backBtn) {
|
|
|
|
|
backBtn.addEventListener('click', function () {
|
|
|
|
|
window.location.href = import.meta.env.BASE_URL;
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
if (fileInput && dropZone) {
|
|
|
|
|
fileInput.addEventListener('change', function (e) {
|
|
|
|
|
handleFileSelect((e.target as HTMLInputElement).files);
|
|
|
|
|
});
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
dropZone.addEventListener('dragover', function (e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
dropZone.classList.add('bg-gray-700');
|
2025-12-11 19:34:14 +05:30
|
|
|
});
|
|
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
dropZone.addEventListener('dragleave', function (e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
dropZone.classList.remove('bg-gray-700');
|
|
|
|
|
});
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
dropZone.addEventListener('drop', function (e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
dropZone.classList.remove('bg-gray-700');
|
|
|
|
|
const files = e.dataTransfer?.files;
|
|
|
|
|
if (files && files.length > 0) {
|
|
|
|
|
const pdfFiles = Array.from(files).filter(function (f) {
|
|
|
|
|
return (
|
|
|
|
|
f.type === 'application/pdf' ||
|
|
|
|
|
f.name.toLowerCase().endsWith('.pdf')
|
|
|
|
|
);
|
2025-12-11 19:34:14 +05:30
|
|
|
});
|
2026-01-10 13:09:52 +05:30
|
|
|
if (pdfFiles.length > 0) {
|
|
|
|
|
const dataTransfer = new DataTransfer();
|
|
|
|
|
dataTransfer.items.add(pdfFiles[0]);
|
|
|
|
|
handleFileSelect(dataTransfer.files);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
fileInput.addEventListener('click', function () {
|
|
|
|
|
fileInput.value = '';
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Language search
|
|
|
|
|
if (langSearch && langList) {
|
|
|
|
|
langSearch.addEventListener('input', function () {
|
|
|
|
|
const searchTerm = langSearch.value.toLowerCase();
|
|
|
|
|
langList.querySelectorAll('label').forEach(function (label) {
|
2026-03-14 15:50:30 +05:30
|
|
|
(label as HTMLElement).style.display = (
|
|
|
|
|
label as HTMLElement
|
|
|
|
|
).dataset.search?.includes(searchTerm)
|
2026-01-10 13:09:52 +05:30
|
|
|
? ''
|
|
|
|
|
: 'none';
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-10 13:09:52 +05:30
|
|
|
langList.addEventListener('change', function () {
|
|
|
|
|
const selected = Array.from(
|
|
|
|
|
langList.querySelectorAll('.lang-checkbox:checked')
|
|
|
|
|
).map(function (cb) {
|
|
|
|
|
return tesseractLanguages[
|
|
|
|
|
(cb as HTMLInputElement).value as keyof typeof tesseractLanguages
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (selectedLangsDisplay) {
|
|
|
|
|
selectedLangsDisplay.textContent =
|
|
|
|
|
selected.length > 0 ? selected.join(', ') : 'None';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (processBtn) {
|
|
|
|
|
processBtn.disabled = selected.length === 0;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Whitelist preset
|
|
|
|
|
if (presetSelect && whitelistInput) {
|
|
|
|
|
presetSelect.addEventListener('change', function () {
|
|
|
|
|
const preset = presetSelect.value;
|
|
|
|
|
if (preset && preset !== 'custom') {
|
|
|
|
|
whitelistInput.value = whitelistPresets[preset] || '';
|
|
|
|
|
whitelistInput.disabled = true;
|
|
|
|
|
} else {
|
|
|
|
|
whitelistInput.disabled = false;
|
|
|
|
|
if (preset === '') {
|
|
|
|
|
whitelistInput.value = '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Details toggle
|
|
|
|
|
document.querySelectorAll('details').forEach(function (details) {
|
|
|
|
|
details.addEventListener('toggle', function () {
|
|
|
|
|
const icon = details.querySelector('.details-icon') as HTMLElement;
|
|
|
|
|
if (icon) {
|
|
|
|
|
icon.style.transform = (details as HTMLDetailsElement).open
|
|
|
|
|
? 'rotate(180deg)'
|
|
|
|
|
: 'rotate(0deg)';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Process button
|
|
|
|
|
if (processBtn) {
|
|
|
|
|
processBtn.addEventListener('click', runOCR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy button
|
|
|
|
|
if (copyBtn) {
|
|
|
|
|
copyBtn.addEventListener('click', function () {
|
|
|
|
|
const textOutput = document.getElementById(
|
|
|
|
|
'ocr-text-output'
|
|
|
|
|
) as HTMLTextAreaElement;
|
|
|
|
|
if (textOutput) {
|
|
|
|
|
navigator.clipboard.writeText(textOutput.value).then(function () {
|
|
|
|
|
copyBtn.innerHTML =
|
|
|
|
|
'<i data-lucide="check" class="w-4 h-4 text-green-400"></i>';
|
|
|
|
|
createIcons({ icons });
|
|
|
|
|
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
copyBtn.innerHTML =
|
|
|
|
|
'<i data-lucide="clipboard-copy" class="w-4 h-4 text-gray-300"></i>';
|
|
|
|
|
createIcons({ icons });
|
|
|
|
|
}, 2000);
|
2025-12-11 19:34:14 +05:30
|
|
|
});
|
2026-01-10 13:09:52 +05:30
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Download txt
|
|
|
|
|
if (downloadTxtBtn) {
|
|
|
|
|
downloadTxtBtn.addEventListener('click', function () {
|
|
|
|
|
const textOutput = document.getElementById(
|
|
|
|
|
'ocr-text-output'
|
|
|
|
|
) as HTMLTextAreaElement;
|
|
|
|
|
if (textOutput) {
|
|
|
|
|
const blob = new Blob([textOutput.value], { type: 'text/plain' });
|
|
|
|
|
downloadFile(blob, 'ocr-text.txt');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Download PDF
|
|
|
|
|
if (downloadPdfBtn) {
|
|
|
|
|
downloadPdfBtn.addEventListener('click', function () {
|
|
|
|
|
if (pageState.searchablePdfBytes) {
|
|
|
|
|
downloadFile(
|
|
|
|
|
new Blob([new Uint8Array(pageState.searchablePdfBytes)], {
|
|
|
|
|
type: 'application/pdf',
|
|
|
|
|
}),
|
|
|
|
|
'searchable.pdf'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-12-11 19:34:14 +05:30
|
|
|
});
|