diff --git a/public/index-editorial.html b/public/index-editorial.html index 98b9c62..2291800 100644 --- a/public/index-editorial.html +++ b/public/index-editorial.html @@ -2954,7 +2954,217 @@ function loadPeeringRecommendations(asn, ixConnections, lookupData) { }); } +// ── Terminal Feedback Widget ────────────────────────────────────────────────── +var termOpen = false; +var termStep = 0; +var termData = { category:'', message:'', name:'' }; + +// Safe DOM builder: parses only text from trusted template strings. +// User-supplied text is always passed via textContent — never interpreted as HTML. +function termPrint(template, fallbackColor) { + var out = document.getElementById('termOutput'); + var line = document.createElement('div'); + if (fallbackColor) line.style.color = fallbackColor; + // Split on tags from our own templates + var parts = template.split(/(]*>[^<]*<\/span>)/g); + parts.forEach(function(part) { + var m = part.match(/^]*)>(.*?)<\/span>$/); + if (m) { + var span = document.createElement('span'); + var sm = m[1].match(/style="([^"]*)"/); + if (sm) span.style.cssText = sm[1]; + span.textContent = m[2]; // text content, never HTML + line.appendChild(span); + } else if (part) { + line.appendChild(document.createTextNode(part)); + } + }); + out.appendChild(line); + out.scrollTop = out.scrollHeight; +} + +function termClear() { document.getElementById('termOutput').innerHTML = ''; } + +function toggleTerm() { + var p = document.getElementById('termPanel'); + termOpen = !termOpen; + p.style.display = termOpen ? 'flex' : 'none'; + if (termOpen) { termBoot(); setTimeout(function(){ document.getElementById('termInput').focus(); }, 80); } +} +function closeTerm() { + termOpen = false; + document.getElementById('termPanel').style.display = 'none'; +} + +function termBoot() { + termClear(); + termStep = 0; + termData = { category:'', message:'', name:'' }; + document.getElementById('termPrompt').textContent = 'peercortex:~$'; + var G = 'color:#27c93f', DIM = 'color:rgba(0,255,65,.35)', MUT = 'color:rgba(0,255,65,.45)'; + var lines = [ + '────────────────────────────────────────────', + ' PeerCortex Feedback Terminal v0.5.0', + '────────────────────────────────────────────', + '', + 'Available commands:', + ' send — submit feedback or bug report', + ' help — show this menu', + ' clear — clear terminal', + '', + 'Type send and press Enter to start.', + '' + ]; + var delay = 0; + lines.forEach(function(l) { setTimeout(function(){ termPrint(l); }, delay); delay += 35; }); +} + +function termStartWizard() { + var G = 'color:#27c93f'; + termStep = 1; + document.getElementById('termPrompt').textContent = '>'; + termPrint(''); + termPrint('Select category:'); + termPrint(' 1 Bug Report'); + termPrint(' 2 Feature Request'); + termPrint(' 3 Design Feedback'); + termPrint(' 4 General'); + termPrint(''); +} + +function termKeydown(e) { + if (e.key !== 'Enter') return; + var inp = document.getElementById('termInput'); + var val = inp.value.trim(); + inp.value = ''; + var prompt = document.getElementById('termPrompt').textContent; + var G = 'color:#27c93f', Y = 'color:#ffbd2e', MUT = 'color:rgba(0,255,65,.4)'; + // Echo: prompt + user text (built with DOM — no injection risk) + (function(){ + var out = document.getElementById('termOutput'); + var d = document.createElement('div'); + var sp = document.createElement('span'); + sp.style.color = 'rgba(0,255,65,.4)'; + sp.textContent = prompt; + d.appendChild(sp); + d.appendChild(document.createTextNode(' ' + val)); + out.appendChild(d); + out.scrollTop = out.scrollHeight; + })(); + + if (termStep === 0) { + var cmd = val.toLowerCase(); + if (cmd === 'send') { termStartWizard(); } + else if (cmd === 'help') { termBoot(); } + else if (cmd === 'clear') { termClear(); } + else if (cmd !== '') { + var out2 = document.getElementById('termOutput'); + var err = document.createElement('div'); + err.style.color = 'rgba(255,100,100,.75)'; + err.textContent = 'bash: ' + val + ': command not found'; + out2.appendChild(err); + out2.scrollTop = out2.scrollHeight; + } + } else if (termStep === 1) { + var cats = {'1':'Bug Report','2':'Feature Request','3':'Design Feedback','4':'General'}; + if (cats[val]) { + termData.category = cats[val]; + termPrint(''); + termPrint('✓ Category: ' + cats[val] + ''); + termPrint(''); + termPrint('Describe the issue or idea:'); + termStep = 2; + document.getElementById('termPrompt').textContent = '>'; + } else { termPrint('Enter 1, 2, 3, or 4.', 'rgba(255,189,46,.8)'); } + + } else if (termStep === 2) { + if (val.length < 5) { + termPrint('Too short — write at least a few words.', 'rgba(255,189,46,.8)'); + } else { + termData.message = val; + termPrint(''); + termPrint('✓ Message recorded.'); + termPrint(''); + termPrint('Your name or handle: (press Enter to stay Anonymous)'); + termStep = 3; + } + + } else if (termStep === 3) { + termData.name = val || 'Anonymous'; + termStep = 0; + document.getElementById('termPrompt').textContent = 'peercortex:~$'; + termPrint(''); + termPrint('Transmitting report...'); + fetch('/api/feedback', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ category: termData.category, message: termData.message, name: termData.name, asn: currentAsn || null }) + }).then(function(r){ return r.json(); }).then(function(d){ + if (d.ok) { + termPrint(''); + termPrint('████████████████████ 100%'); + termPrint(''); + (function(){ + var out3 = document.getElementById('termOutput'); + var ok = document.createElement('div'); + ok.style.color = '#27c93f'; + ok.textContent = '✓ Feedback transmitted. Thank you, ' + termData.name + '.'; + out3.appendChild(ok); + var sub = document.createElement('div'); + sub.style.color = 'rgba(0,255,65,.45)'; + sub.textContent = 'Your report helps improve PeerCortex.'; + out3.appendChild(sub); + out3.scrollTop = out3.scrollHeight; + })(); + termPrint(''); + termPrint('Type send for another report.'); + } else { + (function(){ + var out4 = document.getElementById('termOutput'); + var er = document.createElement('div'); + er.style.color = 'rgba(255,100,100,.8)'; + er.textContent = 'Error: ' + (d.error || 'unknown'); + out4.appendChild(er); out4.scrollTop = out4.scrollHeight; + })(); + } + }).catch(function(){ + (function(){ + var out5 = document.getElementById('termOutput'); + var ne = document.createElement('div'); + ne.style.color = 'rgba(255,100,100,.8)'; + ne.textContent = 'Network error — please try again.'; + out5.appendChild(ne); out5.scrollTop = out5.scrollHeight; + })(); + }); + } +} + +$_ + + + + + + + + + peercortex-feedback — bash + + + + + + peercortex:~$ + + + +