fix(opax): MD-panel — bytt renderMd() med marked.js (Fix 2)

This commit is contained in:
chrischristiansen-glitch 2026-06-13 15:30:02 +02:00
parent d25caf114d
commit 39f714a621

View File

@ -243,6 +243,7 @@
.md-viewer p{margin-bottom:12px;color:var(--text-dim)}
.md-viewer code{font-family:var(--mono);font-size:12px;background:rgba(255,255,255,.06);padding:2px 6px;border-radius:4px}
.md-viewer pre{background:var(--bg-2);border:1px solid var(--border);padding:14px 16px;border-radius:10px;overflow-x:auto;font-family:var(--mono);font-size:12px;margin:12px 0;line-height:1.6}
.md-viewer pre code{background:none;padding:0}
.md-viewer table{width:100%;border-collapse:collapse;margin:14px 0;font-size:13px}
.md-viewer th{text-align:left;padding:8px 12px;border-bottom:1px solid var(--border);font-size:11px;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-faint)}
.md-viewer td{padding:9px 12px;border-bottom:1px solid var(--border);color:var(--text-dim)}
@ -251,6 +252,8 @@
.md-viewer li{margin-bottom:4px}
.md-viewer blockquote{border-left:3px solid var(--accent);padding:8px 16px;margin:12px 0;background:rgba(124,92,255,.06);border-radius:0 8px 8px 0;color:var(--text-dim)}
.md-viewer hr{border:none;border-top:1px solid var(--border);margin:20px 0}
.md-viewer a{color:var(--accent);text-decoration:none}
.md-viewer a:hover{text-decoration:underline}
.md-viewer-loading{display:flex;align-items:center;justify-content:center;height:200px;color:var(--text-faint);font-family:var(--mono);font-size:13px;gap:10px}
/* PRICING PANEL (CG4d) */
@ -296,6 +299,7 @@
.browser-error.show{display:flex}
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked@12/marked.min.js"></script>
</head>
<body>
<div class="shell">
@ -615,6 +619,9 @@ const STORAGE_KEY = 'jason_history_v2'
const SESSION_KEY = 'jason_session_v2'
const GH_BLOB = 'https://github.com/vauco-saas/OSVauco/blob/main/'
// Configure marked.js once
marked.setOptions({ gfm: true, breaks: true })
const MD_DOCS = [
{ icon:'🏛️', name:'MASTERPLAN', path:'docs/MASTERPLAN.md', desc:'Autoritativ sannhet — vinner alltid ved konflikt', badge:'autoritativ', badgeCls:'auth' },
{ icon:'🗺️', name:'ROADMAP', path:'docs/ROADMAP.md', desc:'Hva som skjer akkurat nå — prioriteringer per sprint', badge:'aktiv', badgeCls:'' },
@ -767,7 +774,9 @@ function browserFrameError(){
async function pollBuild(){
try{
const d = await fetch('/opax/build-status').then(r=>r.json())
const st = (d.status||'').toUpperCase()
const builds = d.builds || []
const latest = builds[0] || {}
const st = (latest.status || d.status || '').toUpperCase()
const cls = st==='SUCCESS'?'ok':['FAILURE','TIMEOUT','CANCELLED'].includes(st)?'fail':st?'running':''
;['sb-build-dot','tb-build-dot'].forEach(id=>{ document.getElementById(id).className=(id==='sb-build-dot'?'build-dot ':'build-pill-dot ')+cls })
document.getElementById('tb-build-txt').textContent = 'Build: '+(st||'ukjent')
@ -803,7 +812,7 @@ async function loadBilling(){
h+='<div class="sec-title">⚠️ Anomalier</div><div class="anomaly-list">'
anoms.slice(0,5).forEach(a=>{
const sev=(a.severity||'').toLowerCase()
h+=`<div class="anom"><div class="anom-icon">${sev==='high'?'🔴':'🟡'}</div><div class="anom-info"><div class="anom-title">${esc(a.service||a.type||'Ukjent')}</div><div class="anom-desc">${esc(a.description||a.message||'')}</div></div><div class="anom-badge ${sev==='high'?'high':''}"">${esc(a.severity||'medium')}</div></div>`
h+=`<div class="anom"><div class="anom-icon">${sev==='high'?'🔴':'🟡'}</div><div class="anom-info"><div class="anom-title">${esc(a.service||a.type||'Ukjent')}</div><div class="anom-desc">${esc(a.description||a.message||'')}</div></div><div class="anom-badge ${sev==='high'?'high':''}">${esc(a.severity||'medium')}</div></div>`
})
h+='</div>'
}else{ h+='<div class="sec-title" style="color:var(--green)">✅ Ingen anomalier</div>' }
@ -870,16 +879,20 @@ async function loadBuild(){
el.innerHTML='<div class="empty-state"><div class="empty-icon"></div><div class="empty-title">Laster builds...</div></div>'
const d=await pollBuild()
if(!d){el.innerHTML='<div class="empty-state"><div class="empty-icon"></div><div class="empty-title">Kunne ikke hente build</div></div>';return}
const st=(d.status||'').toUpperCase()
const ico=st==='SUCCESS'?'✅':['FAILURE','TIMEOUT'].includes(st)?'❌':'⏳'
document.getElementById('build-panel-sub').innerHTML=`<span style="width:6px;height:6px;border-radius:50%;background:${st==='SUCCESS'?'var(--green)':['FAILURE','TIMEOUT'].includes(st)?'var(--danger)':'var(--amber)'};display:inline-block;margin-right:5px"></span>${st}`
let h=`<div class="build-list"><div class="build-item"><div class="build-ico">${ico}</div><div class="build-info"><div class="build-id">${esc(d.build_id||d.id||'—')}</div><div class="build-branch">${esc(d.branch||d.trigger||'main')}</div><div class="build-time">${esc(d.created||d.timestamp||'—')}</div></div></div></div>`
const logs=d.log_lines||d.logs||[]
if(logs.length){
h+='<div class="log-box">'
logs.forEach(l=>{ const c=/error|fail/i.test(l)?'ll-fail':/success|done/i.test(l)?'ll-ok':''; h+=`<div class="${c}">${esc(l)}</div>` })
h+='</div>'
const builds = d.builds || []
if(!builds.length){
el.innerHTML='<div class="empty-state"><div class="empty-icon">🔨</div><div class="empty-title">Ingen builds funnet</div></div>'
return
}
let h='<div class="build-list">'
builds.forEach(b=>{
const st=(b.status||'').toUpperCase()
const ico=st==='SUCCESS'?'✅':['FAILURE','TIMEOUT'].includes(st)?'❌':'⏳'
h+=`<div class="build-item"><div class="build-ico">${ico}</div><div class="build-info"><div class="build-id">${esc(b.id||'—')}</div><div class="build-branch">${esc(b.branch||'main')}</div><div class="build-time">${esc(b.createTime||'—')}</div></div></div>`
})
h+='</div>'
const latest=builds[0]
document.getElementById('build-panel-sub').innerHTML=`<span style="width:6px;height:6px;border-radius:50%;background:${latest.status==='SUCCESS'?'var(--green)':['FAILURE','TIMEOUT'].includes(latest.status)?'var(--danger)':'var(--amber)'};display:inline-block;margin-right:5px"></span>${latest.status||'ukjent'}`
el.innerHTML=h
}
@ -916,7 +929,7 @@ function openInline(url, name){
document.getElementById('tb-sub').textContent=url
}
// MD MODULE
// MD MODULE — Fix 2: marked.js rendering
function renderMdGrid(){
const g=document.getElementById('md-grid')
if(g.children.length) return
@ -950,11 +963,11 @@ async function openMd(path, name){
const viewer=document.getElementById('md-viewer')
viewer.innerHTML='<div class="md-viewer-loading"><span style="animation:pulse 1s infinite"></span> Laster '+esc(path)+'...</div>'
try{
// OQ-30: bruk /docs/ proxy i stedet for GH_RAW
const res = await fetch('/docs/'+path+'?t='+Date.now())
if(!res.ok) throw new Error('HTTP '+res.status)
const raw = await res.text()
viewer.innerHTML=renderMd(raw)
// Fix 2: bruk marked.js i stedet for hjemmelaget renderMd()
viewer.innerHTML = marked.parse(raw)
}catch(e){
viewer.innerHTML=`<div class="md-viewer-loading">❌ Kunne ikke laste: ${esc(e.message)}</div>`
}
@ -969,36 +982,6 @@ function mdShowGrid(){
document.getElementById('tb-sub').textContent='Kjerndoks · vauco-saas/OSVauco'
}
function renderMd(text){
let s=esc(text)
s=s.replace(/```[^\n]*\n([\s\S]*?)```/g,'<pre><code>$1</code></pre>')
s=s.replace(/`([^`]+)`/g,'<code>$1</code>')
s=s.replace(/^### (.+)$/gm,'<h3>$1</h3>')
s=s.replace(/^## (.+)$/gm,'<h2>$1</h2>')
s=s.replace(/^# (.+)$/gm,'<h1>$1</h1>')
s=s.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>')
s=s.replace(/^---$/gm,'<hr>')
s=s.replace(/^&gt; (.+)$/gm,'<blockquote>$1</blockquote>')
s=s.replace(/((?:^\|.+\|\n)+)/gm, tbl=>{
const rows=tbl.trim().split('\n').filter(r=>!/^\|[-| :]+\|$/.test(r))
if(!rows.length) return tbl
let out='<table>'
rows.forEach((r,i)=>{
const cells=r.split('|').slice(1,-1)
out+=i===0?'<thead><tr>':'<tr>'
cells.forEach(c=>{ out+=i===0?`<th>${c.trim()}</th>`:`<td>${c.trim()}</td>` })
out+=i===0?'</tr></thead><tbody>':'</tr>'
})
out+='</tbody></table>'
return out
})
s=s.replace(/^[-*] (.+)$/gm,'<li>$1</li>')
s=s.replace(/(<li>.*<\/li>)/s, m=>'<ul>'+m+'</ul>')
s=s.replace(/^\d+\. (.+)$/gm,'<li>$1</li>')
s=s.replace(/^(?!<[htublp|]).+$/gm, line=>line.trim()?`<p>${line}</p>`:'')
return s
}
// TERMINAL
let termInited=false
function initTerminal(){