Alt+1 Β· 2 Β· 3
Ln 1, Col 1 HTML Saved
kodo://preview β€” ready
Console 0
console.log() output appears here.
β€Ί
\n'; } function run(manual){ clearLogs(true); frame.srcdoc = buildDoc(true); var t = new Date(); $('addr').textContent = 'kodo://preview β€” ran at ' + t.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit',second:'2-digit'}); if (manual) { var f = $('flash'); f.classList.add('on'); setTimeout(function(){ f.classList.remove('on'); }, 60); } } var runTimer, saveTimer; function onChange(){ $('saved').classList.add('pending'); $('saved').textContent = 'Unsaved'; clearTimeout(saveTimer); saveTimer = setTimeout(function(){ save(false); }, 700); if (autoBox.checked) { clearTimeout(runTimer); runTimer = setTimeout(function(){ run(false); }, 650); } } /* ================= Console ================= */ var ICONS = { log:'β€Ί', info:'β„Ή', warn:'⚠', error:'βœ•', debug:'Β·', input:'❯', result:'←', system:'Β·' }; function addLog(type, args){ if (type === 'clear') { clearLogs(false); addLog('system', ['Console was cleared']); return; } var empty = logs.querySelector('.empty-logs'); if (empty) empty.remove(); var row = document.createElement('div'); row.className = 'log log-' + type; var ic = document.createElement('span'); ic.className = 'ic'; ic.textContent = ICONS[type] || 'β€Ί'; var pre = document.createElement('pre'); pre.textContent = args.join(' '); row.appendChild(ic); row.appendChild(pre); logs.appendChild(row); if (logs.children.length > 500) logs.removeChild(logs.firstChild); logs.scrollTop = logs.scrollHeight; if (type !== 'input' && type !== 'system') { logTotal++; } if (type === 'error') errTotal++; updateCount(); } function updateCount(){ var b = $('logCount'); b.textContent = errTotal ? errTotal + (errTotal === 1 ? ' error' : ' errors') : logTotal; b.classList.toggle('err', errTotal > 0); } function clearLogs(silent){ logs.innerHTML = silent ? '' : '
console.log() output appears here.
'; logTotal = 0; errTotal = 0; updateCount(); } window.addEventListener('message', function(e){ if (e.source !== frame.contentWindow) return; var d = e.data; if (d && d.__kodo) addLog(d.type, d.args || []); }); $('clearBtn').addEventListener('click', function(){ clearLogs(false); }); $('collapseBtn').addEventListener('click', function(){ var p = $('outPane'); p.classList.toggle('console-collapsed'); this.style.transform = p.classList.contains('console-collapsed') ? 'rotate(180deg)' : ''; }); var hist = [], hIdx = 0; $('promptForm').addEventListener('submit', function(e){ e.preventDefault(); var inp = $('promptInput'), code = inp.value.trim(); if (!code) return; addLog('input', [code]); hist.push(code); hIdx = hist.length; inp.value = ''; frame.contentWindow.postMessage({ __kodoEval:1, code:code }, '*'); }); $('promptInput').addEventListener('keydown', function(e){ if (e.key === 'ArrowUp' && hIdx > 0) { hIdx--; this.value = hist[hIdx]; e.preventDefault(); } else if (e.key === 'ArrowDown') { hIdx = Math.min(hist.length, hIdx+1); this.value = hist[hIdx] || ''; e.preventDefault(); } }); /* ================= Save / load / share ================= */ function getProject(){ return { html:editors.html.getValue(), css:editors.css.getValue(), js:editors.js.getValue() }; } function setProject(p){ ['html','css','js'].forEach(function(l){ editors[l].setValue(p[l] || ''); editors[l].clearHistory(); }); run(false); } function save(manual){ try { localStorage.setItem(STORAGE_KEY, JSON.stringify(getProject())); } catch(e){} $('saved').classList.remove('pending'); $('saved').textContent = 'Saved'; if (manual) toast('Saved to this browser βœ“'); } function encode(obj){ return btoa(unescape(encodeURIComponent(JSON.stringify(obj)))); } function decode(str){ return JSON.parse(decodeURIComponent(escape(atob(str)))); } $('shareBtn').addEventListener('click', function(){ var url = location.origin + location.pathname + '#code=' + encode(getProject()); history.replaceState(null, '', url); var done = function(){ toast('Share link copied to clipboard πŸ”—'); }; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(url).then(done, function(){ toast('Link is in the address bar β€” copy it to share'); }); } else { toast('Link is in the address bar β€” copy it to share'); } }); $('dlBtn').addEventListener('click', function(){ var blob = new Blob([buildDoc(false)], { type:'text/html' }); var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'kodo-project.html'; document.body.appendChild(a); a.click(); a.remove(); setTimeout(function(){ URL.revokeObjectURL(a.href); }, 1000); toast('Downloaded kodo-project.html'); }); $('openBtn').addEventListener('click', function(){ var blob = new Blob([buildDoc(false)], { type:'text/html' }); window.open(URL.createObjectURL(blob), '_blank'); }); $('runBtn').addEventListener('click', function(){ run(true); }); $('refreshBtn').addEventListener('click', function(){ run(true); }); autoBox.addEventListener('change', function(){ try { localStorage.setItem('kodo-auto', autoBox.checked ? '1' : '0'); } catch(e){} if (autoBox.checked) run(false); }); var lastTpl = 'hello'; tplSel.addEventListener('change', function(){ if (confirm('Load the "' + tplSel.options[tplSel.selectedIndex].text.replace(/^\S+\s/, '') + '" template? Your current code will be replaced.')) { lastTpl = tplSel.value; setProject(TEMPLATES[tplSel.value]); save(false); setTab('html'); toast('Template loaded'); } else { tplSel.value = lastTpl; } }); /* ================= Keyboard ================= */ document.addEventListener('keydown', function(e){ var mod = e.ctrlKey || e.metaKey; if (mod && e.key === 'Enter') { e.preventDefault(); run(true); } else if (mod && (e.key === 's' || e.key === 'S')) { e.preventDefault(); save(true); } else if (e.altKey && ['1','2','3'].indexOf(e.key) > -1) { e.preventDefault(); setTab(['html','css','js'][+e.key - 1]); } }); /* ================= Split resize ================= */ var gutter = $('gutter'), dragging = false; gutter.addEventListener('pointerdown', function(e){ dragging = true; gutter.setPointerCapture(e.pointerId); document.body.classList.add('dragging'); }); gutter.addEventListener('pointermove', function(e){ if (!dragging) return; var r = work.getBoundingClientRect(); var pct = Math.max(20, Math.min(80, (e.clientX - r.left) / r.width * 100)); work.style.setProperty('--split', pct + '%'); }); function endDrag(){ if (!dragging) return; dragging = false; document.body.classList.remove('dragging'); editors[current].refresh(); } gutter.addEventListener('pointerup', endDrag); gutter.addEventListener('pointercancel', endDrag); /* ================= Mobile view switch ================= */ document.querySelectorAll('.mswitch button').forEach(function(b){ b.addEventListener('click', function(){ work.dataset.view = b.dataset.view; document.querySelectorAll('.mswitch button').forEach(function(x){ x.setAttribute('aria-selected', x === b); }); if (b.dataset.view === 'code') editors[current].refresh(); }); }); /* ================= Toast ================= */ var toastTimer; function toast(msg){ var t = $('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(function(){ t.classList.remove('show'); }, 2200); } /* ================= Init ================= */ try { if (localStorage.getItem('kodo-auto') === '0') autoBox.checked = false; } catch(e){} var initial = null; var m = location.hash.match(/^#code=(.+)$/); if (m) { try { initial = decode(m[1]); } catch(e){ initial = null; } } if (!initial) { try { initial = JSON.parse(localStorage.getItem(STORAGE_KEY)); } catch(e){} } if (!initial || typeof initial.html !== 'string') initial = TEMPLATES.hello; setProject(initial); $('saved').classList.remove('pending'); $('saved').textContent = 'Saved'; setTimeout(function(){ editors.html.refresh(); updatePos(); }, 50); })();