Skip to content

Commit f85c14e

Browse files
committed
Merge remote-tracking branch 'origin/master' into defaults
2 parents a0cb445 + cf7bf79 commit f85c14e

File tree

15 files changed

+587
-84
lines changed

15 files changed

+587
-84
lines changed

docs/_frontend/actions.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,10 @@ Async action for fetching data files.
150150

151151
Async action for fetching the requested data file.
152152

153-
### > `putDataFile(filename, data)`
153+
### > `putDataFile(filename, data, source="editor")`
154154

155-
Async action for creating/updating the requested data file. It validates the given filename and data before the PUT request.
155+
Async action for creating/updating the requested data file. It validates the given filename and data before the PUT request if the `source` equals `editor` or `null`.
156+
When the `source` is `gui`, the requested file is updated by parsing user input into valid YAML.
156157

157158
### > `deleteDataFile(filename)`
158159

docs/_frontend/containers.md

+16-8
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ Container for displaying header which includes title and homepage link.
3232

3333
## MetaFields
3434

35-
Main container for metafields. Generates list, object or plain inputs
36-
for front matters other than `title`, `body`, `path` and `draft`.
35+
Main container for metafields. Generates list, object or plain inputs for front
36+
matters other than `title`, `body`, `path` and `draft`. Doubles as a GUI to edit
37+
data files. The GUI will parse the input and write valid YAML to file,
38+
consequently removing any comments previously present, and also converting
39+
boolean to string literals i.e. `true` & `false` will be written as `'true'`
40+
and `'false'`.
3741

3842
### PropTypes
3943

@@ -48,7 +52,8 @@ for front matters other than `title`, `body`, `path` and `draft`.
4852
updateFieldKey: Function,
4953
updateFieldValue: Function,
5054
moveArrayItem: Function,
51-
convertField: Function
55+
convertField: Function,
56+
dataview: Boolean
5257
}
5358
```
5459

@@ -228,23 +233,26 @@ Container for DataFiles view. Lists data files.
228233

229234
## DataFileEdit
230235

231-
Container for editing a data file.
236+
Container for editing a data file. Supports editing via a raw text editor or a YAML editor GUI.
232237

233238
### PropTypes
234239

235240
```javascript
236241
{
237242
datafile: Object,
243+
isFetching: Boolean,
244+
updated: Boolean,
245+
datafileChanged: Boolean,
246+
fieldChanged: Boolean,
238247
fetchDataFile: Function,
239248
putDataFile: Function,
240249
deleteDataFile: Function,
241250
clearErrors: Function,
242251
onDataFileChanged: Function,
243-
isFetching: Boolean,
244-
updated: Boolean,
245-
datafileChanged: Boolean,
246252
errors: Array,
247-
params: Object
253+
params: Object,
254+
router: Object,
255+
route: Object
248256
}
249257
```
250258

spec/fixtures/site/_data/template-config.yml

+20-1
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,34 @@
1010
# These are used to personalize your new site. If you look in the HTML files, you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
1111
# You can create any custom variable you would like, and they will be accessible in the templates via {{ site.myvariable }}.
1212
title: Your awesome title
13+
path: /to/north-pole
14+
raw_content: "foo: bar\nbaz: boo"
1315
14-
description: > # this means to ignore newlines until "baseurl:"
16+
description: >- # this means to ignore newlines until "baseurl:"
1517
Write an awesome description for your new site here. You can edit this
1618
line in _config.yml. It will appear in your document head meta (for
1719
Google search results) and in your feed.xml site description.
1820
baseurl: "" # the subpath of your site, e.g. /blog
1921
url: "" # the base hostname & protocol for your site, e.g. http://example.com
2022
twitter_username: jekyllrb
2123
github_username: jekyll
24+
author:
25+
name: John Doe
26+
27+
web: http://www.example.com
28+
29+
complex_data:
30+
simple_key: value
31+
object_key:
32+
simple_key: value
33+
object_key:
34+
simple_key: value
35+
list_key:
36+
- value_1
37+
- value_2
38+
- value_3
39+
- value_4
40+
2241

2342
# Build settings
2443
markdown: kramdown

src/actions/datafiles.js

+16-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as ActionTypes from '../constants/actionTypes';
22
import _ from 'underscore';
33
import { validationError } from './utils';
44
import { get, put, del } from '../utils/fetch';
5+
import { toYAML } from '../utils/helpers';
56
import { validator } from '../utils/validation';
67
import {
78
getParserErrorMessage,
@@ -37,17 +38,25 @@ export function fetchDataFile(filename) {
3738
};
3839
}
3940

40-
export function putDataFile(filename, data) {
41+
export function putDataFile(filename, data, source = "editor") {
4142
return (dispatch, getState) => {
42-
const errors = validateDatafile(filename, data);
43-
if (errors.length) {
44-
return dispatch(validationError(errors));
43+
let payload = {};
44+
if (source == "editor") {
45+
const errors = validateDatafile(filename, data);
46+
if (errors.length) {
47+
return dispatch(validationError(errors));
48+
}
49+
// clear errors
50+
dispatch({type: ActionTypes.CLEAR_ERRORS});
51+
payload = { raw_content: data };
52+
} else if (source == "gui") {
53+
const metadata = getState().metadata.metadata;
54+
const yaml_string = toYAML(metadata);
55+
payload = { raw_content: yaml_string };
4556
}
46-
// clear errors
47-
dispatch({type: ActionTypes.CLEAR_ERRORS});
4857
return put(
4958
datafileAPIUrl(filename),
50-
JSON.stringify({ raw_content: data }),
59+
JSON.stringify(payload),
5160
{ type: ActionTypes.PUT_DATAFILE_SUCCESS, name: "file"},
5261
{ type: ActionTypes.PUT_DATAFILE_FAILURE, name: "error"},
5362
dispatch

src/components/Button.js

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export default class Button extends Component {
3838
case 'upload':
3939
label = labels.upload.label;
4040
break;
41+
case 'view-toggle':
42+
label = labels.viewToggle.label;
43+
triggeredLabel = labels.viewToggle.triggeredLabel;
44+
break;
4145
default:
4246
}
4347

src/components/metadata/MetaField.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class MetaField extends Component {
4646
const CurrentComponent = FieldTypes[type];
4747
return (
4848
<div ref="wrap" className="metafield">
49-
<div className="meta-key">
49+
<div className={`meta-key ${type}`}>
5050
<input ref="field_key"
5151
onBlur={() => this.handleKeyBlur()}
5252
defaultValue={fieldKey}

src/components/metadata/MetaObject.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class MetaObject extends Component {
3434
{items}
3535
<a onClick={() => addField(namePrefix)}
3636
className="add-field-object" title="Add new key/value pair">
37-
New key/value pair
37+
New key/value pair under <strong>{fieldKey}</strong>
3838
</a>
3939
</div>
4040
);

src/components/metadata/MetaObjectItem.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export class MetaObjectItem extends Component {
4545
const CurrentComponent = FieldTypes[type];
4646
return (
4747
<div ref="wrap" className="object-item-wrap">
48-
<div className="object-key">
48+
<div className={`object-key ${type}`}>
4949
<input ref="field_key"
5050
onBlur={(e) => this.handleKeyBlur(e)}
5151
defaultValue={fieldKey}

src/components/metadata/MetaSimple.js

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export class MetaSimple extends Component {
2222
this.handleCloseModal = this.handleCloseModal.bind(this);
2323
}
2424

25+
shouldComponentUpdate(nextProps) {
26+
return nextProps.fieldValue !== this.props.fieldValue;
27+
}
28+
2529
handleOpenModal () {
2630
this.setState({ showModal: true });
2731
}

src/constants/lang/en.js

+4
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,9 @@ export const labels = {
7373
},
7474
upload: {
7575
label: 'Upload files'
76+
},
77+
viewToggle: {
78+
label: 'Switch View to GUI Editor',
79+
triggeredLabel: 'Switch View to Raw Editor'
7680
}
7781
};

src/containers/MetaFields.js

+43-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { Component, PropTypes } from 'react';
2+
import classnames from 'classnames';
23
import { connect } from 'react-redux';
34
import { bindActionCreators } from 'redux';
45
import _ from 'underscore';
@@ -20,12 +21,23 @@ export class MetaFields extends Component {
2021
}
2122

2223
render() {
23-
const { metadata, addField, removeField, updateFieldKey,
24-
updateFieldValue, moveArrayItem, convertField, key_prefix } = this.props;
24+
const {
25+
metadata, addField, removeField, updateFieldKey, updateFieldValue, moveArrayItem,
26+
convertField, key_prefix, dataview
27+
} = this.props;
2528

26-
const { path, title, raw_content, ...rest } = metadata;
29+
let visibleKeys = metadata;
2730

28-
const metafields = _.map(rest, (field, key) => {
31+
if (!dataview) {
32+
visibleKeys = _.omit(visibleKeys, ['title', 'path', 'raw_content']);
33+
}
34+
35+
const metafieldsClass = classnames({
36+
'datafields': dataview,
37+
'metafields': !dataview
38+
});
39+
40+
const metafields = _.map(visibleKeys, (field, key) => {
2941
let type = "simple";
3042
if (_.isObject(field)) type = "object";
3143
if (_.isArray(field)) type = "array";
@@ -48,23 +60,33 @@ export class MetaFields extends Component {
4860
);
4961
});
5062

63+
const newWrapper = dataview ? (
64+
<div className="data-new">
65+
<a onClick={() => addField('metadata')}>
66+
<i className="fa fa-plus-circle" /> New data field
67+
</a>
68+
</div>
69+
) : (
70+
<div className="meta-new">
71+
<a onClick={() => addField('metadata')} className="tooltip">
72+
<i className="fa fa-plus-circle" /> New metadata field
73+
<span className="tooltip-text">
74+
Metadata will be stored as the <b>YAML front matter</b> within the document.
75+
</span>
76+
</a>
77+
<small className="tooltip pull-right">
78+
<i className="fa fa-info-circle" />Special Keys
79+
<span className="tooltip-text">
80+
You can use special keys like <b>date</b>, <b>file</b>, <b>image</b> for user-friendly functionalities.
81+
</span>
82+
</small>
83+
</div>
84+
);
85+
5186
return (
52-
<div className="metafields">
87+
<div className={metafieldsClass}>
5388
{metafields}
54-
<div className="meta-new">
55-
<a onClick={() => addField('metadata')} className="tooltip">
56-
<i className="fa fa-plus-circle" /> New metadata field
57-
<span className="tooltip-text">
58-
Metadata will be stored as the <b>YAML front matter</b> within the document.
59-
</span>
60-
</a>
61-
<small className="tooltip pull-right">
62-
<i className="fa fa-info-circle" />Special Keys
63-
<span className="tooltip-text">
64-
You can use special keys like <b>date</b>, <b>file</b>, <b>image</b> for user-friendly functionalities.
65-
</span>
66-
</small>
67-
</div>
89+
{newWrapper}
6890
</div>
6991
);
7092
}
@@ -80,7 +102,8 @@ MetaFields.propTypes = {
80102
updateFieldKey: PropTypes.func.isRequired,
81103
updateFieldValue: PropTypes.func.isRequired,
82104
moveArrayItem: PropTypes.func.isRequired,
83-
convertField: PropTypes.func.isRequired
105+
convertField: PropTypes.func.isRequired,
106+
dataview: PropTypes.bool
84107
};
85108

86109
const mapStateToProps = (state) => ({

src/containers/tests/metafields.spec.js

+27-8
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ import _ from 'underscore';
55

66
import { MetaFields } from '../MetaFields';
77
import MetaField from '../../components/metadata/MetaField';
8-
98
import { content } from './fixtures';
109

11-
function setup() {
10+
const defaultProps = {
11+
fields: content,
12+
metadata: content,
13+
key_prefix: ''
14+
};
15+
16+
function setup(props=defaultProps) {
1217
const actions = {
1318
storeContentFields: expect.createSpy(),
1419
addField: expect.createSpy(),
@@ -20,31 +25,45 @@ function setup() {
2025
};
2126

2227
const component = mount(
23-
<MetaFields
24-
fields={content}
25-
metadata={content}
26-
key_prefix=""
27-
{...actions} />
28+
<MetaFields {...props} {...actions} />
2829
);
2930

3031
return {
3132
component,
3233
addFieldButton: component.find('.meta-new a'),
34+
addDataFieldButton: component.find('.data-new a'),
3335
metafields: component.find(MetaField),
3436
actions: actions
3537
};
3638
}
3739

3840
describe('Containers::MetaFields', () => {
3941
it('should render MetaFields correctly', () => {
40-
const { component, metafields } = setup();
42+
let { component, metafields, addFieldButton, addDataFieldButton } = setup();
43+
44+
expect(component.find('div').first().hasClass('metafields')).toEqual(true);
45+
expect(addFieldButton.node).toExist();
46+
expect(addDataFieldButton.node).toNotExist();
47+
48+
const updatedSetup = setup(Object.assign({}, defaultProps, {
49+
dataview: true
50+
}));
51+
52+
expect(
53+
updatedSetup.component.find('div').first().hasClass('datafields')
54+
).toEqual(true);
55+
expect(updatedSetup.addFieldButton.node).toNotExist();
56+
expect(updatedSetup.addDataFieldButton.node).toExist();
57+
4158
expect(component.prop('key_prefix')).toBe('');
4259
expect(component.prop('metadata')).toEqual(content);
4360
});
61+
4462
it('should call storeContentFields before mount', () => {
4563
const { actions } = setup();
4664
expect(actions.storeContentFields).toHaveBeenCalled();
4765
});
66+
4867
it('should call addField when the button is clicked', () => {
4968
const { actions, addFieldButton } = setup();
5069
addFieldButton.simulate('click');

0 commit comments

Comments
 (0)