From 3f63a6dcd7ba6a47014e70c677db20ce88235e32 Mon Sep 17 00:00:00 2001 From: Dicken Date: Mon, 1 Jun 2026 11:41:21 +0200 Subject: [PATCH] DevTools TextDiff: Unified Diff Format mit gepaarten Zeilennummern --- frontend/src/tools/devtools.jsx | 146 ++++++++++---------------------- 1 file changed, 45 insertions(+), 101 deletions(-) diff --git a/frontend/src/tools/devtools.jsx b/frontend/src/tools/devtools.jsx index b72dcba..be68047 100644 --- a/frontend/src/tools/devtools.jsx +++ b/frontend/src/tools/devtools.jsx @@ -945,47 +945,37 @@ function diffLines(a, b) { const aLines = a.split('\n'); const bLines = b.split('\n'); const m = aLines.length, n = bLines.length; - // LCS dp const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0)); for (let i = m-1; i >= 0; i--) for (let j = n-1; j >= 0; j--) dp[i][j] = aLines[i] === bLines[j] ? dp[i+1][j+1]+1 : Math.max(dp[i+1][j], dp[i][j+1]); - // Build flat ops - const ops = []; - let i = 0, j = 0; + // Build rows: same / del / add — consecutive del+add stay paired (same display line number) + const rows = []; // {type, lineA, lineB, text} + let i = 0, j = 0, lnA = 0, lnB = 0; while (i < m || j < n) { if (i < m && j < n && aLines[i] === bLines[j]) { - ops.push({type:'same', a:aLines[i], b:bLines[j]}); i++; j++; + lnA++; lnB++; + rows.push({type:'same', ln: lnA, text: aLines[i]}); + i++; j++; } else if (j < n && (i >= m || dp[i][j+1] >= dp[i+1][j])) { - ops.push({type:'add', a:null, b:bLines[j]}); j++; + lnB++; + rows.push({type:'add', ln: lnB, text: bLines[j]}); + j++; } else { - ops.push({type:'del', a:aLines[i], b:null}); i++; + lnA++; + rows.push({type:'del', ln: lnA, text: aLines[i]}); + i++; } } - // Pair up consecutive del+add into 'change' rows - const paired = []; - let k = 0; - while (k < ops.length) { - if (ops[k].type==='del' && k+1 < ops.length && ops[k+1].type==='add') { - paired.push({type:'change', a:ops[k].a, b:ops[k+1].b}); k+=2; - } else { - paired.push(ops[k]); k++; - } - } - return paired; + return rows; } -// Compute set of changed line numbers (1-based) for a given side -function changedLineNums(pairs, side) { +// Which line numbers (1-based) in text A/B are changed +function changedLineNums(rows, side) { const set = new Set(); - let ln = 0; - pairs.forEach(p => { - if (p.type==='same') { ln++; } - else if (p.type==='change') { ln++; set.add(ln); } - else if (p.type==='del' && side==='a') { ln++; set.add(ln); } - else if (p.type==='add' && side==='b') { ln++; set.add(ln); } - else if (p.type==='del' && side==='b') { /* b has no line here */ } - else if (p.type==='add' && side==='a') { /* a has no line here */ } + rows.forEach(r => { + if (r.type === 'del' && side === 'a') set.add(r.ln); + if (r.type === 'add' && side === 'b') set.add(r.ln); }); return set; } @@ -1011,11 +1001,10 @@ function TextDiff({ mobile }) { e.target.value = ''; } - const added = diff ? diff.filter(d=>d.type==='add'||d.type==='change').length : 0; - const removed = diff ? diff.filter(d=>d.type==='del'||d.type==='change').length : 0; + const added = diff ? diff.filter(d=>d.type==='add').length : 0; + const removed = diff ? diff.filter(d=>d.type==='del').length : 0; const same = diff ? diff.filter(d=>d.type==='same').length : 0; - // Which lines are changed in each textarea (for background highlight) const changedA = diff ? changedLineNums(diff, 'a') : new Set(); const changedB = diff ? changedLineNums(diff, 'b') : new Set(); @@ -1023,14 +1012,13 @@ function TextDiff({ mobile }) { boxSizing:'border-box', minHeight:180, resize:'vertical', lineHeight:'1.6', padding:'7px 8px' }; function LineNumCol({ text, changedSet }) { - const lines = (text||' ').split('\n'); return (
- {lines.map((_, i) => ( + {(text||' ').split('\n').map((_,i) => (
− {removed} entfernt/geändert + fontSize:11, padding:'4px 10px', borderRadius:6}}>− {removed} entfernt + {added} hinzugefügt/geändert + fontSize:11, padding:'4px 10px', borderRadius:6}}>+ {added} hinzugefügt = {same} gleich
@@ -1085,73 +1073,29 @@ function TextDiff({ mobile }) { {!!diff && (
- {/* Header */} -
-
Text A (Original)
-
Text B (Neu)
-
- - - - - +
- {(()=>{ - let lnA=0, lnB=0; - return diff.map((d,i)=>{ - const isChange = d.type==='change'; - const isDel = d.type==='del'; - const isAdd = d.type==='add'; - const isSame = d.type==='same'; - if (isSame) { lnA++; lnB++; } - if (isChange) { lnA++; lnB++; } - if (isDel) lnA++; - if (isAdd) lnB++; - - const bgA = (isDel||isChange) ? 'rgba(255,107,157,0.1)' : 'transparent'; - const bgB = (isAdd||isChange) ? 'rgba(78,205,196,0.1)' : 'transparent'; - const colA = isDel||isChange ? '#ffb3cc' : 'rgba(255,255,255,0.65)'; - const colB = isAdd||isChange ? '#a8f0eb' : 'rgba(255,255,255,0.65)'; - const symA = isDel?'−': isChange?'~':''; - const symB = isAdd?'+': isChange?'~':''; - const symColA = isDel?'#ff6b9d': isChange?'#ffe66d':'rgba(255,255,255,0.15)'; - const symColB = isAdd?'#4ecdc4': isChange?'#ffe66d':'rgba(255,255,255,0.15)'; - - return ( - - {/* Side A */} - - - - {/* Side B */} - - - - - ); - }); - })()} + {diff.map((d,i) => ( + + + + + + ))}
- {(isDel||isChange||isSame) ? lnA : ''} - - {symA} - - {(isDel||isChange||isSame) ? (d.a ?? d.text ?? ' ') : ''} - - {(isAdd||isChange||isSame) ? lnB : ''} - - {symB} - - {(isAdd||isChange||isSame) ? (d.b ?? d.text ?? ' ') : ''} -
+ {d.ln} + + {d.type==='add'?'+': d.type==='del'?'−':''} + + {d.text || ' '} +