diff --git a/.eslintrc b/.eslintrc index e36381a4..7b493c6b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,16 +3,34 @@ "extends": [ "react-app", "plugin:import/errors", - "plugin:import/warnings" + "plugin:import/warnings", + "plugin:flowtype/recommended" ], "plugins": [ - "prettier" + "prettier", + "flowtype", ], "rules": { "import/order": "warn", // Prettier automatically uses the least amount of parens possible, so this // does more harm than good. "no-mixed-operators": "off", + // Flow + "flowtype/require-valid-file-annotation": [ + 2, + "always", + { + "annotationStyle": "line" + } + ], + "flowtype/space-after-type-colon": [ + 2, + "always" + ], + "flowtype/space-before-type-colon": [ + 2, + "never" + ], // Enforce that code is formatted with prettier. "prettier/prettier": [ "error", @@ -23,7 +41,10 @@ ] }, "settings": { - "import/resolver": "webpack" + "import/resolver": "webpack", + "flowtype": { + "onlyFilesWithFlowAnnotation": false + } }, "env": { "jest": true @@ -33,6 +54,7 @@ "SLACK_KEY": true, "SLACK_REDIRECT_URI": true, "DEPLOYMENT": true, + "BASE_URL": true, "afterAll": true } } \ No newline at end of file diff --git a/.flowconfig b/.flowconfig index 131f2c26..612463ee 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,7 +1,11 @@ +[include] +.*/frontend/.* + [ignore] .*/node_modules/styled-components/.* .*/node_modules/react-side-effect/.* .*/node_modules/fbjs/.* +.*/node_modules/config-chain/.* [libs] @@ -12,8 +16,11 @@ module.system.node.resolve_dirname=node_modules module.system.node.resolve_dirname=frontend module.name_mapper='^\(.*\)\.s?css$' -> 'empty/object' +module.name_mapper='^\(.*\)\.md$' -> 'empty/object' module.file_ext=.js module.file_ext=.scss +module.file_ext=.md +module.file_ext=.json esproposal.decorators=ignore esproposal.class_static_fields=enable diff --git a/.githooks/pre-commit/flow.sh b/.githooks/pre-commit/flow.sh deleted file mode 100644 index de2b7a79..00000000 --- a/.githooks/pre-commit/flow.sh +++ /dev/null @@ -1 +0,0 @@ -yarn flow diff --git a/flow-typed/globals.js b/flow-typed/globals.js new file mode 100644 index 00000000..7d9d099d --- /dev/null +++ b/flow-typed/globals.js @@ -0,0 +1,6 @@ +// @flow +declare var __DEV__: string; +declare var SLACK_REDIRECT_URI: string; +declare var SLACK_KEY: string; +declare var BASE_URL: string; +declare var DEPLOYMENT: string; diff --git a/frontend/components/Alert/Alert.js b/frontend/components/Alert/Alert.js index 203afd11..83022756 100644 --- a/frontend/components/Alert/Alert.js +++ b/frontend/components/Alert/Alert.js @@ -1,3 +1,4 @@ +// @flow import React, { PropTypes } from 'react'; import { Flex } from 'reflexbox'; import classNames from 'classnames/bind'; diff --git a/frontend/components/Alert/index.js b/frontend/components/Alert/index.js index 08bde8f9..7c95883c 100644 --- a/frontend/components/Alert/index.js +++ b/frontend/components/Alert/index.js @@ -1,2 +1,3 @@ +// @flow import Alert from './Alert'; export default Alert; diff --git a/frontend/components/AtlasPreview/AtlasPreview.js b/frontend/components/AtlasPreview/AtlasPreview.js index a6d70922..e760e24f 100644 --- a/frontend/components/AtlasPreview/AtlasPreview.js +++ b/frontend/components/AtlasPreview/AtlasPreview.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { observer } from 'mobx-react'; import Link from 'react-router/lib/Link'; diff --git a/frontend/components/AtlasPreview/components/DocumentLink/DocumentLink.js b/frontend/components/AtlasPreview/components/DocumentLink/DocumentLink.js index 59a849ac..e83ef42b 100644 --- a/frontend/components/AtlasPreview/components/DocumentLink/DocumentLink.js +++ b/frontend/components/AtlasPreview/components/DocumentLink/DocumentLink.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { observer } from 'mobx-react'; diff --git a/frontend/components/AtlasPreview/components/DocumentLink/index.js b/frontend/components/AtlasPreview/components/DocumentLink/index.js index bc6695bf..9d0d0087 100644 --- a/frontend/components/AtlasPreview/components/DocumentLink/index.js +++ b/frontend/components/AtlasPreview/components/DocumentLink/index.js @@ -1,2 +1,3 @@ +// @flow import DocumentLink from './DocumentLink'; export default DocumentLink; diff --git a/frontend/components/AtlasPreview/index.js b/frontend/components/AtlasPreview/index.js index f68aa0ae..3b6f2ba1 100644 --- a/frontend/components/AtlasPreview/index.js +++ b/frontend/components/AtlasPreview/index.js @@ -1,2 +1,3 @@ +// @flow import AtlasPreview from './AtlasPreview'; export default AtlasPreview; diff --git a/frontend/components/AtlasPreviewLoading/AtlasPreviewLoading.js b/frontend/components/AtlasPreviewLoading/AtlasPreviewLoading.js index ac8fb67b..43ddae04 100644 --- a/frontend/components/AtlasPreviewLoading/AtlasPreviewLoading.js +++ b/frontend/components/AtlasPreviewLoading/AtlasPreviewLoading.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; import styled, { keyframes } from 'styled-components'; diff --git a/frontend/components/AtlasPreviewLoading/index.js b/frontend/components/AtlasPreviewLoading/index.js index f9d8e078..bbf182bc 100644 --- a/frontend/components/AtlasPreviewLoading/index.js +++ b/frontend/components/AtlasPreviewLoading/index.js @@ -1,2 +1,3 @@ +// @flow import AtlasPreviewLoading from './AtlasPreviewLoading'; export default AtlasPreviewLoading; diff --git a/frontend/components/Button/Button.js b/frontend/components/Button/Button.js deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/components/Button/Button.scss b/frontend/components/Button/Button.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/components/Button/index.js b/frontend/components/Button/index.js deleted file mode 100644 index 4ae846f7..00000000 --- a/frontend/components/Button/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import Button from './Button'; -export default Button; diff --git a/frontend/components/CenteredContent/CenteredContent.js b/frontend/components/CenteredContent/CenteredContent.js index 3c32875d..1661cbeb 100644 --- a/frontend/components/CenteredContent/CenteredContent.js +++ b/frontend/components/CenteredContent/CenteredContent.js @@ -3,9 +3,9 @@ import React from 'react'; import styles from './CenteredContent.scss'; type Props = { - children: any, - style: Object, - maxWidth: string, + children?: React.Element, + style?: Object, + maxWidth?: string, }; const CenteredContent = (props: Props) => { diff --git a/frontend/components/CenteredContent/index.js b/frontend/components/CenteredContent/index.js index 9524b627..a9c4ec1d 100644 --- a/frontend/components/CenteredContent/index.js +++ b/frontend/components/CenteredContent/index.js @@ -1,2 +1,3 @@ +// @flow import CenteredContent from './CenteredContent'; export default CenteredContent; diff --git a/frontend/components/Divider/Divider.js b/frontend/components/Divider/Divider.js index 95a56206..0b036b16 100644 --- a/frontend/components/Divider/Divider.js +++ b/frontend/components/Divider/Divider.js @@ -1,8 +1,9 @@ +// @flow import React from 'react'; import styles from './Divider.scss'; -const Divider = props => { +const Divider = () => { return
; }; diff --git a/frontend/components/Divider/index.js b/frontend/components/Divider/index.js index 26c6df9e..6d4a0889 100644 --- a/frontend/components/Divider/index.js +++ b/frontend/components/Divider/index.js @@ -1,2 +1,3 @@ +// @flow import Divider from './Divider'; export default Divider; diff --git a/frontend/components/Document/components/DocumentHtml/DocumentHtml.js b/frontend/components/Document/components/DocumentHtml/DocumentHtml.js index 4b872f8e..5fe4abb0 100644 --- a/frontend/components/Document/components/DocumentHtml/DocumentHtml.js +++ b/frontend/components/Document/components/DocumentHtml/DocumentHtml.js @@ -1,3 +1,4 @@ +// @flow import React, { PropTypes } from 'react'; import ReactDOM from 'react-dom'; import { observer } from 'mobx-react'; @@ -18,9 +19,11 @@ import styles from './DocumentHtml.scss'; }; setExternalLinks = () => { + // $FlowFixMe const links = ReactDOM.findDOMNode(this).querySelectorAll('a'); links.forEach(link => { if (link.hostname !== window.location.hostname) { + // $FlowFixMe link.target = '_blank'; // eslint-disable-line no-param-reassign } }); diff --git a/frontend/components/Document/components/DocumentHtml/index.js b/frontend/components/Document/components/DocumentHtml/index.js index 8c1e31ed..c8cf4992 100644 --- a/frontend/components/Document/components/DocumentHtml/index.js +++ b/frontend/components/Document/components/DocumentHtml/index.js @@ -1,2 +1,3 @@ +// @flow import DocumentHtml from './DocumentHtml'; export default DocumentHtml; diff --git a/frontend/components/Document/index.js b/frontend/components/Document/index.js index 29ee3ad7..1fb24bb8 100644 --- a/frontend/components/Document/index.js +++ b/frontend/components/Document/index.js @@ -1,3 +1,4 @@ +// @flow import Document from './Document'; import DocumentHtml from './components/DocumentHtml'; diff --git a/frontend/components/DocumentList/index.js b/frontend/components/DocumentList/index.js index afe66f55..35fb670b 100644 --- a/frontend/components/DocumentList/index.js +++ b/frontend/components/DocumentList/index.js @@ -1,2 +1,3 @@ +// @flow import DocumentList from './DocumentList'; export default DocumentList; diff --git a/frontend/components/DocumentPreview/DocumentPreview.js b/frontend/components/DocumentPreview/DocumentPreview.js index 96a309c3..0e0777da 100644 --- a/frontend/components/DocumentPreview/DocumentPreview.js +++ b/frontend/components/DocumentPreview/DocumentPreview.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { toJS } from 'mobx'; import { Link } from 'react-router'; diff --git a/frontend/components/DocumentPreview/index.js b/frontend/components/DocumentPreview/index.js index c5569f97..a5a2b1af 100644 --- a/frontend/components/DocumentPreview/index.js +++ b/frontend/components/DocumentPreview/index.js @@ -1,2 +1,3 @@ +// @flow import DocumentPreview from './DocumentPreview'; export default DocumentPreview; diff --git a/frontend/components/DropdownMenu/DropdownMenu.js b/frontend/components/DropdownMenu/DropdownMenu.js index 0726e468..3455ab3c 100644 --- a/frontend/components/DropdownMenu/DropdownMenu.js +++ b/frontend/components/DropdownMenu/DropdownMenu.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { browserHistory } from 'react-router'; diff --git a/frontend/components/DropdownMenu/components/MoreIcon/MoreIcon.js b/frontend/components/DropdownMenu/components/MoreIcon/MoreIcon.js index 18d271d3..29c7af45 100644 --- a/frontend/components/DropdownMenu/components/MoreIcon/MoreIcon.js +++ b/frontend/components/DropdownMenu/components/MoreIcon/MoreIcon.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import styles from './MoreIcon.scss'; diff --git a/frontend/components/DropdownMenu/components/MoreIcon/index.js b/frontend/components/DropdownMenu/components/MoreIcon/index.js index 4f33b485..a4d1358e 100644 --- a/frontend/components/DropdownMenu/components/MoreIcon/index.js +++ b/frontend/components/DropdownMenu/components/MoreIcon/index.js @@ -1,2 +1,3 @@ +// @flow import MoreIcon from './MoreIcon'; export default MoreIcon; diff --git a/frontend/components/DropdownMenu/index.js b/frontend/components/DropdownMenu/index.js index ff7e3b3c..8197d76c 100644 --- a/frontend/components/DropdownMenu/index.js +++ b/frontend/components/DropdownMenu/index.js @@ -1,3 +1,4 @@ +// @flow import DropdownMenu, { MenuItem } from './DropdownMenu'; import MoreIcon from './components/MoreIcon'; export default DropdownMenu; diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index afa7c8d8..95caadec 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { browserHistory, Link } from 'react-router'; import Helmet from 'react-helmet'; @@ -10,24 +11,25 @@ import { Flex } from 'reflexbox'; import DropdownMenu, { MenuItem } from 'components/DropdownMenu'; import LoadingIndicator from 'components/LoadingIndicator'; +import UserStore from 'stores/UserStore'; import styles from './Layout.scss'; import classNames from 'classnames/bind'; const cx = classNames.bind(styles); -@inject('user') -@observer -class Layout extends React.Component { - static propTypes = { - children: React.PropTypes.node, - actions: React.PropTypes.node, - title: React.PropTypes.node, - titleText: React.PropTypes.node, - loading: React.PropTypes.bool, - user: React.PropTypes.object.isRequired, - search: React.PropTypes.bool, - notifications: React.PropTypes.node, - }; +type Props = { + children?: ?React.Element, + actions?: ?React.Element, + title?: ?React.Element, + titleText?: string, + loading?: boolean, + user: UserStore, + search: ?boolean, + notifications?: React.Element, +}; + +@observer class Layout extends React.Component { + props: Props; static defaultProps = { search: true, @@ -114,4 +116,4 @@ const Avatar = styled.img` border-radius: 50%; `; -export default Layout; +export default inject('user')(Layout); diff --git a/frontend/components/Layout/components/HeaderAction/HeaderAction.js b/frontend/components/Layout/components/HeaderAction/HeaderAction.js index 426e66c9..c49806c4 100644 --- a/frontend/components/Layout/components/HeaderAction/HeaderAction.js +++ b/frontend/components/Layout/components/HeaderAction/HeaderAction.js @@ -1,8 +1,11 @@ +// @flow import React from 'react'; import styles from './HeaderAction.scss'; -const HeaderAction = props => { +type Props = { onClick?: ?Function, children?: ?React.Element }; + +const HeaderAction = (props: Props) => { return (
{props.children} @@ -10,8 +13,4 @@ const HeaderAction = props => { ); }; -HeaderAction.propTypes = { - onClick: React.PropTypes.func, -}; - export default HeaderAction; diff --git a/frontend/components/Layout/components/HeaderAction/index.js b/frontend/components/Layout/components/HeaderAction/index.js index c25a3530..e231db11 100644 --- a/frontend/components/Layout/components/HeaderAction/index.js +++ b/frontend/components/Layout/components/HeaderAction/index.js @@ -1,2 +1,3 @@ +// @flow import HeaderAction from './HeaderAction'; export default HeaderAction; diff --git a/frontend/components/Layout/components/SaveAction/SaveAction.js b/frontend/components/Layout/components/SaveAction/SaveAction.js index 306ed37b..b37cce08 100644 --- a/frontend/components/Layout/components/SaveAction/SaveAction.js +++ b/frontend/components/Layout/components/SaveAction/SaveAction.js @@ -1,14 +1,17 @@ +// @flow import React from 'react'; import { observer } from 'mobx-react'; -@observer class SaveAction extends React.Component { - static propTypes = { - onClick: React.PropTypes.func.isRequired, - disabled: React.PropTypes.bool, - isNew: React.PropTypes.bool, - }; +type Props = { + onClick: Function, + disabled?: boolean, + isNew?: boolean, +}; - onClick = event => { +@observer class SaveAction extends React.Component { + props: Props; + + onClick = (event: MouseEvent) => { if (this.props.disabled) return; event.preventDefault(); diff --git a/frontend/components/Layout/components/SaveAction/index.js b/frontend/components/Layout/components/SaveAction/index.js index a3a71d8e..524f5da7 100644 --- a/frontend/components/Layout/components/SaveAction/index.js +++ b/frontend/components/Layout/components/SaveAction/index.js @@ -1,2 +1,3 @@ +// @flow import SaveAction from './SaveAction'; export default SaveAction; diff --git a/frontend/components/Layout/components/Title/Title.js b/frontend/components/Layout/components/Title/Title.js index 9b3fcb21..f85548e4 100644 --- a/frontend/components/Layout/components/Title/Title.js +++ b/frontend/components/Layout/components/Title/Title.js @@ -4,9 +4,9 @@ import _ from 'lodash'; import styled from 'styled-components'; type Props = { - children: string, + content: string, truncate?: number, - placeholder: string, + placeholder?: ?string, }; class Title extends React.Component { @@ -15,9 +15,9 @@ class Title extends React.Component { render() { let title; if (this.props.truncate) { - title = _.truncate(this.props.children, this.props.truncate); + title = _.truncate(this.props.content, this.props.truncate); } else { - title = this.props.children; + title = this.props.content; } let usePlaceholder; @@ -29,7 +29,7 @@ class Title extends React.Component { return ( {title &&  / } - + {title} diff --git a/frontend/components/Layout/index.js b/frontend/components/Layout/index.js index 8abf597f..7956f3e9 100644 --- a/frontend/components/Layout/index.js +++ b/frontend/components/Layout/index.js @@ -1,3 +1,4 @@ +// @flow import Layout from './Layout'; import Title from './components/Title'; import HeaderAction from './components/HeaderAction'; diff --git a/frontend/components/LoadingIndicator/LoadingIndicator.js b/frontend/components/LoadingIndicator/LoadingIndicator.js index 5413887c..a82a85ab 100644 --- a/frontend/components/LoadingIndicator/LoadingIndicator.js +++ b/frontend/components/LoadingIndicator/LoadingIndicator.js @@ -1,8 +1,9 @@ +// @flow import React from 'react'; import styles from './LoadingIndicator.scss'; -const LoadingIndicator = props => { +const LoadingIndicator = () => { return (
diff --git a/frontend/components/LoadingIndicator/index.js b/frontend/components/LoadingIndicator/index.js index 42f30852..df904e0a 100644 --- a/frontend/components/LoadingIndicator/index.js +++ b/frontend/components/LoadingIndicator/index.js @@ -1,2 +1,3 @@ +// @flow import LoadingIndicator from './LoadingIndicator'; export default LoadingIndicator; diff --git a/frontend/components/MarkdownEditor/MarkdownEditor.js b/frontend/components/MarkdownEditor/MarkdownEditor.js index 2927377a..2b670eba 100644 --- a/frontend/components/MarkdownEditor/MarkdownEditor.js +++ b/frontend/components/MarkdownEditor/MarkdownEditor.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { observer } from 'mobx-react'; import Codemirror from 'react-codemirror'; @@ -28,13 +29,13 @@ import { client } from 'utils/ApiClient'; toggleUploadingIndicator: React.PropTypes.func, }; - onChange = newText => { + onChange = (newText: string) => { if (newText !== this.props.text) { this.props.onChange(newText); } }; - onDropAccepted = files => { + onDropAccepted = (files: Object[]) => { const file = files[0]; const editor = this.getEditorInstance(); @@ -62,6 +63,7 @@ import { client } from 'utils/ApiClient'; filename: file.name, }) .then(response => { + // $FlowFixMe need to augment ApiClient const data = response.data; // Upload using FormData API const formData = new FormData(); @@ -77,7 +79,7 @@ import { client } from 'utils/ApiClient'; } fetch(data.uploadUrl, { - method: 'post', + method: 'POST', body: formData, }) .then(_s3Response => { diff --git a/frontend/components/MarkdownEditor/components/ClickablePadding/ClickablePadding.js b/frontend/components/MarkdownEditor/components/ClickablePadding/ClickablePadding.js index 4c95410e..d4464f51 100644 --- a/frontend/components/MarkdownEditor/components/ClickablePadding/ClickablePadding.js +++ b/frontend/components/MarkdownEditor/components/ClickablePadding/ClickablePadding.js @@ -1,8 +1,9 @@ +// @flow import React from 'react'; import styles from './ClickablePadding.scss'; -const ClickablePadding = props => { +const ClickablePadding = (props: { onClick: Function }) => { return
 
; }; diff --git a/frontend/components/MarkdownEditor/components/ClickablePadding/index.js b/frontend/components/MarkdownEditor/components/ClickablePadding/index.js index 28973be3..76c2ddad 100644 --- a/frontend/components/MarkdownEditor/components/ClickablePadding/index.js +++ b/frontend/components/MarkdownEditor/components/ClickablePadding/index.js @@ -1,2 +1,3 @@ +// @flow import ClickablePadding from './ClickablePadding'; export default ClickablePadding; diff --git a/frontend/components/MarkdownEditor/index.js b/frontend/components/MarkdownEditor/index.js index b4ab100e..cdd528e4 100644 --- a/frontend/components/MarkdownEditor/index.js +++ b/frontend/components/MarkdownEditor/index.js @@ -1,2 +1,3 @@ +// @flow import MarkdownEditor from './MarkdownEditor'; export default MarkdownEditor; diff --git a/frontend/components/PublishingInfo/PublishingInfo.js b/frontend/components/PublishingInfo/PublishingInfo.js index 865aff6c..ddf87e17 100644 --- a/frontend/components/PublishingInfo/PublishingInfo.js +++ b/frontend/components/PublishingInfo/PublishingInfo.js @@ -1,3 +1,4 @@ +// @flow import React, { PropTypes } from 'react'; import moment from 'moment'; import styled from 'styled-components'; diff --git a/frontend/components/PublishingInfo/index.js b/frontend/components/PublishingInfo/index.js index bf607ecb..fb57b45f 100644 --- a/frontend/components/PublishingInfo/index.js +++ b/frontend/components/PublishingInfo/index.js @@ -1,2 +1,3 @@ +// @flow import PublishingInfo from './PublishingInfo'; export default PublishingInfo; diff --git a/frontend/components/SlackAuthLink/SlackAuthLink.js b/frontend/components/SlackAuthLink/SlackAuthLink.js index 0fe8e6b8..69348e2b 100644 --- a/frontend/components/SlackAuthLink/SlackAuthLink.js +++ b/frontend/components/SlackAuthLink/SlackAuthLink.js @@ -3,15 +3,15 @@ import React from 'react'; import { observer, inject } from 'mobx-react'; import UserStore from 'stores/UserStore'; -@inject('user') -@observer -class SlackAuthLink extends React.Component { - props: { - children: any, - scopes: Array, - user: UserStore, - redirectUri: string, - }; +type Props = { + children: React.Element, + scopes?: Array, + user: UserStore, + redirectUri: string, +}; + +@observer class SlackAuthLink extends React.Component { + props: Props; static defaultProps = { scopes: [ @@ -25,10 +25,8 @@ class SlackAuthLink extends React.Component { slackUrl = () => { const baseUrl = 'https://slack.com/oauth/authorize'; const params = { - // $FlowIssue global variable client_id: SLACK_KEY, - scope: this.props.scopes.join(' '), - // $FlowIssue global variable + scope: this.props.scopes ? this.props.scopes.join(' ') : '', redirect_uri: this.props.redirectUri || SLACK_REDIRECT_URI, state: this.props.user.getOauthState(), }; @@ -47,4 +45,4 @@ class SlackAuthLink extends React.Component { } } -export default SlackAuthLink; +export default inject('user')(SlackAuthLink); diff --git a/frontend/components/SlackAuthLink/index.js b/frontend/components/SlackAuthLink/index.js index 5dd283ab..5984174e 100644 --- a/frontend/components/SlackAuthLink/index.js +++ b/frontend/components/SlackAuthLink/index.js @@ -1,2 +1,3 @@ +// @flow import SlackAuthLink from './SlackAuthLink'; export default SlackAuthLink; diff --git a/frontend/components/Tree/Node.js b/frontend/components/Tree/Node.js index 5eacd6f7..d8fe634e 100644 --- a/frontend/components/Tree/Node.js +++ b/frontend/components/Tree/Node.js @@ -1,3 +1,4 @@ +/* eslint-disable */ import React from 'react'; import history from 'utils/History'; diff --git a/frontend/components/Tree/Tree.js b/frontend/components/Tree/Tree.js index c0cd0be2..7296da9a 100644 --- a/frontend/components/Tree/Tree.js +++ b/frontend/components/Tree/Tree.js @@ -1,3 +1,4 @@ +/* eslint-disable */ const Tree = require('js-tree'); const proto = Tree.prototype; diff --git a/frontend/components/Tree/UiTree.js b/frontend/components/Tree/UiTree.js index 40aedc69..c0c97bc0 100644 --- a/frontend/components/Tree/UiTree.js +++ b/frontend/components/Tree/UiTree.js @@ -1,3 +1,4 @@ +/* eslint-disable */ const React = require('react'); const Tree = require('./Tree'); const Node = require('./Node'); diff --git a/frontend/components/Tree/index.js b/frontend/components/Tree/index.js index 2a932bd6..fe1ac2ee 100644 --- a/frontend/components/Tree/index.js +++ b/frontend/components/Tree/index.js @@ -1,2 +1,3 @@ +// @flow import UiTree from './UiTree'; export default UiTree; diff --git a/frontend/index.js b/frontend/index.js index 975ddf00..5da45b0f 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'mobx-react'; @@ -115,7 +116,7 @@ render( - {__DEV__ && } + {DevTools && }
, document.getElementById('root') ); diff --git a/frontend/scenes/Application.js b/frontend/scenes/Application.js index 6715daf2..92210e37 100644 --- a/frontend/scenes/Application.js +++ b/frontend/scenes/Application.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { observer } from 'mobx-react'; import Helmet from 'react-helmet'; diff --git a/frontend/scenes/Atlas/Atlas.js b/frontend/scenes/Atlas/Atlas.js index 8c8579a3..ee294095 100644 --- a/frontend/scenes/Atlas/Atlas.js +++ b/frontend/scenes/Atlas/Atlas.js @@ -1,10 +1,12 @@ -import React, { PropTypes } from 'react'; +// @flow +import React from 'react'; import { observer } from 'mobx-react'; import { browserHistory } from 'react-router'; import keydown from 'react-keydown'; import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; import _ from 'lodash'; +// TODO move here argh import store from './AtlasStore'; import Layout, { Title } from 'components/Layout'; @@ -17,12 +19,15 @@ import { Flex } from 'reflexbox'; import styles from './Atlas.scss'; +type Props = { + params: Object, + keydown: Object, +}; + @keydown(['c']) @observer class Atlas extends React.Component { - static propTypes = { - params: PropTypes.object.isRequired, - }; + props: Props; componentDidMount = () => { const { id } = this.props.params; @@ -34,7 +39,7 @@ class Atlas extends React.Component { }); }; - componentWillReceiveProps = nextProps => { + componentWillReceiveProps = (nextProps: Props) => { const key = nextProps.keydown.event; if (key) { if (key.key === 'c') { @@ -43,9 +48,9 @@ class Atlas extends React.Component { } }; - onCreate = event => { + onCreate = (event: Event) => { if (event) event.preventDefault(); - browserHistory.push(`${store.collection.url}/new`); + store.collection && browserHistory.push(`${store.collection.url}/new`); }; render() { @@ -65,7 +70,7 @@ class Atlas extends React.Component { ); - title = {collection.name}; + title = ; titleText = collection.name; } @@ -81,21 +86,22 @@ class Atlas extends React.Component { > {store.isFetching ? <AtlasPreviewLoading /> - : <div className={styles.container}> - <div className={styles.atlasDetails}> - <h2>{collection.name}</h2> - <blockquote> - {collection.description} - </blockquote> - </div> + : collection && + <div className={styles.container}> + <div className={styles.atlasDetails}> + <h2>{collection.name}</h2> + <blockquote> + {collection.description} + </blockquote> + </div> - <Divider /> + <Divider /> - <DocumentList - documents={collection.recentDocuments} - preview - /> - </div>} + <DocumentList + documents={collection.recentDocuments} + preview + /> + </div>} </ReactCSSTransitionGroup> </CenteredContent> </Layout> diff --git a/frontend/scenes/Atlas/AtlasStore.js b/frontend/scenes/Atlas/AtlasStore.js index 8f7d1e49..9d6067af 100644 --- a/frontend/scenes/Atlas/AtlasStore.js +++ b/frontend/scenes/Atlas/AtlasStore.js @@ -1,19 +1,29 @@ -import { observable, action } from 'mobx'; +// @flow +import { observable, action, computed } from 'mobx'; +import invariant from 'invariant'; import { client } from 'utils/ApiClient'; +import type { Collection } from 'types'; const store = new class AtlasStore { - @observable collection; + @observable collection: ?(Collection & { recentDocuments?: Object[] }); @observable isFetching = true; + /* Computed */ + + @computed get isLoaded(): boolean { + return !this.isFetching && !!this.collection; + } + /* Actions */ - @action fetchCollection = async (id, successCallback) => { + @action fetchCollection = async (id: string, successCallback: Function) => { this.isFetching = true; this.collection = null; try { const res = await client.get('/collections.info', { id }); + invariant(res && res.data, 'Data should be available'); const { data } = res; this.collection = data; successCallback(data); diff --git a/frontend/scenes/Atlas/index.js b/frontend/scenes/Atlas/index.js index dd00ddcc..f28d358e 100644 --- a/frontend/scenes/Atlas/index.js +++ b/frontend/scenes/Atlas/index.js @@ -1,2 +1,3 @@ +// @flow import Atlas from './Atlas'; export default Atlas; diff --git a/frontend/scenes/Dashboard/DashboardStore.js b/frontend/scenes/Dashboard/DashboardStore.js index 474816a4..2f57b31e 100644 --- a/frontend/scenes/Dashboard/DashboardStore.js +++ b/frontend/scenes/Dashboard/DashboardStore.js @@ -1,5 +1,6 @@ // @flow import { observable, action, runInAction } from 'mobx'; +import invariant from 'invariant'; import { client } from 'utils/ApiClient'; import type { Pagination, Collection } from 'types'; @@ -23,6 +24,10 @@ class DashboardStore { try { const res = await client.post('/collections.list', { id: this.team.id }); + invariant( + res && res.data && res.pagination, + 'API response should be available' + ); const { data, pagination } = res; runInAction('fetchCollections', () => { this.collections = data; diff --git a/frontend/scenes/Dashboard/index.js b/frontend/scenes/Dashboard/index.js index e5cb98c3..d204b5e4 100644 --- a/frontend/scenes/Dashboard/index.js +++ b/frontend/scenes/Dashboard/index.js @@ -1,2 +1,3 @@ +// @flow import Dashboard from './Dashboard'; export default Dashboard; diff --git a/frontend/scenes/DocumentEdit/DocumentEdit.js b/frontend/scenes/DocumentEdit/DocumentEdit.js index 43e8fefe..8a85fb7b 100644 --- a/frontend/scenes/DocumentEdit/DocumentEdit.js +++ b/frontend/scenes/DocumentEdit/DocumentEdit.js @@ -1,21 +1,29 @@ +// @flow import React, { Component } from 'react'; import { observer } from 'mobx-react'; import { browserHistory, withRouter } from 'react-router'; import keydown from 'react-keydown'; +import { Flex } from 'reflexbox'; import DocumentEditStore, { DOCUMENT_EDIT_SETTINGS } from './DocumentEditStore'; +import EditorLoader from './components/EditorLoader'; import Layout, { Title, HeaderAction, SaveAction } from 'components/Layout'; -import { Flex } from 'reflexbox'; + import AtlasPreviewLoading from 'components/AtlasPreviewLoading'; import CenteredContent from 'components/CenteredContent'; import DropdownMenu, { MenuItem, MoreIcon } from 'components/DropdownMenu'; -import EditorLoader from './components/EditorLoader'; - const DISREGARD_CHANGES = `You have unsaved changes. Are you sure you want to disgard them?`; +type Props = { + route: Object, + router: Object, + params: Object, + keydown: Object, +}; + @keydown([ 'cmd+enter', 'ctrl+enter', @@ -27,13 +35,10 @@ Are you sure you want to disgard them?`; @withRouter @observer class DocumentEdit extends Component { - static propTypes = { - route: React.PropTypes.object.isRequired, - router: React.PropTypes.object.isRequired, - params: React.PropTypes.object, - }; + store: DocumentEditStore; + props: Props; - constructor(props) { + constructor(props: Props) { super(props); this.store = new DocumentEditStore( JSON.parse(localStorage[DOCUMENT_EDIT_SETTINGS] || '{}') @@ -60,6 +65,7 @@ class DocumentEdit extends Component { // Load editor async EditorLoader().then(({ Editor }) => { + // $FlowIssue we can remove after moving to new editor this.setState({ Editor }); }); @@ -72,7 +78,7 @@ class DocumentEdit extends Component { }); }; - componentWillReceiveProps = nextProps => { + componentWillReceiveProps = (nextProps: Props) => { const key = nextProps.keydown.event; if (key) { @@ -109,7 +115,7 @@ class DocumentEdit extends Component { browserHistory.goBack(); }; - onScroll = scrollTop => { + onScroll = (scrollTop: number) => { this.setState({ scrollTop, }); @@ -119,10 +125,9 @@ class DocumentEdit extends Component { const title = ( <Title truncate={60} - placeholder={!this.store.isFetching && 'Untitled document'} - > - {this.store.title} - + placeholder={!this.store.isFetching ? 'Untitled document' : null} + content={this.store.title} + /> ); const titleText = this.store.title; diff --git a/frontend/scenes/DocumentEdit/DocumentEditStore.js b/frontend/scenes/DocumentEdit/DocumentEditStore.js index fe0109df..b5d13d4e 100644 --- a/frontend/scenes/DocumentEdit/DocumentEditStore.js +++ b/frontend/scenes/DocumentEdit/DocumentEditStore.js @@ -1,7 +1,10 @@ +// @flow import { observable, action, toJS, autorun } from 'mobx'; -import { client } from 'utils/ApiClient'; import { browserHistory } from 'react-router'; +import invariant from 'invariant'; +import { client } from 'utils/ApiClient'; import emojify from 'utils/emojify'; +import type { Document } from 'types'; const DOCUMENT_EDIT_SETTINGS = 'DOCUMENT_EDIT_SETTINGS'; @@ -22,17 +25,17 @@ const parseHeader = text => { class DocumentEditStore { @observable documentId = null; @observable collectionId = null; - @observable parentDocument; - @observable title; - @observable text; + @observable parentDocument: ?Document; + @observable title: string; + @observable text: string; @observable hasPendingChanges = false; - @observable newDocument; - @observable newChildDocument; + @observable newDocument: ?boolean; + @observable newChildDocument: ?boolean; - @observable preview; - @observable isFetching; - @observable isSaving; - @observable isUploading; + @observable preview: ?boolean = false; + @observable isFetching: boolean = false; + @observable isSaving: boolean = false; + @observable isUploading: boolean = false; /* Actions */ @@ -40,17 +43,18 @@ class DocumentEditStore { this.isFetching = true; try { - const data = await client.get( + const res = await client.get( '/documents.info', { id: this.documentId, }, { cache: true } ); + invariant(res && res.data, 'Data shoule be available'); if (this.newChildDocument) { - this.parentDocument = data.data; + this.parentDocument = res.data; } else { - const { title, text } = data.data; + const { title, text } = res.data; this.title = title; this.text = text; } @@ -66,17 +70,19 @@ class DocumentEditStore { this.isSaving = true; try { - const data = await client.post( + const res = await client.post( '/documents.create', { parentDocument: this.parentDocument && this.parentDocument.id, + // $FlowFixMe this logic will probably get rewritten soon anyway collection: this.collectionId || this.parentDocument.collection.id, title: this.title || 'Untitled document', text: this.text, }, { cache: true } ); - const { url } = data.data; + invariant(res && res.data, 'Data shoule be available'); + const { url } = res.data; this.hasPendingChanges = false; browserHistory.push(url); @@ -92,7 +98,7 @@ class DocumentEditStore { this.isSaving = true; try { - const data = await client.post( + const res = await client.post( '/documents.update', { id: this.documentId, @@ -101,7 +107,8 @@ class DocumentEditStore { }, { cache: true } ); - const { url } = data.data; + invariant(res && res.data, 'Data shoule be available'); + const { url } = res.data; this.hasPendingChanges = false; browserHistory.push(url); @@ -111,17 +118,17 @@ class DocumentEditStore { this.isSaving = false; }; - @action updateText = text => { + @action updateText = (text: string) => { this.text = text; this.title = parseHeader(text); this.hasPendingChanges = true; }; - @action updateTitle = title => { + @action updateTitle = (title: string) => { this.title = title; }; - @action replaceText = args => { + @action replaceText = (args: { original: string, new: string }) => { this.text = this.text.replace(args.original, args.new); this.hasPendingChanges = true; }; @@ -147,7 +154,7 @@ class DocumentEditStore { }); }; - constructor(settings) { + constructor(settings: { preview: ?boolean }) { // Rehydrate settings this.preview = settings.preview; diff --git a/frontend/scenes/DocumentEdit/components/Editor.js b/frontend/scenes/DocumentEdit/components/Editor.js index 34d93d9a..5634a8ff 100644 --- a/frontend/scenes/DocumentEdit/components/Editor.js +++ b/frontend/scenes/DocumentEdit/components/Editor.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { observer } from 'mobx-react'; import { convertToMarkdown } from 'utils/markdown'; diff --git a/frontend/scenes/DocumentEdit/components/EditorLoader.js b/frontend/scenes/DocumentEdit/components/EditorLoader.js index 9969107a..4305c2dc 100644 --- a/frontend/scenes/DocumentEdit/components/EditorLoader.js +++ b/frontend/scenes/DocumentEdit/components/EditorLoader.js @@ -1,5 +1,7 @@ +// @flow export default () => { return new Promise(resolve => { + // $FlowIssue this is available with webpack require.ensure([], () => { resolve({ Editor: require('./Editor').default, diff --git a/frontend/scenes/DocumentEdit/components/EditorPane.js b/frontend/scenes/DocumentEdit/components/EditorPane.js index 16ade5c0..f001e2cc 100644 --- a/frontend/scenes/DocumentEdit/components/EditorPane.js +++ b/frontend/scenes/DocumentEdit/components/EditorPane.js @@ -1,18 +1,21 @@ +// @flow import React from 'react'; import styles from '../DocumentEdit.scss'; import classNames from 'classnames/bind'; const cx = classNames.bind(styles); -class EditorPane extends React.Component { - static propTypes = { - children: React.PropTypes.node.isRequired, - onScroll: React.PropTypes.func.isRequired, - scrollTop: React.PropTypes.number, - fullWidth: React.PropTypes.bool, - }; +type Props = { + children?: ?React.Element, + onScroll?: Function, + scrollTop?: ?number, + fullWidth?: ?boolean, +}; - componentWillReceiveProps = nextProps => { +class EditorPane extends React.Component { + props: Props; + + componentWillReceiveProps = (nextProps: Props) => { if (nextProps.scrollTop) { this.scrollToPosition(nextProps.scrollTop); } @@ -26,15 +29,16 @@ class EditorPane extends React.Component { this.refs.pane.removeEventListener('scroll', this.handleScroll); }; - handleScroll = e => { + handleScroll = (e: Event) => { setTimeout(() => { const element = this.refs.pane; const contentEl = this.refs.content; - this.props.onScroll(element.scrollTop / contentEl.offsetHeight); + this.props.onScroll && + this.props.onScroll(element.scrollTop / contentEl.offsetHeight); }, 50); }; - scrollToPosition = percentage => { + scrollToPosition = (percentage: number) => { const contentEl = this.refs.content; // Push to edges diff --git a/frontend/scenes/DocumentEdit/components/Preview/Preview.js b/frontend/scenes/DocumentEdit/components/Preview/Preview.js index ceb80dd3..1d5168be 100644 --- a/frontend/scenes/DocumentEdit/components/Preview/Preview.js +++ b/frontend/scenes/DocumentEdit/components/Preview/Preview.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { DocumentHtml } from 'components/Document'; @@ -6,7 +7,11 @@ import styles from './Preview.scss'; import classNames from 'classnames/bind'; const cx = classNames.bind(styles); -const Preview = props => { +type Props = { + html: ?string, +}; + +const Preview = (props: Props) => { return (
@@ -14,8 +19,4 @@ const Preview = props => { ); }; -Preview.propTypes = { - html: React.PropTypes.string.isRequired, -}; - export default Preview; diff --git a/frontend/scenes/DocumentEdit/components/Preview/index.js b/frontend/scenes/DocumentEdit/components/Preview/index.js index 1f98dc6f..f7604c3c 100644 --- a/frontend/scenes/DocumentEdit/components/Preview/index.js +++ b/frontend/scenes/DocumentEdit/components/Preview/index.js @@ -1,2 +1,3 @@ +// @flow import Preview from './Preview'; export default Preview; diff --git a/frontend/scenes/DocumentEdit/index.js b/frontend/scenes/DocumentEdit/index.js index 548ef8a5..051a2430 100644 --- a/frontend/scenes/DocumentEdit/index.js +++ b/frontend/scenes/DocumentEdit/index.js @@ -1,2 +1,3 @@ +// @flow import DocumentEdit from './DocumentEdit'; export default DocumentEdit; diff --git a/frontend/scenes/DocumentScene/DocumentScene.js b/frontend/scenes/DocumentScene/DocumentScene.js index 96daed9e..851082f2 100644 --- a/frontend/scenes/DocumentScene/DocumentScene.js +++ b/frontend/scenes/DocumentScene/DocumentScene.js @@ -1,4 +1,6 @@ +// @flow import React, { PropTypes } from 'react'; +import invariant from 'invariant'; import { Link, browserHistory } from 'react-router'; import { observer, inject } from 'mobx-react'; import { toJS } from 'mobx'; @@ -6,6 +8,7 @@ import keydown from 'react-keydown'; import _ from 'lodash'; import DocumentSceneStore, { DOCUMENT_PREFERENCES } from './DocumentSceneStore'; +import UiStore from 'stores/UiStore'; import Layout from 'components/Layout'; import AtlasPreviewLoading from 'components/AtlasPreviewLoading'; @@ -16,13 +19,20 @@ import { Flex } from 'reflexbox'; import Sidebar from './components/Sidebar'; import styles from './DocumentScene.scss'; -// import classNames from 'classnames/bind'; -// const cx = classNames.bind(styles); + +type Props = { + ui: UiStore, + routeParams: Object, + params: Object, + location: Object, + keydown: Object, +}; @keydown(['cmd+/', 'ctrl+/', 'c', 'e']) @inject('ui') @observer class DocumentScene extends React.Component { + store: DocumentSceneStore; static propTypes = { ui: PropTypes.object.isRequired, routeParams: PropTypes.object, @@ -30,7 +40,7 @@ class DocumentScene extends React.Component { location: PropTypes.object.isRequired, }; - constructor(props) { + constructor(props: Props) { super(props); this.store = new DocumentSceneStore( JSON.parse(localStorage[DOCUMENT_PREFERENCES] || '{}') @@ -41,15 +51,16 @@ class DocumentScene extends React.Component { didScroll: false, }; - componentDidMount = async () => { + componentDidMount = () => { const { id } = this.props.routeParams; - await this.store.fetchDocument(id, { - replaceUrl: !this.props.location.hash, - }); - this.scrollTohash(); + this.store + .fetchDocument(id, { + replaceUrl: !this.props.location.hash, + }) + .then(() => this.scrollTohash()); }; - componentWillReceiveProps = async nextProps => { + componentWillReceiveProps = (nextProps: Props) => { const key = nextProps.keydown.event; if (key) { if (key.key === '/' && (key.metaKey || key.ctrl.Key)) { @@ -57,7 +68,7 @@ class DocumentScene extends React.Component { } if (key.key === 'c') { - _.defer(this.onCreate); + _.defer(this.onCreateDocument); } if (key.key === 'e') { @@ -69,31 +80,38 @@ class DocumentScene extends React.Component { const oldId = this.props.params.id; const newId = nextProps.params.id; if (oldId !== newId) { - await this.store.fetchDocument(newId, { - softLoad: true, - replaceUrl: !this.props.location.hash, - }); + this.store + .fetchDocument(newId, { + softLoad: true, + replaceUrl: !this.props.location.hash, + }) + .then(() => this.scrollTohash()); } - - this.scrollTohash(); }; onEdit = () => { + invariant(this.store.document, 'Document is not available'); const url = `${this.store.document.url}/edit`; browserHistory.push(url); }; onCreateDocument = () => { + invariant(this.store.collectionTree, 'collectionTree is not available'); browserHistory.push(`${this.store.collectionTree.url}/new`); }; onCreateChild = () => { + invariant(this.store.document, 'Document is not available'); browserHistory.push(`${this.store.document.url}/new`); }; onDelete = () => { let msg; - if (this.store.document.collection.type === 'atlas') { + if ( + this.store.document && + this.store.document.collection && + this.store.document.collection.type === 'atlas' + ) { msg = "Are you sure you want to delete this document and all it's child documents (if any)?"; } else { @@ -107,11 +125,13 @@ class DocumentScene extends React.Component { onExport = () => { const doc = this.store.document; - const a = document.createElement('a'); - a.textContent = 'download'; - a.download = `${doc.title}.md`; - a.href = `data:text/markdown;charset=UTF-8,${encodeURIComponent(doc.text)}`; - a.click(); + if (doc) { + const a = document.createElement('a'); + a.textContent = 'download'; + a.download = `${doc.title}.md`; + a.href = `data:text/markdown;charset=UTF-8,${encodeURIComponent(doc.text)}`; + a.click(); + } }; scrollTohash = () => { @@ -130,6 +150,7 @@ class DocumentScene extends React.Component { const { sidebar } = this.props.ui; const doc = this.store.document; + if (!doc) return; const allowDelete = doc && doc.collection.type === 'atlas' && diff --git a/frontend/scenes/DocumentScene/DocumentSceneStore.js b/frontend/scenes/DocumentScene/DocumentSceneStore.js index d0f419bc..fe07d662 100644 --- a/frontend/scenes/DocumentScene/DocumentSceneStore.js +++ b/frontend/scenes/DocumentScene/DocumentSceneStore.js @@ -1,5 +1,7 @@ +// @flow import _ from 'lodash'; import { browserHistory } from 'react-router'; +import invariant from 'invariant'; import { observable, action, @@ -9,26 +11,36 @@ import { autorunAsync, } from 'mobx'; import { client } from 'utils/ApiClient'; +import type { Document as DocumentType, Collection } from 'types'; const DOCUMENT_PREFERENCES = 'DOCUMENT_PREFERENCES'; -class DocumentSceneStore { - @observable document; - @observable collapsedNodes = []; +type Document = { + collection: Collection, +} & DocumentType; - @observable isFetching = true; - @observable updatingContent = false; - @observable updatingStructure = false; - @observable isDeleting; +class DocumentSceneStore { + @observable document: ?Document; + @observable collapsedNodes: string[] = []; + + @observable isFetching: boolean = true; + @observable updatingContent: boolean = false; + @observable updatingStructure: boolean = false; + @observable isDeleting: boolean = false; /* Computed */ - @computed get isCollection() { - return this.document && this.document.collection.type === 'atlas'; + @computed get isCollection(): boolean { + return !!this.document && this.document.collection.type === 'atlas'; } - @computed get collectionTree() { - if (!this.document || this.document.collection.type !== 'atlas') return; + @computed get collectionTree(): ?Object { + if ( + !this.document || + this.document.collection || + this.document.collection.type !== 'atlas' + ) + return; const tree = this.document.collection.navigationTree; const collapseNodes = node => { @@ -45,7 +57,10 @@ class DocumentSceneStore { /* Actions */ - @action fetchDocument = async (id, options = {}) => { + @action fetchDocument = async ( + id: string, + options: { softLoad?: boolean, replaceUrl?: boolean } = {} + ) => { options = { softLoad: false, replaceUrl: true, @@ -57,6 +72,7 @@ class DocumentSceneStore { try { const res = await client.get('/documents.info', { id }); + invariant(res && res.data, 'data should be available'); const { data } = res; runInAction('fetchDocument', () => { this.document = data; @@ -70,10 +86,12 @@ class DocumentSceneStore { }; @action deleteDocument = async () => { + if (!this.document) return; this.isFetching = true; try { await client.post('/documents.delete', { id: this.document.id }); + // $FlowFixMe don't be stupid browserHistory.push(this.document.collection.url); } catch (e) { console.error('Something went wrong'); @@ -81,8 +99,9 @@ class DocumentSceneStore { this.isFetching = false; }; - @action updateNavigationTree = async tree => { + @action updateNavigationTree = async (tree: Object) => { // Only update when tree changes + // $FlowFixMe don't be stupid if (_.isEqual(toJS(tree), toJS(this.document.collection.navigationTree))) { return true; } @@ -91,11 +110,14 @@ class DocumentSceneStore { try { const res = await client.post('/collections.updateNavigationTree', { + // $FlowFixMe don't be stupid id: this.document.collection.id, tree, }); + invariant(res && res.data, 'data should be available'); runInAction('updateNavigationTree', () => { const { data } = res; + // $FlowFixMe don't be stupid this.document.collection = data; }); } catch (e) { @@ -104,7 +126,7 @@ class DocumentSceneStore { this.updatingStructure = false; }; - @action onNodeCollapse = nodeId => { + @action onNodeCollapse = (nodeId: string) => { if (_.indexOf(this.collapsedNodes, nodeId) >= 0) { this.collapsedNodes = _.without(this.collapsedNodes, nodeId); } else { @@ -120,7 +142,7 @@ class DocumentSceneStore { }); }; - constructor(settings, options) { + constructor(settings: { collapsedNodes: string[] }) { // Rehydrate settings this.collapsedNodes = settings.collapsedNodes || []; diff --git a/frontend/scenes/DocumentScene/components/Sidebar/Sidebar.js b/frontend/scenes/DocumentScene/components/Sidebar/Sidebar.js index 624c05fe..72044dd5 100644 --- a/frontend/scenes/DocumentScene/components/Sidebar/Sidebar.js +++ b/frontend/scenes/DocumentScene/components/Sidebar/Sidebar.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react'; +// @flow +import React from 'react'; import { observer } from 'mobx-react'; import { Flex } from 'reflexbox'; @@ -11,22 +12,25 @@ const cx = classNames.bind(styles); import SidebarStore from './SidebarStore'; -@observer class Sidebar extends React.Component { - static propTypes = { - open: PropTypes.bool, - onToggle: PropTypes.func.isRequired, - navigationTree: PropTypes.object.isRequired, - onNavigationUpdate: PropTypes.func.isRequired, - onNodeCollapse: PropTypes.func.isRequired, - }; +type Props = { + open?: boolean, + onToggle: Function, + navigationTree: Object, + onNavigationUpdate: Function, + onNodeCollapse: Function, +}; - constructor(props) { +@observer class Sidebar extends React.Component { + props: Props; + store: SidebarStore; + + constructor(props: Props) { super(props); this.store = new SidebarStore(); } - toggleEdit = e => { + toggleEdit = (e: MouseEvent) => { e.preventDefault(); this.store.toggleEdit(); }; diff --git a/frontend/scenes/DocumentScene/components/Sidebar/SidebarStore.js b/frontend/scenes/DocumentScene/components/Sidebar/SidebarStore.js index c41ec965..b57621c5 100644 --- a/frontend/scenes/DocumentScene/components/Sidebar/SidebarStore.js +++ b/frontend/scenes/DocumentScene/components/Sidebar/SidebarStore.js @@ -1,3 +1,4 @@ +// @flow import { observable, action } from 'mobx'; class SidebarStore { diff --git a/frontend/scenes/DocumentScene/components/Sidebar/components/Separator/Separator.js b/frontend/scenes/DocumentScene/components/Sidebar/components/Separator/Separator.js index 21e17676..8cc898a7 100644 --- a/frontend/scenes/DocumentScene/components/Sidebar/components/Separator/Separator.js +++ b/frontend/scenes/DocumentScene/components/Sidebar/components/Separator/Separator.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import styles from './Separator.scss'; diff --git a/frontend/scenes/DocumentScene/components/Sidebar/components/Separator/index.js b/frontend/scenes/DocumentScene/components/Sidebar/components/Separator/index.js index f2e3d0bf..4b828564 100644 --- a/frontend/scenes/DocumentScene/components/Sidebar/components/Separator/index.js +++ b/frontend/scenes/DocumentScene/components/Sidebar/components/Separator/index.js @@ -1,2 +1,3 @@ +// @flow import Separator from './Separator'; export default Separator; diff --git a/frontend/scenes/DocumentScene/components/Sidebar/index.js b/frontend/scenes/DocumentScene/components/Sidebar/index.js index 0f9e1036..c1b441c2 100644 --- a/frontend/scenes/DocumentScene/components/Sidebar/index.js +++ b/frontend/scenes/DocumentScene/components/Sidebar/index.js @@ -1,2 +1,3 @@ +// @flow import Sidebar from './Sidebar'; export default Sidebar; diff --git a/frontend/scenes/DocumentScene/index.js b/frontend/scenes/DocumentScene/index.js index acbce0f5..54afd750 100644 --- a/frontend/scenes/DocumentScene/index.js +++ b/frontend/scenes/DocumentScene/index.js @@ -1,2 +1,3 @@ +// @flow import DocumentScene from './DocumentScene'; export default DocumentScene; diff --git a/frontend/scenes/Error404/Error404.js b/frontend/scenes/Error404/Error404.js index 607793af..89b3eb2e 100644 --- a/frontend/scenes/Error404/Error404.js +++ b/frontend/scenes/Error404/Error404.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { Link } from 'react-router'; diff --git a/frontend/scenes/Error404/index.js b/frontend/scenes/Error404/index.js index d745c11c..36dea2bd 100644 --- a/frontend/scenes/Error404/index.js +++ b/frontend/scenes/Error404/index.js @@ -1,2 +1,3 @@ +// @flow import Error404 from './Error404'; export default Error404; diff --git a/frontend/scenes/ErrorAuth/ErrorAuth.js b/frontend/scenes/ErrorAuth/ErrorAuth.js index 7398d8ec..40f8447b 100644 --- a/frontend/scenes/ErrorAuth/ErrorAuth.js +++ b/frontend/scenes/ErrorAuth/ErrorAuth.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { Link } from 'react-router'; diff --git a/frontend/scenes/ErrorAuth/index.js b/frontend/scenes/ErrorAuth/index.js index fb4b2724..0e961642 100644 --- a/frontend/scenes/ErrorAuth/index.js +++ b/frontend/scenes/ErrorAuth/index.js @@ -1,2 +1,3 @@ +// @flow import ErrorAuth from './ErrorAuth'; export default ErrorAuth; diff --git a/frontend/scenes/Flatpage/Flatpage.js b/frontend/scenes/Flatpage/Flatpage.js index 403941e0..240f264c 100644 --- a/frontend/scenes/Flatpage/Flatpage.js +++ b/frontend/scenes/Flatpage/Flatpage.js @@ -1,3 +1,4 @@ +// @flow import React, { PropTypes } from 'react'; import { observer } from 'mobx-react'; @@ -16,7 +17,11 @@ import { convertToMarkdown } from 'utils/markdown'; const { title, content } = this.props.route; return ( - {title}} titleText={title} search={false}> + } + titleText={title} + search={false} + > diff --git a/frontend/scenes/Flatpage/index.js b/frontend/scenes/Flatpage/index.js index e43db410..55cdbb81 100644 --- a/frontend/scenes/Flatpage/index.js +++ b/frontend/scenes/Flatpage/index.js @@ -1,2 +1,3 @@ +// @flow import Flatpage from './Flatpage'; export default Flatpage; diff --git a/frontend/scenes/Home/Home.js b/frontend/scenes/Home/Home.js index 4d185a0b..f54ac6d9 100644 --- a/frontend/scenes/Home/Home.js +++ b/frontend/scenes/Home/Home.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { observer, inject } from 'mobx-react'; import { browserHistory } from 'react-router'; @@ -24,7 +25,7 @@ export default class Home extends React.Component { } }; - get notifications() { + get notifications(): React.Element[] { const notifications = []; const { state } = this.props.location; @@ -54,7 +55,7 @@ export default class Home extends React.Component {

}
- + Sign in with Slack { this.store.search(searchTerm); }, 250); - const title = ( - - Search - - ); + const title = ; return ( <Layout diff --git a/frontend/scenes/Search/SearchStore.js b/frontend/scenes/Search/SearchStore.js index e2de4faa..f3e807f6 100644 --- a/frontend/scenes/Search/SearchStore.js +++ b/frontend/scenes/Search/SearchStore.js @@ -1,23 +1,26 @@ +// @flow import { observable, action, runInAction } from 'mobx'; +import invariant from 'invariant'; import { client } from 'utils/ApiClient'; +import type { Pagination, Document } from 'types'; class SearchStore { - @observable documents; - @observable pagination; - @observable selectedDocument; - @observable searchTerm; + @observable documents: ?(Document[]); + @observable pagination: Pagination; + @observable searchTerm: ?string = null; @observable isFetching = false; /* Actions */ - @action search = async query => { + @action search = async (query: string) => { this.searchTerm = query; this.isFetching = true; if (query) { try { const res = await client.get('/documents.search', { query }); + invariant(res && res.data && res.pagination, 'API response'); const { data, pagination } = res; runInAction('search document', () => { this.documents = data; diff --git a/frontend/scenes/Search/components/SearchField/SearchField.js b/frontend/scenes/Search/components/SearchField/SearchField.js index b3f04c6f..0061cb9a 100644 --- a/frontend/scenes/Search/components/SearchField/SearchField.js +++ b/frontend/scenes/Search/components/SearchField/SearchField.js @@ -1,3 +1,4 @@ +// @flow import React, { PropTypes } from 'react'; import { observer } from 'mobx-react'; @@ -8,8 +9,8 @@ import styles from './SearchField.scss'; onChange: PropTypes.func, }; - onChange = event => { - this.props.onChange(event.currentTarget.value); + onChange = (event: SyntheticEvent) => { + event.currentTarget.value && this.props.onChange(event.currentTarget.value); }; render() { diff --git a/frontend/scenes/Search/components/SearchField/index.js b/frontend/scenes/Search/components/SearchField/index.js index 9f46c79b..a45be6bf 100644 --- a/frontend/scenes/Search/components/SearchField/index.js +++ b/frontend/scenes/Search/components/SearchField/index.js @@ -1,2 +1,3 @@ +// @flow import SearchField from './SearchField'; export default SearchField; diff --git a/frontend/scenes/Search/index.js b/frontend/scenes/Search/index.js index ac515625..cab60251 100644 --- a/frontend/scenes/Search/index.js +++ b/frontend/scenes/Search/index.js @@ -1,2 +1,3 @@ +// @flow import Search from './Search'; export default Search; diff --git a/frontend/scenes/Settings/Settings.js b/frontend/scenes/Settings/Settings.js index 7b051662..49df4a97 100644 --- a/frontend/scenes/Settings/Settings.js +++ b/frontend/scenes/Settings/Settings.js @@ -13,7 +13,7 @@ import CenteredContent from 'components/CenteredContent'; import SlackAuthLink from 'components/SlackAuthLink'; @observer class Settings extends React.Component { - store = SettingsStore; + store: SettingsStore; constructor() { super(); @@ -21,13 +21,7 @@ import SlackAuthLink from 'components/SlackAuthLink'; } render() { - const title = ( - <Title> - Settings - - ); - - // $FlowIssue global variable + const title = ; const showSlackSettings = DEPLOYMENT === 'hosted'; return ( @@ -48,8 +42,7 @@ import SlackAuthLink from 'components/SlackAuthLink'; <SlackAuthLink scopes={['commands']} - redirectUri={// $FlowIssue URL is a global variable - `${URL}/auth/slack/commands`} + redirectUri={`${BASE_URL}/auth/slack/commands`} > <img alt="Add to Slack" @@ -71,15 +64,16 @@ import SlackAuthLink from 'components/SlackAuthLink'; {this.store.apiKeys && <table className={styles.apiKeyTable}> <tbody> - {this.store.apiKeys.map(key => ( - <ApiKeyRow - id={key.id} - key={key.id} - name={key.name} - secret={key.secret} - onDelete={this.store.deleteApiKey} - /> - ))} + {this.store.apiKeys && + this.store.apiKeys.map(key => ( + <ApiKeyRow + id={key.id} + key={key.id} + name={key.name} + secret={key.secret} + onDelete={this.store.deleteApiKey} + /> + ))} </tbody> </table>} @@ -106,10 +100,10 @@ class InlineForm extends React.Component { placeholder: string, buttonLabel: string, name: string, - value: string, + value: ?string, onChange: Function, onSubmit: Function, - disabled?: boolean, + disabled?: ?boolean, }; validationTimeout: number; diff --git a/frontend/scenes/Settings/SettingsStore.js b/frontend/scenes/Settings/SettingsStore.js index 6ede16b9..317874e2 100644 --- a/frontend/scenes/Settings/SettingsStore.js +++ b/frontend/scenes/Settings/SettingsStore.js @@ -1,18 +1,23 @@ +// @flow import { observable, action, runInAction } from 'mobx'; +import invariant from 'invariant'; import { client } from 'utils/ApiClient'; +import type { ApiKey } from 'types'; class SearchStore { - @observable apiKeys = []; - @observable keyName; + @observable apiKeys: ApiKey[] = []; + @observable keyName: ?string; - @observable isFetching; + @observable isFetching: boolean = false; @action fetchApiKeys = async () => { this.isFetching = true; try { const res = await client.post('/apiKeys.list'); + invariant(res && res.data, 'Data shoule be available'); const { data } = res; + runInAction('fetchApiKeys', () => { this.apiKeys = data; }); @@ -27,8 +32,9 @@ class SearchStore { try { const res = await client.post('/apiKeys.create', { - name: `${this.keyName}` || 'Untitled key', + name: this.keyName ? this.keyName : 'Untitled key', }); + invariant(res && res.data, 'Data shoule be available'); const { data } = res; runInAction('createApiKey', () => { this.apiKeys.push(data); @@ -40,7 +46,7 @@ class SearchStore { this.isFetching = false; }; - @action deleteApiKey = async id => { + @action deleteApiKey = async (id: string) => { this.isFetching = true; try { @@ -56,7 +62,7 @@ class SearchStore { this.isFetching = false; }; - @action setKeyName = value => { + @action setKeyName = (value: SyntheticInputEvent) => { this.keyName = value.target.value; }; diff --git a/frontend/scenes/Settings/components/ApiKeyRow/ApiKeyRow.js b/frontend/scenes/Settings/components/ApiKeyRow/ApiKeyRow.js index 5a49bcd1..a5f6aebb 100644 --- a/frontend/scenes/Settings/components/ApiKeyRow/ApiKeyRow.js +++ b/frontend/scenes/Settings/components/ApiKeyRow/ApiKeyRow.js @@ -1,3 +1,4 @@ +// @flow import React, { PropTypes } from 'react'; import styles from './ApiKeyRow.scss'; diff --git a/frontend/scenes/Settings/components/ApiKeyRow/index.js b/frontend/scenes/Settings/components/ApiKeyRow/index.js index 5a4a1f59..1c94f8f0 100644 --- a/frontend/scenes/Settings/components/ApiKeyRow/index.js +++ b/frontend/scenes/Settings/components/ApiKeyRow/index.js @@ -1,2 +1,3 @@ +// @flow import ApiKeyRow from './ApiKeyRow'; export default ApiKeyRow; diff --git a/frontend/scenes/Settings/index.js b/frontend/scenes/Settings/index.js index 469bf960..c0492da1 100644 --- a/frontend/scenes/Settings/index.js +++ b/frontend/scenes/Settings/index.js @@ -1,2 +1,3 @@ +// @flow import Settings from './Settings'; export default Settings; diff --git a/frontend/scenes/SlackAuth/SlackAuth.js b/frontend/scenes/SlackAuth/SlackAuth.js index d5243a85..dd3f800f 100644 --- a/frontend/scenes/SlackAuth/SlackAuth.js +++ b/frontend/scenes/SlackAuth/SlackAuth.js @@ -1,3 +1,4 @@ +// @flow import React from 'react'; import { observer, inject } from 'mobx-react'; import { browserHistory } from 'react-router'; @@ -12,6 +13,7 @@ class SlackAuth extends React.Component { route: React.PropTypes.object.isRequired, }; + // $FlowIssue wtf componentDidMount = async () => { const { error, code, state } = this.props.location.query; @@ -22,6 +24,7 @@ class SlackAuth extends React.Component { } else { browserHistory.push('/auth/error'); } + // $FlowIssue wtf return; } diff --git a/frontend/scenes/SlackAuth/index.js b/frontend/scenes/SlackAuth/index.js index 1e94b5c2..cf1b93d0 100644 --- a/frontend/scenes/SlackAuth/index.js +++ b/frontend/scenes/SlackAuth/index.js @@ -1,2 +1,3 @@ +// @flow import SlackAuth from './SlackAuth'; export default SlackAuth; diff --git a/frontend/static/flatpages/api.markdown b/frontend/static/flatpages/api.md similarity index 100% rename from frontend/static/flatpages/api.markdown rename to frontend/static/flatpages/api.md diff --git a/frontend/static/flatpages/index.js b/frontend/static/flatpages/index.js index 42ddef8f..731dd6b7 100644 --- a/frontend/static/flatpages/index.js +++ b/frontend/static/flatpages/index.js @@ -1,5 +1,6 @@ -import api from './api.markdown'; -import keyboard from './keyboard.markdown'; +// @flow +import api from './api.md'; +import keyboard from './keyboard.md'; export default { api, diff --git a/frontend/static/flatpages/keyboard.markdown b/frontend/static/flatpages/keyboard.md similarity index 100% rename from frontend/static/flatpages/keyboard.markdown rename to frontend/static/flatpages/keyboard.md diff --git a/frontend/stores/UiStore.js b/frontend/stores/UiStore.js index f3b1829c..64b795dc 100644 --- a/frontend/stores/UiStore.js +++ b/frontend/stores/UiStore.js @@ -1,13 +1,14 @@ +// @flow import { observable, action, computed } from 'mobx'; const UI_STORE = 'UI_STORE'; class UiStore { - @observable sidebar; + @observable sidebar: boolean = false; /* Computed */ - @computed get asJson() { + @computed get asJson(): string { return JSON.stringify({ sidebar: this.sidebar, }); @@ -15,7 +16,7 @@ class UiStore { /* Actions */ - @action toggleSidebar = () => { + @action toggleSidebar = (): void => { this.sidebar = !this.sidebar; }; diff --git a/frontend/stores/UserStore.js b/frontend/stores/UserStore.js index dd369412..b002e82f 100644 --- a/frontend/stores/UserStore.js +++ b/frontend/stores/UserStore.js @@ -1,25 +1,28 @@ +// @flow import { observable, action, computed } from 'mobx'; +import invariant from 'invariant'; import { browserHistory } from 'react-router'; import { client } from 'utils/ApiClient'; +import type { User, Team } from 'types'; const USER_STORE = 'USER_STORE'; class UserStore { - @observable user; - @observable team; + @observable user: ?User; + @observable team: ?Team; - @observable token; - @observable oauthState; + @observable token: ?string; + @observable oauthState: string; - @observable isLoading; + @observable isLoading: boolean = false; /* Computed */ - @computed get authenticated() { + @computed get authenticated(): boolean { return !!this.token; } - @computed get asJson() { + @computed get asJson(): string { return JSON.stringify({ user: this.user, team: this.team, @@ -42,7 +45,11 @@ class UserStore { return this.oauthState; }; - @action authWithSlack = async (code, state, redirectTo) => { + @action authWithSlack = async ( + code: string, + state: string, + redirectTo: ?string + ) => { if (state !== this.oauthState) { browserHistory.push('/auth-error'); return; @@ -56,6 +63,10 @@ class UserStore { return; } + invariant( + res && res.data && res.data.user && res.data.team && res.data.accessToken, + 'All values should be available' + ); this.user = res.data.user; this.team = res.data.team; this.token = res.data.accessToken; diff --git a/frontend/stores/index.js b/frontend/stores/index.js index b980007b..f90a12f9 100644 --- a/frontend/stores/index.js +++ b/frontend/stores/index.js @@ -1,3 +1,4 @@ +// @flow import { autorunAsync } from 'mobx'; import UserStore, { USER_STORE } from './UserStore'; import UiStore, { UI_STORE } from './UiStore'; diff --git a/frontend/types/index.js b/frontend/types/index.js index 3ac30e74..17eadbbb 100644 --- a/frontend/types/index.js +++ b/frontend/types/index.js @@ -6,12 +6,18 @@ export type User = { username: string, }; +export type Team = { + id: string, + name: string, +}; + export type Collection = { createdAt: string, description: ?string, id: string, name: string, type: 'atlas' | 'journal', + navigationTree: Object, // TODO updatedAt: string, url: string, }; @@ -37,3 +43,9 @@ export type Pagination = { nextPath: string, offset: number, }; + +export type ApiKey = { + id: string, + name: ?string, + secret: string, +}; diff --git a/frontend/utils/ApiClient.js b/frontend/utils/ApiClient.js index 2b8a22c2..a09b2633 100644 --- a/frontend/utils/ApiClient.js +++ b/frontend/utils/ApiClient.js @@ -1,19 +1,36 @@ +// @flow import _ from 'lodash'; import { browserHistory } from 'react-router'; import stores from 'stores'; +type Options = { + baseUrl?: string, +}; + class ApiClient { - constructor(options = {}) { + baseUrl: string; + userAgent: string; + + constructor(options: Options = {}) { this.baseUrl = options.baseUrl || '/api'; this.userAgent = 'AtlasFrontend'; } - fetch = (path, method, data, options = {}) => { + fetch = ( + path: string, + method: string, + data: ?Object, + options: Object = {} + ) => { let body; let modifiedPath; if (method === 'GET') { - modifiedPath = `${path}?${this.constructQueryString(data)}`; + if (data) { + modifiedPath = `${path}?${data && this.constructQueryString(data)}`; + } else { + modifiedPath = path; + } } else if (method === 'POST' || method === 'PUT') { body = JSON.stringify(data); } @@ -24,10 +41,12 @@ class ApiClient { 'Content-Type': 'application/json', }); if (stores.user.authenticated) { + // $FlowFixMe this is not great, need to refactor headers.set('Authorization', `Bearer ${stores.user.token}`); } // Construct request + // $FlowFixMe don't care much about this right now const request = fetch(this.baseUrl + (modifiedPath || path), { method, body, @@ -61,7 +80,7 @@ class ApiClient { throw error; }) .then(response => { - return response.json(); + return response && response.json(); }) .then(json => { resolve(json); @@ -75,17 +94,17 @@ class ApiClient { }); }; - get = (path, data, options) => { + get = (path: string, data?: Object, options?: Object) => { return this.fetch(path, 'GET', data, options); }; - post = (path, data, options) => { + post = (path: string, data?: Object, options?: Object) => { return this.fetch(path, 'POST', data, options); }; // Helpers - constructQueryString = data => { + constructQueryString = (data: Object) => { return _.map(data, (v, k) => { return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`; }).join('&'); diff --git a/frontend/utils/History.js b/frontend/utils/History.js index 7a86a35d..76b01758 100644 --- a/frontend/utils/History.js +++ b/frontend/utils/History.js @@ -1,3 +1,4 @@ +// @flow // https://github.com/reactjs/react-router/blob/master/docs/guides/NavigatingOutsideOfComponents.md import browserHistory from 'react-router/lib/browserHistory'; export default browserHistory; diff --git a/frontend/utils/actions.js b/frontend/utils/actions.js deleted file mode 100644 index 42d96b88..00000000 --- a/frontend/utils/actions.js +++ /dev/null @@ -1,9 +0,0 @@ -export default (type, ...argNames) => { - return function(...args) { - const action = { type }; - argNames.forEach((arg, index) => { - action[argNames[index]] = args[index]; - }); - return action; - }; -}; diff --git a/frontend/utils/emojify.js b/frontend/utils/emojify.js index 5491663c..eeee3e1f 100644 --- a/frontend/utils/emojify.js +++ b/frontend/utils/emojify.js @@ -1,8 +1,9 @@ +// @flow import emojiMapping from './emoji-mapping.json'; const EMOJI_REGEX = /:([A-Za-z0-9_\-+]+?):/gm; -const emojify = (text = '') => { +const emojify = (text: string = '') => { let emojifiedText = text; emojifiedText = text.replace(EMOJI_REGEX, (match, p1, offset, string) => { diff --git a/frontend/utils/markdown.js b/frontend/utils/markdown.js index 18f08fe5..ab4593a7 100644 --- a/frontend/utils/markdown.js +++ b/frontend/utils/markdown.js @@ -1,3 +1,4 @@ +// @flow import slug from 'slug'; import marked from 'marked'; import sanitizedRenderer from 'marked-sanitized'; @@ -27,7 +28,7 @@ renderer.heading = (text, level) => { `; }; -const convertToMarkdown = text => { +const convertToMarkdown = (text: string) => { // Add TOC text = toc.insert(text || '', { slugify: heading => { diff --git a/frontend/utils/random.js b/frontend/utils/random.js index 5604e907..7ce9f425 100644 --- a/frontend/utils/random.js +++ b/frontend/utils/random.js @@ -1,4 +1,5 @@ -const randomInteger = (min, max) => { +// @flow +const randomInteger = (min: number, max: number) => { return Math.floor(Math.random() * (max - min + 1) + min); }; diff --git a/frontend/utils/testUtils.js b/frontend/utils/testUtils.js index 8570c9d4..dcf730d1 100644 --- a/frontend/utils/testUtils.js +++ b/frontend/utils/testUtils.js @@ -1,3 +1,5 @@ +/* eslint-disable */ +import React from 'react'; import renderer from 'react-test-renderer'; const snap = children => { diff --git a/frontend/utils/toc/index.js b/frontend/utils/toc/index.js index 45a0815f..c015cde2 100644 --- a/frontend/utils/toc/index.js +++ b/frontend/utils/toc/index.js @@ -1,3 +1,4 @@ +// @flow /* eslint-disable */ /** @@ -121,7 +122,7 @@ function generate(str, options) { * toc */ -function toc(str, options) { +function toc(str: string, options: Object) { return generate(str, options).toc; } diff --git a/package.json b/package.json index f05f5f60..abc34f4e 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "html-webpack-plugin": "2.17.0", "http-errors": "1.4.0", "imports-loader": "0.6.5", + "invariant": "^2.2.2", "isomorphic-fetch": "2.2.1", "js-tree": "1.1.0", "json-loader": "0.5.4", diff --git a/webpack.config.js b/webpack.config.js index 3e161f23..4d69a7a4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,3 +1,4 @@ +/* eslint-disable */ const path = require('path'); const webpack = require('webpack'); @@ -10,7 +11,7 @@ const definePlugin = new webpack.DefinePlugin({ ), SLACK_REDIRECT_URI: JSON.stringify(process.env.SLACK_REDIRECT_URI), SLACK_KEY: JSON.stringify(process.env.SLACK_KEY), - URL: JSON.stringify(process.env.URL), + BASE_URL: JSON.stringify(process.env.URL), DEPLOYMENT: JSON.stringify(process.env.DEPLOYMENT || 'hosted'), }); @@ -34,7 +35,7 @@ module.exports = { test: /\.woff$/, loader: 'url-loader?limit=1&mimetype=application/font-woff&name=public/fonts/[name].[ext]', }, - { test: /\.markdown/, loader: 'raw-loader' }, + { test: /\.md/, loader: 'raw-loader' }, // Excludes {