diff --git a/packages/patternfly-3/patternfly-react-extensions/less/diffview.less b/packages/patternfly-3/patternfly-react-extensions/less/diffview.less
new file mode 100644
index 00000000000..dd9eda8b9bb
--- /dev/null
+++ b/packages/patternfly-3/patternfly-react-extensions/less/diffview.less
@@ -0,0 +1,47 @@
+@import '~react-diff-view/index.css';
+
+@diff-light-green-200: #cdffd8;
+@diff-light-green-100: #e6ffed;
+@diff-green: #acf2bd;
+@diff-light-red: #ffeef0;
+@diff-red: #fdb8c0;
+
+.diff-pf.diff {
+ border: 1px solid @color-pf-black-300;
+ max-height: 400px;
+ overflow: auto;
+ clear: both;
+
+ .diff-line {
+ line-height: 1.7;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
+ }
+
+ .diff-gutter {
+ border-left: 1px solid @color-pf-black-300;
+ }
+
+ .diff-gutter.diff-gutter-insert {
+ background-color: @diff-light-green-200;
+ }
+
+ .diff-hunk-header {
+ border: 1px solid @color-pf-black-300;
+ }
+
+ .diff-code-insert {
+ background-color: @diff-light-green-100;
+
+ mark.diff-code-edit {
+ background-color: @diff-green;
+ }
+ }
+
+ .diff-code-delete {
+ background-color: @diff-light-red;
+
+ mark.diff-code-edit {
+ background-color: @diff-red;
+ }
+ }
+}
diff --git a/packages/patternfly-3/patternfly-react-extensions/less/patternfly-react-extensions.less b/packages/patternfly-3/patternfly-react-extensions/less/patternfly-react-extensions.less
index 4d84fa8caed..58e1206e41e 100644
--- a/packages/patternfly-3/patternfly-react-extensions/less/patternfly-react-extensions.less
+++ b/packages/patternfly-3/patternfly-react-extensions/less/patternfly-react-extensions.less
@@ -6,3 +6,4 @@
@import 'properties-side-panel';
@import 'table-grid';
@import 'vertical-tabs';
+@import 'diff-view';
diff --git a/packages/patternfly-3/patternfly-react-extensions/package.json b/packages/patternfly-3/patternfly-react-extensions/package.json
index aedc81ca5e0..18cacf0631f 100644
--- a/packages/patternfly-3/patternfly-react-extensions/package.json
+++ b/packages/patternfly-3/patternfly-react-extensions/package.json
@@ -44,7 +44,9 @@
"patternfly": "^3.58.0",
"patternfly-react": "^2.28.2",
"react-bootstrap": "^0.32.1",
- "react-virtualized": "9.x"
+ "react-diff-view": "^1.8.1",
+ "react-virtualized": "9.x",
+ "unidiff": "^1.0.1"
},
"peerDependencies": {
"prop-types": "^15.6.1",
diff --git a/packages/patternfly-3/patternfly-react-extensions/sass/patternfly-react-extensions/_diffview.scss b/packages/patternfly-3/patternfly-react-extensions/sass/patternfly-react-extensions/_diffview.scss
new file mode 100644
index 00000000000..03aaf002442
--- /dev/null
+++ b/packages/patternfly-3/patternfly-react-extensions/sass/patternfly-react-extensions/_diffview.scss
@@ -0,0 +1,47 @@
+@import '~react-diff-view/index.css';
+
+$diff-light-green-200: #cdffd8;
+$diff-light-green-100: #e6ffed;
+$diff-green: #acf2bd;
+$diff-light-red: #ffeef0;
+$diff-red: #fdb8c0;
+
+.diff-pf.diff {
+ border: 1px solid $color-pf-black-300;
+ max-height: 400px;
+ overflow: auto;
+ clear: both;
+
+ .diff-line {
+ line-height: 1.7;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
+ }
+
+ .diff-gutter {
+ border-left: 1px solid $color-pf-black-300;
+ }
+
+ .diff-gutter.diff-gutter-insert {
+ background-color: $diff-light-green-200;
+ }
+
+ .diff-hunk-header {
+ border: 1px solid $color-pf-black-300;
+ }
+
+ .diff-code-insert {
+ background-color: $diff-light-green-100;
+
+ mark.diff-code-edit {
+ background-color: $diff-green;
+ }
+ }
+
+ .diff-code-delete {
+ background-color: $diff-light-red;
+
+ mark.diff-code-edit {
+ background-color: $diff-red;
+ }
+ }
+}
diff --git a/packages/patternfly-3/patternfly-react-extensions/sass/patternfly-react-extensions/_patternfly-react-extensions.scss b/packages/patternfly-3/patternfly-react-extensions/sass/patternfly-react-extensions/_patternfly-react-extensions.scss
index 724313479d1..9cf211f0c53 100644
--- a/packages/patternfly-3/patternfly-react-extensions/sass/patternfly-react-extensions/_patternfly-react-extensions.scss
+++ b/packages/patternfly-3/patternfly-react-extensions/sass/patternfly-react-extensions/_patternfly-react-extensions.scss
@@ -9,3 +9,4 @@
@import 'properties-side-panel';
@import 'table-grid';
@import 'vertical-tabs';
+@import 'diffview';
diff --git a/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/DiffView.js b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/DiffView.js
new file mode 100644
index 00000000000..469f0f6622f
--- /dev/null
+++ b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/DiffView.js
@@ -0,0 +1,91 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+import { parseDiff, Diff, markCharacterEdits, markWordEdits } from 'react-diff-view';
+import { formatLines, diffLines } from 'unidiff';
+
+const getDiff = (oldText, newText) => {
+ const diffText = formatLines(diffLines(oldText, newText));
+ // these two lines are faked to mock git diff output
+ const header = ['diff --git a/a b/b', 'index 0000000..1111111 100644'];
+ return `${header.join('\n')}\n${diffText}`;
+};
+
+const DiffView = ({
+ oldText,
+ newText,
+ className,
+ viewType,
+ patch,
+ markEditsByWord,
+ emptyState,
+ markThreshold,
+ markLongDistanceDiff,
+ ...props
+}) => {
+ const markEditProps = { markThreshold, markLongDistanceDiff };
+ const markEdits = markEditsByWord ? markWordEdits(markEditProps) : markCharacterEdits(markEditProps);
+ const classes = classNames('diff-pf', className);
+
+ // Old, New Text
+ if (!patch) {
+ const gitDiff = getDiff(oldText, newText);
+ const files = parseDiff(gitDiff);
+ const hunk = files[0].hunks;
+
+ if (hunk.length === 0) return emptyState;
+ return hunk && ;
+ }
+
+ // Patch
+ const files = parseDiff(patch);
+ // eslint-disable-next-line react/prop-types
+ const renderFile = ({ oldRevision, newRevision, type, hunks }) => (
+
+ );
+
+ if (patch === '') return emptyState;
+ return
{files.map(renderFile)}
;
+};
+
+DiffView.propTypes = {
+ /** className */
+ className: PropTypes.string,
+ /** oldText to compare */
+ oldText: PropTypes.string,
+ /** newText to compare */
+ newText: PropTypes.string,
+ /** viewType of the DiffView (Unified, Split) */
+ viewType: PropTypes.string.isRequired,
+ /** git diff output */
+ patch: PropTypes.string,
+ /** mark Diff by Words instead of Characters */
+ markEditsByWord: PropTypes.bool,
+ /** emptyState node when there is no diff */
+ emptyState: PropTypes.node.isRequired,
+ /** The maximum string distance when this function should try to mark edits */
+ markThreshold: PropTypes.number,
+ /** If true, two strings with distance greater than threshold will create an edit containing the whole string */
+ markLongDistanceDiff: PropTypes.bool
+};
+
+DiffView.defaultProps = {
+ oldText: null,
+ newText: null,
+ patch: null,
+ markEditsByWord: false,
+ markThreshold: 30,
+ markLongDistanceDiff: true,
+ className: ''
+};
+
+export default DiffView;
diff --git a/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/DiffView.stories.js b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/DiffView.stories.js
new file mode 100644
index 00000000000..cdc18077abd
--- /dev/null
+++ b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/DiffView.stories.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { withInfo } from '@storybook/addon-info/dist/index';
+import { defaultTemplate } from 'storybook/decorators/storyTemplates';
+import { storybookPackageName, STORYBOOK_CATEGORY } from 'storybook/constants/siteConstants';
+
+import { DiffView } from './index';
+
+import { name } from '../../../package.json';
+import { boolean, text, select, withKnobs } from '@storybook/addon-knobs';
+
+const stories = storiesOf(`${storybookPackageName(name)}/${STORYBOOK_CATEGORY.CONTENT_VIEWS}/DiffView`, module);
+const emptyState = No Diff
;
+
+stories.addDecorator(
+ defaultTemplate({
+ title: 'Diff View',
+ description: (
+
+ The Diff View is based on the
react-diff-view{' '}
+ package. This Component supports both git diff Patch and Old/New text inputs and displays the diff in Unified or
+ Split View.
+
+ )
+ })
+);
+
+stories.addDecorator(withKnobs);
+stories.add(
+ 'DiffView',
+ withInfo({
+ source: true,
+ propTables: [DiffView]
+ })(() => {
+ const viewType = select('View Type', { split: 'split', unified: 'unified' }, 'split');
+
+ return (
+
+
+
+ );
+ })
+);
diff --git a/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/DiffView.test.js b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/DiffView.test.js
new file mode 100644
index 00000000000..6e15f90cbc9
--- /dev/null
+++ b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/DiffView.test.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import toJson from 'enzyme-to-json';
+
+import { DiffView } from './index';
+import { diffMock, patchMock, emptyState } from './_mocks_/DiffViewMocks';
+
+test('Diff View renders w/Patch properly', () => {
+ const component = shallow();
+ expect(toJson(component.render())).toMatchSnapshot();
+});
+
+test('Diff View renders w/Text properly', () => {
+ const component = shallow();
+ expect(toJson(component.render())).toMatchSnapshot();
+});
+
+test('Diff View renders emptyState', () => {
+ const component = shallow();
+ expect(toJson(component.render())).toMatchSnapshot();
+});
diff --git a/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/__snapshots__/DiffView.test.js.snap b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/__snapshots__/DiffView.test.js.snap
new file mode 100644
index 00000000000..d8dabdd28cb
--- /dev/null
+++ b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/__snapshots__/DiffView.test.js.snap
@@ -0,0 +1,95 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Diff View renders emptyState 1`] = `
+
+ empty
+
+`;
+
+exports[`Diff View renders w/Patch properly 1`] = `
+
+`;
+
+exports[`Diff View renders w/Text properly 1`] = `
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ hello friend
+
+ |
+ |
+
+
+ hello
+
+ there
+
+ friend
+
+ |
+
+
+
+`;
diff --git a/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/_mocks_/DiffViewMocks.js b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/_mocks_/DiffViewMocks.js
new file mode 100644
index 00000000000..894cb5fa1d8
--- /dev/null
+++ b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/_mocks_/DiffViewMocks.js
@@ -0,0 +1,21 @@
+import React from 'react';
+
+export const diffMock = {
+ oldText: 'hello friend',
+ newText: 'hello there friend',
+ viewType: 'split',
+ emptyState: empty
+};
+
+export const patchMock = {
+ viewType: 'unified',
+ patch: '---',
+ emptyState: empty
+};
+
+export const emptyState = {
+ oldText: 'hello friend',
+ newText: 'hello friend',
+ viewType: 'split',
+ emptyState: empty
+};
diff --git a/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/index.js b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/index.js
new file mode 100644
index 00000000000..08c77422f0a
--- /dev/null
+++ b/packages/patternfly-3/patternfly-react-extensions/src/components/DiffView/index.js
@@ -0,0 +1,3 @@
+import DiffView from './DiffView';
+
+export { DiffView };
diff --git a/packages/patternfly-3/patternfly-react-extensions/src/index.js b/packages/patternfly-3/patternfly-react-extensions/src/index.js
index 34e0a66e9a8..659836a6908 100644
--- a/packages/patternfly-3/patternfly-react-extensions/src/index.js
+++ b/packages/patternfly-3/patternfly-react-extensions/src/index.js
@@ -6,3 +6,4 @@ export * from './components/FilterSidePanel';
export * from './components/PropertiesSidePanel';
export * from './components/TableGrid';
export * from './components/VerticalTabs';
+export * from './components/DiffView';
diff --git a/yarn.lock b/yarn.lock
index dd6ce42a608..e0fc49c6871 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4299,7 +4299,7 @@ classnames@^2.2.0, classnames@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
-classnames@^2.2.3:
+classnames@^2.2.3, classnames@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
@@ -5825,6 +5825,11 @@ diff@3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
+diff@^2.2.2:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99"
+ integrity sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=
+
diff@^3.2.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@@ -7755,6 +7760,11 @@ gitconfiglocal@^1.0.0:
dependencies:
ini "^1.3.2"
+gitdiff-parser@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/gitdiff-parser/-/gitdiff-parser-0.1.2.tgz#26a256e05e9c2d5016b512a96c1dacb40862b92a"
+ integrity sha512-glDM6E1AwLYYTOPyI0CqamNEUSuwwAkmwULWpE2sHMpMZNzGJwErt7+eV+yIZcsbDza0pVSlwlBHFWbTf2Wu7A==
+
glamor@^2.20.40:
version "2.20.40"
resolved "https://registry.yarnpkg.com/glamor/-/glamor-2.20.40.tgz#f606660357b7cf18dface731ad1a2cfa93817f05"
@@ -10163,6 +10173,11 @@ lodash.defaults@^4.0.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
+lodash.escape@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
+ integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=
+
lodash.filter@^4.4.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
@@ -10171,6 +10186,11 @@ lodash.findkey@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.findkey/-/lodash.findkey-4.6.0.tgz#83058e903b51cbb759d09ccf546dea3ea39c4718"
+lodash.findlastindex@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.findlastindex/-/lodash.findlastindex-4.6.0.tgz#b8375ac0f02e9b926375cdf8dc3ea814abf9c6ac"
+ integrity sha1-uDdawPAum5Jjdc343D6oFKv5xqw=
+
lodash.flatten@^4.2.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
@@ -10215,6 +10235,11 @@ lodash.map@^4.4.0, lodash.map@^4.5.1:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
+lodash.mapvalues@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
+ integrity sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=
+
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -13174,6 +13199,19 @@ react-dev-utils@^5.0.0:
strip-ansi "3.0.1"
text-table "0.2.0"
+react-diff-view@^1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/react-diff-view/-/react-diff-view-1.8.1.tgz#0b9b4adcb92de6730d28177d68654dfcc2097f73"
+ integrity sha512-+soJL85Xnsak/VOdxSgiDKhhaFiOkckiswwrXdiWVCxV3LP9POyJR4AqGVFGdkntJ3YT63mtwTYuunFeId+XSA==
+ dependencies:
+ classnames "^2.2.6"
+ gitdiff-parser "^0.1.2"
+ leven "^2.1.0"
+ lodash.escape "^4.0.1"
+ lodash.findlastindex "^4.6.0"
+ lodash.mapvalues "^4.6.0"
+ warning "^4.0.1"
+
react-docgen-typescript@^1.2.3:
version "1.12.2"
resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-1.12.2.tgz#d5fb578d12f6876efdde69176f4ea658e75a9a29"
@@ -16011,6 +16049,13 @@ unicode-property-aliases-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0"
+unidiff@^0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/unidiff/-/unidiff-0.0.4.tgz#257fc346ac6f134b0b75d08895320872a737e222"
+ integrity sha1-JX/DRqxvE0sLddCIlTIIcqc34iI=
+ dependencies:
+ diff "^2.2.2"
+
unified@^6.0.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba"
@@ -16619,6 +16664,13 @@ warning@^3.0.0:
dependencies:
loose-envify "^1.0.0"
+warning@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607"
+ integrity sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==
+ dependencies:
+ loose-envify "^1.0.0"
+
watch@~0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc"