diff --git a/.eslintrc b/.eslintrc
index fb3fe23e..73de372a 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -57,6 +57,7 @@ rules:
no-mixed-spaces-and-tabs: 2
no-multiple-empty-lines: [2, {max: 2}]
no-trailing-spaces: 2
+ object-curly-spacing: [1, "always"]
quotes: [2, "single", "avoid-escape"]
semi: 2
space-after-keywords: 2
@@ -84,6 +85,7 @@ rules:
no-unused-vars: [2, {"args": "none"}]
+ react/prop-types: 1
react/forbid-prop-types: 1
react/jsx-boolean-value: 1
react/jsx-closing-bracket-location: 1
@@ -109,6 +111,7 @@ rules:
react/no-string-refs: 1
react/no-unknown-property: 1
react/prefer-es6-class: 1
+ react/prefer-stateless-function: 1
react/react-in-jsx-scope: 1
react/require-extension: 1
react/self-closing-comp: 1
diff --git a/package.json b/package.json
index 659fb57f..68f1ff9f 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
"babel-runtime": "^6.5.0",
"eslint": "^1.10.3",
"eslint-loader": "^1.2.1",
- "eslint-plugin-react": "^3.16.1",
+ "eslint-plugin-react": "^5.1.1",
"exports-loader": "^0.6.3",
"express": "^4.13.4",
"file-loader": "^0.8.5",
@@ -39,8 +39,8 @@
"mocha": "^2.4.5",
"moment": "^2.11.2",
"normalizr": "^2.0.0",
- "react": "^0.14.7",
- "react-dom": "^0.14.7",
+ "react": "^15.1.0",
+ "react-dom": "^15.1.0",
"react-immutable-proptypes": "^1.6.0",
"react-lazy-load": "^3.0.3",
"react-pure-render": "^1.0.2",
@@ -54,7 +54,7 @@
"webpack": "^1.12.13",
"webpack-dev-server": "^1.14.1",
"webpack-postcss-tools": "^1.1.1",
- "whatwg-fetch": "^0.11.0"
+ "whatwg-fetch": "^1.0.0"
},
"dependencies": {
"commonmark": "^0.24.0",
diff --git a/src/backends/backend.js b/src/backends/backend.js
index 760c7845..ee639f0c 100644
--- a/src/backends/backend.js
+++ b/src/backends/backend.js
@@ -1,5 +1,5 @@
-import TestRepoBackend from './test-repo/Implementation';
-import GitHubBackend from './github/Implementation';
+import TestRepoBackend from './test-repo/implementation';
+import GitHubBackend from './github/implementation';
import { resolveFormat } from '../formats/formats';
class LocalStorageAuthStore {
diff --git a/src/components/ControlPane.js b/src/components/ControlPane.js
index 18d4fae5..a86ecaf5 100644
--- a/src/components/ControlPane.js
+++ b/src/components/ControlPane.js
@@ -1,4 +1,5 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
import Widgets from './Widgets';
export default class ControlPane extends React.Component {
@@ -6,7 +7,6 @@ export default class ControlPane extends React.Component {
const { entry, getMedia, onChange, onAddMedia, onRemoveMedia } = this.props;
const widget = Widgets[field.get('widget')] || Widgets._unknown;
return React.createElement(widget.Control, {
- key: field.get('name'),
field: field,
value: entry.getIn(['data', field.get('name')]),
onChange: (value) => onChange(entry.setIn(['data', field.get('name')], value)),
@@ -19,9 +19,17 @@ export default class ControlPane extends React.Component {
render() {
const { collection } = this.props;
if (!collection) { return null; }
-
return
-
Entry in {collection.get('label')}
-
{entry && entry.get('title')}
-
-
-
-
-
+export default function EntryEditor({ collection, entry, getMedia, onChange, onAddMedia, onRemoveMedia, onPersist }) {
+ return
+
Entry in {collection.get('label')}
+
{entry && entry.get('title')}
+
;
- }
+
+
+
+
;
}
const styles = {
@@ -37,3 +34,13 @@ const styles = {
width: '50%'
}
};
+
+EntryEditor.propTypes = {
+ collection: ImmutablePropTypes.map.isRequired,
+ entry: ImmutablePropTypes.map.isRequired,
+ getMedia: PropTypes.func.isRequired,
+ onAddMedia: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onPersist: PropTypes.func.isRequired,
+ onRemoveMedia: PropTypes.func.isRequired,
+};
diff --git a/src/components/EntryListing.js b/src/components/EntryListing.js
index ab1ffd40..311af02a 100644
--- a/src/components/EntryListing.js
+++ b/src/components/EntryListing.js
@@ -1,19 +1,21 @@
import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
import { Link } from 'react-router';
-export default class EntryListing extends React.Component {
- render() {
- const { collection, entries } = this.props;
- const name = collection.get('name');
-
- return
-
Listing entries!
- {entries.map((entry) => {
- const path = `/collections/${name}/entries/${entry.get('slug')}`;
- return
- {entry.getIn(['data', 'title'])}
- ;
- })}
- ;
- }
+export default function EntryListing({ collection, entries }) {
+ const name = collection.get('name');
+ return
+
Listing entries!
+ {entries.map((entry) => {
+ const path = `/collections/${name}/entries/${entry.get('slug')}`;
+ return
+ {entry.getIn(['data', 'title'])}
+ ;
+ })}
+ ;
}
+
+EntryListing.propTypes = {
+ collection: ImmutablePropTypes.map.isRequired,
+ entries: ImmutablePropTypes.list,
+};
diff --git a/src/components/PreviewPane.js b/src/components/PreviewPane.js
index bce60c92..2964ae93 100644
--- a/src/components/PreviewPane.js
+++ b/src/components/PreviewPane.js
@@ -1,4 +1,5 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
import Widgets from './Widgets';
export default class PreviewPane extends React.Component {
@@ -6,7 +7,6 @@ export default class PreviewPane extends React.Component {
const { entry, getMedia } = this.props;
const widget = Widgets[field.get('widget')] || Widgets._unknown;
return React.createElement(widget.Preview, {
- key: field.get('name'),
field: field,
value: entry.getIn(['data', field.get('name')]),
getMedia: getMedia,
@@ -17,8 +17,15 @@ export default class PreviewPane extends React.Component {
const { collection } = this.props;
if (!collection) { return null; }
+
return
- {collection.get('fields').map((field) =>
{this.previewFor(field)}
)}
+ {collection.get('fields').map((field) =>
{this.previewFor(field)}
)}
;
}
}
+
+PreviewPane.propTypes = {
+ collection: ImmutablePropTypes.map.isRequired,
+ entry: ImmutablePropTypes.map.isRequired,
+ getMedia: PropTypes.func.isRequired,
+};
diff --git a/src/components/Widgets/ImageControl.js b/src/components/Widgets/ImageControl.js
index dbf01c67..bd9757d7 100644
--- a/src/components/Widgets/ImageControl.js
+++ b/src/components/Widgets/ImageControl.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
import { truncateMiddle } from '../../lib/textHelper';
import MediaProxy from '../../valueObjects/MediaProxy';
@@ -98,3 +98,10 @@ const styles = {
display: 'none'
}
};
+
+ImageControl.propTypes = {
+ onAddMedia: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onRemoveMedia: PropTypes.func.isRequired,
+ value: PropTypes.node,
+};
diff --git a/src/components/Widgets/ImagePreview.js b/src/components/Widgets/ImagePreview.js
index 8e0a857e..1f45f84c 100644
--- a/src/components/Widgets/ImagePreview.js
+++ b/src/components/Widgets/ImagePreview.js
@@ -1,12 +1,12 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
-export default class ImagePreview extends React.Component {
- constructor(props) {
- super(props);
- }
-
- render() {
- const { value, getMedia } = this.props;
- return value ?
: null;
- }
+export default function ImagePreview({ value, getMedia }) {
+ return
+ {value ? : null}
+ ;
}
+
+ImagePreview.propTypes = {
+ getMedia: PropTypes.func.isRequired,
+ value: PropTypes.node,
+};
diff --git a/src/components/Widgets/MarkdownControl.js b/src/components/Widgets/MarkdownControl.js
index 0475a4e7..20210b9f 100644
--- a/src/components/Widgets/MarkdownControl.js
+++ b/src/components/Widgets/MarkdownControl.js
@@ -1,7 +1,7 @@
-import React from 'react';
-import {Editor, EditorState, RichUtils} from 'draft-js';
-import {stateToMarkdown} from 'draft-js-export-markdown';
-import {stateFromMarkdown} from 'draft-js-import-markdown';
+import React, { PropTypes } from 'react';
+import { Editor, EditorState, RichUtils } from 'draft-js';
+import { stateToMarkdown } from 'draft-js-export-markdown';
+import { stateFromMarkdown } from 'draft-js-import-markdown';
export default class MarkdownControl extends React.Component {
constructor(props) {
@@ -15,7 +15,7 @@ export default class MarkdownControl extends React.Component {
handleChange(editorState) {
const content = editorState.getCurrentContent();
- this.setState({editorState});
+ this.setState({ editorState });
this.props.onChange(stateToMarkdown(content));
}
@@ -29,7 +29,7 @@ export default class MarkdownControl extends React.Component {
}
render() {
- const {editorState} = this.state;
+ const { editorState } = this.state;
return (
);
}
}
+
+MarkdownControl.propTypes = {
+ onChange: PropTypes.func.isRequired,
+ value: PropTypes.node,
+};
diff --git a/src/components/Widgets/MarkdownPreview.js b/src/components/Widgets/MarkdownPreview.js
index 72f6c420..37fe084a 100644
--- a/src/components/Widgets/MarkdownPreview.js
+++ b/src/components/Widgets/MarkdownPreview.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
import CommonMark from 'commonmark';
import ReactRenderer from 'commonmark-react-renderer';
@@ -14,3 +14,7 @@ export default class MarkdownPreview extends React.Component {
return React.createElement.apply(React, ['div', {}].concat(renderer.render(ast)));
}
}
+
+MarkdownPreview.propTypes = {
+ value: PropTypes.node,
+};
diff --git a/src/components/Widgets/StringControl.js b/src/components/Widgets/StringControl.js
index fb4d1b3a..b159a6e9 100644
--- a/src/components/Widgets/StringControl.js
+++ b/src/components/Widgets/StringControl.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
export default class StringControl extends React.Component {
constructor(props) {
@@ -14,3 +14,8 @@ export default class StringControl extends React.Component {
return
;
}
}
+
+StringControl.propTypes = {
+ onChange: PropTypes.func.isRequired,
+ value: PropTypes.node,
+};
diff --git a/src/components/Widgets/StringPreview.js b/src/components/Widgets/StringPreview.js
index 95804209..972e068c 100644
--- a/src/components/Widgets/StringPreview.js
+++ b/src/components/Widgets/StringPreview.js
@@ -1,9 +1,9 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
-export default class StringPreview extends React.Component {
- render() {
- const { value } = this.props;
-
- return
{value};
- }
+export default function StringPreview({ value }) {
+ return
{value};
}
+
+StringPreview.propTypes = {
+ value: PropTypes.node,
+};
diff --git a/src/components/Widgets/UnknownControl.js b/src/components/Widgets/UnknownControl.js
index 4599523d..2787f34a 100644
--- a/src/components/Widgets/UnknownControl.js
+++ b/src/components/Widgets/UnknownControl.js
@@ -1,9 +1,10 @@
import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
-export default class UnknownControl extends React.Component {
- render() {
- const { field } = this.props;
-
- return
No control for widget '{field.get('widget')}'.
;
- }
+export default function UnknownControl({ field }) {
+ return
No control for widget '{field.get('widget')}'.
;
}
+
+UnknownControl.propTypes = {
+ field: ImmutablePropTypes.map,
+};
diff --git a/src/components/Widgets/UnknownPreview.js b/src/components/Widgets/UnknownPreview.js
index d3204959..10b08e26 100644
--- a/src/components/Widgets/UnknownPreview.js
+++ b/src/components/Widgets/UnknownPreview.js
@@ -1,9 +1,10 @@
import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
-export default class UnknownPreview extends React.Component {
- render() {
- const { field } = this.props;
-
- return
No preview for widget '{field.widget}'.
;
- }
+export default function UnknownPreview({ field }) {
+ return
No preview for widget '{field.get('widget')}'.
;
}
+
+UnknownPreview.propTypes = {
+ field: ImmutablePropTypes.map,
+};
diff --git a/src/containers/App.js b/src/containers/App.js
index 74808844..23277f24 100644
--- a/src/containers/App.js
+++ b/src/containers/App.js
@@ -76,7 +76,7 @@ function mapStateToProps(state) {
const { auth, config } = state;
const user = auth && auth.get('user');
- return {auth, config, user};
+ return { auth, config, user };
}
export default connect(mapStateToProps)(App);
diff --git a/src/containers/CollectionPage.js b/src/containers/CollectionPage.js
index 2311b8d3..8d8b12b4 100644
--- a/src/containers/CollectionPage.js
+++ b/src/containers/CollectionPage.js
@@ -1,4 +1,5 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { loadEntries } from '../actions/entries';
@@ -23,7 +24,6 @@ class DashboardPage extends React.Component {
render() {
const { collections, collection, entries } = this.props;
-
if (collections == null) {
return
No collections defined in your config.yml
;
}
@@ -44,13 +44,20 @@ class DashboardPage extends React.Component {
}
}
+DashboardPage.propTypes = {
+ collection: ImmutablePropTypes.map.isRequired,
+ collections: ImmutablePropTypes.orderedMap.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ entries: ImmutablePropTypes.list,
+};
+
function mapStateToProps(state, ownProps) {
const { collections } = state;
const { name, slug } = ownProps.params;
const collection = name ? collections.get(name) : collections.first();
const entries = selectEntries(state, collection.get('name'));
- return {slug, collection, collections, entries};
+ return { slug, collection, collections, entries };
}
export default connect(mapStateToProps)(DashboardPage);
diff --git a/src/containers/EntryPage.js b/src/containers/EntryPage.js
index aff9eb35..d03b744b 100644
--- a/src/containers/EntryPage.js
+++ b/src/containers/EntryPage.js
@@ -1,4 +1,5 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import {
loadEntry,
@@ -39,6 +40,7 @@ class EntryPage extends React.Component {
}
render() {
+
const {
entry, entryDraft, boundGetMedia, collection, changeDraft, addMedia, removeMedia
} = this.props;
@@ -60,13 +62,28 @@ class EntryPage extends React.Component {
}
}
+EntryPage.propTypes = {
+ addMedia: PropTypes.func.isRequired,
+ boundGetMedia: PropTypes.func.isRequired,
+ changeDraft: PropTypes.func.isRequired,
+ collection: ImmutablePropTypes.map.isRequired,
+ createDraft: PropTypes.func.isRequired,
+ discardDraft: PropTypes.func.isRequired,
+ entry: ImmutablePropTypes.map.isRequired,
+ entryDraft: ImmutablePropTypes.map.isRequired,
+ loadEntry: PropTypes.func.isRequired,
+ persist: PropTypes.func.isRequired,
+ removeMedia: PropTypes.func.isRequired,
+ slug: PropTypes.string.isRequired,
+};
+
function mapStateToProps(state, ownProps) {
const { collections, entryDraft } = state;
const collection = collections.get(ownProps.params.name);
const slug = ownProps.params.slug;
const entry = selectEntry(state, collection.get('name'), slug);
const boundGetMedia = getMedia.bind(null, state);
- return {collection, collections, entryDraft, boundGetMedia, slug, entry};
+ return { collection, collections, entryDraft, boundGetMedia, slug, entry };
}
export default connect(
diff --git a/src/reducers/auth.js b/src/reducers/auth.js
index 69fbdbff..04776859 100644
--- a/src/reducers/auth.js
+++ b/src/reducers/auth.js
@@ -4,12 +4,12 @@ import { AUTH_REQUEST, AUTH_SUCCESS, AUTH_FAILURE } from '../actions/auth';
const auth = (state = null, action) => {
switch (action.type) {
case AUTH_REQUEST:
- return Immutable.Map({isFetching: true});
+ return Immutable.Map({ isFetching: true });
case AUTH_SUCCESS:
- return Immutable.fromJS({user: action.payload});
+ return Immutable.fromJS({ user: action.payload });
case AUTH_FAILURE:
console.error(action.payload);
- return Immutable.Map({error: action.payload.toString()});
+ return Immutable.Map({ error: action.payload.toString() });
default:
return state;
}
diff --git a/src/reducers/config.js b/src/reducers/config.js
index 135f2af1..088b014c 100644
--- a/src/reducers/config.js
+++ b/src/reducers/config.js
@@ -4,11 +4,11 @@ import { CONFIG_REQUEST, CONFIG_SUCCESS, CONFIG_FAILURE } from '../actions/confi
const config = (state = null, action) => {
switch (action.type) {
case CONFIG_REQUEST:
- return Immutable.Map({isFetching: true});
+ return Immutable.Map({ isFetching: true });
case CONFIG_SUCCESS:
return Immutable.fromJS(action.payload);
case CONFIG_FAILURE:
- return Immutable.Map({error: action.payload.toString()});
+ return Immutable.Map({ error: action.payload.toString() });
default:
return state;
}
diff --git a/src/reducers/entries.js b/src/reducers/entries.js
index ae77ea05..70e73cd2 100644
--- a/src/reducers/entries.js
+++ b/src/reducers/entries.js
@@ -3,7 +3,7 @@ import {
ENTRY_REQUEST, ENTRY_SUCCESS, ENTRIES_REQUEST, ENTRIES_SUCCESS
} from '../actions/entries';
-const entries = (state = Map({entities: Map(), pages: Map()}), action) => {
+const entries = (state = Map({ entities: Map(), pages: Map() }), action) => {
switch (action.type) {
case ENTRY_REQUEST:
return state.setIn(['entities', `${action.payload.collection}.${action.payload.slug}`, 'isFetching'], true);
diff --git a/src/reducers/entryDraft.js b/src/reducers/entryDraft.js
index 67c999ff..801f3ea0 100644
--- a/src/reducers/entryDraft.js
+++ b/src/reducers/entryDraft.js
@@ -2,7 +2,7 @@ import { Map, List } from 'immutable';
import { DRAFT_CREATE, DRAFT_DISCARD, DRAFT_CHANGE } from '../actions/entries';
import { ADD_MEDIA, REMOVE_MEDIA } from '../actions/media';
-const initialState = Map({entry: Map(), mediaFiles: List()});
+const initialState = Map({ entry: Map(), mediaFiles: List() });
const entryDraft = (state = Map(), action) => {
switch (action.type) {
diff --git a/src/valueObjects/MediaProxy.js b/src/valueObjects/MediaProxy.js
index 13e6742c..7fd93ca0 100644
--- a/src/valueObjects/MediaProxy.js
+++ b/src/valueObjects/MediaProxy.js
@@ -9,6 +9,6 @@ export default function MediaProxy(value, file, uploaded = false) {
this.uploaded = uploaded;
this.uri = config.media_folder && !uploaded ? config.media_folder + '/' + value : value;
this.toString = function() {
- return this.uploaded ? this.uri : window.URL.createObjectURL(this.file, {oneTimeOnly: true});
+ return this.uploaded ? this.uri : window.URL.createObjectURL(this.file, { oneTimeOnly: true });
};
}