Skip to content

Commit 6b8a395

Browse files
ritz078wyattdanger
authored andcommitted
syntax highlighting in action logger (storybookjs#118)
* feat(highlight):added syntax highlighting in the action logger * fix lint error * fixed tests * added tests for highlight
1 parent 0a8d9bf commit 6b8a395

File tree

6 files changed

+108
-10
lines changed

6 files changed

+108
-10
lines changed

dist/client/ui/foldable.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ var _jsonStringifySafe = require('json-stringify-safe');
3636

3737
var _jsonStringifySafe2 = _interopRequireDefault(_jsonStringifySafe);
3838

39+
var _highlight = require('./highlight');
40+
41+
var _highlight2 = _interopRequireDefault(_highlight);
42+
3943
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
4044

4145
var folderStyle = {
@@ -124,11 +128,9 @@ var Foldable = function (_React$Component) {
124128
this.state.collapsed ? '►' : '▼'
125129
)
126130
),
127-
_react2.default.createElement(
128-
'div',
129-
{ ref: 'foldable-content', style: folderContentStyle },
130-
content
131-
)
131+
_react2.default.createElement('div', { ref: 'foldable-content', style: folderContentStyle,
132+
dangerouslySetInnerHTML: { __html: (0, _highlight2.default)(content) }
133+
})
132134
);
133135
}
134136
}]);

dist/client/ui/highlight.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.default = highlight;
7+
/**
8+
* Parses the JSON string and adds styling and class based on whether
9+
* a part is string, number, undefined, null or key. Also removes quotes
10+
* from keys.
11+
*
12+
* @param data A stringified JSON
13+
* @returns {string} String with styling
14+
*/
15+
function highlight(data) {
16+
var json = data.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
17+
var regex = /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g; // eslint-disable-line
18+
return json.replace(regex, function (match) {
19+
var className = 'number';
20+
var style = void 0;
21+
var result = match;
22+
if (/^"/.test(result)) {
23+
if (/:$/.test(result)) {
24+
className = 'key';
25+
style = 'color:#800080';
26+
result = match.replace(/"/g, '');
27+
} else {
28+
className = 'string';
29+
style = 'color:#a31515';
30+
}
31+
} else if (/true|false/.test(result)) {
32+
className = 'boolean';
33+
style = 'color:#066066';
34+
} else if (/null|undefined/.test(result)) {
35+
className = 'null';
36+
style = 'color:#a31515';
37+
}
38+
return '<span class="' + className + '" style="' + style + '">' + result + '</span>';
39+
});
40+
}

src/client/ui/__tests__/foldable.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe('<Foldable />', function () {
1212
args: 'things',
1313
};
1414

15-
const compactString = '{"name":"test action","args":"things"}';
15+
const compactString = '{name:"test action",args:"things"}';
1616

1717
const wrap = mount(<Foldable action={data} />);
1818
const content = wrap.ref('foldable-content');
@@ -25,7 +25,7 @@ describe('<Foldable />', function () {
2525
args: 'things',
2626
};
2727

28-
const fullString = '{\n "name": "test action",\n "args": "things"\n}';
28+
const fullString = '{\n name: "test action",\n args: "things"\n}';
2929

3030
const wrap = mount(<Foldable action={data} />);
3131
const toggle = wrap.ref('foldable-toggle');
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const { describe, it } = global;
2+
import { expect } from 'chai';
3+
import highlight from '../highlight';
4+
5+
describe('highlight', function () {
6+
it('should remove quotes from keys and add correct colour', function () {
7+
const data = '{ "name": "react-storybook" }';
8+
const expected = '{ <span class="key" style="color:#800080">name:</span> <span class="string" style="color:#a31515">"react-storybook"</span> }'; // eslint-disable-line
9+
expect(highlight(data)).to.equal(expected);
10+
});
11+
12+
it('should preserve new lines also', function () {
13+
const data = '{\n "name": "test action",\n "args": "things"\n}';
14+
const expected = '{\n ' +
15+
'<span class="key" style="color:#800080">name:</span> ' +
16+
'<span class="string" style="color:#a31515">"test action"</span>,\n ' +
17+
'<span class="key" style="color:#800080">args:</span> ' +
18+
'<span class="string" style="color:#a31515">"things"</span>\n}';
19+
expect(highlight(data)).to.equal(expected);
20+
});
21+
});

src/client/ui/foldable.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import stringify from 'json-stringify-safe';
3+
import highlight from './highlight';
34

45
const folderStyle = {
56
display: 'block',
@@ -70,9 +71,9 @@ class Foldable extends React.Component {
7071
{ this.state.collapsed ? '►' : '▼' }
7172
</span>
7273
</div>
73-
74-
<div ref="foldable-content" style={ folderContentStyle }>
75-
{ content }
74+
<div ref="foldable-content" style={ folderContentStyle }
75+
dangerouslySetInnerHTML={ { __html: highlight(content) } }
76+
>
7677
</div>
7778
</div>
7879
);

src/client/ui/highlight.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Parses the JSON string and adds styling and class based on whether
3+
* a part is string, number, undefined, null or key. Also removes quotes
4+
* from keys.
5+
*
6+
* @param data A stringified JSON
7+
* @returns {string} String with styling
8+
*/
9+
export default function highlight(data) {
10+
const json = data.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
11+
const regex = /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g; // eslint-disable-line
12+
return json.replace(regex, (match) => {
13+
let className = 'number';
14+
let style;
15+
let result = match;
16+
if (/^"/.test(result)) {
17+
if (/:$/.test(result)) {
18+
className = 'key';
19+
style = 'color:#800080';
20+
result = match.replace(/"/g, '');
21+
} else {
22+
className = 'string';
23+
style = 'color:#a31515';
24+
}
25+
} else if (/true|false/.test(result)) {
26+
className = 'boolean';
27+
style = 'color:#066066';
28+
} else if (/null|undefined/.test(result)) {
29+
className = 'null';
30+
style = 'color:#a31515';
31+
}
32+
return `<span class="${className}" style="${style}">${result}</span>`;
33+
});
34+
}

0 commit comments

Comments
 (0)