writer / static /editor.js
abidlabs's picture
abidlabs HF Staff
changes
6981282
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();