Spaces:
Running
Running
| const DEFAULT_LATEX = `\\documentclass{article} | |
| \\usepackage[utf8]{inputenc} | |
| \\usepackage{amsmath} | |
| \\usepackage{graphicx} | |
| \\title{My Document} | |
| \\author{Author Name} | |
| \\date{\\today} | |
| \\begin{document} | |
| \\maketitle | |
| \\section{Introduction} | |
| Welcome to the \\LaTeX\\ editor! Start typing to see instant preview. | |
| \\section{Mathematics} | |
| Here's a famous equation: | |
| \\[ | |
| E = mc^2 | |
| \\] | |
| And an inline equation: $a^2 + b^2 = c^2$ | |
| \\section{Lists} | |
| \\begin{itemize} | |
| \\item First item | |
| \\item Second item | |
| \\item Third item | |
| \\end{itemize} | |
| \\end{document}`; | |
| const editor = CodeMirror.fromTextArea(document.getElementById('editor'), { | |
| mode: 'stex', | |
| theme: 'dracula', | |
| lineNumbers: true, | |
| lineWrapping: true, | |
| autofocus: true, | |
| tabSize: 4, | |
| indentUnit: 4, | |
| matchBrackets: true, | |
| autoCloseBrackets: true | |
| }); | |
| editor.setValue(DEFAULT_LATEX); | |
| const statusEl = document.getElementById('status'); | |
| const pdfCanvas = document.getElementById('pdf-canvas'); | |
| const pdfContainer = document.getElementById('pdf-container'); | |
| const placeholder = document.getElementById('placeholder'); | |
| const ctx = pdfCanvas.getContext('2d'); | |
| let currentPdf = null; | |
| let currentPage = 1; | |
| let scale = 1.5; | |
| let debounceTimer = null; | |
| let ws = null; | |
| let reconnectAttempts = 0; | |
| const MAX_RECONNECT_ATTEMPTS = 5; | |
| function setStatus(text, type = 'info') { | |
| statusEl.textContent = text; | |
| statusEl.className = 'status ' + type; | |
| } | |
| function connectWebSocket() { | |
| const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; | |
| ws = new WebSocket(`${protocol}//${window.location.host}/ws`); | |
| ws.onopen = () => { | |
| setStatus('Connected', 'success'); | |
| reconnectAttempts = 0; | |
| compile(); | |
| }; | |
| ws.onmessage = async (event) => { | |
| const data = JSON.parse(event.data); | |
| if (data.type === 'compiling') { | |
| setStatus('Compiling...', 'info'); | |
| } else if (data.type === 'pdf') { | |
| setStatus('Ready', 'success'); | |
| await renderPdf(data.data); | |
| } else if (data.type === 'error') { | |
| setStatus('Compilation Error', 'error'); | |
| showError(data.log); | |
| } | |
| }; | |
| ws.onclose = () => { | |
| setStatus('Disconnected', 'error'); | |
| if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { | |
| reconnectAttempts++; | |
| setTimeout(connectWebSocket, 1000 * reconnectAttempts); | |
| } | |
| }; | |
| ws.onerror = () => { | |
| setStatus('Connection Error', 'error'); | |
| }; | |
| } | |
| function compile() { | |
| if (ws && ws.readyState === WebSocket.OPEN) { | |
| const latex = editor.getValue(); | |
| ws.send(JSON.stringify({ type: 'compile', latex })); | |
| } | |
| } | |
| function debounceCompile() { | |
| clearTimeout(debounceTimer); | |
| debounceTimer = setTimeout(compile, 500); | |
| } | |
| async function renderPdf(base64Data) { | |
| try { | |
| const pdfData = atob(base64Data); | |
| const uint8Array = new Uint8Array(pdfData.length); | |
| for (let i = 0; i < pdfData.length; i++) { | |
| uint8Array[i] = pdfData.charCodeAt(i); | |
| } | |
| currentPdf = await pdfjsLib.getDocument({ data: uint8Array }).promise; | |
| await renderPage(currentPage); | |
| placeholder.style.display = 'none'; | |
| pdfCanvas.style.display = 'block'; | |
| } catch (e) { | |
| console.error('PDF render error:', e); | |
| } | |
| } | |
| async function renderPage(pageNum) { | |
| if (!currentPdf) return; | |
| const page = await currentPdf.getPage(pageNum); | |
| const viewport = page.getViewport({ scale }); | |
| pdfCanvas.height = viewport.height; | |
| pdfCanvas.width = viewport.width; | |
| await page.render({ | |
| canvasContext: ctx, | |
| viewport: viewport | |
| }).promise; | |
| } | |
| function showError(log) { | |
| placeholder.innerHTML = `<pre class="error-log">${escapeHtml(log)}</pre>`; | |
| placeholder.style.display = 'block'; | |
| pdfCanvas.style.display = 'none'; | |
| } | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| document.getElementById('zoom-in').addEventListener('click', async () => { | |
| scale = Math.min(scale + 0.25, 3); | |
| document.getElementById('zoom-level').textContent = Math.round(scale / 1.5 * 100) + '%'; | |
| await renderPage(currentPage); | |
| }); | |
| document.getElementById('zoom-out').addEventListener('click', async () => { | |
| scale = Math.max(scale - 0.25, 0.5); | |
| document.getElementById('zoom-level').textContent = Math.round(scale / 1.5 * 100) + '%'; | |
| await renderPage(currentPage); | |
| }); | |
| const resizer = document.getElementById('resizer'); | |
| const editorPanel = document.querySelector('.editor-panel'); | |
| let isResizing = false; | |
| resizer.addEventListener('mousedown', (e) => { | |
| isResizing = true; | |
| document.body.style.cursor = 'col-resize'; | |
| document.body.style.userSelect = 'none'; | |
| }); | |
| document.addEventListener('mousemove', (e) => { | |
| if (!isResizing) return; | |
| const container = document.querySelector('.main'); | |
| const containerRect = container.getBoundingClientRect(); | |
| const percentage = ((e.clientX - containerRect.left) / containerRect.width) * 100; | |
| const clamped = Math.min(Math.max(percentage, 20), 80); | |
| editorPanel.style.width = clamped + '%'; | |
| }); | |
| document.addEventListener('mouseup', () => { | |
| isResizing = false; | |
| document.body.style.cursor = ''; | |
| document.body.style.userSelect = ''; | |
| }); | |
| editor.on('change', debounceCompile); | |
| connectWebSocket(); | |