diff --git a/static/opax.html b/static/opax.html index 0657e84..fa29af6 100644 --- a/static/opax.html +++ b/static/opax.html @@ -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} +
@@ -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+='
⚠️ Anomalier
' anoms.slice(0,5).forEach(a=>{ const sev=(a.severity||'').toLowerCase() - h+=`
${sev==='high'?'πŸ”΄':'🟑'}
${esc(a.service||a.type||'Ukjent')}
${esc(a.description||a.message||'')}
${esc(a.severity||'medium')}
` + h+=`
${sev==='high'?'πŸ”΄':'🟑'}
${esc(a.service||a.type||'Ukjent')}
${esc(a.description||a.message||'')}
${esc(a.severity||'medium')}
` }) h+='
' }else{ h+='
βœ… Ingen anomalier
' } @@ -870,16 +879,20 @@ async function loadBuild(){ el.innerHTML='
⏳
Laster builds...
' const d=await pollBuild() if(!d){el.innerHTML='
❌
Kunne ikke hente build
';return} - const st=(d.status||'').toUpperCase() - const ico=st==='SUCCESS'?'βœ…':['FAILURE','TIMEOUT'].includes(st)?'❌':'⏳' - document.getElementById('build-panel-sub').innerHTML=`${st}` - let h=`
${ico}
${esc(d.build_id||d.id||'β€”')}
${esc(d.branch||d.trigger||'main')}
${esc(d.created||d.timestamp||'β€”')}
` - const logs=d.log_lines||d.logs||[] - if(logs.length){ - h+='
' - logs.forEach(l=>{ const c=/error|fail/i.test(l)?'ll-fail':/success|done/i.test(l)?'ll-ok':''; h+=`
${esc(l)}
` }) - h+='
' + const builds = d.builds || [] + if(!builds.length){ + el.innerHTML='
πŸ”¨
Ingen builds funnet
' + return } + let h='
' + builds.forEach(b=>{ + const st=(b.status||'').toUpperCase() + const ico=st==='SUCCESS'?'βœ…':['FAILURE','TIMEOUT'].includes(st)?'❌':'⏳' + h+=`
${ico}
${esc(b.id||'β€”')}
${esc(b.branch||'main')}
${esc(b.createTime||'β€”')}
` + }) + h+='
' + const latest=builds[0] + document.getElementById('build-panel-sub').innerHTML=`${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='
⏳ Laster '+esc(path)+'...
' 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=`
❌ Kunne ikke laste: ${esc(e.message)}
` } @@ -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,'
$1
') - s=s.replace(/`([^`]+)`/g,'$1') - s=s.replace(/^### (.+)$/gm,'

$1

') - s=s.replace(/^## (.+)$/gm,'

$1

') - s=s.replace(/^# (.+)$/gm,'

$1

') - s=s.replace(/\*\*(.+?)\*\*/g,'$1') - s=s.replace(/^---$/gm,'
') - s=s.replace(/^> (.+)$/gm,'
$1
') - s=s.replace(/((?:^\|.+\|\n)+)/gm, tbl=>{ - const rows=tbl.trim().split('\n').filter(r=>!/^\|[-| :]+\|$/.test(r)) - if(!rows.length) return tbl - let out='' - rows.forEach((r,i)=>{ - const cells=r.split('|').slice(1,-1) - out+=i===0?'':'' - cells.forEach(c=>{ out+=i===0?``:`` }) - out+=i===0?'':'' - }) - out+='
${c.trim()}${c.trim()}
' - return out - }) - s=s.replace(/^[-*] (.+)$/gm,'
  • $1
  • ') - s=s.replace(/(
  • .*<\/li>)/s, m=>'') - s=s.replace(/^\d+\. (.+)$/gm,'
  • $1
  • ') - s=s.replace(/^(?!<[htublp|]).+$/gm, line=>line.trim()?`

    ${line}

    `:'') - return s -} - // TERMINAL let termInited=false function initTerminal(){