/* Editor Page — LaTeX editor with PDF.js preview (no iframe, sandbox-safe) */ const DEFAULT_LATEX = `\\documentclass[12pt,a4paper]{article} \\usepackage[margin=1in]{geometry} \\usepackage{amsmath} \\usepackage{graphicx} \\usepackage{hyperref} \\title{My Document} \\author{Your Name} \\date{\\today} \\begin{document} \\maketitle \\begin{abstract} Write your abstract here. This is a brief summary of your work. \\end{abstract} \\section{Introduction} Start writing your introduction here. You can use \\LaTeX{} commands to format your text. \\section{Main Content} This is the main content of your document. You can add: \\begin{itemize} \\item Bullet points \\item Mathematics: $E = mc^2$ \\item References and citations \\end{itemize} \\subsection{A Subsection} Add subsections as needed. \\begin{equation} \\int_{0}^{\\infty} e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2} \\end{equation} \\section{Conclusion} Your conclusion goes here. \\end{document}`; const SNIPPETS = { equation: `\\begin{equation}\n f(x) = \\sum_{i=0}^{n} a_i x^i\n\\end{equation}`, table: `\\begin{table}[h]\n\\centering\n\\begin{tabular}{|l|c|r|}\n\\hline\nHeader 1 & Header 2 & Header 3 \\\\\n\\hline\nRow 1 & Value & Data \\\\\nRow 2 & Value & Data \\\\\n\\hline\n\\end{tabular}\n\\caption{Table caption}\n\\label{tab:mytable}\n\\end{table}`, figure: `\\begin{figure}[h]\n\\centering\n\\includegraphics[width=0.8\\linewidth]{filename.png}\n\\caption{Figure caption}\n\\label{fig:myfig}\n\\end{figure}`, itemize: `\\begin{itemize}\n \\item First item\n \\item Second item\n \\item Third item\n\\end{itemize}`, enumerate: `\\begin{enumerate}\n \\item First item\n \\item Second item\n \\item Third item\n\\end{enumerate}`, section: `\\section{Section Title}\n\nSection content here.\n\n\\subsection{Subsection}\n\nSubsection content.`, code: `\\usepackage{listings} % add to preamble\n\n\\begin{lstlisting}[language=Python]\ndef hello_world():\n print("Hello, World!")\n\\end{lstlisting}`, columns: `\\begin{columns}\n\\begin{column}{0.5\\textwidth}\n Left column content\n\\end{column}\n\\begin{column}{0.5\\textwidth}\n Right column content\n\\end{column}\n\\end{columns}`, }; // ─── State ──────────────────────────────────────────────────────────────────── let currentPdfBlob = null; // kept for PDF download let zoomLevel = 1.5; // PDF.js render scale let templateId = null; let pdfDoc = null; // loaded PDF.js document // ─── DOM refs ───────────────────────────────────────────────────────────────── const codeEditor = document.getElementById('code-editor'); const lineNumbers = document.getElementById('line-numbers'); const statusDot = document.querySelector('.status-dot'); const statusText = document.getElementById('status-text'); const canvasContainer = document.getElementById('pdf-canvas-container'); const previewPlaceholder = document.getElementById('preview-placeholder'); const previewLoading = document.getElementById('preview-loading'); const errorPanel = document.getElementById('error-panel'); const errorList = document.getElementById('error-list'); const docTitle = document.getElementById('doc-title'); // ─── Init ───────────────────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded', async () => { const params = new URLSearchParams(location.search); templateId = params.get('template'); if (templateId) { await loadTemplate(templateId); } else { codeEditor.value = DEFAULT_LATEX; updateLineNumbers(); } codeEditor.addEventListener('input', () => { updateLineNumbers(); setStatus('modified', 'Modified'); }); codeEditor.addEventListener('scroll', syncScroll); codeEditor.addEventListener('keydown', handleKeydown); document.getElementById('btn-compile').addEventListener('click', compileDocument); document.getElementById('placeholder-compile')?.addEventListener('click', compileDocument); document.getElementById('btn-check').addEventListener('click', checkSyntax); document.getElementById('btn-pdf').addEventListener('click', () => downloadFile('pdf')); document.getElementById('btn-docx').addEventListener('click', () => downloadFile('docx')); document.getElementById('btn-zoom-in').addEventListener('click', () => adjustZoom(0.25)); document.getElementById('btn-zoom-out').addEventListener('click', () => adjustZoom(-0.25)); document.getElementById('btn-fullscreen').addEventListener('click', toggleFullscreen); document.getElementById('btn-snippet').addEventListener('click', toggleSnippetPanel); document.getElementById('snippet-close').addEventListener('click', () => { document.getElementById('snippet-panel').style.display = 'none'; }); document.getElementById('error-close').addEventListener('click', () => { errorPanel.style.display = 'none'; }); document.getElementById('btn-upload-tex')?.addEventListener('click', () => { document.getElementById('tex-upload-input').click(); }); document.getElementById('tex-upload-input')?.addEventListener('change', handleTexUpload); document.querySelectorAll('.snippet-btn').forEach(btn => { btn.addEventListener('click', () => { const key = btn.dataset.snippet; if (SNIPPETS[key]) insertSnippet(SNIPPETS[key]); document.getElementById('snippet-panel').style.display = 'none'; }); }); initResizer(); document.getElementById('btn-format')?.addEventListener('click', formatCode); // Disable downloads until first compile document.getElementById('btn-pdf').disabled = true; document.getElementById('btn-docx').disabled = true; // Sync zoom label with initial render scale document.getElementById('zoom-label').textContent = `${Math.round(zoomLevel * 100)}%`; updateLineNumbers(); }); // ─── Template loader ────────────────────────────────────────────────────────── async function loadTemplate(id) { try { setStatus('compiling', 'Loading…'); const tpl = await api.getTemplate(id); codeEditor.value = tpl.latex_content || DEFAULT_LATEX; docTitle.value = tpl.name || 'Untitled'; updateLineNumbers(); setStatus('', 'Ready'); } catch (err) { codeEditor.value = DEFAULT_LATEX; updateLineNumbers(); setStatus('', 'Ready'); showToast(`Could not load template: ${err.message}`, 'error'); } } // ─── .tex file upload ───────────────────────────────────────────────────────── async function handleTexUpload(e) { const file = e.target.files[0]; if (!file) return; if (!file.name.endsWith('.tex')) { showToast('Please select a .tex file', 'error'); return; } const text = await file.text(); codeEditor.value = text; docTitle.value = file.name.replace('.tex', ''); updateLineNumbers(); setStatus('modified', 'Loaded from file'); showToast(`Loaded ${file.name}`, 'success'); e.target.value = ''; } // ─── Line Numbers ───────────────────────────────────────────────────────────── function updateLineNumbers() { const lines = codeEditor.value.split('\n').length; lineNumbers.textContent = Array.from({ length: lines }, (_, i) => i + 1).join('\n'); } function syncScroll() { lineNumbers.scrollTop = codeEditor.scrollTop; } // ─── Status ─────────────────────────────────────────────────────────────────── function setStatus(state, text) { statusDot.className = 'status-dot' + (state ? ` ${state}` : ''); statusText.textContent = text; } // ─── Keyboard Shortcuts ─────────────────────────────────────────────────────── function handleKeydown(e) { if (e.key === 'Tab') { e.preventDefault(); const start = codeEditor.selectionStart; const end = codeEditor.selectionEnd; codeEditor.value = codeEditor.value.substring(0, start) + ' ' + codeEditor.value.substring(end); codeEditor.selectionStart = codeEditor.selectionEnd = start + 2; updateLineNumbers(); } if ((e.ctrlKey || e.metaKey) && (e.key === 'Enter' || e.key === 's')) { e.preventDefault(); compileDocument(); } } // ─── Compile + PDF.js Render ────────────────────────────────────────────────── async function compileDocument() { const latex = codeEditor.value.trim(); if (!latex) { showToast('Editor is empty', 'error'); return; } setStatus('compiling', 'Compiling…'); previewPlaceholder.style.display = 'none'; previewLoading.style.display = 'flex'; canvasContainer.style.display = 'none'; const compileBtn = document.getElementById('btn-compile'); compileBtn.disabled = true; try { // Fetch compiled PDF as a blob (binary download from server) const blob = await api.compileDocument(latex, 'pdf'); currentPdfBlob = blob; // Render with PDF.js — works in any sandbox, no iframe at all await renderPdfBlob(blob); previewLoading.style.display = 'none'; canvasContainer.style.display = 'block'; errorPanel.style.display = 'none'; setStatus('success', 'Compiled'); showToast('Compiled successfully!', 'success'); document.getElementById('btn-pdf').disabled = false; document.getElementById('btn-docx').disabled = false; } catch (err) { previewLoading.style.display = 'none'; previewPlaceholder.style.display = 'flex'; setStatus('error', 'Error'); const errorMsg = err.message || 'Compilation failed'; errorList.innerHTML = errorMsg.split('\n') .filter(l => l.trim()) .map(l => `