356 lines
13 KiB
JavaScript
356 lines
13 KiB
JavaScript
/**
|
|
* Automated Table of Contents Generator
|
|
* Scans document for headings and generates TOC with REAL page numbers from PDF
|
|
*/
|
|
|
|
class TOCGenerator {
|
|
constructor() {
|
|
this.tocContainer = null;
|
|
this.headings = [];
|
|
this.pageNumbers = {};
|
|
this.totalPages = 0;
|
|
}
|
|
|
|
/**
|
|
* Initialize the TOC generator
|
|
*/
|
|
async init() {
|
|
console.log('🚀 TOC Generator initializing...');
|
|
|
|
// Find the TOC container
|
|
this.tocContainer = document.querySelector('.toc-auto');
|
|
if (!this.tocContainer) {
|
|
console.error('❌ TOC container not found. Looking for .toc-auto class.');
|
|
return;
|
|
}
|
|
|
|
console.log('✅ TOC container found:', this.tocContainer);
|
|
|
|
// Load real page numbers from PDF analysis
|
|
await this.loadPageNumbers();
|
|
|
|
// Generate TOC
|
|
this.generateTOC();
|
|
|
|
// Add event listener for dynamic updates
|
|
this.setupDynamicUpdates();
|
|
|
|
console.log('✅ TOC Generator initialized successfully');
|
|
}
|
|
|
|
/**
|
|
* Load real page numbers from the JSON file generated by PDF analysis
|
|
*/
|
|
async loadPageNumbers() {
|
|
try {
|
|
// Try to load page numbers from the JSON file
|
|
const response = await fetch('../page_numbers.json');
|
|
if (response.ok) {
|
|
this.pageNumbers = await response.json();
|
|
console.log('✅ Loaded real page numbers from PDF analysis');
|
|
|
|
// Calculate total pages from the highest page number
|
|
this.totalPages = Math.max(...Object.values(this.pageNumbers));
|
|
console.log(`📊 Total pages from PDF analysis: ${this.totalPages}`);
|
|
|
|
// Update CSS variable immediately
|
|
if (this.totalPages) {
|
|
document.documentElement.style.setProperty('--total-pages', this.totalPages);
|
|
console.log(`🎨 CSS variable --total-pages set to: ${this.totalPages}`);
|
|
} else {
|
|
console.error('❌ Cannot set CSS variable: totalPages is null');
|
|
}
|
|
} else {
|
|
console.error('❌ Page numbers file not found or empty');
|
|
this.pageNumbers = {};
|
|
this.totalPages = null; // No hardcoded value!
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ ERROR: Cannot load page numbers from JSON file!');
|
|
console.error(' This usually means:');
|
|
console.error(' 1. The HTML file is opened directly (file://) instead of through a web server');
|
|
console.error(' 2. The page_numbers.json file is missing or corrupted');
|
|
console.error(' 3. CORS policy is blocking the request');
|
|
console.error(' Solution: Run the HTML through a local web server or fix the JSON file path');
|
|
console.error(' Original error:', error);
|
|
|
|
// Show error in TOC container instead of hardcoded values
|
|
this.tocContainer.innerHTML = `
|
|
<div style="color: red; padding: 20px; border: 2px solid red; background: #ffe6e6;">
|
|
<h2>❌ Fehler beim Laden der Seitennummern</h2>
|
|
<p><strong>Problem:</strong> Die Datei page_numbers.json kann nicht geladen werden.</p>
|
|
<p><strong>Mögliche Ursachen:</strong></p>
|
|
<ul>
|
|
<li>HTML-Datei wird direkt geöffnet (file://) statt über Webserver</li>
|
|
<li>page_numbers.json fehlt oder ist beschädigt</li>
|
|
<li>CORS-Richtlinie blockiert den Zugriff</li>
|
|
</ul>
|
|
<p><strong>Lösung:</strong> Führen Sie main.py aus, um PDF zu generieren und zu analysieren.</p>
|
|
</div>
|
|
`;
|
|
|
|
this.pageNumbers = {};
|
|
this.totalPages = null; // No hardcoded value!
|
|
|
|
// Still try to generate TOC with estimated numbers
|
|
console.log('🔄 Attempting to generate TOC with estimated numbers...');
|
|
this.generateTOC();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate the complete TOC
|
|
*/
|
|
generateTOC() {
|
|
console.log('🔄 Generating TOC...');
|
|
|
|
// Clear existing content
|
|
this.tocContainer.innerHTML = '<h1 class="toc-title">Inhaltsverzeichnis</h1>';
|
|
|
|
// Find all headings with specific classes
|
|
this.headings = [];
|
|
|
|
// Try different selectors to find headings
|
|
const headingSelectors = [
|
|
'h1.content-heading.heading-level-1',
|
|
'h2.content-heading.heading-level-2',
|
|
'h3.content-heading.heading-level-3',
|
|
'h1.heading-level-1',
|
|
'h2.heading-level-2',
|
|
'h3.heading-level-3',
|
|
'h1.content-heading',
|
|
'h2.content-heading',
|
|
'h3.content-heading'
|
|
];
|
|
|
|
console.log('🔍 Looking for headings with selectors:', headingSelectors);
|
|
|
|
let foundAny = false;
|
|
|
|
headingSelectors.forEach(selector => {
|
|
const elements = document.querySelectorAll(selector);
|
|
console.log(`📋 Found ${elements.length} elements for selector: ${selector}`);
|
|
|
|
if (elements.length > 0) {
|
|
foundAny = true;
|
|
}
|
|
|
|
elements.forEach(element => {
|
|
// Get the text content WITH the numbering
|
|
const text = element.textContent.trim();
|
|
const level = element.classList.contains('heading-level-1') ? 1 :
|
|
element.classList.contains('heading-level-2') ? 2 : 3;
|
|
|
|
console.log(`📝 Heading: "${text}" (Level ${level})`);
|
|
|
|
// Only add if we don't already have this text
|
|
if (!this.headings.some(h => h.text === text)) {
|
|
this.headings.push({
|
|
element: element,
|
|
text: text,
|
|
level: level,
|
|
page: this.estimatePageNumber(element)
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
if (!foundAny) {
|
|
console.log('⚠️ No headings found with any selector. Trying generic h1, h2, h3...');
|
|
const genericHeadings = document.querySelectorAll('h1, h2, h3');
|
|
console.log(`📋 Found ${genericHeadings.length} generic headings`);
|
|
|
|
genericHeadings.forEach((element, index) => {
|
|
const text = element.textContent.trim();
|
|
const level = element.tagName === 'H1' ? 1 : element.tagName === 'H2' ? 2 : 3;
|
|
|
|
console.log(`📝 Generic Heading: "${text}" (Level ${level})`);
|
|
|
|
this.headings.push({
|
|
element: element,
|
|
text: text,
|
|
level: level,
|
|
page: 3 + Math.floor(index / 2) // Simple page estimation
|
|
});
|
|
});
|
|
}
|
|
|
|
console.log(`📊 Total headings found: ${this.headings.length}`);
|
|
|
|
// Sort headings by their position in the document
|
|
this.headings.sort((a, b) => {
|
|
const posA = a.element.getBoundingClientRect().top;
|
|
const posB = b.element.getBoundingClientRect().top;
|
|
return posA - posB;
|
|
});
|
|
|
|
// Generate TOC entries
|
|
this.headings.forEach(heading => {
|
|
const entry = this.createTOCEntry(heading);
|
|
this.tocContainer.appendChild(entry);
|
|
});
|
|
|
|
// Update total pages based on actual content
|
|
if (this.headings.length > 0) {
|
|
const maxPage = Math.max(...this.headings.map(h => h.page));
|
|
this.totalPages = maxPage;
|
|
console.log(`📊 Total pages calculated: ${this.totalPages}`);
|
|
|
|
// Update CSS variable for footers
|
|
document.documentElement.style.setProperty('--total-pages', this.totalPages);
|
|
|
|
// Update footer text to show total pages
|
|
this.updateFooterText();
|
|
}
|
|
|
|
console.log('✅ TOC generation complete');
|
|
}
|
|
|
|
/* Removed getRealPageNumber function - using direct estimation instead */
|
|
|
|
/**
|
|
* Estimate page number (fallback method)
|
|
*/
|
|
estimatePageNumber(element) {
|
|
try {
|
|
if (!element) return 3;
|
|
|
|
const rect = element.getBoundingClientRect();
|
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
const absoluteTop = rect.top + scrollTop;
|
|
|
|
// A4 page height in pixels (assuming 96 DPI)
|
|
// A4 = 210mm x 297mm, 1 inch = 25.4mm, 1 inch = 96 pixels
|
|
const pageHeightPixels = (297 * 96) / 25.4; // ~1123 pixels per page
|
|
|
|
// Account for margins and spacing - use actual content height
|
|
const effectivePageHeight = pageHeightPixels - 150; // More realistic margin
|
|
|
|
// Calculate page number (start from page 3 since pages 1-2 are title and TOC)
|
|
let pageNumber = Math.floor(absoluteTop / effectivePageHeight) + 3;
|
|
|
|
// Don't clamp - let it go to actual page count
|
|
console.log(`📄 Element "${element.textContent.trim()}" at position ${absoluteTop}px = page ${pageNumber}`);
|
|
|
|
return pageNumber;
|
|
} catch (error) {
|
|
console.warn('Error estimating page number:', error);
|
|
return 3; // Default fallback
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a TOC entry element
|
|
*/
|
|
createTOCEntry(heading) {
|
|
const entry = document.createElement('div');
|
|
entry.className = `toc-entry toc-level-${heading.level}`;
|
|
|
|
// Create the text content
|
|
const textSpan = document.createElement('span');
|
|
textSpan.className = 'toc-text';
|
|
textSpan.textContent = heading.text;
|
|
|
|
// Create the dots area (now just spacing)
|
|
const dotsSpan = document.createElement('span');
|
|
dotsSpan.className = 'dots';
|
|
|
|
// Create the page number
|
|
const pageSpan = document.createElement('span');
|
|
pageSpan.className = 'toc-page';
|
|
pageSpan.textContent = heading.page;
|
|
|
|
// Assemble the entry
|
|
entry.appendChild(textSpan);
|
|
entry.appendChild(dotsSpan);
|
|
entry.appendChild(pageSpan);
|
|
|
|
// Add click functionality to jump to heading
|
|
entry.addEventListener('click', () => {
|
|
heading.element.scrollIntoView({ behavior: 'smooth' });
|
|
});
|
|
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* Setup dynamic updates
|
|
*/
|
|
setupDynamicUpdates() {
|
|
// Refresh TOC when window is resized
|
|
window.addEventListener('resize', () => {
|
|
setTimeout(() => this.refresh(), 100);
|
|
});
|
|
|
|
// Refresh TOC when content changes
|
|
const observer = new MutationObserver(() => {
|
|
this.refresh();
|
|
});
|
|
|
|
// Observe the main content area
|
|
const mainContent = document.querySelector('.main-content');
|
|
if (mainContent) {
|
|
observer.observe(mainContent, {
|
|
childList: true,
|
|
subtree: true,
|
|
characterData: true
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh the TOC
|
|
*/
|
|
async refresh() {
|
|
await this.loadPageNumbers();
|
|
this.generateTOC();
|
|
}
|
|
|
|
/**
|
|
* Estimate page number based on element position
|
|
*/
|
|
estimatePageNumber(element) {
|
|
const pageHeightPixels = 1123; // A4 height in pixels at 96 DPI
|
|
const absoluteTop = element.offsetTop;
|
|
const estimatedPage = Math.floor(absoluteTop / pageHeightPixels) + 1;
|
|
console.log(`📍 Element "${element.textContent.trim()}" at position ${absoluteTop}px -> estimated page ${estimatedPage}`);
|
|
return estimatedPage;
|
|
}
|
|
|
|
/**
|
|
* Get total page count for footer updates
|
|
*/
|
|
getTotalPages() {
|
|
return this.totalPages;
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('🚀 TOC Generator: DOM loaded, starting...');
|
|
|
|
// Wait for numbering script to finish
|
|
setTimeout(() => {
|
|
console.log('🔄 TOC Generator: Creating instance...');
|
|
window.tocGenerator = new TOCGenerator();
|
|
window.tocGenerator.init();
|
|
}, 100);
|
|
});
|
|
|
|
// Also try immediate execution as fallback
|
|
console.log('📜 TOC Generator script loaded');
|
|
|
|
// Simple test - if this shows up, JavaScript is working
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('🧪 SIMPLE TEST: JavaScript is working!');
|
|
|
|
// Test if we can find the TOC container
|
|
const tocContainer = document.querySelector('.toc-auto');
|
|
if (tocContainer) {
|
|
console.log('✅ Found TOC container:', tocContainer);
|
|
// Don't overwrite the TOC content - just log that we found it
|
|
} else {
|
|
console.log('❌ TOC container NOT found');
|
|
}
|
|
});
|
|
|