// Viewer.js - Document viewer functionality // Configuration const DOC_MAP = { 'USER_GUIDE': '/docs/USER_GUIDE.md', 'E2E_TESTING_GUIDE': '/docs/E2E_TESTING_GUIDE.md', 'IMPLEMENTATION_COMPLETE': '/docs/IMPLEMENTATION_COMPLETE.md', 'ARCHITECTURE_GUIDE': '/docs/ARCHITECTURE_GUIDE.md', 'QUICK_START': '/docs/QUICK_START.md', 'DOCUMENTATION_INDEX': '/docs/DOCUMENTATION_INDEX.md', 'IMPLEMENTATION_SUMMARY': '/docs/IMPLEMENTATION_SUMMARY.md' }; let currentDoc = null; let sidebarOpen = true; // Initialize on page load document.addEventListener('DOMContentLoaded', function() { initViewer(); setupEventListeners(); loadDocumentFromURL(); }); function initViewer() { // Configure marked.js if (typeof marked !== 'undefined') { marked.setOptions({ breaks: true, gfm: true, highlight: function(code, lang) { if (typeof hljs !== 'undefined' && lang && hljs.getLanguage(lang)) { try { return hljs.highlight(code, { language: lang }).value; } catch (err) { console.error('Highlight error:', err); } } return code; } }); } } function setupEventListeners() { // Sidebar toggle const toggleBtn = document.getElementById('toggle-sidebar'); if (toggleBtn) { toggleBtn.addEventListener('click', toggleSidebar); } // Document selector const docSelector = document.getElementById('doc-selector'); if (docSelector) { docSelector.addEventListener('change', function() { if (this.value) { loadDocument(this.value); } }); } // Print button const printBtn = document.getElementById('print-doc'); if (printBtn) { printBtn.addEventListener('click', function() { window.print(); }); } // Download button const downloadBtn = document.getElementById('download-doc'); if (downloadBtn) { downloadBtn.addEventListener('click', downloadCurrentDoc); } // Sidebar navigation links document.querySelectorAll('.sidebar-nav .nav-link').forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const url = this.getAttribute('href'); const params = new URLSearchParams(url.split('?')[1]); const doc = params.get('doc'); if (doc) { loadDocument(doc); updateURL(doc); } }); }); } function loadDocumentFromURL() { const params = new URLSearchParams(window.location.search); const doc = params.get('doc'); if (doc && DOC_MAP[doc]) { loadDocument(doc); } else { showError('No document specified. Please select a document from the navigation.'); } } async function loadDocument(docKey) { const docPath = DOC_MAP[docKey]; if (!docPath) { showError(`Document "${docKey}" not found`); return; } currentDoc = docKey; showLoading(); hideError(); try { const response = await fetch(docPath); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const markdown = await response.text(); renderMarkdown(markdown); generateTOC(); updateActiveNav(docKey); updateDocSelector(docKey); updateURL(docKey); // Scroll to top window.scrollTo(0, 0); } catch (error) { console.error('Error loading document:', error); showError(`Failed to load document: ${error.message}`); } finally { hideLoading(); } } function renderMarkdown(markdown) { const contentDiv = document.getElementById('content'); if (!contentDiv) return; try { // Parse markdown to HTML const html = marked.parse(markdown); // Sanitize HTML using DOMPurify if available, otherwise use trusted content const safeHTML = (typeof DOMPurify !== 'undefined') ? DOMPurify.sanitize(html, { ADD_ATTR: ['target'] }) : html; contentDiv.innerHTML = safeHTML; // Syntax highlighting for code blocks if (typeof hljs !== 'undefined') { contentDiv.querySelectorAll('pre code').forEach((block) => { hljs.highlightElement(block); }); } // Make external links open in new tab contentDiv.querySelectorAll('a[href^="http"]').forEach(link => { link.setAttribute('target', '_blank'); link.setAttribute('rel', 'noopener noreferrer'); }); // Add IDs to headings for anchor links contentDiv.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach((heading, index) => { if (!heading.id) { heading.id = `heading-${index}`; } }); } catch (error) { console.error('Error rendering markdown:', error); showError(`Failed to render document: ${error.message}`); } } function generateTOC() { const tocContent = document.getElementById('toc-content'); const content = document.getElementById('content'); if (!tocContent || !content) return; const headings = content.querySelectorAll('h2, h3'); if (headings.length === 0) { tocContent.textContent = 'No headings found'; return; } // Clear existing content tocContent.innerHTML = ''; // Create TOC links using DOM methods headings.forEach(heading => { const level = heading.tagName.toLowerCase(); const text = heading.textContent; const id = heading.id || `heading-${text.replace(/\s+/g, '-').toLowerCase()}`; heading.id = id; const link = document.createElement('a'); link.href = `#${id}`; link.textContent = text; if (level === 'h3') { link.style.marginLeft = '1rem'; } link.addEventListener('click', function(e) { e.preventDefault(); const target = document.getElementById(id); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); tocContent.appendChild(link); }); } function toggleSidebar() { const sidebar = document.querySelector('.sidebar'); const icon = document.getElementById('sidebar-icon'); if (sidebar) { sidebarOpen = !sidebarOpen; sidebar.classList.toggle('active'); if (icon) { icon.textContent = sidebarOpen ? '☰' : '✕'; } } } function updateActiveNav(docKey) { document.querySelectorAll('.sidebar-nav .nav-link').forEach(link => { link.classList.remove('active'); const url = link.getAttribute('href'); if (url && url.includes(`doc=${docKey}`)) { link.classList.add('active'); } }); } function updateDocSelector(docKey) { const selector = document.getElementById('doc-selector'); if (selector) { selector.value = docKey; } } function updateURL(docKey) { const newURL = `${window.location.pathname}?doc=${docKey}`; window.history.pushState({ doc: docKey }, '', newURL); } function downloadCurrentDoc() { if (!currentDoc) { alert('No document loaded'); return; } const docPath = DOC_MAP[currentDoc]; const filename = docPath.split('/').pop(); fetch(docPath) .then(response => response.text()) .then(text => { const blob = new Blob([text], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }) .catch(error => { console.error('Download error:', error); alert('Failed to download document'); }); } function showLoading() { const loading = document.getElementById('loading'); const content = document.getElementById('content'); if (loading) loading.style.display = 'block'; if (content) content.style.display = 'none'; } function hideLoading() { const loading = document.getElementById('loading'); const content = document.getElementById('content'); if (loading) loading.style.display = 'none'; if (content) content.style.display = 'block'; } function showError(message) { const error = document.getElementById('error'); const errorMessage = document.getElementById('error-message'); const content = document.getElementById('content'); if (error) error.style.display = 'block'; if (errorMessage) errorMessage.textContent = message; if (content) content.style.display = 'none'; } function hideError() { const error = document.getElementById('error'); if (error) error.style.display = 'none'; } // Handle browser back/forward buttons window.addEventListener('popstate', function(event) { if (event.state && event.state.doc) { loadDocument(event.state.doc); } }); // Search functionality (future enhancement) function searchDocumentation(query) { // TODO: Implement search across all documentation console.log('Search query:', query); }