Files
audiobook-maker-pro-v4.2/static/js/pdf-handler.js

276 lines
9.1 KiB
JavaScript

/**
* Document Handler Module (PDF, DOCX, DOC)
* AUTHORITATIVE renderDocumentBlocks() — single source of truth
* Section markers are data-driven via editorBlocks[].sectionStart
*/
function initPdfHandler() {
const uploadZone = document.getElementById('uploadZone');
const docInput = document.getElementById('docInput');
if (!uploadZone || !docInput) return;
uploadZone.addEventListener('click', (e) => {
if (e.target.closest('button')) return;
docInput.click();
});
docInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
handleDocumentFile(file);
}
});
uploadZone.addEventListener('dragover', (e) => {
e.preventDefault();
uploadZone.classList.add('drag-over');
});
uploadZone.addEventListener('dragleave', () => {
uploadZone.classList.remove('drag-over');
});
uploadZone.addEventListener('drop', (e) => {
e.preventDefault();
uploadZone.classList.remove('drag-over');
const files = e.dataTransfer.files;
if (files.length > 0) {
const file = files[0];
const name = file.name.toLowerCase();
if (name.endsWith('.pdf') || name.endsWith('.docx') || name.endsWith('.doc')) {
handleDocumentFile(file);
} else {
alert('Please drop a valid PDF, DOCX, or DOC file.');
}
}
});
console.log('📄 Document handler initialized (PDF, DOCX, DOC)');
}
function handleDocumentFile(file) {
const name = file.name.toLowerCase();
if (name.endsWith('.pdf')) {
handlePdfFile(file);
} else if (name.endsWith('.docx') || name.endsWith('.doc')) {
handleWordFile(file);
} else {
alert('Unsupported file type. Please upload a PDF, DOCX, or DOC file.');
}
}
async function handlePdfFile(file) {
if (!file.name.toLowerCase().endsWith('.pdf')) {
alert('Please select a valid PDF file.');
return;
}
showLoader('Processing PDF...', `Extracting content from ${file.name}`);
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload-pdf', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
console.log(`✅ PDF processed: ${data.page_count} pages, ${data.blocks.length} blocks`);
const projectName = file.name.replace('.pdf', '');
document.getElementById('projectName').value = projectName;
currentProject.name = projectName;
// v4.4: auto-generated thumbnail token সংরক্ষণ
pendingThumbnailToken = data.pending_thumbnail || null;
renderDocumentBlocks(data.blocks);
document.getElementById('uploadSection').style.display = 'none';
document.getElementById('editorSection').style.display = 'block';
const panel = document.getElementById('audiobookMakerPanel');
if (panel) panel.style.display = 'flex';
const sidebar = document.getElementById('documentOutlineSidebar');
if (sidebar) sidebar.style.display = 'block';
hideLoader();
showNotification(`PDF processed: ${data.blocks.length} blocks extracted`, 'success');
updateWorkflowProgress('edit');
} catch (error) {
hideLoader();
console.error('PDF processing error:', error);
alert('Failed to process PDF: ' + error.message);
}
}
async function handleWordFile(file) {
const name = file.name.toLowerCase();
if (!name.endsWith('.docx') && !name.endsWith('.doc')) {
alert('Please select a valid DOCX or DOC file.');
return;
}
const fileType = name.endsWith('.docx') ? 'DOCX' : 'DOC';
showLoader(`Processing ${fileType}...`, `Extracting content from ${file.name}`);
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload-docx', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
console.log(`${fileType} processed: ${data.blocks.length} blocks`);
const projectName = file.name.replace(/\.(docx|doc)$/i, '');
document.getElementById('projectName').value = projectName;
currentProject.name = projectName;
// v4.4: auto-generated thumbnail token সংরক্ষণ
pendingThumbnailToken = data.pending_thumbnail || null;
renderDocumentBlocks(data.blocks);
document.getElementById('uploadSection').style.display = 'none';
document.getElementById('editorSection').style.display = 'block';
const panel = document.getElementById('audiobookMakerPanel');
if (panel) panel.style.display = 'flex';
const sidebar = document.getElementById('documentOutlineSidebar');
if (sidebar) sidebar.style.display = 'block';
hideLoader();
showNotification(`${fileType} processed: ${data.blocks.length} blocks extracted`, 'success');
updateWorkflowProgress('edit');
} catch (error) {
hideLoader();
console.error(`${fileType} processing error:`, error);
alert(`Failed to process ${fileType}: ` + error.message);
}
}
/**
* AUTHORITATIVE renderDocumentBlocks()
* This is the ONLY version of this function in the entire app.
* Section dividers are rendered from editorBlocks[].sectionStart data.
*/
function renderDocumentBlocks(blocks) {
const editor = document.getElementById('markdownEditor');
if (!editor) return;
editor.innerHTML = '';
editorBlocks = [];
const emptyMessage = document.getElementById('emptyEditorMessage');
if (emptyMessage) {
emptyMessage.style.display = 'none';
}
for (const block of blocks) {
let type = 'paragraph';
let content = block.content || '';
if (block.type === 'image') {
type = 'image';
} else if (block.type === 'heading1' || content.startsWith('# ')) {
type = 'heading1';
} else if (block.type === 'heading2' || content.startsWith('## ')) {
type = 'heading2';
} else if (block.type === 'heading3' || content.startsWith('### ')) {
type = 'heading3';
} else if (block.type === 'list_item' || content.startsWith('- ')) {
type = 'bulletList';
} else if (block.type === 'quote' || content.startsWith('> ')) {
type = 'quote';
} else if (block.type === 'table') {
type = 'table';
}
let images = [];
if (block.type === 'image' && block.data) {
images = [{
data: block.data,
format: block.format || 'png',
alt_text: 'Document Image',
position: 'before'
}];
content = `![Document Image](embedded-image.${block.format || 'png'})`;
}
const lastChild = editor.lastElementChild;
const blockId = addBlock(type, content, lastChild, images);
// Store section info in editorBlocks data (data-driven approach)
if (block.is_section_start) {
const blockData = editorBlocks.find(b => b.id === blockId);
if (blockData) {
blockData.sectionStart = true;
blockData.sectionName = block.section_name || 'Section';
}
}
if (block.type === 'image' && block.data) {
const blockEl = document.getElementById(blockId);
if (blockEl) {
const contentDiv = blockEl.querySelector('.md-block-content');
if (contentDiv) {
contentDiv.innerHTML = `
<div class="image-block">
<img src="data:image/${block.format || 'png'};base64,${block.data}" alt="Document Image">
</div>
`;
}
}
}
const blockEl = document.getElementById(blockId);
if (blockEl) {
ensureNewBlockLineAfter(blockEl);
}
}
// Now render all section dividers from data
renderAllSectionDividers();
repairAllNewBlockLines();
// Initialize panel state for new document
const textBlocks = getTextBlocks();
if (textBlocks.length > 0) {
panelState.startingBlockId = textBlocks[0].id;
panelState.blockCount = textBlocks.length; // Modified: set to total blocks
}
updatePanelUI();
renderDocumentOutline();
checkEmptyEditor();
}
// Keep backward compatibility alias
function renderPdfBlocks(blocks) {
renderDocumentBlocks(blocks);
}