From e53a882cd711909e3b15244addecc9b959771951 Mon Sep 17 00:00:00 2001 From: Derek Hammond Date: Tue, 19 Nov 2019 00:18:03 -0600 Subject: [PATCH 1/3] fixes #31 --- README.md | 38 +++++++ examples/src/index.tsx | 52 +++++++-- src/compute-lines.ts | 41 ++++--- src/index.tsx | 12 ++- test/compute-lines-test.ts | 214 ++++++++++++++++++++++++++++++++++++- 5 files changed, 329 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3fb7c91b..d96ff533 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. | +|jsDiffCompareMethod |`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..443ef9d4 100644 --- a/examples/src/index.tsx +++ b/examples/src/index.tsx @@ -19,6 +19,7 @@ interface ExampleState { highlightLine?: string[]; language?: string; enableSyntaxHighlighting?: boolean; + jsDiffCompareMethod?: string; } const P = (window as any).Prism; @@ -31,6 +32,7 @@ class Example extends React.Component<{}, ExampleState> { highlightLine: [], language: 'javascript', enableSyntaxHighlighting: true, + jsDiffCompareMethod: 'diffChars' }; } @@ -45,6 +47,9 @@ class Example extends React.Component<{}, ExampleState> { private onLanguageChange = (e: any): void => this.setState({ language: e.target.value, highlightLine: [] }); + private onJsDiffCompareMethodChange = (e: any): void => + this.setState({ jsDiffCompareMethod: 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 jsDiffCompareMethod 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, jsDiffCompareMethod: string): ComputedDiffInformation => { + const diffArray = diff[jsDiffCompareMethod](oldValue, newValue); + const computedDiff: ComputedDiffInformation = { left: [], right: [], }; @@ -77,22 +88,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 +117,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 jsDiffCompareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api */ const computeLineInformation = ( oldString: string, newString: string, disableWordDiff: boolean = false, + jsDiffCompareMethod: string = DiffMethod.CHARS, ): ComputedLineInformation => { const diffArray = diff.diffLines( oldString.trimRight(), @@ -173,12 +186,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(jsDiffCompareMethod)) { 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, jsDiffCompareMethod); + right.value = computedDiff.right; + left.value = computedDiff.left; } } } diff --git a/src/index.tsx b/src/index.tsx index 716990c3..240d603b 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 + jsDiffCompareMethod?: 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, jsDiffCompareMethod } = this.props; const { lineInformation, diffLines } = computeLineInformation( oldValue, newValue, - this.props.disableWordDiff, + disableWordDiff, + jsDiffCompareMethod, ); 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 jsDiffCompareMethod 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 jsDiffCompareMethod 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 + ], + }); + }); }); From 4c714aff7dcac85d5773443753d1877025ecae5c Mon Sep 17 00:00:00 2001 From: Derek Hammond Date: Tue, 19 Nov 2019 11:13:46 -0600 Subject: [PATCH 2/3] fix typescript errors --- src/compute-lines.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compute-lines.ts b/src/compute-lines.ts index fd730196..bc75bfa8 100644 --- a/src/compute-lines.ts +++ b/src/compute-lines.ts @@ -1,5 +1,7 @@ import * as diff from 'diff'; +const jsDiff: { [key: string]: any } = diff; + export enum DiffType { DEFAULT = 0, ADDED = 1, @@ -17,6 +19,8 @@ export enum DiffMethod { } export interface DiffInformation { + added?: boolean; + removed?: boolean; value?: string | DiffInformation[]; lineNumber?: number; type?: DiffType; @@ -77,13 +81,13 @@ const constructLines = (value: string): string[] => { * @param jsDiffCompareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api */ const computeDiff = (oldValue: string, newValue: string, jsDiffCompareMethod: string): ComputedDiffInformation => { - const diffArray = diff[jsDiffCompareMethod](oldValue, newValue); + const diffArray = jsDiff[jsDiffCompareMethod](oldValue, newValue); const computedDiff: ComputedDiffInformation = { left: [], right: [], }; diffArray - .forEach(({ added, removed, value }): DiffInformation => { + .forEach(({ added, removed, value }: DiffInformation): DiffInformation => { const diffInformation: DiffInformation = {}; if (added) { diffInformation.type = DiffType.ADDED; From 736bb622755519a85daae32eaed07768a3949fcb Mon Sep 17 00:00:00 2001 From: Derek Hammond Date: Tue, 26 Nov 2019 11:08:20 -0600 Subject: [PATCH 3/3] update prop name, update type definition, update README --- README.md | 4 ++-- examples/src/index.tsx | 16 ++++++++-------- src/compute-lines.ts | 26 ++++++++++++++++---------- src/index.tsx | 10 +++++----- test/compute-lines-test.ts | 4 ++-- 5 files changed, 33 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index d96ff533..5a48b11d 100644 --- a/README.md +++ b/README.md @@ -76,7 +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. | -|jsDiffCompareMethod |`string` |`'diffChars'` |JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api | +|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` | @@ -175,7 +175,7 @@ class Diff extends PureComponent { diff --git a/examples/src/index.tsx b/examples/src/index.tsx index 443ef9d4..a4022db2 100644 --- a/examples/src/index.tsx +++ b/examples/src/index.tsx @@ -19,7 +19,7 @@ interface ExampleState { highlightLine?: string[]; language?: string; enableSyntaxHighlighting?: boolean; - jsDiffCompareMethod?: string; + compareMethod?: string; } const P = (window as any).Prism; @@ -32,7 +32,7 @@ class Example extends React.Component<{}, ExampleState> { highlightLine: [], language: 'javascript', enableSyntaxHighlighting: true, - jsDiffCompareMethod: 'diffChars' + compareMethod: 'diffChars' }; } @@ -47,8 +47,8 @@ class Example extends React.Component<{}, ExampleState> { private onLanguageChange = (e: any): void => this.setState({ language: e.target.value, highlightLine: [] }); - private onJsDiffCompareMethodChange = (e: any): void => - this.setState({ jsDiffCompareMethod: e.target.value }); + private onCompareMethodChange = (e: any): void => + this.setState({ compareMethod: e.target.value }); private onLineNumberClick = ( id: string, @@ -168,8 +168,8 @@ class Example extends React.Component<{}, ExampleState> {