import styled from '@emotion/styled'; import {Change, diffChars, diffLines, diffWords} from 'diff'; const diffFnMap = { chars: diffChars, words: diffWords, lines: diffLines, } as const; type Props = { base: string; target: string; type?: keyof typeof diffFnMap; className?: string; }; const SplitDiff = ({className, type = 'lines', base, target}: Props) => { const diffFn = diffFnMap[type]; const baseLines = base.split('\n'); const targetLines = target.split('\n'); const [largerArray] = baseLines.length > targetLines.length ? [baseLines, targetLines] : [targetLines, baseLines]; const results = largerArray.map((_line, index) => diffFn(baseLines[index] || '', targetLines[index] || '', {newlineIsToken: true}) ); return ( <SplitTable className={className}> <SplitBody> {results.map((line, j) => { const highlightAdded = line.find(result => result.added); const highlightRemoved = line.find(result => result.removed); return ( <tr key={j}> <Cell isRemoved={highlightRemoved}> <Line> {line .filter(result => !result.added) .map((result, i) => ( <Word key={i} isRemoved={result.removed}> {result.value} </Word> ))} </Line> </Cell> <Gap /> <Cell isAdded={highlightAdded}> <Line> {line .filter(result => !result.removed) .map((result, i) => ( <Word key={i} isAdded={result.added}> {result.value} </Word> ))} </Line> </Cell> </tr> ); })} </SplitBody> </SplitTable> ); }; const SplitTable = styled('table')` table-layout: fixed; border-collapse: collapse; width: 100%; `; const SplitBody = styled('tbody')` font-family: ${p => p.theme.text.familyMono}; font-size: ${p => p.theme.fontSizeSmall}; `; const Cell = styled('td')<{isRemoved?: Change; isAdded?: Change}>` vertical-align: top; ${p => p.isRemoved && `background-color: ${p.theme.diff.removedRow}`}; ${p => p.isAdded && `background-color: ${p.theme.diff.addedRow}`}; `; const Gap = styled('td')` width: 20px; `; const Line = styled('div')` display: flex; flex-wrap: wrap; `; const Word = styled('span')<{isRemoved?: boolean; isAdded?: boolean}>` white-space: pre-wrap; word-break: break-all; ${p => p.isRemoved && `background-color: ${p.theme.diff.removed}`}; ${p => p.isAdded && `background-color: ${p.theme.diff.added}`}; `; export default SplitDiff;