DevTools TextDiff: Unified Diff Format mit gepaarten Zeilennummern
This commit is contained in:
@@ -945,47 +945,37 @@ function diffLines(a, b) {
|
|||||||
const aLines = a.split('\n');
|
const aLines = a.split('\n');
|
||||||
const bLines = b.split('\n');
|
const bLines = b.split('\n');
|
||||||
const m = aLines.length, n = bLines.length;
|
const m = aLines.length, n = bLines.length;
|
||||||
// LCS dp
|
|
||||||
const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));
|
const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));
|
||||||
for (let i = m-1; i >= 0; i--)
|
for (let i = m-1; i >= 0; i--)
|
||||||
for (let j = n-1; j >= 0; j--)
|
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]);
|
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
|
// Build rows: same / del / add — consecutive del+add stay paired (same display line number)
|
||||||
const ops = [];
|
const rows = []; // {type, lineA, lineB, text}
|
||||||
let i = 0, j = 0;
|
let i = 0, j = 0, lnA = 0, lnB = 0;
|
||||||
while (i < m || j < n) {
|
while (i < m || j < n) {
|
||||||
if (i < m && j < n && aLines[i] === bLines[j]) {
|
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])) {
|
} 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 {
|
} 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
|
return 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute set of changed line numbers (1-based) for a given side
|
// Which line numbers (1-based) in text A/B are changed
|
||||||
function changedLineNums(pairs, side) {
|
function changedLineNums(rows, side) {
|
||||||
const set = new Set();
|
const set = new Set();
|
||||||
let ln = 0;
|
rows.forEach(r => {
|
||||||
pairs.forEach(p => {
|
if (r.type === 'del' && side === 'a') set.add(r.ln);
|
||||||
if (p.type==='same') { ln++; }
|
if (r.type === 'add' && side === 'b') set.add(r.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 */ }
|
|
||||||
});
|
});
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
@@ -1011,11 +1001,10 @@ function TextDiff({ mobile }) {
|
|||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const added = diff ? diff.filter(d=>d.type==='add'||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'||d.type==='change').length : 0;
|
const removed = diff ? diff.filter(d=>d.type==='del').length : 0;
|
||||||
const same = diff ? diff.filter(d=>d.type==='same').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 changedA = diff ? changedLineNums(diff, 'a') : new Set();
|
||||||
const changedB = diff ? changedLineNums(diff, 'b') : 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' };
|
boxSizing:'border-box', minHeight:180, resize:'vertical', lineHeight:'1.6', padding:'7px 8px' };
|
||||||
|
|
||||||
function LineNumCol({ text, changedSet }) {
|
function LineNumCol({ text, changedSet }) {
|
||||||
const lines = (text||' ').split('\n');
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
padding:'7px 6px', minWidth:36, textAlign:'right', userSelect:'none',
|
padding:'7px 6px', minWidth:36, textAlign:'right', userSelect:'none',
|
||||||
fontFamily:"'Courier New',monospace", fontSize:12, lineHeight:'1.6',
|
fontFamily:"'Courier New',monospace", fontSize:12, lineHeight:'1.6',
|
||||||
background:'rgba(0,0,0,0.2)', borderRight:'1px solid rgba(255,255,255,0.07)',
|
background:'rgba(0,0,0,0.2)', borderRight:'1px solid rgba(255,255,255,0.07)',
|
||||||
whiteSpace:'pre', flexShrink:0}}>
|
whiteSpace:'pre', flexShrink:0}}>
|
||||||
{lines.map((_, i) => (
|
{(text||' ').split('\n').map((_,i) => (
|
||||||
<div key={i} style={{
|
<div key={i} style={{
|
||||||
color: changedSet.has(i+1) ? '#ff6b9d' : 'rgba(255,255,255,0.25)',
|
color: changedSet.has(i+1) ? '#ff6b9d' : 'rgba(255,255,255,0.25)',
|
||||||
background: changedSet.has(i+1) ? 'rgba(255,107,157,0.12)' : 'transparent',
|
background: changedSet.has(i+1) ? 'rgba(255,107,157,0.12)' : 'transparent',
|
||||||
@@ -1073,9 +1061,9 @@ function TextDiff({ mobile }) {
|
|||||||
{!!diff && (
|
{!!diff && (
|
||||||
<div style={{display:'flex', gap:10, alignItems:'center', flexWrap:'wrap'}}>
|
<div style={{display:'flex', gap:10, alignItems:'center', flexWrap:'wrap'}}>
|
||||||
<span style={{background:'rgba(255,107,157,0.15)', color:'#ff6b9d', fontFamily:'monospace',
|
<span style={{background:'rgba(255,107,157,0.15)', color:'#ff6b9d', fontFamily:'monospace',
|
||||||
fontSize:11, padding:'4px 10px', borderRadius:6}}>− {removed} entfernt/geändert</span>
|
fontSize:11, padding:'4px 10px', borderRadius:6}}>− {removed} entfernt</span>
|
||||||
<span style={{background:'rgba(78,205,196,0.15)', color:'#4ecdc4', fontFamily:'monospace',
|
<span style={{background:'rgba(78,205,196,0.15)', color:'#4ecdc4', fontFamily:'monospace',
|
||||||
fontSize:11, padding:'4px 10px', borderRadius:6}}>+ {added} hinzugefügt/geändert</span>
|
fontSize:11, padding:'4px 10px', borderRadius:6}}>+ {added} hinzugefügt</span>
|
||||||
<span style={{background:'rgba(255,255,255,0.05)', color:'rgba(255,255,255,0.45)', fontFamily:'monospace',
|
<span style={{background:'rgba(255,255,255,0.05)', color:'rgba(255,255,255,0.45)', fontFamily:'monospace',
|
||||||
fontSize:11, padding:'4px 10px', borderRadius:6}}>= {same} gleich</span>
|
fontSize:11, padding:'4px 10px', borderRadius:6}}>= {same} gleich</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1085,73 +1073,29 @@ function TextDiff({ mobile }) {
|
|||||||
{!!diff && (
|
{!!diff && (
|
||||||
<div style={{borderRadius:9, border:'1px solid rgba(255,255,255,0.08)',
|
<div style={{borderRadius:9, border:'1px solid rgba(255,255,255,0.08)',
|
||||||
overflow:'auto', maxHeight:520, background:'rgba(0,0,0,0.2)'}}>
|
overflow:'auto', maxHeight:520, background:'rgba(0,0,0,0.2)'}}>
|
||||||
{/* Header */}
|
<table style={{width:'100%', borderCollapse:'collapse', fontFamily:"'Courier New',monospace", fontSize:12}}>
|
||||||
<div style={{display:'grid', gridTemplateColumns:'36px 16px 1fr 36px 16px 1fr',
|
|
||||||
borderBottom:'1px solid rgba(255,255,255,0.1)', background:'rgba(255,255,255,0.04)'}}>
|
|
||||||
<div style={{gridColumn:'1/4', padding:'5px 10px', color:'rgba(255,255,255,0.4)', fontFamily:'monospace', fontSize:10, borderRight:'1px solid rgba(255,255,255,0.08)'}}>Text A (Original)</div>
|
|
||||||
<div style={{gridColumn:'4/7', padding:'5px 10px', color:'rgba(255,255,255,0.4)', fontFamily:'monospace', fontSize:10}}>Text B (Neu)</div>
|
|
||||||
</div>
|
|
||||||
<table style={{width:'100%', borderCollapse:'collapse', fontFamily:"'Courier New',monospace", fontSize:12, tableLayout:'fixed'}}>
|
|
||||||
<colgroup>
|
|
||||||
<col style={{width:36}}/><col style={{width:16}}/><col/>
|
|
||||||
<col style={{width:36}}/><col style={{width:16}}/><col/>
|
|
||||||
</colgroup>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{(()=>{
|
{diff.map((d,i) => (
|
||||||
let lnA=0, lnB=0;
|
<tr key={i} style={{
|
||||||
return diff.map((d,i)=>{
|
background: d.type==='add' ? 'rgba(78,205,196,0.1)' : d.type==='del' ? 'rgba(255,107,157,0.1)' : 'transparent',
|
||||||
const isChange = d.type==='change';
|
borderBottom:'1px solid rgba(255,255,255,0.03)'}}>
|
||||||
const isDel = d.type==='del';
|
<td style={{width:36, textAlign:'right', padding:'2px 8px', userSelect:'none',
|
||||||
const isAdd = d.type==='add';
|
color:'rgba(255,255,255,0.2)', fontSize:11,
|
||||||
const isSame = d.type==='same';
|
borderRight:'1px solid rgba(255,255,255,0.05)'}}>
|
||||||
if (isSame) { lnA++; lnB++; }
|
{d.ln}
|
||||||
if (isChange) { lnA++; lnB++; }
|
</td>
|
||||||
if (isDel) lnA++;
|
<td style={{width:20, textAlign:'center', padding:'2px 4px', userSelect:'none',
|
||||||
if (isAdd) lnB++;
|
color: d.type==='add'?'#4ecdc4': d.type==='del'?'#ff6b9d':'rgba(255,255,255,0.15)',
|
||||||
|
fontWeight:700, fontSize:14,
|
||||||
const bgA = (isDel||isChange) ? 'rgba(255,107,157,0.1)' : 'transparent';
|
borderRight:'1px solid rgba(255,255,255,0.05)'}}>
|
||||||
const bgB = (isAdd||isChange) ? 'rgba(78,205,196,0.1)' : 'transparent';
|
{d.type==='add'?'+': d.type==='del'?'−':''}
|
||||||
const colA = isDel||isChange ? '#ffb3cc' : 'rgba(255,255,255,0.65)';
|
</td>
|
||||||
const colB = isAdd||isChange ? '#a8f0eb' : 'rgba(255,255,255,0.65)';
|
<td style={{padding:'3px 10px', whiteSpace:'pre-wrap', wordBreak:'break-all',
|
||||||
const symA = isDel?'−': isChange?'~':'';
|
color: d.type==='add'?'#a8f0eb': d.type==='del'?'#ffb3cc':'rgba(255,255,255,0.65)'}}>
|
||||||
const symB = isAdd?'+': isChange?'~':'';
|
{d.text || ' '}
|
||||||
const symColA = isDel?'#ff6b9d': isChange?'#ffe66d':'rgba(255,255,255,0.15)';
|
</td>
|
||||||
const symColB = isAdd?'#4ecdc4': isChange?'#ffe66d':'rgba(255,255,255,0.15)';
|
</tr>
|
||||||
|
))}
|
||||||
return (
|
|
||||||
<tr key={i} style={{borderBottom:'1px solid rgba(255,255,255,0.03)'}}>
|
|
||||||
{/* Side A */}
|
|
||||||
<td style={{background:bgA, textAlign:'right', padding:'2px 6px', color:'rgba(255,255,255,0.2)',
|
|
||||||
borderRight:'1px solid rgba(255,255,255,0.05)', userSelect:'none', fontSize:11}}>
|
|
||||||
{(isDel||isChange||isSame) ? lnA : ''}
|
|
||||||
</td>
|
|
||||||
<td style={{background:bgA, textAlign:'center', padding:'2px 3px', color:symColA,
|
|
||||||
fontWeight:700, fontSize:13, userSelect:'none',
|
|
||||||
borderRight:'1px solid rgba(255,255,255,0.06)'}}>
|
|
||||||
{symA}
|
|
||||||
</td>
|
|
||||||
<td style={{background:bgA, padding:'3px 8px', whiteSpace:'pre-wrap', wordBreak:'break-all',
|
|
||||||
color:colA, borderRight:'2px solid rgba(255,255,255,0.06)'}}>
|
|
||||||
{(isDel||isChange||isSame) ? (d.a ?? d.text ?? ' ') : ''}
|
|
||||||
</td>
|
|
||||||
{/* Side B */}
|
|
||||||
<td style={{background:bgB, textAlign:'right', padding:'2px 6px', color:'rgba(255,255,255,0.2)',
|
|
||||||
borderRight:'1px solid rgba(255,255,255,0.05)', userSelect:'none', fontSize:11}}>
|
|
||||||
{(isAdd||isChange||isSame) ? lnB : ''}
|
|
||||||
</td>
|
|
||||||
<td style={{background:bgB, textAlign:'center', padding:'2px 3px', color:symColB,
|
|
||||||
fontWeight:700, fontSize:13, userSelect:'none',
|
|
||||||
borderRight:'1px solid rgba(255,255,255,0.06)'}}>
|
|
||||||
{symB}
|
|
||||||
</td>
|
|
||||||
<td style={{background:bgB, padding:'3px 8px', whiteSpace:'pre-wrap', wordBreak:'break-all',
|
|
||||||
color:colB}}>
|
|
||||||
{(isAdd||isChange||isSame) ? (d.b ?? d.text ?? ' ') : ''}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})()}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user