diff --git a/README.md b/README.md index 3fb7c91b..5a48b11d 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ class Diff extends PureComponent { |newValue |`string` |`''` |New value as string. | |splitView |`boolean` |`true` |Switch between `unified` and `split` view. | |disableWordDiff |`boolean` |`false` |Show and hide word diff in a diff line. | +|compareMethod |`string` |`'diffChars'` |JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api | |hideLineNumbers |`boolean` |`false` |Show and hide line numbers. | |renderContent |`function` |`undefined` |Render Prop API to render code in the diff viewer. Helpful for [syntax highlighting](#syntax-highlighting) | |onLineNumberClick |`function` |`undefined` |Event handler for line number click. `(lineId: string) => void` | @@ -146,6 +147,43 @@ class Diff extends PureComponent { } ``` +## Text block diff comparison + +Different styles of text block diffing are possible by using the enums corresponding to various v4.0.1 JsDiff text block method names ([learn more](https://github.com/kpdecker/jsdiff/tree/v4.0.1#api)). + +```javascript +import React, { PureComponent } from 'react' +import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer' + +const oldCode = ` +{ + "name": "Original name", + "description": null +} +` +const newCode = ` +{ + "name": "My updated name", + "description": "Brand new description", + "status": "running" +} +` + +class Diff extends PureComponent { + render = () => { + return ( + + ) + } +} +``` + ## Overriding Styles diff --git a/examples/src/index.tsx b/examples/src/index.tsx index 56ae650e..a4022db2 100644 --- a/examples/src/index.tsx +++ b/examples/src/index.tsx @@ -19,6 +19,7 @@ interface ExampleState { highlightLine?: string[]; language?: string; enableSyntaxHighlighting?: boolean; + compareMethod?: string; } const P = (window as any).Prism; @@ -31,6 +32,7 @@ class Example extends React.Component<{}, ExampleState> { highlightLine: [], language: 'javascript', enableSyntaxHighlighting: true, + compareMethod: 'diffChars' }; } @@ -45,6 +47,9 @@ class Example extends React.Component<{}, ExampleState> { private onLanguageChange = (e: any): void => this.setState({ language: e.target.value, highlightLine: [] }); + private onCompareMethodChange = (e: any): void => + this.setState({ compareMethod: e.target.value }); + private onLineNumberClick = ( id: string, e: React.MouseEvent, @@ -155,16 +160,41 @@ class Example extends React.Component<{}, ExampleState> {
- + + + {' '} + (Learn More) + {' '} + + + + + {' '} + +
{ /** * Computes word diff information in the line. + * [TODO]: Consider adding options argument for JsDiff text block comparison * * @param oldValue Old word in the line. * @param newValue New word in the line. + * @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api */ -const computeWordDiff = (oldValue: string, newValue: string): WordDiffInformation => { - const diffArray = diff - .diffChars(oldValue, newValue); - const wordDiff: WordDiffInformation = { +const computeDiff = (oldValue: string, newValue: string, compareMethod: string): ComputedDiffInformation => { + const diffArray: Array = jsDiff[compareMethod](oldValue, newValue); + const computedDiff: ComputedDiffInformation = { left: [], right: [], }; @@ -77,22 +98,22 @@ const computeWordDiff = (oldValue: string, newValue: string): WordDiffInformatio if (added) { diffInformation.type = DiffType.ADDED; diffInformation.value = value; - wordDiff.right.push(diffInformation); + computedDiff.right.push(diffInformation); } if (removed) { diffInformation.type = DiffType.REMOVED; diffInformation.value = value; - wordDiff.left.push(diffInformation); + computedDiff.left.push(diffInformation); } if (!removed && !added) { diffInformation.type = DiffType.DEFAULT; diffInformation.value = value; - wordDiff.right.push(diffInformation); - wordDiff.left.push(diffInformation); + computedDiff.right.push(diffInformation); + computedDiff.left.push(diffInformation); } return diffInformation; }); - return wordDiff; + return computedDiff; }; /** @@ -106,11 +127,13 @@ const computeWordDiff = (oldValue: string, newValue: string): WordDiffInformatio * @param oldString Old string to compare. * @param newString New string to compare with old string. * @param disableWordDiff Flag to enable/disable word diff. + * @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api */ const computeLineInformation = ( oldString: string, newString: string, disableWordDiff: boolean = false, + compareMethod: string = DiffMethod.CHARS, ): ComputedLineInformation => { const diffArray = diff.diffLines( oldString.trimRight(), @@ -173,12 +196,12 @@ const computeLineInformation = ( right.type = type; // Do word level diff and assign the corresponding values to the // left and right diff information object. - if (disableWordDiff) { + if (disableWordDiff || !(Object).values(DiffMethod).includes(compareMethod)) { right.value = rightValue; } else { - const wordDiff = computeWordDiff(line, rightValue as string); - right.value = wordDiff.right; - left.value = wordDiff.left; + const computedDiff = computeDiff(line, rightValue as string, compareMethod); + right.value = computedDiff.right; + left.value = computedDiff.left; } } } diff --git a/src/index.tsx b/src/index.tsx index 716990c3..2568090a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,6 +7,7 @@ import { LineInformation, DiffInformation, DiffType, + DiffMethod, } from './compute-lines'; import computeStyles, { ReactDiffViewerStylesOverride, ReactDiffViewerStyles } from './styles'; @@ -29,6 +30,8 @@ export interface ReactDiffViewerProps { splitView?: boolean; // Enable/Disable word diff. disableWordDiff?: boolean; + // JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api + compareMethod?: string; // Number of unmodified lines surrounding each line diff. extraLinesSurroundingDiff?: number; // Show/hide line number. @@ -68,6 +71,7 @@ class DiffViewer extends React.Component { - const { oldValue, newValue, splitView } = this.props; + const { oldValue, newValue, splitView, disableWordDiff, compareMethod } = this.props; const { lineInformation, diffLines } = computeLineInformation( oldValue, newValue, - this.props.disableWordDiff, + disableWordDiff, + compareMethod, ); const extraLines = this.props.extraLinesSurroundingDiff < 0 ? 0 @@ -501,4 +507,4 @@ class DiffViewer extends React.Component { it('Should it avoid trailing spaces', (): void => { @@ -198,4 +198,216 @@ describe('Testing compute lines utils', (): void => { diffLines: [1], }); }); + + it('Should call "diffChars" jsDiff method when compareMethod is not provided', (): void => { + const oldCode = `Hello World`; + const newCode = `My Updated Name +Also this info`; + + expect(computeLineInformation(oldCode, newCode)) + .toMatchObject({ + lineInformation: [ + { + "right": { + "lineNumber": 1, + "type": 1, + "value": [ + { + "type": 1, + "value": "My Updat" + }, + { + "type": 0, + "value": "e" + }, + { + "type": 1, + "value": "d" + }, + { + "type": 0, + "value": " " + }, + { + "type": 1, + "value": "Name" + } + ] + }, + "left": { + "lineNumber": 1, + "type": 2, + "value": [ + { + "type": 2, + "value": "H" + }, + { + "type": 0, + "value": "e" + }, + { + "type": 2, + "value": "llo" + }, + { + "type": 0, + "value": " " + }, + { + "type": 2, + "value": "World" + } + ] + } + }, + { + "right": { + "lineNumber": 2, + "type": 1, + "value": "Also this info" + }, + "left": {} + } + ], + diffLines: [ + 0, + 2, + ], + }); + }); + + it('Should call "diffWords" jsDiff method when a compareMethod IS provided', (): void => { + const oldCode = `Hello World`; + const newCode = `My Updated Name +Also this info`; + + expect(computeLineInformation(oldCode, newCode, false, DiffMethod.WORDS)) + .toMatchObject({ + lineInformation: [ + { + "right": { + "lineNumber": 1, + "type": 1, + "value": [ + { + "type": 1, + "value": "My" + }, + { + "type": 0, + "value": " " + }, + { + "type": 1, + "value": "Updated Name" + } + ] + }, + "left": { + "lineNumber": 1, + "type": 2, + "value": [ + { + "type": 2, + "value": "Hello" + }, + { + "type": 0, + "value": " " + }, + { + "type": 2, + "value": "World" + } + ] + } + }, + { + "right": { + "lineNumber": 2, + "type": 1, + "value": "Also this info" + }, + "left": {} + } + ], + diffLines: [ + 0, + 2 + ], + }); + }); + + it('Should not call jsDiff method and not diff text when unknown JsDiffMethod is passed', (): void => { + const oldCode = `Hello World`; + const newCode = `My Updated Name +Also this info`; + + expect(computeLineInformation(oldCode, newCode, false, 'unknownMethod')) + .toMatchObject({ + lineInformation: [ + { + "right": { + "lineNumber": 1, + "type": 1, + "value": "My Updated Name" + }, + "left": { + "lineNumber": 1, + "type": 2, + "value": "Hello World" + } + }, + { + "right": { + "lineNumber": 2, + "type": 1, + "value": "Also this info" + }, + "left": {} + } + ], + diffLines: [ + 0, + 2 + ], + }); + }); + + it('Should not call jsDiff method and not diff text when disableWordDiff is true', (): void => { + const oldCode = `Hello World`; + const newCode = `My Updated Name +Also this info`; + + expect(computeLineInformation(oldCode, newCode, true)) + .toMatchObject({ + lineInformation: [ + { + "right": { + "lineNumber": 1, + "type": 1, + "value": "My Updated Name" + }, + "left": { + "lineNumber": 1, + "type": 2, + "value": "Hello World" + } + }, + { + "right": { + "lineNumber": 2, + "type": 1, + "value": "Also this info" + }, + "left": {} + } + ], + diffLines: [ + 0, + 2 + ], + }); + }); });