/* eslint-env browser */ /* global CodeMirror */ // Lightweight CodeMirror editor skeleton // This file initializes a CodeMirror instance on demand and exposes a simple API const EditorSkeleton = (function() { let editor = null; let container = null; let currentMode = 'javascript'; function createContainer() { container = document.createElement('div'); container.id = 'editor-modal'; container.style.position = 'fixed'; container.style.top = '10%'; container.style.left = '50%'; container.style.transform = 'translateX(-50%)'; container.style.width = '80%'; container.style.maxWidth = '900px'; container.style.zIndex = 9999; container.style.background = 'var(--card-bg, #fff)'; container.style.border = '1px solid rgba(0,0,0,0.08)'; container.style.boxShadow = '0 8px 24px rgba(2,6,23,0.12)'; container.style.padding = '0'; container.style.display = 'none'; const header = document.createElement('div'); header.style.display = 'flex'; header.style.justifyContent = 'space-between'; header.style.alignItems = 'center'; header.style.padding = '8px 12px'; header.style.borderBottom = '1px solid rgba(0,0,0,0.06)'; const title = document.createElement('div'); title.textContent = 'Editor'; title.style.fontWeight = '600'; const controls = document.createElement('div'); controls.className = 'group-buttons'; const runBtn = document.createElement('button'); runBtn.textContent = 'Run'; runBtn.className = 'btn btn-primary btn-sm'; runBtn.addEventListener('click', () => { const code = editor ? editor.getValue() : ''; runCodeInSandbox(code); }); const stopBtn = document.createElement('button'); stopBtn.textContent = 'Stop'; stopBtn.className = 'btn btn-sm'; stopBtn.title = 'Stop execution'; stopBtn.addEventListener('click', () => { stopExecution(); }); const clearBtn = document.createElement('button'); clearBtn.textContent = 'Clear'; clearBtn.className = 'btn btn-sm'; clearBtn.title = 'Clear output'; clearBtn.addEventListener('click', () => { clearOutput(); }); const closeBtn = document.createElement('button'); closeBtn.textContent = 'ปิด'; closeBtn.className = 'btn btn-sm'; closeBtn.addEventListener('click', () => hide()); controls.appendChild(runBtn); controls.appendChild(stopBtn); controls.appendChild(clearBtn); controls.appendChild(closeBtn); header.appendChild(title); header.appendChild(controls); const editorHost = document.createElement('div'); editorHost.id = 'editor-host'; const outputHost = document.createElement('div'); outputHost.id = 'editor-output'; outputHost.style.height = '20vh'; outputHost.style.borderTop = '1px solid rgba(0,0,0,0.06)'; outputHost.style.padding = '8px'; outputHost.style.overflow = 'auto'; container.appendChild(header); container.appendChild(editorHost); container.appendChild(outputHost); document.body.appendChild(container); } function init(initialCode = '', mode = 'javascript') { currentMode = mode || 'javascript'; if (!container) createContainer(); const host = document.getElementById('editor-host'); if (!host) return; // If CodeMirror is available, initialize or update the editor if (window.CodeMirror) { if (!editor) { editor = CodeMirror(host, { value: initialCode, mode: mode, lineNumbers: true, theme: 'default', viewportMargin: Infinity, }); } else { editor.setValue(initialCode); editor.setOption('mode', mode); } } else { host.textContent = 'CodeMirror ไม่พร้อมใช้งาน'; } } function runCodeInSandbox(code) { let outputHost = document.getElementById('editor-output'); if (!outputHost) return; // create or reuse iframe let iframe = document.getElementById('editor-run-iframe'); if (iframe) iframe.remove(); iframe = document.createElement('iframe'); iframe.id = 'editor-run-iframe'; iframe.style.border = 'none'; iframe.sandbox = 'allow-scripts'; // clear previous output and iframe outputHost.innerHTML = ''; // helper: console override script to post messages to parent const consoleOverride = ` `; const safeUser = String(code); let html = ''; const mode = (currentMode || '').toLowerCase(); // detect HTML-like input regardless of selected mode to avoid running HTML inside a `; // keep iframe hidden for pure JS unless it writes to DOM; but show it so document writes are visible iframe.style.display = 'none'; outputHost.style.background = '#0b1220'; outputHost.style.color = '#d1fae5'; } else if (effectiveMode === 'html' || effectiveMode === 'htmlmixed' || /, attempt to inject consoleOverride into head if (/]*>/i.test(user)) { user = user.replace(/]*>/i, match => match + consoleOverride); html = user; } else if (/]*>/i.test(user)) { // insert a head with consoleOverride after html = user.replace(/]*)>/i, (m, g1) => `${consoleOverride}`); } else { // wrap the user's fragment html = `${consoleOverride}${user}`; } iframe.style.display = 'block'; iframe.style.width = '100%'; iframe.style.height = '18vh'; outputHost.style.background = '#FFFFFF'; } else { // fallback: treat as plain text html = `${consoleOverride}
${safeUser}
`; iframe.style.display = 'block'; iframe.style.width = '100%'; iframe.style.height = '18vh'; outputHost.style.background = '#FFFF00'; } iframe.srcdoc = html; outputHost.appendChild(iframe); // Listen for messages from iframe function onMessage(e) { if (!e.data) return; if (e.data.type === 'editor-log') { appendOutput(e.data.msg); } else if (e.data.type === 'editor-error') { appendOutput('Error: ' + e.data.msg); } } // ensure we don't attach multiple handlers window.removeEventListener('message', window._editorRunListener || (() => {})); window._editorRunListener = onMessage; window.addEventListener('message', window._editorRunListener); } function clearOutput() { const out = document.getElementById('editor-output'); if (!out) return; // remove iframe if exists const iframe = document.getElementById('editor-run-iframe'); if (iframe) iframe.remove(); // clear textual output out.innerHTML = ''; } function stopExecution() { // remove iframe and message listener const iframe = document.getElementById('editor-run-iframe'); if (iframe) iframe.remove(); if (window._editorRunListener) { window.removeEventListener('message', window._editorRunListener); delete window._editorRunListener; } // also clear any output clearOutput(); } function appendOutput(text) { const out = document.getElementById('editor-output'); if (!out) return; const line = document.createElement('div'); line.style.background = '#0b1220'; line.style.color = '#d1fae5'; line.textContent = text; line.style.whiteSpace = 'pre-wrap'; out.appendChild(line); } function show() { if (!container) createContainer(); container.style.display = 'block'; setTimeout(() => { if (editor) editor.refresh(); }, 50); } function hide() { if (container) container.style.display = 'none'; } function getValue() { return editor ? editor.getValue() : ''; } function setValue(v) { if (editor) editor.setValue(v); } return { init, show, hide, getValue, setValue, run: runCodeInSandbox, }; })(); // Auto-wire: any element with data-action="open-editor" will open editor with code from data-code window.addEventListener('click', (e) => { const target = e.target.closest('[data-action="open-editor"]'); if (!target) return; const code = target.getAttribute('data-code') || ''; const mode = target.getAttribute('data-mode') || 'javascript'; EditorSkeleton.init(code, mode); EditorSkeleton.show(); }); // Expose for other scripts window.EditorSkeleton = EditorSkeleton;