@automattic/jetpack-components 0.57.0 → 0.58.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/components/boost-score-graph/style-tooltip.scss +16 -0
- package/components/boost-score-graph/tooltips-plugin.ts +16 -21
- package/components/diff-viewer/README.md +49 -0
- package/components/diff-viewer/index.tsx +97 -0
- package/components/diff-viewer/parse-filename.ts +75 -0
- package/components/diff-viewer/parse-patch.ts +212 -0
- package/components/diff-viewer/styles.module.scss +52 -0
- package/components/threat-severity-badge/index.tsx +34 -0
- package/components/threat-severity-badge/styles.module.scss +27 -0
- package/index.ts +2 -0
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
### This is a list detailing changes for the Jetpack RNA Components package releases.
|
|
4
4
|
|
|
5
|
+
## [0.58.1] - 2024-11-04
|
|
6
|
+
### Added
|
|
7
|
+
- Enable test coverage. [#39961]
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Fix tooltip behavior. [#39879]
|
|
11
|
+
|
|
12
|
+
## [0.58.0] - 2024-10-15
|
|
13
|
+
### Added
|
|
14
|
+
- Add DiffViewer component [#39672]
|
|
15
|
+
- Add ThreatSeverityBadge component [#39758]
|
|
16
|
+
|
|
5
17
|
## [0.57.0] - 2024-10-14
|
|
6
18
|
### Added
|
|
7
19
|
- Add JetpackProtectLogo component. [#39703]
|
|
@@ -1190,6 +1202,8 @@
|
|
|
1190
1202
|
### Changed
|
|
1191
1203
|
- Update node version requirement to 14.16.1
|
|
1192
1204
|
|
|
1205
|
+
[0.58.1]: https://github.com/Automattic/jetpack-components/compare/0.58.0...0.58.1
|
|
1206
|
+
[0.58.0]: https://github.com/Automattic/jetpack-components/compare/0.57.0...0.58.0
|
|
1193
1207
|
[0.57.0]: https://github.com/Automattic/jetpack-components/compare/0.56.3...0.57.0
|
|
1194
1208
|
[0.56.3]: https://github.com/Automattic/jetpack-components/compare/0.56.2...0.56.3
|
|
1195
1209
|
[0.56.2]: https://github.com/Automattic/jetpack-components/compare/0.56.1...0.56.2
|
|
@@ -5,6 +5,17 @@ $white: #ffffff;
|
|
|
5
5
|
.jb-score-tooltips-container {
|
|
6
6
|
width: 100%;
|
|
7
7
|
position: relative;
|
|
8
|
+
pointer-events: none;
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.jb-score-tooltip-react-root {
|
|
13
|
+
position: absolute;
|
|
14
|
+
bottom: -20px;
|
|
15
|
+
translate: -50% calc( 100% - 20px );
|
|
16
|
+
z-index: 1000;
|
|
17
|
+
pointer-events: auto;
|
|
18
|
+
user-select: text;
|
|
8
19
|
}
|
|
9
20
|
|
|
10
21
|
.jb-score-tooltip {
|
|
@@ -17,6 +28,11 @@ $white: #ffffff;
|
|
|
17
28
|
width: 20em;
|
|
18
29
|
position: relative;
|
|
19
30
|
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
|
|
31
|
+
display: none;
|
|
32
|
+
|
|
33
|
+
.visible &, &:hover {
|
|
34
|
+
display: block;
|
|
35
|
+
}
|
|
20
36
|
|
|
21
37
|
hr {
|
|
22
38
|
border-top: 1px solid $grey;
|
|
@@ -25,35 +25,26 @@ export function tooltipsPlugin( periods ) {
|
|
|
25
25
|
if ( ! reactDom ) {
|
|
26
26
|
reactDom = ReactDOM.createRoot( reactRoot );
|
|
27
27
|
}
|
|
28
|
-
reactRoot.
|
|
29
|
-
reactRoot.style.bottom = -20 + 'px';
|
|
30
|
-
reactRoot.style.translate = '-50% calc( 100% - 20px )';
|
|
31
|
-
reactRoot.style.zIndex = '1000';
|
|
28
|
+
reactRoot.classList.add( 'jb-score-tooltip-react-root' );
|
|
32
29
|
|
|
33
30
|
container.appendChild( reactRoot );
|
|
34
31
|
|
|
35
32
|
u.over.appendChild( container );
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
function hideTips() {
|
|
41
|
-
reactRoot.style.display = 'none';
|
|
42
|
-
}
|
|
34
|
+
u.over.addEventListener( 'mouseenter', () => {
|
|
35
|
+
container.classList.add( 'visible' );
|
|
36
|
+
} );
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
function showTips() {
|
|
48
|
-
reactRoot.style.display = null;
|
|
49
|
-
}
|
|
38
|
+
u.over.addEventListener( 'mouseleave', () => {
|
|
39
|
+
container.classList.remove( 'visible' );
|
|
40
|
+
} );
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
reactRoot.addEventListener( 'mouseenter', () => {
|
|
43
|
+
reactRoot.classList.add( 'visible' );
|
|
53
44
|
} );
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
reactRoot.addEventListener( 'mouseleave', () => {
|
|
47
|
+
reactRoot.classList.remove( 'visible' );
|
|
57
48
|
} );
|
|
58
49
|
}
|
|
59
50
|
|
|
@@ -62,7 +53,7 @@ export function tooltipsPlugin( periods ) {
|
|
|
62
53
|
* @param {uPlot} u - The uPlot instance.
|
|
63
54
|
*/
|
|
64
55
|
function setSize( u: uPlot ) {
|
|
65
|
-
container.style.
|
|
56
|
+
container.style.paddingTop = u.over.clientHeight + 'px';
|
|
66
57
|
}
|
|
67
58
|
|
|
68
59
|
/**
|
|
@@ -75,6 +66,10 @@ export function tooltipsPlugin( periods ) {
|
|
|
75
66
|
|
|
76
67
|
const period = periods[ idx ];
|
|
77
68
|
|
|
69
|
+
if ( ! period ) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
78
73
|
// Timestamp of the cursor position
|
|
79
74
|
const timestamp = u.data[ 0 ][ idx ];
|
|
80
75
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Unified Diff Viewer
|
|
2
|
+
|
|
3
|
+
Originally forked over from [Calypso](https://github.com/Automattic/wp-calypso/tree/b7a4a07/client/components/diff-viewer).
|
|
4
|
+
|
|
5
|
+
This component renders the output of a unified diff (`git diff` or `diff -u`) in a
|
|
6
|
+
visual format recognizable by someone who works with `diff` and comparing files.
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
```jsx
|
|
11
|
+
import DiffViewer from 'components/diff-viewer';
|
|
12
|
+
export const CommitView = ( { commitHash, description, diff } ) => (
|
|
13
|
+
<div>
|
|
14
|
+
<div>
|
|
15
|
+
<a href="https://wordpress.com">{ commitHash }</a>
|
|
16
|
+
</div>
|
|
17
|
+
<p>{ description }</p>
|
|
18
|
+
<DiffViewer diff={ diff } />
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Props
|
|
24
|
+
|
|
25
|
+
| Name | Type | Default | Description |
|
|
26
|
+
| -------- | -------- | ------- | ------------------------------ |
|
|
27
|
+
| `diff`\* | `string` | `''` | Actual text output of the diff |
|
|
28
|
+
|
|
29
|
+
### Additional usage information
|
|
30
|
+
|
|
31
|
+
The diff output should be the full text produced by the diff command (including newlines).
|
|
32
|
+
Internally this component parses the output (the patch) and produces the data structure
|
|
33
|
+
used to display files, hunks (sections of change in the files), and the actual lines of
|
|
34
|
+
change and context.
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
diff --git a/circle.yml b/circle.yml
|
|
38
|
+
index 51455bdb14..bc0622d001 100644
|
|
39
|
+
--- a/circle.yml
|
|
40
|
+
+++ b/circle.yml
|
|
41
|
+
@@ -1,6 +1,6 @@
|
|
42
|
+
machine:
|
|
43
|
+
node:
|
|
44
|
+
- version: 8.9.4
|
|
45
|
+
+ version: 8.11.0
|
|
46
|
+
test:
|
|
47
|
+
pre:
|
|
48
|
+
- ? |
|
|
49
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Fragment } from 'react';
|
|
2
|
+
import parseFilename from './parse-filename';
|
|
3
|
+
import parsePatch from './parse-patch';
|
|
4
|
+
import styles from './styles.module.scss';
|
|
5
|
+
|
|
6
|
+
const filename = ( {
|
|
7
|
+
oldFileName,
|
|
8
|
+
newFileName,
|
|
9
|
+
}: {
|
|
10
|
+
oldFileName: string;
|
|
11
|
+
newFileName: string;
|
|
12
|
+
} ): JSX.Element => {
|
|
13
|
+
const { prev, next } = parseFilename( oldFileName, newFileName );
|
|
14
|
+
|
|
15
|
+
if ( prev.prefix + prev.path === next.prefix + next.path ) {
|
|
16
|
+
return (
|
|
17
|
+
<Fragment>
|
|
18
|
+
{ prev.prefix && (
|
|
19
|
+
<span className={ styles[ 'diff-viewer__path-prefix' ] }>{ prev.prefix }</span>
|
|
20
|
+
) }
|
|
21
|
+
<span className={ styles[ 'diff-viewer__path' ] }>{ prev.path }</span>
|
|
22
|
+
</Fragment>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Fragment>
|
|
28
|
+
{ !! prev.prefix && (
|
|
29
|
+
<span className={ styles[ 'diff-viewer__path-prefix' ] }>{ prev.prefix }</span>
|
|
30
|
+
) }
|
|
31
|
+
<span className={ styles[ 'diff-viewer__path' ] }>{ prev.path }</span>
|
|
32
|
+
{ ' → ' }
|
|
33
|
+
{ !! next.prefix && (
|
|
34
|
+
<span className={ styles[ 'diff-viewer__path-prefix' ] }>{ next.prefix }</span>
|
|
35
|
+
) }
|
|
36
|
+
<span className={ styles[ 'diff-viewer__path' ] }>{ next.path }</span>
|
|
37
|
+
</Fragment>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const DiffViewer = ( { diff } ) => (
|
|
42
|
+
<div className={ styles[ 'diff-viewer' ] }>
|
|
43
|
+
{ parsePatch( diff ).map( ( file, fileIndex ) => (
|
|
44
|
+
<Fragment key={ fileIndex }>
|
|
45
|
+
<div key={ `file-${ fileIndex }` } className={ styles[ 'diff-viewer__filename' ] }>
|
|
46
|
+
{ filename( file ) }
|
|
47
|
+
</div>
|
|
48
|
+
<div key={ `diff-${ fileIndex }` } className={ styles[ 'diff-viewer__file' ] }>
|
|
49
|
+
<div key="left-numbers" className={ styles[ 'diff-viewer__line-numbers' ] }>
|
|
50
|
+
{ file.hunks.map( ( hunk, hunkIndex ) => {
|
|
51
|
+
let lineOffset = 0;
|
|
52
|
+
return hunk.lines.map( ( line, index ) => (
|
|
53
|
+
<div key={ `${ hunkIndex }-${ index }` }>
|
|
54
|
+
{ line[ 0 ] === '+' ? '\u00a0' : hunk.oldStart + lineOffset++ }
|
|
55
|
+
</div>
|
|
56
|
+
) );
|
|
57
|
+
} ) }
|
|
58
|
+
</div>
|
|
59
|
+
<div key="right-numbers" className={ styles[ 'diff-viewer__line-numbers' ] }>
|
|
60
|
+
{ file.hunks.map( ( hunk, hunkIndex ) => {
|
|
61
|
+
let lineOffset = 0;
|
|
62
|
+
return hunk.lines.map( ( line, index ) => (
|
|
63
|
+
<div key={ `${ hunkIndex }-${ index }` }>
|
|
64
|
+
{ line[ 0 ] === '-' ? '\u00a0' : hunk.newStart + lineOffset++ }
|
|
65
|
+
</div>
|
|
66
|
+
) );
|
|
67
|
+
} ) }
|
|
68
|
+
</div>
|
|
69
|
+
<div className={ styles[ 'diff-viewer__lines' ] }>
|
|
70
|
+
{ file.hunks.map( ( hunk, hunkIndex ) =>
|
|
71
|
+
hunk.lines.map( ( line, index ) => {
|
|
72
|
+
const output = line.slice( 1 ).replace( /^\s*$/, '\u00a0' );
|
|
73
|
+
const key = `${ hunkIndex }-${ index }`;
|
|
74
|
+
|
|
75
|
+
switch ( line[ 0 ] ) {
|
|
76
|
+
case ' ':
|
|
77
|
+
return <div key={ key }>{ output }</div>;
|
|
78
|
+
|
|
79
|
+
case '-':
|
|
80
|
+
return <del key={ key }>{ output }</del>;
|
|
81
|
+
|
|
82
|
+
case '+':
|
|
83
|
+
return <ins key={ key }>{ output }</ins>;
|
|
84
|
+
|
|
85
|
+
default:
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
} )
|
|
89
|
+
) }
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</Fragment>
|
|
93
|
+
) ) }
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
export default DiffViewer;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
type ParsedFilename = {
|
|
2
|
+
prefix: string;
|
|
3
|
+
path: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const decompose = ( path: string ): ParsedFilename => {
|
|
7
|
+
const lastSlash = path.lastIndexOf( '/' );
|
|
8
|
+
|
|
9
|
+
return lastSlash > -1
|
|
10
|
+
? { prefix: path.slice( 0, lastSlash ), path: path.slice( lastSlash ) }
|
|
11
|
+
: { prefix: '', path };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse the filename from a diff
|
|
16
|
+
*
|
|
17
|
+
* Uses a heuristic to return proper file name indicators
|
|
18
|
+
*
|
|
19
|
+
* It searches for the longest shared prefix and returns
|
|
20
|
+
* whatever remains after that. If the paths are identical
|
|
21
|
+
* it only returns a single filename as we have detected
|
|
22
|
+
* that the diff compares changes to only one file.
|
|
23
|
+
*
|
|
24
|
+
* An exception is made for `a/` and `b/` prefixes often
|
|
25
|
+
* added by `git` and other utilities to separate the left
|
|
26
|
+
* from the right when looking at the contents of a single
|
|
27
|
+
* file over time.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} prev - filename of left contents
|
|
30
|
+
* @param {string} next - filename of right contents
|
|
31
|
+
*
|
|
32
|
+
* @return {object} - parsed filename
|
|
33
|
+
*/
|
|
34
|
+
export default function (
|
|
35
|
+
prev: string,
|
|
36
|
+
next: string
|
|
37
|
+
): { prev: ParsedFilename; next: ParsedFilename } {
|
|
38
|
+
// Remove 'a/' and 'b/' prefixes if present
|
|
39
|
+
const isLikelyPrefixed = prev.startsWith( 'a/' ) && next.startsWith( 'b/' );
|
|
40
|
+
prev = isLikelyPrefixed ? prev.slice( 2 ) : prev;
|
|
41
|
+
next = isLikelyPrefixed ? next.slice( 2 ) : next;
|
|
42
|
+
|
|
43
|
+
if ( prev === next ) {
|
|
44
|
+
// Paths are identical
|
|
45
|
+
const { prefix, path } = decompose( prev );
|
|
46
|
+
return { prev: { prefix, path }, next: { prefix, path } };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Find longest shared base path ending with a slash
|
|
50
|
+
const length = Math.max( prev.length, next.length );
|
|
51
|
+
for ( let i = 0, slash = 0; i < length; i++ ) {
|
|
52
|
+
if ( prev[ i ] === '/' && next[ i ] === '/' ) {
|
|
53
|
+
slash = i;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if ( prev[ i ] !== next[ i ] ) {
|
|
57
|
+
return {
|
|
58
|
+
prev: {
|
|
59
|
+
prefix: prev.slice( 0, slash ),
|
|
60
|
+
path: prev.slice( slash ),
|
|
61
|
+
},
|
|
62
|
+
next: {
|
|
63
|
+
prefix: next.slice( 0, slash ),
|
|
64
|
+
path: next.slice( slash ),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// No shared base path
|
|
71
|
+
return {
|
|
72
|
+
prev: decompose( prev ),
|
|
73
|
+
next: decompose( next ),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
type Hunk = {
|
|
2
|
+
oldStart: number;
|
|
3
|
+
oldLines: number;
|
|
4
|
+
newStart: number;
|
|
5
|
+
newLines: number;
|
|
6
|
+
lines: string[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type Index = {
|
|
10
|
+
index?: string;
|
|
11
|
+
hunks?: Hunk[];
|
|
12
|
+
oldFileName?: string;
|
|
13
|
+
newFileName?: string;
|
|
14
|
+
oldHeader?: string;
|
|
15
|
+
newHeader?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse Patch
|
|
20
|
+
*
|
|
21
|
+
* Adapted from https://github.com/kpdecker/jsdiff/blob/master/src/patch/parse.js
|
|
22
|
+
*
|
|
23
|
+
* @param {string} uniDiff - diff string
|
|
24
|
+
* @return {Array} - array of parsed files
|
|
25
|
+
*/
|
|
26
|
+
export default function parsePatch( uniDiff: string ) {
|
|
27
|
+
const diffstr = uniDiff.split( /\n/ );
|
|
28
|
+
const list = [];
|
|
29
|
+
let i = 0;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse Index
|
|
33
|
+
*/
|
|
34
|
+
function parseIndex() {
|
|
35
|
+
const index: Index = {};
|
|
36
|
+
|
|
37
|
+
list.push( index );
|
|
38
|
+
|
|
39
|
+
// Parse diff metadata
|
|
40
|
+
while ( i < diffstr.length ) {
|
|
41
|
+
const line = diffstr[ i ];
|
|
42
|
+
|
|
43
|
+
// File header found, end parsing diff metadata
|
|
44
|
+
if ( /^(---|\+\+\+|@@)\s/.test( line ) ) {
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Diff index
|
|
49
|
+
const header = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec( line );
|
|
50
|
+
|
|
51
|
+
if ( header ) {
|
|
52
|
+
index.index = header[ 1 ];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
i++;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Parse file headers if they are defined. Unified diff requires them, but
|
|
59
|
+
// there's no technical issues to have an isolated hunk without file header
|
|
60
|
+
parseFileHeader( index );
|
|
61
|
+
parseFileHeader( index );
|
|
62
|
+
|
|
63
|
+
// Parse hunks
|
|
64
|
+
index.hunks = [];
|
|
65
|
+
|
|
66
|
+
while ( i < diffstr.length ) {
|
|
67
|
+
const _line = diffstr[ i ];
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
/^(Index:\s|diff\s|---\s|\+\+\+\s|===================================================================)/.test(
|
|
71
|
+
_line
|
|
72
|
+
)
|
|
73
|
+
) {
|
|
74
|
+
break;
|
|
75
|
+
} else if ( /^@@/.test( _line ) ) {
|
|
76
|
+
index.hunks.push( parseHunk() );
|
|
77
|
+
} else if ( _line ) {
|
|
78
|
+
throw new Error( 'Unknown line ' + ( i + 1 ) + ' ' + JSON.stringify( _line ) );
|
|
79
|
+
} else {
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parse File Header
|
|
87
|
+
*
|
|
88
|
+
* Parses the --- and +++ headers, if none are found, no lines
|
|
89
|
+
* are consumed.
|
|
90
|
+
*
|
|
91
|
+
* @param {Array} index - array of parsed files
|
|
92
|
+
* @param {unknown} index.index - index
|
|
93
|
+
* @param {object[]} index.hunks - hunks
|
|
94
|
+
*/
|
|
95
|
+
function parseFileHeader( index: Index ) {
|
|
96
|
+
const fileHeader = /^(---|\+\+\+)\s+(.*)\r?$/.exec( diffstr[ i ] );
|
|
97
|
+
|
|
98
|
+
if ( fileHeader ) {
|
|
99
|
+
const keyPrefix = fileHeader[ 1 ] === '---' ? 'old' : 'new';
|
|
100
|
+
|
|
101
|
+
const data = fileHeader[ 2 ].split( '\t', 2 );
|
|
102
|
+
|
|
103
|
+
let fileName = data[ 0 ].replace( /\\\\/g, '\\' );
|
|
104
|
+
|
|
105
|
+
if ( /^".*"$/.test( fileName ) ) {
|
|
106
|
+
fileName = fileName.substr( 1, fileName.length - 2 );
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
index[ keyPrefix + 'FileName' ] = fileName;
|
|
110
|
+
|
|
111
|
+
index[ keyPrefix + 'Header' ] = ( data[ 1 ] || '' ).trim();
|
|
112
|
+
|
|
113
|
+
i++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parse Hunk
|
|
119
|
+
* This assumes that we are at the start of a hunk.
|
|
120
|
+
*
|
|
121
|
+
* @return {object} - The parsed hunk.
|
|
122
|
+
*/
|
|
123
|
+
function parseHunk(): Hunk {
|
|
124
|
+
const chunkHeaderIndex = i,
|
|
125
|
+
chunkHeaderLine = diffstr[ i++ ],
|
|
126
|
+
chunkHeader = chunkHeaderLine.split( /@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/ );
|
|
127
|
+
|
|
128
|
+
const hunk = {
|
|
129
|
+
oldStart: +chunkHeader[ 1 ],
|
|
130
|
+
oldLines: typeof chunkHeader[ 2 ] === 'undefined' ? 1 : +chunkHeader[ 2 ],
|
|
131
|
+
newStart: +chunkHeader[ 3 ],
|
|
132
|
+
newLines: typeof chunkHeader[ 4 ] === 'undefined' ? 1 : +chunkHeader[ 4 ],
|
|
133
|
+
lines: [],
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Unified Diff Format quirk: If the chunk size is 0,
|
|
137
|
+
// the first number is one lower than one would expect.
|
|
138
|
+
// https://www.artima.com/weblogs/viewpost.jsp?thread=164293
|
|
139
|
+
|
|
140
|
+
if ( hunk.oldLines === 0 ) {
|
|
141
|
+
hunk.oldStart += 1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if ( hunk.newLines === 0 ) {
|
|
145
|
+
hunk.newStart += 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let addCount = 0,
|
|
149
|
+
removeCount = 0,
|
|
150
|
+
_diffstr$i;
|
|
151
|
+
for (
|
|
152
|
+
;
|
|
153
|
+
i < diffstr.length &&
|
|
154
|
+
( removeCount < hunk.oldLines ||
|
|
155
|
+
addCount < hunk.newLines ||
|
|
156
|
+
( ( _diffstr$i = diffstr[ i ] ) !== null &&
|
|
157
|
+
_diffstr$i !== void 0 &&
|
|
158
|
+
_diffstr$i.startsWith( '\\' ) ) );
|
|
159
|
+
i++
|
|
160
|
+
) {
|
|
161
|
+
const operation =
|
|
162
|
+
diffstr[ i ].length === 0 && i !== diffstr.length - 1 ? ' ' : diffstr[ i ][ 0 ];
|
|
163
|
+
|
|
164
|
+
if ( operation === '+' || operation === '-' || operation === ' ' || operation === '\\' ) {
|
|
165
|
+
hunk.lines.push( diffstr[ i ] );
|
|
166
|
+
|
|
167
|
+
if ( operation === '+' ) {
|
|
168
|
+
addCount++;
|
|
169
|
+
} else if ( operation === '-' ) {
|
|
170
|
+
removeCount++;
|
|
171
|
+
} else if ( operation === ' ' ) {
|
|
172
|
+
addCount++;
|
|
173
|
+
removeCount++;
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Hunk at line ${ chunkHeaderIndex + 1 } contained invalid line ${ diffstr[ i ] }`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Handle the empty block count case
|
|
183
|
+
if ( ! addCount && hunk.newLines === 1 ) {
|
|
184
|
+
hunk.newLines = 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if ( ! removeCount && hunk.oldLines === 1 ) {
|
|
188
|
+
hunk.oldLines = 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Perform sanity checking
|
|
192
|
+
if ( addCount !== hunk.newLines ) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
'Added line count did not match for hunk at line ' + ( chunkHeaderIndex + 1 )
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if ( removeCount !== hunk.oldLines ) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
'Removed line count did not match for hunk at line ' + ( chunkHeaderIndex + 1 )
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return hunk;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
while ( i < diffstr.length ) {
|
|
208
|
+
parseIndex();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return list;
|
|
212
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.diff-viewer {
|
|
2
|
+
font-size: var( --font-body );
|
|
3
|
+
line-height: 1.5;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.diff-viewer__filename {
|
|
7
|
+
padding: calc( var( --spacing-base ) / 2 ) var( --spacing-base ); // 4px | 8px
|
|
8
|
+
background-color: var( --jp-gray-10 );
|
|
9
|
+
font-weight: 600;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.diff-viewer__file {
|
|
13
|
+
background-color: var( --jp-gray-0 );
|
|
14
|
+
display: flex;
|
|
15
|
+
font-family: "Courier 10 Pitch", Courier, monospace;
|
|
16
|
+
flex-direction: row;
|
|
17
|
+
overflow-x: scroll;
|
|
18
|
+
white-space: pre;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.diff-viewer__line-numbers {
|
|
22
|
+
padding: 0 var( --spacing-base ); // 0px | 8px
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
text-align: right;
|
|
26
|
+
background-color: var( --jp-gray-10 );
|
|
27
|
+
color: var( --jp-gray-50 );
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.diff-viewer__lines {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
flex-grow: 1;
|
|
34
|
+
overflow-x: visible;
|
|
35
|
+
|
|
36
|
+
& div,
|
|
37
|
+
& del,
|
|
38
|
+
& ins {
|
|
39
|
+
padding: 0 var( --spacing-base ); // 0px | 8px
|
|
40
|
+
text-decoration: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
& del {
|
|
44
|
+
background-color: var( --jp-red-0 );
|
|
45
|
+
color: var( --jp-red-60 );
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
& ins {
|
|
49
|
+
background-color: var( --jp-green-5 );
|
|
50
|
+
color: var( --jp-green-60 );
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { _x } from '@wordpress/i18n';
|
|
2
|
+
import styles from './styles.module.scss';
|
|
3
|
+
|
|
4
|
+
const severityClassNames = severity => {
|
|
5
|
+
if ( severity >= 5 ) {
|
|
6
|
+
return 'is-critical';
|
|
7
|
+
} else if ( severity >= 3 && severity < 5 ) {
|
|
8
|
+
return 'is-high';
|
|
9
|
+
}
|
|
10
|
+
return 'is-low';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const severityText = severity => {
|
|
14
|
+
if ( severity >= 5 ) {
|
|
15
|
+
return _x( 'Critical', 'Severity label for issues rated 5 or higher.', 'jetpack' );
|
|
16
|
+
} else if ( severity >= 3 && severity < 5 ) {
|
|
17
|
+
return _x( 'High', 'Severity label for issues rated between 3 and 5.', 'jetpack' );
|
|
18
|
+
}
|
|
19
|
+
return _x( 'Low', 'Severity label for issues rated below 3.', 'jetpack' );
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ThreatSeverityBadge = ( { severity } ) => {
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className={ `${ styles[ 'threat-severity-badge' ] } ${
|
|
26
|
+
styles[ severityClassNames( severity ) ]
|
|
27
|
+
}` }
|
|
28
|
+
>
|
|
29
|
+
{ severityText( severity ) }
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default ThreatSeverityBadge;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.threat-severity-badge {
|
|
2
|
+
border-radius: 32px;
|
|
3
|
+
flex-shrink: 0;
|
|
4
|
+
font-size: 12px;
|
|
5
|
+
font-style: normal;
|
|
6
|
+
font-weight: 600;
|
|
7
|
+
line-height: 16px;
|
|
8
|
+
padding: calc( var( --spacing-base ) / 2 ); // 4px
|
|
9
|
+
position: relative;
|
|
10
|
+
text-align: center;
|
|
11
|
+
width: 60px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.is-critical {
|
|
15
|
+
background: var( --jp-red-5 );
|
|
16
|
+
color: var( --jp-red-60 );
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.is-high {
|
|
20
|
+
background: var( --jp-yellow-5 );
|
|
21
|
+
color: var( --jp-yellow-60 );
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.is-low {
|
|
25
|
+
background: var( --jp-gray-0 );
|
|
26
|
+
color: var( --jp-gray-50 );
|
|
27
|
+
}
|
package/index.ts
CHANGED
|
@@ -44,6 +44,7 @@ export { default as CopyToClipboard } from './components/copy-to-clipboard';
|
|
|
44
44
|
export * from './components/icons';
|
|
45
45
|
export { default as SplitButton } from './components/split-button';
|
|
46
46
|
export { default as ThemeProvider } from './components/theme-provider';
|
|
47
|
+
export { default as ThreatSeverityBadge } from './components/threat-severity-badge';
|
|
47
48
|
export { default as Text, H2, H3, Title } from './components/text';
|
|
48
49
|
export { default as ToggleControl } from './components/toggle-control';
|
|
49
50
|
export { default as numberFormat } from './components/number-format';
|
|
@@ -79,5 +80,6 @@ export { default as UpsellBanner } from './components/upsell-banner';
|
|
|
79
80
|
export { getUserLocale, cleanLocale } from './lib/locale';
|
|
80
81
|
export { default as RadioControl } from './components/radio-control';
|
|
81
82
|
export { default as StatCard } from './components/stat-card';
|
|
83
|
+
export { default as DiffViewer } from './components/diff-viewer';
|
|
82
84
|
export { default as MarkedLines } from './components/marked-lines';
|
|
83
85
|
export * from './components/global-notices';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/jetpack-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.1",
|
|
4
4
|
"description": "Jetpack Components Package",
|
|
5
5
|
"author": "Automattic",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@automattic/format-currency": "1.0.1",
|
|
18
|
-
"@automattic/jetpack-boost-score-api": "^0.1.
|
|
18
|
+
"@automattic/jetpack-boost-score-api": "^0.1.44",
|
|
19
19
|
"@babel/runtime": "^7",
|
|
20
20
|
"@wordpress/browserslist-config": "6.9.0",
|
|
21
21
|
"@wordpress/components": "28.9.0",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"prop-types": "^15.7.2",
|
|
31
31
|
"qrcode.react": "3.1.0",
|
|
32
32
|
"react-slider": "2.0.5",
|
|
33
|
-
"social-logos": "^3.1.
|
|
33
|
+
"social-logos": "^3.1.10",
|
|
34
34
|
"uplot": "1.6.31",
|
|
35
35
|
"uplot-react": "1.1.4"
|
|
36
36
|
},
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"scripts": {
|
|
81
81
|
"build": "pnpm run compile-ts",
|
|
82
82
|
"compile-ts": "tsc --pretty",
|
|
83
|
-
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
|
|
83
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
84
|
+
"test-coverage": "pnpm run test --coverage"
|
|
84
85
|
}
|
|
85
86
|
}
|