Flow for all the files
This commit is contained in:
parent
a98199599a
commit
0a76d6af9e
28
.eslintrc
28
.eslintrc
|
@ -3,16 +3,34 @@
|
||||||
"extends": [
|
"extends": [
|
||||||
"react-app",
|
"react-app",
|
||||||
"plugin:import/errors",
|
"plugin:import/errors",
|
||||||
"plugin:import/warnings"
|
"plugin:import/warnings",
|
||||||
|
"plugin:flowtype/recommended"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"prettier"
|
"prettier",
|
||||||
|
"flowtype",
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"import/order": "warn",
|
"import/order": "warn",
|
||||||
// Prettier automatically uses the least amount of parens possible, so this
|
// Prettier automatically uses the least amount of parens possible, so this
|
||||||
// does more harm than good.
|
// does more harm than good.
|
||||||
"no-mixed-operators": "off",
|
"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.
|
// Enforce that code is formatted with prettier.
|
||||||
"prettier/prettier": [
|
"prettier/prettier": [
|
||||||
"error",
|
"error",
|
||||||
|
@ -23,7 +41,10 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"import/resolver": "webpack"
|
"import/resolver": "webpack",
|
||||||
|
"flowtype": {
|
||||||
|
"onlyFilesWithFlowAnnotation": false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"jest": true
|
"jest": true
|
||||||
|
@ -33,6 +54,7 @@
|
||||||
"SLACK_KEY": true,
|
"SLACK_KEY": true,
|
||||||
"SLACK_REDIRECT_URI": true,
|
"SLACK_REDIRECT_URI": true,
|
||||||
"DEPLOYMENT": true,
|
"DEPLOYMENT": true,
|
||||||
|
"BASE_URL": true,
|
||||||
"afterAll": true
|
"afterAll": true
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,11 @@
|
||||||
|
[include]
|
||||||
|
.*/frontend/.*
|
||||||
|
|
||||||
[ignore]
|
[ignore]
|
||||||
.*/node_modules/styled-components/.*
|
.*/node_modules/styled-components/.*
|
||||||
.*/node_modules/react-side-effect/.*
|
.*/node_modules/react-side-effect/.*
|
||||||
.*/node_modules/fbjs/.*
|
.*/node_modules/fbjs/.*
|
||||||
|
.*/node_modules/config-chain/.*
|
||||||
|
|
||||||
[libs]
|
[libs]
|
||||||
|
|
||||||
|
@ -12,8 +16,11 @@ module.system.node.resolve_dirname=node_modules
|
||||||
module.system.node.resolve_dirname=frontend
|
module.system.node.resolve_dirname=frontend
|
||||||
|
|
||||||
module.name_mapper='^\(.*\)\.s?css$' -> 'empty/object'
|
module.name_mapper='^\(.*\)\.s?css$' -> 'empty/object'
|
||||||
|
module.name_mapper='^\(.*\)\.md$' -> 'empty/object'
|
||||||
module.file_ext=.js
|
module.file_ext=.js
|
||||||
module.file_ext=.scss
|
module.file_ext=.scss
|
||||||
|
module.file_ext=.md
|
||||||
|
module.file_ext=.json
|
||||||
|
|
||||||
esproposal.decorators=ignore
|
esproposal.decorators=ignore
|
||||||
esproposal.class_static_fields=enable
|
esproposal.class_static_fields=enable
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
yarn flow
|
|
|
@ -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;
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { Flex } from 'reflexbox';
|
import { Flex } from 'reflexbox';
|
||||||
import classNames from 'classnames/bind';
|
import classNames from 'classnames/bind';
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Alert from './Alert';
|
import Alert from './Alert';
|
||||||
export default Alert;
|
export default Alert;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import Link from 'react-router/lib/Link';
|
import Link from 'react-router/lib/Link';
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import DocumentLink from './DocumentLink';
|
import DocumentLink from './DocumentLink';
|
||||||
export default DocumentLink;
|
export default DocumentLink;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import AtlasPreview from './AtlasPreview';
|
import AtlasPreview from './AtlasPreview';
|
||||||
export default AtlasPreview;
|
export default AtlasPreview;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||||
import styled, { keyframes } from 'styled-components';
|
import styled, { keyframes } from 'styled-components';
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import AtlasPreviewLoading from './AtlasPreviewLoading';
|
import AtlasPreviewLoading from './AtlasPreviewLoading';
|
||||||
export default AtlasPreviewLoading;
|
export default AtlasPreviewLoading;
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
import Button from './Button';
|
|
||||||
export default Button;
|
|
|
@ -3,9 +3,9 @@ import React from 'react';
|
||||||
import styles from './CenteredContent.scss';
|
import styles from './CenteredContent.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: any,
|
children?: React.Element<any>,
|
||||||
style: Object,
|
style?: Object,
|
||||||
maxWidth: string,
|
maxWidth?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const CenteredContent = (props: Props) => {
|
const CenteredContent = (props: Props) => {
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import CenteredContent from './CenteredContent';
|
import CenteredContent from './CenteredContent';
|
||||||
export default CenteredContent;
|
export default CenteredContent;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import styles from './Divider.scss';
|
import styles from './Divider.scss';
|
||||||
|
|
||||||
const Divider = props => {
|
const Divider = () => {
|
||||||
return <div className={styles.divider}><span /></div>;
|
return <div className={styles.divider}><span /></div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Divider from './Divider';
|
import Divider from './Divider';
|
||||||
export default Divider;
|
export default Divider;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
@ -18,9 +19,11 @@ import styles from './DocumentHtml.scss';
|
||||||
};
|
};
|
||||||
|
|
||||||
setExternalLinks = () => {
|
setExternalLinks = () => {
|
||||||
|
// $FlowFixMe
|
||||||
const links = ReactDOM.findDOMNode(this).querySelectorAll('a');
|
const links = ReactDOM.findDOMNode(this).querySelectorAll('a');
|
||||||
links.forEach(link => {
|
links.forEach(link => {
|
||||||
if (link.hostname !== window.location.hostname) {
|
if (link.hostname !== window.location.hostname) {
|
||||||
|
// $FlowFixMe
|
||||||
link.target = '_blank'; // eslint-disable-line no-param-reassign
|
link.target = '_blank'; // eslint-disable-line no-param-reassign
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import DocumentHtml from './DocumentHtml';
|
import DocumentHtml from './DocumentHtml';
|
||||||
export default DocumentHtml;
|
export default DocumentHtml;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import Document from './Document';
|
import Document from './Document';
|
||||||
import DocumentHtml from './components/DocumentHtml';
|
import DocumentHtml from './components/DocumentHtml';
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import DocumentList from './DocumentList';
|
import DocumentList from './DocumentList';
|
||||||
export default DocumentList;
|
export default DocumentList;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { toJS } from 'mobx';
|
import { toJS } from 'mobx';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import DocumentPreview from './DocumentPreview';
|
import DocumentPreview from './DocumentPreview';
|
||||||
export default DocumentPreview;
|
export default DocumentPreview;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import styles from './MoreIcon.scss';
|
import styles from './MoreIcon.scss';
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import MoreIcon from './MoreIcon';
|
import MoreIcon from './MoreIcon';
|
||||||
export default MoreIcon;
|
export default MoreIcon;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import DropdownMenu, { MenuItem } from './DropdownMenu';
|
import DropdownMenu, { MenuItem } from './DropdownMenu';
|
||||||
import MoreIcon from './components/MoreIcon';
|
import MoreIcon from './components/MoreIcon';
|
||||||
export default DropdownMenu;
|
export default DropdownMenu;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { browserHistory, Link } from 'react-router';
|
import { browserHistory, Link } from 'react-router';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
@ -10,24 +11,25 @@ import { Flex } from 'reflexbox';
|
||||||
import DropdownMenu, { MenuItem } from 'components/DropdownMenu';
|
import DropdownMenu, { MenuItem } from 'components/DropdownMenu';
|
||||||
|
|
||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
|
import UserStore from 'stores/UserStore';
|
||||||
|
|
||||||
import styles from './Layout.scss';
|
import styles from './Layout.scss';
|
||||||
import classNames from 'classnames/bind';
|
import classNames from 'classnames/bind';
|
||||||
const cx = classNames.bind(styles);
|
const cx = classNames.bind(styles);
|
||||||
|
|
||||||
@inject('user')
|
type Props = {
|
||||||
@observer
|
children?: ?React.Element<any>,
|
||||||
class Layout extends React.Component {
|
actions?: ?React.Element<any>,
|
||||||
static propTypes = {
|
title?: ?React.Element<any>,
|
||||||
children: React.PropTypes.node,
|
titleText?: string,
|
||||||
actions: React.PropTypes.node,
|
loading?: boolean,
|
||||||
title: React.PropTypes.node,
|
user: UserStore,
|
||||||
titleText: React.PropTypes.node,
|
search: ?boolean,
|
||||||
loading: React.PropTypes.bool,
|
notifications?: React.Element<any>,
|
||||||
user: React.PropTypes.object.isRequired,
|
};
|
||||||
search: React.PropTypes.bool,
|
|
||||||
notifications: React.PropTypes.node,
|
@observer class Layout extends React.Component {
|
||||||
};
|
props: Props;
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
search: true,
|
search: true,
|
||||||
|
@ -114,4 +116,4 @@ const Avatar = styled.img`
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default Layout;
|
export default inject('user')(Layout);
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import styles from './HeaderAction.scss';
|
import styles from './HeaderAction.scss';
|
||||||
|
|
||||||
const HeaderAction = props => {
|
type Props = { onClick?: ?Function, children?: ?React.Element<any> };
|
||||||
|
|
||||||
|
const HeaderAction = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div onClick={props.onClick} className={styles.container}>
|
<div onClick={props.onClick} className={styles.container}>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
@ -10,8 +13,4 @@ const HeaderAction = props => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
HeaderAction.propTypes = {
|
|
||||||
onClick: React.PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default HeaderAction;
|
export default HeaderAction;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import HeaderAction from './HeaderAction';
|
import HeaderAction from './HeaderAction';
|
||||||
export default HeaderAction;
|
export default HeaderAction;
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
@observer class SaveAction extends React.Component {
|
type Props = {
|
||||||
static propTypes = {
|
onClick: Function,
|
||||||
onClick: React.PropTypes.func.isRequired,
|
disabled?: boolean,
|
||||||
disabled: React.PropTypes.bool,
|
isNew?: boolean,
|
||||||
isNew: React.PropTypes.bool,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
onClick = event => {
|
@observer class SaveAction extends React.Component {
|
||||||
|
props: Props;
|
||||||
|
|
||||||
|
onClick = (event: MouseEvent) => {
|
||||||
if (this.props.disabled) return;
|
if (this.props.disabled) return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import SaveAction from './SaveAction';
|
import SaveAction from './SaveAction';
|
||||||
export default SaveAction;
|
export default SaveAction;
|
||||||
|
|
|
@ -4,9 +4,9 @@ import _ from 'lodash';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: string,
|
content: string,
|
||||||
truncate?: number,
|
truncate?: number,
|
||||||
placeholder: string,
|
placeholder?: ?string,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Title extends React.Component {
|
class Title extends React.Component {
|
||||||
|
@ -15,9 +15,9 @@ class Title extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
let title;
|
let title;
|
||||||
if (this.props.truncate) {
|
if (this.props.truncate) {
|
||||||
title = _.truncate(this.props.children, this.props.truncate);
|
title = _.truncate(this.props.content, this.props.truncate);
|
||||||
} else {
|
} else {
|
||||||
title = this.props.children;
|
title = this.props.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
let usePlaceholder;
|
let usePlaceholder;
|
||||||
|
@ -29,7 +29,7 @@ class Title extends React.Component {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{title && <span> / </span>}
|
{title && <span> / </span>}
|
||||||
<TitleText title={this.props.children} untitled={usePlaceholder}>
|
<TitleText title={this.props.content} untitled={usePlaceholder}>
|
||||||
{title}
|
{title}
|
||||||
</TitleText>
|
</TitleText>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import Layout from './Layout';
|
import Layout from './Layout';
|
||||||
import Title from './components/Title';
|
import Title from './components/Title';
|
||||||
import HeaderAction from './components/HeaderAction';
|
import HeaderAction from './components/HeaderAction';
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import styles from './LoadingIndicator.scss';
|
import styles from './LoadingIndicator.scss';
|
||||||
|
|
||||||
const LoadingIndicator = props => {
|
const LoadingIndicator = () => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.loading}>
|
<div className={styles.loading}>
|
||||||
<div className={styles.loader} />
|
<div className={styles.loader} />
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import LoadingIndicator from './LoadingIndicator';
|
import LoadingIndicator from './LoadingIndicator';
|
||||||
export default LoadingIndicator;
|
export default LoadingIndicator;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import Codemirror from 'react-codemirror';
|
import Codemirror from 'react-codemirror';
|
||||||
|
@ -28,13 +29,13 @@ import { client } from 'utils/ApiClient';
|
||||||
toggleUploadingIndicator: React.PropTypes.func,
|
toggleUploadingIndicator: React.PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
onChange = newText => {
|
onChange = (newText: string) => {
|
||||||
if (newText !== this.props.text) {
|
if (newText !== this.props.text) {
|
||||||
this.props.onChange(newText);
|
this.props.onChange(newText);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onDropAccepted = files => {
|
onDropAccepted = (files: Object[]) => {
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
const editor = this.getEditorInstance();
|
const editor = this.getEditorInstance();
|
||||||
|
|
||||||
|
@ -62,6 +63,7 @@ import { client } from 'utils/ApiClient';
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
// $FlowFixMe need to augment ApiClient
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
// Upload using FormData API
|
// Upload using FormData API
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
@ -77,7 +79,7 @@ import { client } from 'utils/ApiClient';
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(data.uploadUrl, {
|
fetch(data.uploadUrl, {
|
||||||
method: 'post',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then(_s3Response => {
|
.then(_s3Response => {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import styles from './ClickablePadding.scss';
|
import styles from './ClickablePadding.scss';
|
||||||
|
|
||||||
const ClickablePadding = props => {
|
const ClickablePadding = (props: { onClick: Function }) => {
|
||||||
return <div className={styles.container} onClick={props.onClick}> </div>;
|
return <div className={styles.container} onClick={props.onClick}> </div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import ClickablePadding from './ClickablePadding';
|
import ClickablePadding from './ClickablePadding';
|
||||||
export default ClickablePadding;
|
export default ClickablePadding;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import MarkdownEditor from './MarkdownEditor';
|
import MarkdownEditor from './MarkdownEditor';
|
||||||
export default MarkdownEditor;
|
export default MarkdownEditor;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import PublishingInfo from './PublishingInfo';
|
import PublishingInfo from './PublishingInfo';
|
||||||
export default PublishingInfo;
|
export default PublishingInfo;
|
||||||
|
|
|
@ -3,15 +3,15 @@ import React from 'react';
|
||||||
import { observer, inject } from 'mobx-react';
|
import { observer, inject } from 'mobx-react';
|
||||||
import UserStore from 'stores/UserStore';
|
import UserStore from 'stores/UserStore';
|
||||||
|
|
||||||
@inject('user')
|
type Props = {
|
||||||
@observer
|
children: React.Element<any>,
|
||||||
class SlackAuthLink extends React.Component {
|
scopes?: Array<string>,
|
||||||
props: {
|
|
||||||
children: any,
|
|
||||||
scopes: Array<string>,
|
|
||||||
user: UserStore,
|
user: UserStore,
|
||||||
redirectUri: string,
|
redirectUri: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@observer class SlackAuthLink extends React.Component {
|
||||||
|
props: Props;
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
scopes: [
|
scopes: [
|
||||||
|
@ -25,10 +25,8 @@ class SlackAuthLink extends React.Component {
|
||||||
slackUrl = () => {
|
slackUrl = () => {
|
||||||
const baseUrl = 'https://slack.com/oauth/authorize';
|
const baseUrl = 'https://slack.com/oauth/authorize';
|
||||||
const params = {
|
const params = {
|
||||||
// $FlowIssue global variable
|
|
||||||
client_id: SLACK_KEY,
|
client_id: SLACK_KEY,
|
||||||
scope: this.props.scopes.join(' '),
|
scope: this.props.scopes ? this.props.scopes.join(' ') : '',
|
||||||
// $FlowIssue global variable
|
|
||||||
redirect_uri: this.props.redirectUri || SLACK_REDIRECT_URI,
|
redirect_uri: this.props.redirectUri || SLACK_REDIRECT_URI,
|
||||||
state: this.props.user.getOauthState(),
|
state: this.props.user.getOauthState(),
|
||||||
};
|
};
|
||||||
|
@ -47,4 +45,4 @@ class SlackAuthLink extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SlackAuthLink;
|
export default inject('user')(SlackAuthLink);
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import SlackAuthLink from './SlackAuthLink';
|
import SlackAuthLink from './SlackAuthLink';
|
||||||
export default SlackAuthLink;
|
export default SlackAuthLink;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import history from 'utils/History';
|
import history from 'utils/History';
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable */
|
||||||
const Tree = require('js-tree');
|
const Tree = require('js-tree');
|
||||||
const proto = Tree.prototype;
|
const proto = Tree.prototype;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable */
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const Tree = require('./Tree');
|
const Tree = require('./Tree');
|
||||||
const Node = require('./Node');
|
const Node = require('./Node');
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import UiTree from './UiTree';
|
import UiTree from './UiTree';
|
||||||
export default UiTree;
|
export default UiTree;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import { Provider } from 'mobx-react';
|
import { Provider } from 'mobx-react';
|
||||||
|
@ -115,7 +116,7 @@ render(
|
||||||
</Route>
|
</Route>
|
||||||
</Router>
|
</Router>
|
||||||
</Provider>
|
</Provider>
|
||||||
{__DEV__ && <DevTools position={{ bottom: 0, right: 0 }} />}
|
{DevTools && <DevTools position={{ bottom: 0, right: 0 }} />}
|
||||||
</div>,
|
</div>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React, { PropTypes } from 'react';
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
import keydown from 'react-keydown';
|
import keydown from 'react-keydown';
|
||||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
// TODO move here argh
|
||||||
import store from './AtlasStore';
|
import store from './AtlasStore';
|
||||||
|
|
||||||
import Layout, { Title } from 'components/Layout';
|
import Layout, { Title } from 'components/Layout';
|
||||||
|
@ -17,12 +19,15 @@ import { Flex } from 'reflexbox';
|
||||||
|
|
||||||
import styles from './Atlas.scss';
|
import styles from './Atlas.scss';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
params: Object,
|
||||||
|
keydown: Object,
|
||||||
|
};
|
||||||
|
|
||||||
@keydown(['c'])
|
@keydown(['c'])
|
||||||
@observer
|
@observer
|
||||||
class Atlas extends React.Component {
|
class Atlas extends React.Component {
|
||||||
static propTypes = {
|
props: Props;
|
||||||
params: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount = () => {
|
componentDidMount = () => {
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
|
@ -34,7 +39,7 @@ class Atlas extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps = nextProps => {
|
componentWillReceiveProps = (nextProps: Props) => {
|
||||||
const key = nextProps.keydown.event;
|
const key = nextProps.keydown.event;
|
||||||
if (key) {
|
if (key) {
|
||||||
if (key.key === 'c') {
|
if (key.key === 'c') {
|
||||||
|
@ -43,9 +48,9 @@ class Atlas extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onCreate = event => {
|
onCreate = (event: Event) => {
|
||||||
if (event) event.preventDefault();
|
if (event) event.preventDefault();
|
||||||
browserHistory.push(`${store.collection.url}/new`);
|
store.collection && browserHistory.push(`${store.collection.url}/new`);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -65,7 +70,7 @@ class Atlas extends React.Component {
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
title = <Title>{collection.name}</Title>;
|
title = <Title content={collection.name} />;
|
||||||
titleText = collection.name;
|
titleText = collection.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +86,8 @@ class Atlas extends React.Component {
|
||||||
>
|
>
|
||||||
{store.isFetching
|
{store.isFetching
|
||||||
? <AtlasPreviewLoading />
|
? <AtlasPreviewLoading />
|
||||||
: <div className={styles.container}>
|
: collection &&
|
||||||
|
<div className={styles.container}>
|
||||||
<div className={styles.atlasDetails}>
|
<div className={styles.atlasDetails}>
|
||||||
<h2>{collection.name}</h2>
|
<h2>{collection.name}</h2>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
|
|
|
@ -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 { client } from 'utils/ApiClient';
|
||||||
|
import type { Collection } from 'types';
|
||||||
|
|
||||||
const store = new class AtlasStore {
|
const store = new class AtlasStore {
|
||||||
@observable collection;
|
@observable collection: ?(Collection & { recentDocuments?: Object[] });
|
||||||
|
|
||||||
@observable isFetching = true;
|
@observable isFetching = true;
|
||||||
|
|
||||||
|
/* Computed */
|
||||||
|
|
||||||
|
@computed get isLoaded(): boolean {
|
||||||
|
return !this.isFetching && !!this.collection;
|
||||||
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
@action fetchCollection = async (id, successCallback) => {
|
@action fetchCollection = async (id: string, successCallback: Function) => {
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
this.collection = null;
|
this.collection = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await client.get('/collections.info', { id });
|
const res = await client.get('/collections.info', { id });
|
||||||
|
invariant(res && res.data, 'Data should be available');
|
||||||
const { data } = res;
|
const { data } = res;
|
||||||
this.collection = data;
|
this.collection = data;
|
||||||
successCallback(data);
|
successCallback(data);
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Atlas from './Atlas';
|
import Atlas from './Atlas';
|
||||||
export default Atlas;
|
export default Atlas;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { observable, action, runInAction } from 'mobx';
|
import { observable, action, runInAction } from 'mobx';
|
||||||
|
import invariant from 'invariant';
|
||||||
import { client } from 'utils/ApiClient';
|
import { client } from 'utils/ApiClient';
|
||||||
import type { Pagination, Collection } from 'types';
|
import type { Pagination, Collection } from 'types';
|
||||||
|
|
||||||
|
@ -23,6 +24,10 @@ class DashboardStore {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await client.post('/collections.list', { id: this.team.id });
|
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;
|
const { data, pagination } = res;
|
||||||
runInAction('fetchCollections', () => {
|
runInAction('fetchCollections', () => {
|
||||||
this.collections = data;
|
this.collections = data;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Dashboard from './Dashboard';
|
import Dashboard from './Dashboard';
|
||||||
export default Dashboard;
|
export default Dashboard;
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
|
// @flow
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { browserHistory, withRouter } from 'react-router';
|
import { browserHistory, withRouter } from 'react-router';
|
||||||
import keydown from 'react-keydown';
|
import keydown from 'react-keydown';
|
||||||
|
import { Flex } from 'reflexbox';
|
||||||
|
|
||||||
import DocumentEditStore, { DOCUMENT_EDIT_SETTINGS } from './DocumentEditStore';
|
import DocumentEditStore, { DOCUMENT_EDIT_SETTINGS } from './DocumentEditStore';
|
||||||
|
import EditorLoader from './components/EditorLoader';
|
||||||
|
|
||||||
import Layout, { Title, HeaderAction, SaveAction } from 'components/Layout';
|
import Layout, { Title, HeaderAction, SaveAction } from 'components/Layout';
|
||||||
import { Flex } from 'reflexbox';
|
|
||||||
import AtlasPreviewLoading from 'components/AtlasPreviewLoading';
|
import AtlasPreviewLoading from 'components/AtlasPreviewLoading';
|
||||||
import CenteredContent from 'components/CenteredContent';
|
import CenteredContent from 'components/CenteredContent';
|
||||||
import DropdownMenu, { MenuItem, MoreIcon } from 'components/DropdownMenu';
|
import DropdownMenu, { MenuItem, MoreIcon } from 'components/DropdownMenu';
|
||||||
|
|
||||||
import EditorLoader from './components/EditorLoader';
|
|
||||||
|
|
||||||
const DISREGARD_CHANGES = `You have unsaved changes.
|
const DISREGARD_CHANGES = `You have unsaved changes.
|
||||||
Are you sure you want to disgard them?`;
|
Are you sure you want to disgard them?`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
route: Object,
|
||||||
|
router: Object,
|
||||||
|
params: Object,
|
||||||
|
keydown: Object,
|
||||||
|
};
|
||||||
|
|
||||||
@keydown([
|
@keydown([
|
||||||
'cmd+enter',
|
'cmd+enter',
|
||||||
'ctrl+enter',
|
'ctrl+enter',
|
||||||
|
@ -27,13 +35,10 @@ Are you sure you want to disgard them?`;
|
||||||
@withRouter
|
@withRouter
|
||||||
@observer
|
@observer
|
||||||
class DocumentEdit extends Component {
|
class DocumentEdit extends Component {
|
||||||
static propTypes = {
|
store: DocumentEditStore;
|
||||||
route: React.PropTypes.object.isRequired,
|
props: Props;
|
||||||
router: React.PropTypes.object.isRequired,
|
|
||||||
params: React.PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.store = new DocumentEditStore(
|
this.store = new DocumentEditStore(
|
||||||
JSON.parse(localStorage[DOCUMENT_EDIT_SETTINGS] || '{}')
|
JSON.parse(localStorage[DOCUMENT_EDIT_SETTINGS] || '{}')
|
||||||
|
@ -60,6 +65,7 @@ class DocumentEdit extends Component {
|
||||||
|
|
||||||
// Load editor async
|
// Load editor async
|
||||||
EditorLoader().then(({ Editor }) => {
|
EditorLoader().then(({ Editor }) => {
|
||||||
|
// $FlowIssue we can remove after moving to new editor
|
||||||
this.setState({ Editor });
|
this.setState({ Editor });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,7 +78,7 @@ class DocumentEdit extends Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps = nextProps => {
|
componentWillReceiveProps = (nextProps: Props) => {
|
||||||
const key = nextProps.keydown.event;
|
const key = nextProps.keydown.event;
|
||||||
|
|
||||||
if (key) {
|
if (key) {
|
||||||
|
@ -109,7 +115,7 @@ class DocumentEdit extends Component {
|
||||||
browserHistory.goBack();
|
browserHistory.goBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
onScroll = scrollTop => {
|
onScroll = (scrollTop: number) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
scrollTop,
|
scrollTop,
|
||||||
});
|
});
|
||||||
|
@ -119,10 +125,9 @@ class DocumentEdit extends Component {
|
||||||
const title = (
|
const title = (
|
||||||
<Title
|
<Title
|
||||||
truncate={60}
|
truncate={60}
|
||||||
placeholder={!this.store.isFetching && 'Untitled document'}
|
placeholder={!this.store.isFetching ? 'Untitled document' : null}
|
||||||
>
|
content={this.store.title}
|
||||||
{this.store.title}
|
/>
|
||||||
</Title>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const titleText = this.store.title;
|
const titleText = this.store.title;
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
// @flow
|
||||||
import { observable, action, toJS, autorun } from 'mobx';
|
import { observable, action, toJS, autorun } from 'mobx';
|
||||||
import { client } from 'utils/ApiClient';
|
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
|
import invariant from 'invariant';
|
||||||
|
import { client } from 'utils/ApiClient';
|
||||||
import emojify from 'utils/emojify';
|
import emojify from 'utils/emojify';
|
||||||
|
import type { Document } from 'types';
|
||||||
|
|
||||||
const DOCUMENT_EDIT_SETTINGS = 'DOCUMENT_EDIT_SETTINGS';
|
const DOCUMENT_EDIT_SETTINGS = 'DOCUMENT_EDIT_SETTINGS';
|
||||||
|
|
||||||
|
@ -22,17 +25,17 @@ const parseHeader = text => {
|
||||||
class DocumentEditStore {
|
class DocumentEditStore {
|
||||||
@observable documentId = null;
|
@observable documentId = null;
|
||||||
@observable collectionId = null;
|
@observable collectionId = null;
|
||||||
@observable parentDocument;
|
@observable parentDocument: ?Document;
|
||||||
@observable title;
|
@observable title: string;
|
||||||
@observable text;
|
@observable text: string;
|
||||||
@observable hasPendingChanges = false;
|
@observable hasPendingChanges = false;
|
||||||
@observable newDocument;
|
@observable newDocument: ?boolean;
|
||||||
@observable newChildDocument;
|
@observable newChildDocument: ?boolean;
|
||||||
|
|
||||||
@observable preview;
|
@observable preview: ?boolean = false;
|
||||||
@observable isFetching;
|
@observable isFetching: boolean = false;
|
||||||
@observable isSaving;
|
@observable isSaving: boolean = false;
|
||||||
@observable isUploading;
|
@observable isUploading: boolean = false;
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
|
@ -40,17 +43,18 @@ class DocumentEditStore {
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await client.get(
|
const res = await client.get(
|
||||||
'/documents.info',
|
'/documents.info',
|
||||||
{
|
{
|
||||||
id: this.documentId,
|
id: this.documentId,
|
||||||
},
|
},
|
||||||
{ cache: true }
|
{ cache: true }
|
||||||
);
|
);
|
||||||
|
invariant(res && res.data, 'Data shoule be available');
|
||||||
if (this.newChildDocument) {
|
if (this.newChildDocument) {
|
||||||
this.parentDocument = data.data;
|
this.parentDocument = res.data;
|
||||||
} else {
|
} else {
|
||||||
const { title, text } = data.data;
|
const { title, text } = res.data;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
@ -66,17 +70,19 @@ class DocumentEditStore {
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await client.post(
|
const res = await client.post(
|
||||||
'/documents.create',
|
'/documents.create',
|
||||||
{
|
{
|
||||||
parentDocument: this.parentDocument && this.parentDocument.id,
|
parentDocument: this.parentDocument && this.parentDocument.id,
|
||||||
|
// $FlowFixMe this logic will probably get rewritten soon anyway
|
||||||
collection: this.collectionId || this.parentDocument.collection.id,
|
collection: this.collectionId || this.parentDocument.collection.id,
|
||||||
title: this.title || 'Untitled document',
|
title: this.title || 'Untitled document',
|
||||||
text: this.text,
|
text: this.text,
|
||||||
},
|
},
|
||||||
{ cache: true }
|
{ cache: true }
|
||||||
);
|
);
|
||||||
const { url } = data.data;
|
invariant(res && res.data, 'Data shoule be available');
|
||||||
|
const { url } = res.data;
|
||||||
|
|
||||||
this.hasPendingChanges = false;
|
this.hasPendingChanges = false;
|
||||||
browserHistory.push(url);
|
browserHistory.push(url);
|
||||||
|
@ -92,7 +98,7 @@ class DocumentEditStore {
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await client.post(
|
const res = await client.post(
|
||||||
'/documents.update',
|
'/documents.update',
|
||||||
{
|
{
|
||||||
id: this.documentId,
|
id: this.documentId,
|
||||||
|
@ -101,7 +107,8 @@ class DocumentEditStore {
|
||||||
},
|
},
|
||||||
{ cache: true }
|
{ cache: true }
|
||||||
);
|
);
|
||||||
const { url } = data.data;
|
invariant(res && res.data, 'Data shoule be available');
|
||||||
|
const { url } = res.data;
|
||||||
|
|
||||||
this.hasPendingChanges = false;
|
this.hasPendingChanges = false;
|
||||||
browserHistory.push(url);
|
browserHistory.push(url);
|
||||||
|
@ -111,17 +118,17 @@ class DocumentEditStore {
|
||||||
this.isSaving = false;
|
this.isSaving = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action updateText = text => {
|
@action updateText = (text: string) => {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.title = parseHeader(text);
|
this.title = parseHeader(text);
|
||||||
this.hasPendingChanges = true;
|
this.hasPendingChanges = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action updateTitle = title => {
|
@action updateTitle = (title: string) => {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action replaceText = args => {
|
@action replaceText = (args: { original: string, new: string }) => {
|
||||||
this.text = this.text.replace(args.original, args.new);
|
this.text = this.text.replace(args.original, args.new);
|
||||||
this.hasPendingChanges = true;
|
this.hasPendingChanges = true;
|
||||||
};
|
};
|
||||||
|
@ -147,7 +154,7 @@ class DocumentEditStore {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(settings) {
|
constructor(settings: { preview: ?boolean }) {
|
||||||
// Rehydrate settings
|
// Rehydrate settings
|
||||||
this.preview = settings.preview;
|
this.preview = settings.preview;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { convertToMarkdown } from 'utils/markdown';
|
import { convertToMarkdown } from 'utils/markdown';
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
// @flow
|
||||||
export default () => {
|
export default () => {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
// $FlowIssue this is available with webpack
|
||||||
require.ensure([], () => {
|
require.ensure([], () => {
|
||||||
resolve({
|
resolve({
|
||||||
Editor: require('./Editor').default,
|
Editor: require('./Editor').default,
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import styles from '../DocumentEdit.scss';
|
import styles from '../DocumentEdit.scss';
|
||||||
import classNames from 'classnames/bind';
|
import classNames from 'classnames/bind';
|
||||||
const cx = classNames.bind(styles);
|
const cx = classNames.bind(styles);
|
||||||
|
|
||||||
class EditorPane extends React.Component {
|
type Props = {
|
||||||
static propTypes = {
|
children?: ?React.Element<any>,
|
||||||
children: React.PropTypes.node.isRequired,
|
onScroll?: Function,
|
||||||
onScroll: React.PropTypes.func.isRequired,
|
scrollTop?: ?number,
|
||||||
scrollTop: React.PropTypes.number,
|
fullWidth?: ?boolean,
|
||||||
fullWidth: React.PropTypes.bool,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
componentWillReceiveProps = nextProps => {
|
class EditorPane extends React.Component {
|
||||||
|
props: Props;
|
||||||
|
|
||||||
|
componentWillReceiveProps = (nextProps: Props) => {
|
||||||
if (nextProps.scrollTop) {
|
if (nextProps.scrollTop) {
|
||||||
this.scrollToPosition(nextProps.scrollTop);
|
this.scrollToPosition(nextProps.scrollTop);
|
||||||
}
|
}
|
||||||
|
@ -26,15 +29,16 @@ class EditorPane extends React.Component {
|
||||||
this.refs.pane.removeEventListener('scroll', this.handleScroll);
|
this.refs.pane.removeEventListener('scroll', this.handleScroll);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleScroll = e => {
|
handleScroll = (e: Event) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const element = this.refs.pane;
|
const element = this.refs.pane;
|
||||||
const contentEl = this.refs.content;
|
const contentEl = this.refs.content;
|
||||||
|
this.props.onScroll &&
|
||||||
this.props.onScroll(element.scrollTop / contentEl.offsetHeight);
|
this.props.onScroll(element.scrollTop / contentEl.offsetHeight);
|
||||||
}, 50);
|
}, 50);
|
||||||
};
|
};
|
||||||
|
|
||||||
scrollToPosition = percentage => {
|
scrollToPosition = (percentage: number) => {
|
||||||
const contentEl = this.refs.content;
|
const contentEl = this.refs.content;
|
||||||
|
|
||||||
// Push to edges
|
// Push to edges
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { DocumentHtml } from 'components/Document';
|
import { DocumentHtml } from 'components/Document';
|
||||||
|
@ -6,7 +7,11 @@ import styles from './Preview.scss';
|
||||||
import classNames from 'classnames/bind';
|
import classNames from 'classnames/bind';
|
||||||
const cx = classNames.bind(styles);
|
const cx = classNames.bind(styles);
|
||||||
|
|
||||||
const Preview = props => {
|
type Props = {
|
||||||
|
html: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Preview = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className={cx(styles.container)}>
|
<div className={cx(styles.container)}>
|
||||||
<DocumentHtml html={props.html} />
|
<DocumentHtml html={props.html} />
|
||||||
|
@ -14,8 +19,4 @@ const Preview = props => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Preview.propTypes = {
|
|
||||||
html: React.PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Preview;
|
export default Preview;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Preview from './Preview';
|
import Preview from './Preview';
|
||||||
export default Preview;
|
export default Preview;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import DocumentEdit from './DocumentEdit';
|
import DocumentEdit from './DocumentEdit';
|
||||||
export default DocumentEdit;
|
export default DocumentEdit;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
// @flow
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
import invariant from 'invariant';
|
||||||
import { Link, browserHistory } from 'react-router';
|
import { Link, browserHistory } from 'react-router';
|
||||||
import { observer, inject } from 'mobx-react';
|
import { observer, inject } from 'mobx-react';
|
||||||
import { toJS } from 'mobx';
|
import { toJS } from 'mobx';
|
||||||
|
@ -6,6 +8,7 @@ import keydown from 'react-keydown';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import DocumentSceneStore, { DOCUMENT_PREFERENCES } from './DocumentSceneStore';
|
import DocumentSceneStore, { DOCUMENT_PREFERENCES } from './DocumentSceneStore';
|
||||||
|
import UiStore from 'stores/UiStore';
|
||||||
|
|
||||||
import Layout from 'components/Layout';
|
import Layout from 'components/Layout';
|
||||||
import AtlasPreviewLoading from 'components/AtlasPreviewLoading';
|
import AtlasPreviewLoading from 'components/AtlasPreviewLoading';
|
||||||
|
@ -16,13 +19,20 @@ import { Flex } from 'reflexbox';
|
||||||
import Sidebar from './components/Sidebar';
|
import Sidebar from './components/Sidebar';
|
||||||
|
|
||||||
import styles from './DocumentScene.scss';
|
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'])
|
@keydown(['cmd+/', 'ctrl+/', 'c', 'e'])
|
||||||
@inject('ui')
|
@inject('ui')
|
||||||
@observer
|
@observer
|
||||||
class DocumentScene extends React.Component {
|
class DocumentScene extends React.Component {
|
||||||
|
store: DocumentSceneStore;
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ui: PropTypes.object.isRequired,
|
ui: PropTypes.object.isRequired,
|
||||||
routeParams: PropTypes.object,
|
routeParams: PropTypes.object,
|
||||||
|
@ -30,7 +40,7 @@ class DocumentScene extends React.Component {
|
||||||
location: PropTypes.object.isRequired,
|
location: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.store = new DocumentSceneStore(
|
this.store = new DocumentSceneStore(
|
||||||
JSON.parse(localStorage[DOCUMENT_PREFERENCES] || '{}')
|
JSON.parse(localStorage[DOCUMENT_PREFERENCES] || '{}')
|
||||||
|
@ -41,15 +51,16 @@ class DocumentScene extends React.Component {
|
||||||
didScroll: false,
|
didScroll: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount = async () => {
|
componentDidMount = () => {
|
||||||
const { id } = this.props.routeParams;
|
const { id } = this.props.routeParams;
|
||||||
await this.store.fetchDocument(id, {
|
this.store
|
||||||
|
.fetchDocument(id, {
|
||||||
replaceUrl: !this.props.location.hash,
|
replaceUrl: !this.props.location.hash,
|
||||||
});
|
})
|
||||||
this.scrollTohash();
|
.then(() => this.scrollTohash());
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps = async nextProps => {
|
componentWillReceiveProps = (nextProps: Props) => {
|
||||||
const key = nextProps.keydown.event;
|
const key = nextProps.keydown.event;
|
||||||
if (key) {
|
if (key) {
|
||||||
if (key.key === '/' && (key.metaKey || key.ctrl.Key)) {
|
if (key.key === '/' && (key.metaKey || key.ctrl.Key)) {
|
||||||
|
@ -57,7 +68,7 @@ class DocumentScene extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.key === 'c') {
|
if (key.key === 'c') {
|
||||||
_.defer(this.onCreate);
|
_.defer(this.onCreateDocument);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.key === 'e') {
|
if (key.key === 'e') {
|
||||||
|
@ -69,31 +80,38 @@ class DocumentScene extends React.Component {
|
||||||
const oldId = this.props.params.id;
|
const oldId = this.props.params.id;
|
||||||
const newId = nextProps.params.id;
|
const newId = nextProps.params.id;
|
||||||
if (oldId !== newId) {
|
if (oldId !== newId) {
|
||||||
await this.store.fetchDocument(newId, {
|
this.store
|
||||||
|
.fetchDocument(newId, {
|
||||||
softLoad: true,
|
softLoad: true,
|
||||||
replaceUrl: !this.props.location.hash,
|
replaceUrl: !this.props.location.hash,
|
||||||
});
|
})
|
||||||
|
.then(() => this.scrollTohash());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scrollTohash();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onEdit = () => {
|
onEdit = () => {
|
||||||
|
invariant(this.store.document, 'Document is not available');
|
||||||
const url = `${this.store.document.url}/edit`;
|
const url = `${this.store.document.url}/edit`;
|
||||||
browserHistory.push(url);
|
browserHistory.push(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
onCreateDocument = () => {
|
onCreateDocument = () => {
|
||||||
|
invariant(this.store.collectionTree, 'collectionTree is not available');
|
||||||
browserHistory.push(`${this.store.collectionTree.url}/new`);
|
browserHistory.push(`${this.store.collectionTree.url}/new`);
|
||||||
};
|
};
|
||||||
|
|
||||||
onCreateChild = () => {
|
onCreateChild = () => {
|
||||||
|
invariant(this.store.document, 'Document is not available');
|
||||||
browserHistory.push(`${this.store.document.url}/new`);
|
browserHistory.push(`${this.store.document.url}/new`);
|
||||||
};
|
};
|
||||||
|
|
||||||
onDelete = () => {
|
onDelete = () => {
|
||||||
let msg;
|
let msg;
|
||||||
if (this.store.document.collection.type === 'atlas') {
|
if (
|
||||||
|
this.store.document &&
|
||||||
|
this.store.document.collection &&
|
||||||
|
this.store.document.collection.type === 'atlas'
|
||||||
|
) {
|
||||||
msg =
|
msg =
|
||||||
"Are you sure you want to delete this document and all it's child documents (if any)?";
|
"Are you sure you want to delete this document and all it's child documents (if any)?";
|
||||||
} else {
|
} else {
|
||||||
|
@ -107,11 +125,13 @@ class DocumentScene extends React.Component {
|
||||||
|
|
||||||
onExport = () => {
|
onExport = () => {
|
||||||
const doc = this.store.document;
|
const doc = this.store.document;
|
||||||
|
if (doc) {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.textContent = 'download';
|
a.textContent = 'download';
|
||||||
a.download = `${doc.title}.md`;
|
a.download = `${doc.title}.md`;
|
||||||
a.href = `data:text/markdown;charset=UTF-8,${encodeURIComponent(doc.text)}`;
|
a.href = `data:text/markdown;charset=UTF-8,${encodeURIComponent(doc.text)}`;
|
||||||
a.click();
|
a.click();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
scrollTohash = () => {
|
scrollTohash = () => {
|
||||||
|
@ -130,6 +150,7 @@ class DocumentScene extends React.Component {
|
||||||
const { sidebar } = this.props.ui;
|
const { sidebar } = this.props.ui;
|
||||||
|
|
||||||
const doc = this.store.document;
|
const doc = this.store.document;
|
||||||
|
if (!doc) return;
|
||||||
const allowDelete =
|
const allowDelete =
|
||||||
doc &&
|
doc &&
|
||||||
doc.collection.type === 'atlas' &&
|
doc.collection.type === 'atlas' &&
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
// @flow
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
|
import invariant from 'invariant';
|
||||||
import {
|
import {
|
||||||
observable,
|
observable,
|
||||||
action,
|
action,
|
||||||
|
@ -9,26 +11,36 @@ import {
|
||||||
autorunAsync,
|
autorunAsync,
|
||||||
} from 'mobx';
|
} from 'mobx';
|
||||||
import { client } from 'utils/ApiClient';
|
import { client } from 'utils/ApiClient';
|
||||||
|
import type { Document as DocumentType, Collection } from 'types';
|
||||||
|
|
||||||
const DOCUMENT_PREFERENCES = 'DOCUMENT_PREFERENCES';
|
const DOCUMENT_PREFERENCES = 'DOCUMENT_PREFERENCES';
|
||||||
|
|
||||||
class DocumentSceneStore {
|
type Document = {
|
||||||
@observable document;
|
collection: Collection,
|
||||||
@observable collapsedNodes = [];
|
} & DocumentType;
|
||||||
|
|
||||||
@observable isFetching = true;
|
class DocumentSceneStore {
|
||||||
@observable updatingContent = false;
|
@observable document: ?Document;
|
||||||
@observable updatingStructure = false;
|
@observable collapsedNodes: string[] = [];
|
||||||
@observable isDeleting;
|
|
||||||
|
@observable isFetching: boolean = true;
|
||||||
|
@observable updatingContent: boolean = false;
|
||||||
|
@observable updatingStructure: boolean = false;
|
||||||
|
@observable isDeleting: boolean = false;
|
||||||
|
|
||||||
/* Computed */
|
/* Computed */
|
||||||
|
|
||||||
@computed get isCollection() {
|
@computed get isCollection(): boolean {
|
||||||
return this.document && this.document.collection.type === 'atlas';
|
return !!this.document && this.document.collection.type === 'atlas';
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get collectionTree() {
|
@computed get collectionTree(): ?Object {
|
||||||
if (!this.document || this.document.collection.type !== 'atlas') return;
|
if (
|
||||||
|
!this.document ||
|
||||||
|
this.document.collection ||
|
||||||
|
this.document.collection.type !== 'atlas'
|
||||||
|
)
|
||||||
|
return;
|
||||||
const tree = this.document.collection.navigationTree;
|
const tree = this.document.collection.navigationTree;
|
||||||
|
|
||||||
const collapseNodes = node => {
|
const collapseNodes = node => {
|
||||||
|
@ -45,7 +57,10 @@ class DocumentSceneStore {
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
@action fetchDocument = async (id, options = {}) => {
|
@action fetchDocument = async (
|
||||||
|
id: string,
|
||||||
|
options: { softLoad?: boolean, replaceUrl?: boolean } = {}
|
||||||
|
) => {
|
||||||
options = {
|
options = {
|
||||||
softLoad: false,
|
softLoad: false,
|
||||||
replaceUrl: true,
|
replaceUrl: true,
|
||||||
|
@ -57,6 +72,7 @@ class DocumentSceneStore {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await client.get('/documents.info', { id });
|
const res = await client.get('/documents.info', { id });
|
||||||
|
invariant(res && res.data, 'data should be available');
|
||||||
const { data } = res;
|
const { data } = res;
|
||||||
runInAction('fetchDocument', () => {
|
runInAction('fetchDocument', () => {
|
||||||
this.document = data;
|
this.document = data;
|
||||||
|
@ -70,10 +86,12 @@ class DocumentSceneStore {
|
||||||
};
|
};
|
||||||
|
|
||||||
@action deleteDocument = async () => {
|
@action deleteDocument = async () => {
|
||||||
|
if (!this.document) return;
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.post('/documents.delete', { id: this.document.id });
|
await client.post('/documents.delete', { id: this.document.id });
|
||||||
|
// $FlowFixMe don't be stupid
|
||||||
browserHistory.push(this.document.collection.url);
|
browserHistory.push(this.document.collection.url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Something went wrong');
|
console.error('Something went wrong');
|
||||||
|
@ -81,8 +99,9 @@ class DocumentSceneStore {
|
||||||
this.isFetching = false;
|
this.isFetching = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action updateNavigationTree = async tree => {
|
@action updateNavigationTree = async (tree: Object) => {
|
||||||
// Only update when tree changes
|
// Only update when tree changes
|
||||||
|
// $FlowFixMe don't be stupid
|
||||||
if (_.isEqual(toJS(tree), toJS(this.document.collection.navigationTree))) {
|
if (_.isEqual(toJS(tree), toJS(this.document.collection.navigationTree))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -91,11 +110,14 @@ class DocumentSceneStore {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await client.post('/collections.updateNavigationTree', {
|
const res = await client.post('/collections.updateNavigationTree', {
|
||||||
|
// $FlowFixMe don't be stupid
|
||||||
id: this.document.collection.id,
|
id: this.document.collection.id,
|
||||||
tree,
|
tree,
|
||||||
});
|
});
|
||||||
|
invariant(res && res.data, 'data should be available');
|
||||||
runInAction('updateNavigationTree', () => {
|
runInAction('updateNavigationTree', () => {
|
||||||
const { data } = res;
|
const { data } = res;
|
||||||
|
// $FlowFixMe don't be stupid
|
||||||
this.document.collection = data;
|
this.document.collection = data;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -104,7 +126,7 @@ class DocumentSceneStore {
|
||||||
this.updatingStructure = false;
|
this.updatingStructure = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action onNodeCollapse = nodeId => {
|
@action onNodeCollapse = (nodeId: string) => {
|
||||||
if (_.indexOf(this.collapsedNodes, nodeId) >= 0) {
|
if (_.indexOf(this.collapsedNodes, nodeId) >= 0) {
|
||||||
this.collapsedNodes = _.without(this.collapsedNodes, nodeId);
|
this.collapsedNodes = _.without(this.collapsedNodes, nodeId);
|
||||||
} else {
|
} else {
|
||||||
|
@ -120,7 +142,7 @@ class DocumentSceneStore {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(settings, options) {
|
constructor(settings: { collapsedNodes: string[] }) {
|
||||||
// Rehydrate settings
|
// Rehydrate settings
|
||||||
this.collapsedNodes = settings.collapsedNodes || [];
|
this.collapsedNodes = settings.collapsedNodes || [];
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { PropTypes } from 'react';
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import { Flex } from 'reflexbox';
|
import { Flex } from 'reflexbox';
|
||||||
|
@ -11,22 +12,25 @@ const cx = classNames.bind(styles);
|
||||||
|
|
||||||
import SidebarStore from './SidebarStore';
|
import SidebarStore from './SidebarStore';
|
||||||
|
|
||||||
@observer class Sidebar extends React.Component {
|
type Props = {
|
||||||
static propTypes = {
|
open?: boolean,
|
||||||
open: PropTypes.bool,
|
onToggle: Function,
|
||||||
onToggle: PropTypes.func.isRequired,
|
navigationTree: Object,
|
||||||
navigationTree: PropTypes.object.isRequired,
|
onNavigationUpdate: Function,
|
||||||
onNavigationUpdate: PropTypes.func.isRequired,
|
onNodeCollapse: Function,
|
||||||
onNodeCollapse: PropTypes.func.isRequired,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
@observer class Sidebar extends React.Component {
|
||||||
|
props: Props;
|
||||||
|
store: SidebarStore;
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.store = new SidebarStore();
|
this.store = new SidebarStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleEdit = e => {
|
toggleEdit = (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.store.toggleEdit();
|
this.store.toggleEdit();
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import { observable, action } from 'mobx';
|
import { observable, action } from 'mobx';
|
||||||
|
|
||||||
class SidebarStore {
|
class SidebarStore {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import styles from './Separator.scss';
|
import styles from './Separator.scss';
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Separator from './Separator';
|
import Separator from './Separator';
|
||||||
export default Separator;
|
export default Separator;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Sidebar from './Sidebar';
|
import Sidebar from './Sidebar';
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import DocumentScene from './DocumentScene';
|
import DocumentScene from './DocumentScene';
|
||||||
export default DocumentScene;
|
export default DocumentScene;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Error404 from './Error404';
|
import Error404 from './Error404';
|
||||||
export default Error404;
|
export default Error404;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import ErrorAuth from './ErrorAuth';
|
import ErrorAuth from './ErrorAuth';
|
||||||
export default ErrorAuth;
|
export default ErrorAuth;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
@ -16,7 +17,11 @@ import { convertToMarkdown } from 'utils/markdown';
|
||||||
const { title, content } = this.props.route;
|
const { title, content } = this.props.route;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout title={<Title>{title}</Title>} titleText={title} search={false}>
|
<Layout
|
||||||
|
title={<Title content={title} />}
|
||||||
|
titleText={title}
|
||||||
|
search={false}
|
||||||
|
>
|
||||||
<CenteredContent>
|
<CenteredContent>
|
||||||
<DocumentHtml html={convertToMarkdown(content)} />
|
<DocumentHtml html={convertToMarkdown(content)} />
|
||||||
</CenteredContent>
|
</CenteredContent>
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Flatpage from './Flatpage';
|
import Flatpage from './Flatpage';
|
||||||
export default Flatpage;
|
export default Flatpage;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer, inject } from 'mobx-react';
|
import { observer, inject } from 'mobx-react';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
|
@ -24,7 +25,7 @@ export default class Home extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get notifications() {
|
get notifications(): React.Element<any>[] {
|
||||||
const notifications = [];
|
const notifications = [];
|
||||||
const { state } = this.props.location;
|
const { state } = this.props.location;
|
||||||
|
|
||||||
|
@ -54,7 +55,7 @@ export default class Home extends React.Component {
|
||||||
</p>
|
</p>
|
||||||
</div>}
|
</div>}
|
||||||
<div className={styles.action}>
|
<div className={styles.action}>
|
||||||
<SlackAuthLink redirectUri={`${URL}/auth/slack`}>
|
<SlackAuthLink redirectUri={`${BASE_URL}/auth/slack`}>
|
||||||
<img
|
<img
|
||||||
alt="Sign in with Slack"
|
alt="Sign in with Slack"
|
||||||
height="40"
|
height="40"
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Home from './Home';
|
import Home from './Home';
|
||||||
export default Home;
|
export default Home;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { PropTypes } from 'react';
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { Flex } from 'reflexbox';
|
import { Flex } from 'reflexbox';
|
||||||
|
@ -11,13 +12,16 @@ import Layout, { Title } from 'components/Layout';
|
||||||
import CenteredContent from 'components/CenteredContent';
|
import CenteredContent from 'components/CenteredContent';
|
||||||
import DocumentPreview from 'components/DocumentPreview';
|
import DocumentPreview from 'components/DocumentPreview';
|
||||||
|
|
||||||
@observer class Search extends React.Component {
|
type Props = {
|
||||||
static propTypes = {
|
route: Object,
|
||||||
route: PropTypes.object.isRequired,
|
routeParams: Object,
|
||||||
routeParams: PropTypes.object.isRequired,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
@observer class Search extends React.Component {
|
||||||
|
props: Props;
|
||||||
|
store: SearchStore;
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.store = new SearchStore();
|
this.store = new SearchStore();
|
||||||
}
|
}
|
||||||
|
@ -31,7 +35,7 @@ import DocumentPreview from 'components/DocumentPreview';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get viewNotFound() {
|
get viewNotFound(): boolean {
|
||||||
const { sceneType } = this.props.route;
|
const { sceneType } = this.props.route;
|
||||||
return sceneType === 'notFound';
|
return sceneType === 'notFound';
|
||||||
}
|
}
|
||||||
|
@ -40,11 +44,7 @@ import DocumentPreview from 'components/DocumentPreview';
|
||||||
const search = _.debounce(searchTerm => {
|
const search = _.debounce(searchTerm => {
|
||||||
this.store.search(searchTerm);
|
this.store.search(searchTerm);
|
||||||
}, 250);
|
}, 250);
|
||||||
const title = (
|
const title = <Title content="Search" />;
|
||||||
<Title>
|
|
||||||
Search
|
|
||||||
</Title>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout
|
<Layout
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
|
// @flow
|
||||||
import { observable, action, runInAction } from 'mobx';
|
import { observable, action, runInAction } from 'mobx';
|
||||||
|
import invariant from 'invariant';
|
||||||
import { client } from 'utils/ApiClient';
|
import { client } from 'utils/ApiClient';
|
||||||
|
import type { Pagination, Document } from 'types';
|
||||||
|
|
||||||
class SearchStore {
|
class SearchStore {
|
||||||
@observable documents;
|
@observable documents: ?(Document[]);
|
||||||
@observable pagination;
|
@observable pagination: Pagination;
|
||||||
@observable selectedDocument;
|
@observable searchTerm: ?string = null;
|
||||||
@observable searchTerm;
|
|
||||||
|
|
||||||
@observable isFetching = false;
|
@observable isFetching = false;
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
@action search = async query => {
|
@action search = async (query: string) => {
|
||||||
this.searchTerm = query;
|
this.searchTerm = query;
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
try {
|
try {
|
||||||
const res = await client.get('/documents.search', { query });
|
const res = await client.get('/documents.search', { query });
|
||||||
|
invariant(res && res.data && res.pagination, 'API response');
|
||||||
const { data, pagination } = res;
|
const { data, pagination } = res;
|
||||||
runInAction('search document', () => {
|
runInAction('search document', () => {
|
||||||
this.documents = data;
|
this.documents = data;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
@ -8,8 +9,8 @@ import styles from './SearchField.scss';
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
onChange = event => {
|
onChange = (event: SyntheticEvent) => {
|
||||||
this.props.onChange(event.currentTarget.value);
|
event.currentTarget.value && this.props.onChange(event.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import SearchField from './SearchField';
|
import SearchField from './SearchField';
|
||||||
export default SearchField;
|
export default SearchField;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Search from './Search';
|
import Search from './Search';
|
||||||
export default Search;
|
export default Search;
|
||||||
|
|
|
@ -13,7 +13,7 @@ import CenteredContent from 'components/CenteredContent';
|
||||||
import SlackAuthLink from 'components/SlackAuthLink';
|
import SlackAuthLink from 'components/SlackAuthLink';
|
||||||
|
|
||||||
@observer class Settings extends React.Component {
|
@observer class Settings extends React.Component {
|
||||||
store = SettingsStore;
|
store: SettingsStore;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -21,13 +21,7 @@ import SlackAuthLink from 'components/SlackAuthLink';
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const title = (
|
const title = <Title content="Settings" />;
|
||||||
<Title>
|
|
||||||
Settings
|
|
||||||
</Title>
|
|
||||||
);
|
|
||||||
|
|
||||||
// $FlowIssue global variable
|
|
||||||
const showSlackSettings = DEPLOYMENT === 'hosted';
|
const showSlackSettings = DEPLOYMENT === 'hosted';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -48,8 +42,7 @@ import SlackAuthLink from 'components/SlackAuthLink';
|
||||||
|
|
||||||
<SlackAuthLink
|
<SlackAuthLink
|
||||||
scopes={['commands']}
|
scopes={['commands']}
|
||||||
redirectUri={// $FlowIssue URL is a global variable
|
redirectUri={`${BASE_URL}/auth/slack/commands`}
|
||||||
`${URL}/auth/slack/commands`}
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Add to Slack"
|
alt="Add to Slack"
|
||||||
|
@ -71,7 +64,8 @@ import SlackAuthLink from 'components/SlackAuthLink';
|
||||||
{this.store.apiKeys &&
|
{this.store.apiKeys &&
|
||||||
<table className={styles.apiKeyTable}>
|
<table className={styles.apiKeyTable}>
|
||||||
<tbody>
|
<tbody>
|
||||||
{this.store.apiKeys.map(key => (
|
{this.store.apiKeys &&
|
||||||
|
this.store.apiKeys.map(key => (
|
||||||
<ApiKeyRow
|
<ApiKeyRow
|
||||||
id={key.id}
|
id={key.id}
|
||||||
key={key.id}
|
key={key.id}
|
||||||
|
@ -106,10 +100,10 @@ class InlineForm extends React.Component {
|
||||||
placeholder: string,
|
placeholder: string,
|
||||||
buttonLabel: string,
|
buttonLabel: string,
|
||||||
name: string,
|
name: string,
|
||||||
value: string,
|
value: ?string,
|
||||||
onChange: Function,
|
onChange: Function,
|
||||||
onSubmit: Function,
|
onSubmit: Function,
|
||||||
disabled?: boolean,
|
disabled?: ?boolean,
|
||||||
};
|
};
|
||||||
validationTimeout: number;
|
validationTimeout: number;
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
|
// @flow
|
||||||
import { observable, action, runInAction } from 'mobx';
|
import { observable, action, runInAction } from 'mobx';
|
||||||
|
import invariant from 'invariant';
|
||||||
import { client } from 'utils/ApiClient';
|
import { client } from 'utils/ApiClient';
|
||||||
|
import type { ApiKey } from 'types';
|
||||||
|
|
||||||
class SearchStore {
|
class SearchStore {
|
||||||
@observable apiKeys = [];
|
@observable apiKeys: ApiKey[] = [];
|
||||||
@observable keyName;
|
@observable keyName: ?string;
|
||||||
|
|
||||||
@observable isFetching;
|
@observable isFetching: boolean = false;
|
||||||
|
|
||||||
@action fetchApiKeys = async () => {
|
@action fetchApiKeys = async () => {
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await client.post('/apiKeys.list');
|
const res = await client.post('/apiKeys.list');
|
||||||
|
invariant(res && res.data, 'Data shoule be available');
|
||||||
const { data } = res;
|
const { data } = res;
|
||||||
|
|
||||||
runInAction('fetchApiKeys', () => {
|
runInAction('fetchApiKeys', () => {
|
||||||
this.apiKeys = data;
|
this.apiKeys = data;
|
||||||
});
|
});
|
||||||
|
@ -27,8 +32,9 @@ class SearchStore {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await client.post('/apiKeys.create', {
|
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;
|
const { data } = res;
|
||||||
runInAction('createApiKey', () => {
|
runInAction('createApiKey', () => {
|
||||||
this.apiKeys.push(data);
|
this.apiKeys.push(data);
|
||||||
|
@ -40,7 +46,7 @@ class SearchStore {
|
||||||
this.isFetching = false;
|
this.isFetching = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action deleteApiKey = async id => {
|
@action deleteApiKey = async (id: string) => {
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -56,7 +62,7 @@ class SearchStore {
|
||||||
this.isFetching = false;
|
this.isFetching = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action setKeyName = value => {
|
@action setKeyName = (value: SyntheticInputEvent) => {
|
||||||
this.keyName = value.target.value;
|
this.keyName = value.target.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
|
||||||
import styles from './ApiKeyRow.scss';
|
import styles from './ApiKeyRow.scss';
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import ApiKeyRow from './ApiKeyRow';
|
import ApiKeyRow from './ApiKeyRow';
|
||||||
export default ApiKeyRow;
|
export default ApiKeyRow;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import Settings from './Settings';
|
import Settings from './Settings';
|
||||||
export default Settings;
|
export default Settings;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer, inject } from 'mobx-react';
|
import { observer, inject } from 'mobx-react';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
|
@ -12,6 +13,7 @@ class SlackAuth extends React.Component {
|
||||||
route: React.PropTypes.object.isRequired,
|
route: React.PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// $FlowIssue wtf
|
||||||
componentDidMount = async () => {
|
componentDidMount = async () => {
|
||||||
const { error, code, state } = this.props.location.query;
|
const { error, code, state } = this.props.location.query;
|
||||||
|
|
||||||
|
@ -22,6 +24,7 @@ class SlackAuth extends React.Component {
|
||||||
} else {
|
} else {
|
||||||
browserHistory.push('/auth/error');
|
browserHistory.push('/auth/error');
|
||||||
}
|
}
|
||||||
|
// $FlowIssue wtf
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
// @flow
|
||||||
import SlackAuth from './SlackAuth';
|
import SlackAuth from './SlackAuth';
|
||||||
export default SlackAuth;
|
export default SlackAuth;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import api from './api.markdown';
|
// @flow
|
||||||
import keyboard from './keyboard.markdown';
|
import api from './api.md';
|
||||||
|
import keyboard from './keyboard.md';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
api,
|
api,
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
|
// @flow
|
||||||
import { observable, action, computed } from 'mobx';
|
import { observable, action, computed } from 'mobx';
|
||||||
|
|
||||||
const UI_STORE = 'UI_STORE';
|
const UI_STORE = 'UI_STORE';
|
||||||
|
|
||||||
class UiStore {
|
class UiStore {
|
||||||
@observable sidebar;
|
@observable sidebar: boolean = false;
|
||||||
|
|
||||||
/* Computed */
|
/* Computed */
|
||||||
|
|
||||||
@computed get asJson() {
|
@computed get asJson(): string {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
sidebar: this.sidebar,
|
sidebar: this.sidebar,
|
||||||
});
|
});
|
||||||
|
@ -15,7 +16,7 @@ class UiStore {
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
@action toggleSidebar = () => {
|
@action toggleSidebar = (): void => {
|
||||||
this.sidebar = !this.sidebar;
|
this.sidebar = !this.sidebar;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
|
// @flow
|
||||||
import { observable, action, computed } from 'mobx';
|
import { observable, action, computed } from 'mobx';
|
||||||
|
import invariant from 'invariant';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
import { client } from 'utils/ApiClient';
|
import { client } from 'utils/ApiClient';
|
||||||
|
import type { User, Team } from 'types';
|
||||||
|
|
||||||
const USER_STORE = 'USER_STORE';
|
const USER_STORE = 'USER_STORE';
|
||||||
|
|
||||||
class UserStore {
|
class UserStore {
|
||||||
@observable user;
|
@observable user: ?User;
|
||||||
@observable team;
|
@observable team: ?Team;
|
||||||
|
|
||||||
@observable token;
|
@observable token: ?string;
|
||||||
@observable oauthState;
|
@observable oauthState: string;
|
||||||
|
|
||||||
@observable isLoading;
|
@observable isLoading: boolean = false;
|
||||||
|
|
||||||
/* Computed */
|
/* Computed */
|
||||||
|
|
||||||
@computed get authenticated() {
|
@computed get authenticated(): boolean {
|
||||||
return !!this.token;
|
return !!this.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get asJson() {
|
@computed get asJson(): string {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
user: this.user,
|
user: this.user,
|
||||||
team: this.team,
|
team: this.team,
|
||||||
|
@ -42,7 +45,11 @@ class UserStore {
|
||||||
return this.oauthState;
|
return this.oauthState;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action authWithSlack = async (code, state, redirectTo) => {
|
@action authWithSlack = async (
|
||||||
|
code: string,
|
||||||
|
state: string,
|
||||||
|
redirectTo: ?string
|
||||||
|
) => {
|
||||||
if (state !== this.oauthState) {
|
if (state !== this.oauthState) {
|
||||||
browserHistory.push('/auth-error');
|
browserHistory.push('/auth-error');
|
||||||
return;
|
return;
|
||||||
|
@ -56,6 +63,10 @@ class UserStore {
|
||||||
return;
|
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.user = res.data.user;
|
||||||
this.team = res.data.team;
|
this.team = res.data.team;
|
||||||
this.token = res.data.accessToken;
|
this.token = res.data.accessToken;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import { autorunAsync } from 'mobx';
|
import { autorunAsync } from 'mobx';
|
||||||
import UserStore, { USER_STORE } from './UserStore';
|
import UserStore, { USER_STORE } from './UserStore';
|
||||||
import UiStore, { UI_STORE } from './UiStore';
|
import UiStore, { UI_STORE } from './UiStore';
|
||||||
|
|
|
@ -6,12 +6,18 @@ export type User = {
|
||||||
username: string,
|
username: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Team = {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
};
|
||||||
|
|
||||||
export type Collection = {
|
export type Collection = {
|
||||||
createdAt: string,
|
createdAt: string,
|
||||||
description: ?string,
|
description: ?string,
|
||||||
id: string,
|
id: string,
|
||||||
name: string,
|
name: string,
|
||||||
type: 'atlas' | 'journal',
|
type: 'atlas' | 'journal',
|
||||||
|
navigationTree: Object, // TODO
|
||||||
updatedAt: string,
|
updatedAt: string,
|
||||||
url: string,
|
url: string,
|
||||||
};
|
};
|
||||||
|
@ -37,3 +43,9 @@ export type Pagination = {
|
||||||
nextPath: string,
|
nextPath: string,
|
||||||
offset: number,
|
offset: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiKey = {
|
||||||
|
id: string,
|
||||||
|
name: ?string,
|
||||||
|
secret: string,
|
||||||
|
};
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue