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
- {collection.get('fields').map((field) =>
{this.controlFor(field)}
)} + {collection.get('fields').map((field) =>
{this.controlFor(field)}
)}
; } } + +ControlPane.propTypes = { + collection: ImmutablePropTypes.map.isRequired, + entry: ImmutablePropTypes.map.isRequired, + getMedia: PropTypes.func.isRequired, + onAddMedia: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onRemoveMedia: PropTypes.func.isRequired, +}; diff --git a/src/components/EntryEditor.js b/src/components/EntryEditor.js index 5c344364..1ab0f003 100644 --- a/src/components/EntryEditor.js +++ b/src/components/EntryEditor.js @@ -1,32 +1,29 @@ -import React from 'react'; +import React, { PropTypes } from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; import ControlPane from './ControlPane'; import PreviewPane from './PreviewPane'; -export default class EntryEditor extends React.Component { - - render() { - const { collection, entry, getMedia, onChange, onAddMedia, onRemoveMedia, onPersist } = this.props; - 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 }); }; }