Fix prettier integration, format (#31)

* Fix prettier integration, format

* Reformat again
This commit is contained in:
Tom Moor
2017-04-27 21:48:13 -07:00
committed by GitHub
parent 51fade7439
commit 2095b3a874
74 changed files with 823 additions and 808 deletions

View File

@ -1,23 +1,16 @@
{ {
"extends": [
"airbnb",
"prettier",
"prettier/react"
],
"parser": "babel-eslint", "parser": "babel-eslint",
"extends": ["react-app", "plugin:import/errors", "plugin:import/warnings"],
"plugins": [
"prettier"
],
"rules": { "rules": {
"arrow-body-style":[0, "as-needed"], // fix `this` shortcut on ES6 classes "import/order": "warn",
"react/jsx-no-bind": 0, // Makes difficult to pass args to prop functions // Prettier automatically uses the least amount of parens possible, so this
"react/jsx-curly-spacing": [2, "always", {"spacing": { // does more harm than good.
"objectLiterals": "never" "no-mixed-operators": "off",
}}], // Spaces inside curlies, except prop={{}} // Enforce that code is formatted with prettier.
"react/prefer-stateless-function": 0, // Don't prefer stateless components "prettier/prettier": ["error", {"trailingComma": "es5", "singleQuote": true}]
"no-else-return": 0,
"new-cap": 0,
"no-param-reassign": 0,
no-unused-vars: ["error", {
"argsIgnorePattern": "^_",
}],
}, },
"settings" : { "settings" : {
"import/resolver": { "import/resolver": {
@ -34,7 +27,6 @@
SLACK_KEY: true, SLACK_KEY: true,
SLACK_REDIRECT_URI: true, SLACK_REDIRECT_URI: true,
DEPLOYMENT: true, DEPLOYMENT: true,
afterAll: true afterAll: true
}, },
} }

View File

@ -1,9 +1,8 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { Flex } from 'reflexbox'; import { Flex } from 'reflexbox';
import styles from './Alert.scss';
import classNames from 'classnames/bind'; import classNames from 'classnames/bind';
import styles from './Alert.scss';
const cx = classNames.bind(styles); const cx = classNames.bind(styles);
class Alert extends React.Component { class Alert extends React.Component {
@ -12,9 +11,8 @@ class Alert extends React.Component {
danger: PropTypes.bool, danger: PropTypes.bool,
warning: PropTypes.bool, warning: PropTypes.bool,
success: PropTypes.bool, success: PropTypes.bool,
info: PropTypes.bool,
offline: PropTypes.bool, offline: PropTypes.bool,
} };
render() { render() {
let alertType; let alertType;
@ -25,8 +23,12 @@ class Alert extends React.Component {
if (!alertType) alertType = 'info'; // default if (!alertType) alertType = 'info'; // default
return ( return (
<Flex align="center" justify="center" className={ cx(styles.container, styles[alertType]) }> <Flex
{ this.props.children } align="center"
justify="center"
className={cx(styles.container, styles[alertType])}
>
{this.props.children}
</Flex> </Flex>
); );
} }

View File

@ -8,29 +8,29 @@ import styles from './AtlasPreview.scss';
// import classNames from 'classnames/bind'; // import classNames from 'classnames/bind';
// const cx = classNames.bind(styles); // const cx = classNames.bind(styles);
@observer @observer class AtlasPreview extends React.Component {
class AtlasPreview extends React.Component {
static propTypes = { static propTypes = {
data: React.PropTypes.object.isRequired, data: React.PropTypes.object.isRequired,
} };
render() { render() {
const data = this.props.data; const data = this.props.data;
return ( return (
<div className={ styles.container }> <div className={styles.container}>
<h2><Link to={ data.url } className={ styles.atlasLink }>{ data.name }</Link></h2> <h2>
{ data.recentDocuments.length > 0 ? <Link to={data.url} className={styles.atlasLink}>{data.name}</Link>
data.recentDocuments.map(document => { </h2>
return ( {data.recentDocuments.length > 0
<DocumentLink document={ document } key={ document.id } /> ? data.recentDocuments.map(document => {
); return <DocumentLink document={document} key={document.id} />;
}) })
: ( : <div className={styles.description}>
<div className={ styles.description }> No documents. Why not
No documents. Why not <Link to={ `${data.url}/new` }>create one</Link>? {' '}
</div> <Link to={`${data.url}/new`}>create one</Link>
) } ?
</div>}
</div> </div>
); );
} }

View File

@ -1,16 +1,18 @@
import React from 'react'; import React from 'react';
import { observer } from "mobx-react" import { observer } from 'mobx-react';
import moment from 'moment'; import moment from 'moment';
import Link from 'react-router/lib/Link'; import Link from 'react-router/lib/Link';
import styles from './DocumentLink.scss'; import styles from './DocumentLink.scss';
const DocumentLink = observer((props) => { const DocumentLink = observer(props => {
return ( return (
<Link to={ props.document.url } className={ styles.link }> <Link to={props.document.url} className={styles.link}>
<h3 className={ styles.title }>{ props.document.title }</h3> <h3 className={styles.title}>{props.document.title}</h3>
<span className={ styles.timestamp }>{ moment(props.document.updatedAt).fromNow() }</span> <span className={styles.timestamp}>
{moment(props.document.updatedAt).fromNow()}
</span>
</Link> </Link>
); );
}); });

View File

@ -7,37 +7,40 @@ const cx = classNames.bind(styles);
import { randomInteger } from 'utils/random'; import { randomInteger } from 'utils/random';
const randomValues = Array.from(new Array(5), () => `${randomInteger(85, 100)}%`); const randomValues = Array.from(
new Array(5),
() => `${randomInteger(85, 100)}%`
);
export default (_props) => { export default _props => {
return ( return (
<ReactCSSTransitionGroup <ReactCSSTransitionGroup
transitionName="fadeIn" transitionName="fadeIn"
transitionAppear transitionAppear
transitionEnter transitionEnter
transitionLeave transitionLeave
transitionAppearTimeout={ 0 } transitionAppearTimeout={0}
transitionEnterTimeout={ 0 } transitionEnterTimeout={0}
transitionLeaveTimeout={ 0 } transitionLeaveTimeout={0}
> >
<div> <div>
<div className={ cx(styles.container, styles.animated) }> <div className={cx(styles.container, styles.animated)}>
<div <div
className={ cx(styles.mask, styles.header) } className={cx(styles.mask, styles.header)}
style={{ width: randomValues[0] }} style={{ width: randomValues[0] }}
>&nbsp;</div> />
<div <div
className={ cx(styles.mask, styles.bodyText) } className={cx(styles.mask, styles.bodyText)}
style={{ width: randomValues[1] }} style={{ width: randomValues[1] }}
>&nbsp;</div> />
<div <div
className={ cx(styles.mask, styles.bodyText) } className={cx(styles.mask, styles.bodyText)}
style={{ width: randomValues[2] }} style={{ width: randomValues[2] }}
>&nbsp;</div> />
<div <div
className={ cx(styles.mask, styles.bodyText) } className={cx(styles.mask, styles.bodyText)}
style={{ width: randomValues[3] }} style={{ width: randomValues[3] }}
>&nbsp;</div> />
</div> </div>
</div> </div>
</ReactCSSTransitionGroup> </ReactCSSTransitionGroup>

View File

@ -2,15 +2,15 @@ import React from 'react';
import styles from './CenteredContent.scss'; import styles from './CenteredContent.scss';
const CenteredContent = (props) => { const CenteredContent = props => {
const style = { const style = {
maxWidth: props.maxWidth, maxWidth: props.maxWidth,
...props.style, ...props.style,
}; };
return ( return (
<div className={ styles.content } style={ style }> <div className={styles.content} style={style}>
{ props.children } {props.children}
</div> </div>
); );
}; };

View File

@ -2,10 +2,8 @@ import React from 'react';
import styles from './Divider.scss'; import styles from './Divider.scss';
const Divider = (props) => { const Divider = props => {
return( return <div className={styles.divider}><span /></div>;
<div className={ styles.divider }><span></span></div>
);
}; };
export default Divider; export default Divider;

View File

@ -7,23 +7,22 @@ import DocumentHtml from './components/DocumentHtml';
import styles from './Document.scss'; import styles from './Document.scss';
@observer @observer class Document extends React.Component {
class Document extends React.Component {
static propTypes = { static propTypes = {
document: PropTypes.object.isRequired, document: PropTypes.object.isRequired,
} };
render() { render() {
return ( return (
<div className={ styles.container }> <div className={styles.container}>
<PublishingInfo <PublishingInfo
createdAt={ this.props.document.createdAt } createdAt={this.props.document.createdAt}
createdBy={ this.props.document.createdBy } createdBy={this.props.document.createdBy}
updatedAt={ this.props.document.updatedAt } updatedAt={this.props.document.updatedAt}
updatedBy={ this.props.document.updatedBy } updatedBy={this.props.document.updatedBy}
collaborators={ toJS(this.props.document.collaborators) } collaborators={toJS(this.props.document.collaborators)}
/> />
<DocumentHtml html={ this.props.document.html } /> <DocumentHtml html={this.props.document.html} />
</div> </div>
); );
} }

View File

@ -4,19 +4,18 @@ import { observer } from 'mobx-react';
import styles from './DocumentHtml.scss'; import styles from './DocumentHtml.scss';
@observer @observer class DocumentHtml extends React.Component {
class DocumentHtml extends React.Component {
static propTypes = { static propTypes = {
html: PropTypes.string.isRequired, html: PropTypes.string.isRequired,
} };
componentDidMount = () => { componentDidMount = () => {
this.setExternalLinks(); this.setExternalLinks();
} };
componentDidUpdate = () => { componentDidUpdate = () => {
this.setExternalLinks(); this.setExternalLinks();
} };
setExternalLinks = () => { setExternalLinks = () => {
const links = ReactDOM.findDOMNode(this).querySelectorAll('a'); const links = ReactDOM.findDOMNode(this).querySelectorAll('a');
@ -25,12 +24,12 @@ class DocumentHtml extends React.Component {
link.target = '_blank'; // eslint-disable-line no-param-reassign link.target = '_blank'; // eslint-disable-line no-param-reassign
} }
}); });
} };
render() { render() {
return ( return (
<div <div
className={ styles.document } className={styles.document}
dangerouslySetInnerHTML={{ __html: this.props.html }} dangerouslySetInnerHTML={{ __html: this.props.html }}
/> />
); );

View File

@ -2,6 +2,4 @@ import Document from './Document';
import DocumentHtml from './components/DocumentHtml'; import DocumentHtml from './components/DocumentHtml';
export default Document; export default Document;
export { export { DocumentHtml };
DocumentHtml,
};

View File

@ -8,22 +8,23 @@ import styles from './DocumentList.scss';
class DocumentList extends React.Component { class DocumentList extends React.Component {
static propTypes = { static propTypes = {
documents: React.PropTypes.arrayOf(React.PropTypes.object), documents: React.PropTypes.arrayOf(React.PropTypes.object),
} };
render() { render() {
return ( return (
<div> <div>
{ this.props.documents && this.props.documents.map((document) => { {this.props.documents &&
this.props.documents.map(document => {
return ( return (
<div> <div>
<DocumentPreview document={ document } /> <DocumentPreview document={document} />
<Divider /> <Divider />
</div> </div>
); );
}) } })}
</div> </div>
); );
} }
}; }
export default DocumentList; export default DocumentList;

View File

@ -10,33 +10,29 @@ import styles from './DocumentPreview.scss';
class Document extends React.Component { class Document extends React.Component {
static propTypes = { static propTypes = {
document: React.PropTypes.object.isRequired, document: React.PropTypes.object.isRequired,
} };
render() { render() {
return ( return (
<div className={ styles.container }> <div className={styles.container}>
<PublishingInfo <PublishingInfo
createdAt={ this.props.document.createdAt } createdAt={this.props.document.createdAt}
createdBy={ this.props.document.createdBy } createdBy={this.props.document.createdBy}
updatedAt={ this.props.document.updatedAt } updatedAt={this.props.document.updatedAt}
updatedBy={ this.props.document.updatedBy } updatedBy={this.props.document.updatedBy}
collaborators={ toJS(this.props.document.collaborators) } collaborators={toJS(this.props.document.collaborators)}
/> />
<Link <Link to={this.props.document.url} className={styles.title}>
to={ this.props.document.url } <h2>{this.props.document.title}</h2>
className={ styles.title }
>
<h2>{ this.props.document.title }</h2>
</Link> </Link>
<div dangerouslySetInnerHTML={{ __html: this.props.document.preview }} /> <div
dangerouslySetInnerHTML={{ __html: this.props.document.preview }}
/>
<div> <div>
<Link <Link to={this.props.document.url} className={styles.continueLink}>
to={ this.props.document.url }
className={ styles.continueLink }
>
Continue reading... Continue reading...
</Link> </Link>
</div> </div>

View File

@ -10,14 +10,13 @@ class MenuItem extends React.Component {
} else { } else {
this.props.onClick(); this.props.onClick();
} }
} };
render() { render() {
return ( return (
<div <div className={styles.menuItem} onClick={this.onClick}>
className={ styles.menuItem } {this.props.children}
onClick={ this.onClick } </div>
>{ this.props.children }</div>
); );
} }
} }
@ -34,46 +33,44 @@ class DropdownMenu extends React.Component {
static propTypes = { static propTypes = {
label: React.PropTypes.node.isRequired, label: React.PropTypes.node.isRequired,
children: React.PropTypes.node.isRequired, children: React.PropTypes.node.isRequired,
} };
state = { state = {
menuVisible: false, menuVisible: false,
} };
onMouseEnter = () => { onMouseEnter = () => {
this.setState({ menuVisible: true }); this.setState({ menuVisible: true });
} };
onMouseLeave = () => { onMouseLeave = () => {
this.setState({ menuVisible: false }); this.setState({ menuVisible: false });
} };
onClick = () => { onClick = () => {
this.setState({ menuVisible: !this.state.menuVisible }); this.setState({ menuVisible: !this.state.menuVisible });
} };
render() { render() {
return ( return (
<div <div
className={ styles.menuContainer } className={styles.menuContainer}
onMouseEnter={ this.onMouseEnter } onMouseEnter={this.onMouseEnter}
onMouseLeave={ this.onMouseLeave } onMouseLeave={this.onMouseLeave}
> >
<div className={ styles.label } onClick={ this.onClick }> <div className={styles.label} onClick={this.onClick}>
{ this.props.label } {this.props.label}
</div> </div>
{ this.state.menuVisible ? ( {this.state.menuVisible
<div className={ styles.menu }> ? <div className={styles.menu}>
{ this.props.children } {this.props.children}
</div> </div>
) : null } : null}
</div> </div>
); );
} }
}; }
export default DropdownMenu; export default DropdownMenu;
export { export { MenuItem };
MenuItem,
}

View File

@ -2,13 +2,8 @@ import React from 'react';
import styles from './MoreIcon.scss'; import styles from './MoreIcon.scss';
const MoreIcon = (props) => { const MoreIcon = props => {
return ( return <img src={require('./assets/more.svg')} className={styles.icon} />;
<img
src={ require("./assets/more.svg") }
className={ styles.icon }
/>
);
}; };
export default MoreIcon; export default MoreIcon;

View File

@ -1,7 +1,4 @@
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;
export { export { MenuItem, MoreIcon };
MenuItem,
MoreIcon,
};

View File

@ -5,10 +5,10 @@ export default ({ style = {}, className }) => {
<span className={className}> <span className={className}>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width={ style.width || 208 } width={style.width || 208}
height={ style.height || 128 } height={style.height || 128}
viewBox="0 0 208 128" viewBox="0 0 208 128"
color={ style.color } color={style.color}
> >
<rect <rect
width="198" width="198"

View File

@ -2,12 +2,11 @@ import React from 'react';
import styles from './HeaderAction.scss'; import styles from './HeaderAction.scss';
const HeaderAction = (props) => { const HeaderAction = props => {
return ( return (
<div <div onClick={props.onClick} className={styles.container}>
onClick={ props.onClick } {props.children}
className={ styles.container } </div>
>{ props.children }</div>
); );
}; };

View File

@ -1,20 +1,19 @@
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
@observer @observer class SaveAction extends React.Component {
class SaveAction extends React.Component {
static propTypes = { static propTypes = {
onClick: React.PropTypes.func.isRequired, onClick: React.PropTypes.func.isRequired,
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
isNew: React.PropTypes.bool, isNew: React.PropTypes.bool,
} };
onClick = (event) => { onClick = event => {
if (this.props.disabled) return; if (this.props.disabled) return;
event.preventDefault(); event.preventDefault();
this.props.onClick(); this.props.onClick();
} };
render() { render() {
const { disabled, isNew } = this.props; const { disabled, isNew } = this.props;
@ -23,11 +22,11 @@ class SaveAction extends React.Component {
<div> <div>
<a <a
href href
onClick={ this.onClick } onClick={this.onClick}
style={{ opacity: disabled ? 0.5 : 1 }} style={{ opacity: disabled ? 0.5 : 1 }}
title="Save changes (Cmd+Enter)" title="Save changes (Cmd+Enter)"
> >
{ isNew ? 'Publish' : 'Save' } {isNew ? 'Publish' : 'Save'}
</a> </a>
</div> </div>
); );

View File

@ -10,7 +10,7 @@ class Title extends React.Component {
children: React.PropTypes.string, children: React.PropTypes.string,
truncate: React.PropTypes.number, truncate: React.PropTypes.number,
placeholder: React.PropTypes.string, placeholder: React.PropTypes.string,
} };
render() { render() {
let title; let title;
@ -28,12 +28,12 @@ class Title extends React.Component {
return ( return (
<span> <span>
{ title && (<span>&nbsp;/&nbsp;</span>) } {title && <span>&nbsp;/&nbsp;</span>}
<span <span
title={ this.props.children } title={this.props.children}
className={ cx(styles.title, { untitled: usePlaceholder }) } className={cx(styles.title, { untitled: usePlaceholder })}
> >
{ title } {title}
</span> </span>
</span> </span>
); );

View File

@ -5,8 +5,4 @@ import SaveAction from './components/SaveAction';
export default Layout; export default Layout;
export { export { Title, HeaderAction, SaveAction };
Title,
HeaderAction,
SaveAction,
};

View File

@ -2,10 +2,10 @@ import React from 'react';
import styles from './LoadingIndicator.scss'; import styles from './LoadingIndicator.scss';
const LoadingIndicator = (props) => { const LoadingIndicator = props => {
return ( return (
<div className={ styles.loading }> <div className={styles.loading}>
<div className={ styles.loader }></div> <div className={styles.loader} />
</div> </div>
); );
}; };

View File

@ -14,8 +14,7 @@ import './codemirror.scss';
import { client } from 'utils/ApiClient'; import { client } from 'utils/ApiClient';
@observer @observer class MarkdownEditor extends React.Component {
class MarkdownEditor extends React.Component {
static propTypes = { static propTypes = {
text: React.PropTypes.string, text: React.PropTypes.string,
onChange: React.PropTypes.func.isRequired, onChange: React.PropTypes.func.isRequired,
@ -27,15 +26,15 @@ class MarkdownEditor extends React.Component {
// re-render to help with CodeMirror focus issues // re-render to help with CodeMirror focus issues
preview: React.PropTypes.bool, preview: React.PropTypes.bool,
toggleUploadingIndicator: React.PropTypes.func, toggleUploadingIndicator: React.PropTypes.func,
} };
onChange = (newText) => { onChange = newText => {
if (newText !== this.props.text) { if (newText !== this.props.text) {
this.props.onChange(newText); this.props.onChange(newText);
} }
} };
onDropAccepted = (files) => { onDropAccepted = files => {
const file = files[0]; const file = files[0];
const editor = this.getEditorInstance(); const editor = this.getEditorInstance();
@ -56,7 +55,8 @@ class MarkdownEditor extends React.Component {
} }
editor.setCursor(newCursorPositionLine, 0); editor.setCursor(newCursorPositionLine, 0);
client.post('/user.s3Upload', { client
.post('/user.s3Upload', {
kind: file.type, kind: file.type,
size: file.size, size: file.size,
filename: file.name, filename: file.name,
@ -100,23 +100,23 @@ class MarkdownEditor extends React.Component {
.catch(_err => { .catch(_err => {
this.props.toggleUploadingIndicator(); this.props.toggleUploadingIndicator();
}); });
} };
onPaddingTopClick = () => { onPaddingTopClick = () => {
const cm = this.getEditorInstance(); const cm = this.getEditorInstance();
cm.setCursor(0, 0); cm.setCursor(0, 0);
cm.focus(); cm.focus();
} };
onPaddingBottomClick = () => { onPaddingBottomClick = () => {
const cm = this.getEditorInstance(); const cm = this.getEditorInstance();
cm.setCursor(cm.lineCount(), 0); cm.setCursor(cm.lineCount(), 0);
cm.focus(); cm.focus();
} };
getEditorInstance = () => { getEditorInstance = () => {
return this.refs.editor.getCodeMirror(); return this.refs.editor.getCodeMirror();
} };
render = () => { render = () => {
const options = { const options = {
@ -146,24 +146,24 @@ class MarkdownEditor extends React.Component {
return ( return (
<Dropzone <Dropzone
onDropAccepted={ this.onDropAccepted } onDropAccepted={this.onDropAccepted}
disableClick disableClick
multiple={ false } multiple={false}
accept="image/*" accept="image/*"
className={ styles.container } className={styles.container}
> >
<ClickablePadding onClick={ this.onPaddingTopClick } /> <ClickablePadding onClick={this.onPaddingTopClick} />
<Codemirror <Codemirror
value={ this.props.text } value={this.props.text}
onChange={ this.onChange } onChange={this.onChange}
options={ options } options={options}
ref="editor" ref="editor"
className={ styles.codeMirrorContainer } className={styles.codeMirrorContainer}
/> />
<ClickablePadding onClick={ this.onPaddingBottomClick } /> <ClickablePadding onClick={this.onPaddingBottomClick} />
</Dropzone> </Dropzone>
); );
} };
} }
export default MarkdownEditor; export default MarkdownEditor;

View File

@ -2,13 +2,8 @@ import React from 'react';
import styles from './ClickablePadding.scss'; import styles from './ClickablePadding.scss';
const ClickablePadding = (props) => { const ClickablePadding = props => {
return ( return <div className={styles.container} onClick={props.onClick}>&nbsp;</div>;
<div
className={ styles.container }
onClick={ props.onClick }
>&nbsp;</div>
)
}; };
ClickablePadding.propTypes = { ClickablePadding.propTypes = {

View File

@ -11,7 +11,7 @@ class Offline extends React.Component {
state = { state = {
offline: !navigator.onLine, offline: !navigator.onLine,
} };
getChildContext() { getChildContext() {
return { return {
@ -22,7 +22,7 @@ class Offline extends React.Component {
componentDidMount = () => { componentDidMount = () => {
window.addEventListener('offline', this.handleConnectionState); window.addEventListener('offline', this.handleConnectionState);
window.addEventListener('online', this.handleConnectionState); window.addEventListener('online', this.handleConnectionState);
} };
componentWillUnmount = () => { componentWillUnmount = () => {
window.removeEventListener('offline', this.handleConnectionState); window.removeEventListener('offline', this.handleConnectionState);
@ -33,7 +33,7 @@ class Offline extends React.Component {
this.setState({ this.setState({
offline: !navigator.onLine, offline: !navigator.onLine,
}); });
} };
render() { render() {
return React.Children.only(this.props.children); return React.Children.only(this.props.children);

View File

@ -1,7 +1,4 @@
import Offline from './Offline'; import Offline from './Offline';
import injectOffline from './injectOffline'; import injectOffline from './injectOffline';
export { export { Offline, injectOffline };
Offline,
injectOffline,
}

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
const injectOffline = (WrappedComponent) => { const injectOffline = WrappedComponent => {
return class OfflineWrapper extends React.Component { return class OfflineWrapper extends React.Component {
static contextTypes = { static contextTypes = {
offline: React.PropTypes.bool, offline: React.PropTypes.bool,
@ -11,7 +11,7 @@ const injectOffline = (WrappedComponent) => {
offline: this.context.offline, offline: this.context.offline,
}; };
return (<WrappedComponent { ...this.props } { ...newProps } />); return <WrappedComponent {...this.props} {...newProps} />;
} }
}; };
}; };

View File

@ -17,31 +17,35 @@ class PublishingInfo extends React.Component {
render() { render() {
return ( return (
<Flex align="center" className={ styles.user }> <Flex align="center" className={styles.user}>
<Flex className={ styles.avatarLine }> <Flex className={styles.avatarLine}>
{ this.props.collaborators.reverse().map(user => ( {this.props.collaborators.reverse().map(user => (
<Avatar <Avatar
key={ `avatar-${user.id}` } key={`avatar-${user.id}`}
src={ user.avatarUrl } src={user.avatarUrl}
size={ 26 } size={26}
style={{ style={{
marginRight: '-12px', marginRight: '-12px',
border: '2px solid #FFFFFF', border: '2px solid #FFFFFF',
}} }}
title={ user.name } title={user.name}
/> />
)) } ))}
</Flex> </Flex>
<span className={ styles.userName }> <span className={styles.userName}>
{ this.props.createdBy.name } published { moment(this.props.createdAt).fromNow() } {this.props.createdBy.name}
{ this.props.createdAt !== this.props.updatedAt ? ( {' '}
<span> published
{' '}
{moment(this.props.createdAt).fromNow()}
{this.props.createdAt !== this.props.updatedAt
? <span>
&nbsp;and&nbsp; &nbsp;and&nbsp;
{ this.props.createdBy.id !== this.props.updatedBy.id && {this.props.createdBy.id !== this.props.updatedBy.id &&
` ${this.props.updatedBy.name} ` } ` ${this.props.updatedBy.name} `}
modified { moment(this.props.updatedAt).fromNow() } modified {moment(this.props.updatedAt).fromNow()}
</span> </span>
) : null } : null}
</span> </span>
</Flex> </Flex>
); );

View File

@ -6,52 +6,57 @@ import classNames from 'classnames/bind';
const cx = classNames.bind(styles); const cx = classNames.bind(styles);
class Node extends React.Component { class Node extends React.Component {
displayName: 'UITreeNode' displayName: 'UITreeNode';
renderCollapse = () => { renderCollapse = () => {
var index = this.props.index; const index = this.props.index;
if(index.children && index.children.length) { if (index.children && index.children.length) {
var collapsed = index.node.collapsed; const collapsed = index.node.collapsed;
return ( return (
<span <span
className={cx(styles.collapse, collapsed ? styles.caretRight : styles.caretDown)} className={cx(
onMouseDown={function(e) {e.stopPropagation()}} styles.collapse,
collapsed ? styles.caretRight : styles.caretDown
)}
onMouseDown={function(e) {
e.stopPropagation();
}}
onClick={this.handleCollapse} onClick={this.handleCollapse}
> >
<img src={ require("./assets/chevron.svg") } /> <img src={require('./assets/chevron.svg')} />
</span> </span>
); );
} }
return null; return null;
} };
renderChildren = () => { renderChildren = () => {
var index = this.props.index; const index = this.props.index;
var tree = this.props.tree; const tree = this.props.tree;
var dragging = this.props.dragging; const dragging = this.props.dragging;
if(index.children && index.children.length) { if (index.children && index.children.length) {
var childrenStyles = {}; const childrenStyles = {};
if (!this.props.rootNode) { if (!this.props.rootNode) {
if(index.node.collapsed) childrenStyles.display = 'none'; if (index.node.collapsed) childrenStyles.display = 'none';
childrenStyles['paddingLeft'] = this.props.paddingLeft + 'px'; childrenStyles.paddingLeft = `${this.props.paddingLeft}px`;
} }
return ( return (
<div className={ styles.children } style={childrenStyles}> <div className={styles.children} style={childrenStyles}>
{index.children.map((child) => { {index.children.map(child => {
var childIndex = tree.getIndex(child); const childIndex = tree.getIndex(child);
return ( return (
<Node <Node
tree={tree} tree={tree}
index={childIndex} index={childIndex}
key={childIndex.id} key={childIndex.id}
dragging={dragging} dragging={dragging}
allowUpdates={ this.props.allowUpdates } allowUpdates={this.props.allowUpdates}
paddingLeft={this.props.paddingLeft} paddingLeft={this.props.paddingLeft}
onCollapse={this.props.onCollapse} onCollapse={this.props.onCollapse}
onDragStart={this.props.onDragStart} onDragStart={this.props.onDragStart}
@ -63,18 +68,18 @@ class Node extends React.Component {
} }
return null; return null;
} };
isModifying = () => { isModifying = () => {
return this.props.allowUpdates && !this.props.rootNode; return this.props.allowUpdates && !this.props.rootNode;
} };
onClick = () => { onClick = () => {
const index = this.props.index; const index = this.props.index;
const node = index.node; const node = index.node;
if (!this.isModifying()) history.push(node.url); if (!this.isModifying()) history.push(node.url);
} };
render() { render() {
const tree = this.props.tree; const tree = this.props.tree;
@ -84,24 +89,31 @@ class Node extends React.Component {
const style = {}; const style = {};
return ( return (
<div className={cx(styles.node, { <div
className={cx(styles.node, {
placeholder: index.id === dragging, placeholder: index.id === dragging,
rootNode: this.props.rootNode, rootNode: this.props.rootNode,
})} style={style}> })}
<div style={style}
className={ styles.inner }
ref="inner"
onMouseDown={this.props.rootNode || !this.props.allowUpdates ? (e) => e.stopPropagation() : this.handleMouseDown}
> >
{ !this.props.rootNode && this.renderCollapse() } <div
className={styles.inner}
ref="inner"
onMouseDown={
this.props.rootNode || !this.props.allowUpdates
? e => e.stopPropagation()
: this.handleMouseDown
}
>
{!this.props.rootNode && this.renderCollapse()}
<span <span
className={ cx(styles.nodeLabel, { className={cx(styles.nodeLabel, {
rootLabel: this.props.rootNode, rootLabel: this.props.rootNode,
isModifying: this.isModifying(), isModifying: this.isModifying(),
}) } })}
onClick={ this.onClick } onClick={this.onClick}
> >
{ node.title } {node.title}
</span> </span>
</div> </div>
{this.renderChildren()} {this.renderChildren()}
@ -109,20 +121,20 @@ class Node extends React.Component {
); );
} }
handleCollapse = (e) => { handleCollapse = e => {
e.stopPropagation(); e.stopPropagation();
var nodeId = this.props.index.id; const nodeId = this.props.index.id;
if(this.props.onCollapse) this.props.onCollapse(nodeId); if (this.props.onCollapse) this.props.onCollapse(nodeId);
} };
handleMouseDown = (e) => { handleMouseDown = e => {
var nodeId = this.props.index.id; const nodeId = this.props.index.id;
var dom = this.refs.inner; const dom = this.refs.inner;
if(this.props.onDragStart) { if (this.props.onDragStart) {
this.props.onDragStart(nodeId, dom, e); this.props.onDragStart(nodeId, dom, e);
} }
} };
}; }
module.exports = Node; module.exports = Node;

View File

@ -1,24 +1,24 @@
var Tree = require('js-tree'); const Tree = require('js-tree');
var proto = Tree.prototype; const proto = Tree.prototype;
proto.updateNodesPosition = function() { proto.updateNodesPosition = function() {
var top = 1; let top = 1;
var left = 1; let left = 1;
var root = this.getIndex(1); const root = this.getIndex(1);
var self = this; const self = this;
root.top = top++; root.top = top++;
root.left = left++; root.left = left++;
if(root.children && root.children.length) { if (root.children && root.children.length) {
walk(root.children, root, left, root.node.collapsed); walk(root.children, root, left, root.node.collapsed);
} }
function walk(children, parent, left, collapsed) { function walk(children, parent, left, collapsed) {
var height = 1; let height = 1;
children.forEach(function(id) { children.forEach(id => {
var node = self.getIndex(id); const node = self.getIndex(id);
if(collapsed) { if (collapsed) {
node.top = null; node.top = null;
node.left = null; node.left = null;
} else { } else {
@ -26,30 +26,35 @@ proto.updateNodesPosition = function() {
node.left = left; node.left = left;
} }
if(node.children && node.children.length) { if (node.children && node.children.length) {
height += walk(node.children, node, left+1, collapsed || node.node.collapsed); height += walk(
node.children,
node,
left + 1,
collapsed || node.node.collapsed
);
} else { } else {
node.height = 1; node.height = 1;
height += 1; height += 1;
} }
}); });
if(parent.node.collapsed) parent.height = 1; if (parent.node.collapsed) parent.height = 1;
else parent.height = height; else parent.height = height;
return parent.height; return parent.height;
} }
}; };
proto.move = function(fromId, toId, placement) { proto.move = function(fromId, toId, placement) {
if(fromId === toId || toId === 1) return; if (fromId === toId || toId === 1) return;
var obj = this.remove(fromId); const obj = this.remove(fromId);
var index = null; let index = null;
if(placement === 'before') index = this.insertBefore(obj, toId); if (placement === 'before') index = this.insertBefore(obj, toId);
else if(placement === 'after') index = this.insertAfter(obj, toId); else if (placement === 'after') index = this.insertAfter(obj, toId);
else if(placement === 'prepend') index = this.prepend(obj, toId); else if (placement === 'prepend') index = this.prepend(obj, toId);
else if(placement === 'append') index = this.append(obj, toId); else if (placement === 'append') index = this.append(obj, toId);
// todo: perf // todo: perf
this.updateNodesPosition(); this.updateNodesPosition();
@ -57,10 +62,10 @@ proto.move = function(fromId, toId, placement) {
}; };
proto.getNodeByTop = function(top) { proto.getNodeByTop = function(top) {
var indexes = this.indexes; const indexes = this.indexes;
for(var id in indexes) { for (const id in indexes) {
if(indexes.hasOwnProperty(id)) { if (indexes.hasOwnProperty(id)) {
if(indexes[id].top === top) return indexes[id]; if (indexes[id].top === top) return indexes[id];
} }
} }
}; };

View File

@ -1,6 +1,6 @@
var React = require('react'); const React = require('react');
var Tree = require('./Tree'); const Tree = require('./Tree');
var Node = require('./Node'); const Node = require('./Node');
import styles from './Tree.scss'; import styles from './Tree.scss';
@ -16,7 +16,7 @@ module.exports = React.createClass({
getDefaultProps() { getDefaultProps() {
return { return {
paddingLeft: 20 paddingLeft: 20,
}; };
}, },
@ -25,47 +25,47 @@ module.exports = React.createClass({
}, },
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if(!this._updated) this.setState(this.init(nextProps)); if (!this._updated) this.setState(this.init(nextProps));
else this._updated = false; else this._updated = false;
}, },
init(props) { init(props) {
var tree = new Tree(props.tree); const tree = new Tree(props.tree);
tree.isNodeCollapsed = props.isNodeCollapsed; tree.isNodeCollapsed = props.isNodeCollapsed;
tree.changeNodeCollapsed = props.changeNodeCollapsed; tree.changeNodeCollapsed = props.changeNodeCollapsed;
tree.updateNodesPosition(); tree.updateNodesPosition();
return { return {
tree: tree, tree,
dragging: { dragging: {
id: null, id: null,
x: null, x: null,
y: null, y: null,
w: null, w: null,
h: null h: null,
} },
}; };
}, },
getDraggingDom() { getDraggingDom() {
var tree = this.state.tree; const tree = this.state.tree;
var dragging = this.state.dragging; const dragging = this.state.dragging;
if(dragging && dragging.id) { if (dragging && dragging.id) {
var draggingIndex = tree.getIndex(dragging.id); const draggingIndex = tree.getIndex(dragging.id);
var draggingStyles = { const draggingStyles = {
top: dragging.y, top: dragging.y,
left: dragging.x, left: dragging.x,
width: dragging.w width: dragging.w,
}; };
return ( return (
<div className={ styles.draggable } style={ draggingStyles }> <div className={styles.draggable} style={draggingStyles}>
<Node <Node
tree={ tree } tree={tree}
index={ draggingIndex } index={draggingIndex}
paddingLeft={ this.props.paddingLeft } paddingLeft={this.props.paddingLeft}
allowUpdates={ this.props.allowUpdates } allowUpdates={this.props.allowUpdates}
/> />
</div> </div>
); );
@ -75,23 +75,23 @@ module.exports = React.createClass({
}, },
render() { render() {
var tree = this.state.tree; const tree = this.state.tree;
var dragging = this.state.dragging; const dragging = this.state.dragging;
var draggingDom = this.getDraggingDom(); const draggingDom = this.getDraggingDom();
return ( return (
<div className={ styles.tree }> <div className={styles.tree}>
{ draggingDom } {draggingDom}
<Node <Node
rootNode rootNode
tree={ tree } tree={tree}
index={ tree.getIndex(1) } index={tree.getIndex(1)}
key={ 1 } key={1}
paddingLeft={ this.props.paddingLeft } paddingLeft={this.props.paddingLeft}
allowUpdates={ this.props.allowUpdates } allowUpdates={this.props.allowUpdates}
onDragStart={ this.dragStart } onDragStart={this.dragStart}
onCollapse={ this.toggleCollapse } onCollapse={this.toggleCollapse}
dragging={ dragging && dragging.id } dragging={dragging && dragging.id}
/> />
</div> </div>
); );
@ -99,11 +99,11 @@ module.exports = React.createClass({
dragStart(id, dom, e) { dragStart(id, dom, e) {
this.dragging = { this.dragging = {
id: id, id,
w: dom.offsetWidth, w: dom.offsetWidth,
h: dom.offsetHeight, h: dom.offsetHeight,
x: dom.offsetLeft, x: dom.offsetLeft,
y: dom.offsetTop y: dom.offsetTop,
}; };
this._startX = dom.offsetLeft; this._startX = dom.offsetLeft;
@ -118,69 +118,73 @@ module.exports = React.createClass({
// oh // oh
drag(e) { drag(e) {
if(this._start) { if (this._start) {
this.setState({ this.setState({
dragging: this.dragging dragging: this.dragging,
}); });
this._start = false; this._start = false;
} }
var tree = this.state.tree; const tree = this.state.tree;
var dragging = this.state.dragging; const dragging = this.state.dragging;
var paddingLeft = this.props.paddingLeft; const paddingLeft = this.props.paddingLeft;
var newIndex = null; let newIndex = null;
var index = tree.getIndex(dragging.id); let index = tree.getIndex(dragging.id);
var collapsed = index.node.collapsed; const collapsed = index.node.collapsed;
var _startX = this._startX; const _startX = this._startX;
var _startY = this._startY; const _startY = this._startY;
var _offsetX = this._offsetX; const _offsetX = this._offsetX;
var _offsetY = this._offsetY; const _offsetY = this._offsetY;
var pos = { const pos = {
x: _startX + e.clientX - _offsetX, x: _startX + e.clientX - _offsetX,
y: _startY + e.clientY - _offsetY y: _startY + e.clientY - _offsetY,
}; };
dragging.x = pos.x; dragging.x = pos.x;
dragging.y = pos.y; dragging.y = pos.y;
var diffX = dragging.x - paddingLeft/2 - (index.left-2) * paddingLeft; const diffX = dragging.x - paddingLeft / 2 - (index.left - 2) * paddingLeft;
var diffY = dragging.y - dragging.h/2 - (index.top-2) * dragging.h; const diffY = dragging.y - dragging.h / 2 - (index.top - 2) * dragging.h;
if(diffX < 0) { // left if (diffX < 0) {
if(index.parent && !index.next) { // left
if (index.parent && !index.next) {
newIndex = tree.move(index.id, index.parent, 'after'); newIndex = tree.move(index.id, index.parent, 'after');
} }
} else if(diffX > paddingLeft) { // right } else if (diffX > paddingLeft) {
if(index.prev) { // right
var prevNode = tree.getIndex(index.prev).node; if (index.prev) {
if(!prevNode.collapsed && !prevNode.leaf) { const prevNode = tree.getIndex(index.prev).node;
if (!prevNode.collapsed && !prevNode.leaf) {
newIndex = tree.move(index.id, index.prev, 'append'); newIndex = tree.move(index.id, index.prev, 'append');
} }
} }
} }
if(newIndex) { if (newIndex) {
index = newIndex; index = newIndex;
newIndex.node.collapsed = collapsed; newIndex.node.collapsed = collapsed;
dragging.id = newIndex.id; dragging.id = newIndex.id;
} }
if(diffY < 0) { // up if (diffY < 0) {
var above = tree.getNodeByTop(index.top-1); // up
const above = tree.getNodeByTop(index.top - 1);
newIndex = tree.move(index.id, above.id, 'before'); newIndex = tree.move(index.id, above.id, 'before');
} else if(diffY > dragging.h) { // down } else if (diffY > dragging.h) {
if(index.next) { // down
if (index.next) {
var below = tree.getIndex(index.next); var below = tree.getIndex(index.next);
if(below.children && below.children.length && !below.node.collapsed) { if (below.children && below.children.length && !below.node.collapsed) {
newIndex = tree.move(index.id, index.next, 'prepend'); newIndex = tree.move(index.id, index.next, 'prepend');
} else { } else {
newIndex = tree.move(index.id, index.next, 'after'); newIndex = tree.move(index.id, index.next, 'after');
} }
} else { } else {
var below = tree.getNodeByTop(index.top+index.height); var below = tree.getNodeByTop(index.top + index.height);
if(below && below.parent !== index.id) { if (below && below.parent !== index.id) {
if(below.children && below.children.length) { if (below.children && below.children.length) {
newIndex = tree.move(index.id, below.id, 'prepend'); newIndex = tree.move(index.id, below.id, 'prepend');
} else { } else {
newIndex = tree.move(index.id, below.id, 'after'); newIndex = tree.move(index.id, below.id, 'after');
@ -189,14 +193,14 @@ module.exports = React.createClass({
} }
} }
if(newIndex) { if (newIndex) {
newIndex.node.collapsed = collapsed; newIndex.node.collapsed = collapsed;
dragging.id = newIndex.id; dragging.id = newIndex.id;
} }
this.setState({ this.setState({
tree: tree, tree,
dragging: dragging dragging,
}); });
}, },
@ -207,8 +211,8 @@ module.exports = React.createClass({
x: null, x: null,
y: null, y: null,
w: null, w: null,
h: null h: null,
} },
}); });
this.change(this.state.tree); this.change(this.state.tree);
@ -218,21 +222,21 @@ module.exports = React.createClass({
change(tree) { change(tree) {
this._updated = true; this._updated = true;
if(this.props.onChange) this.props.onChange(tree.obj); if (this.props.onChange) this.props.onChange(tree.obj);
}, },
toggleCollapse(nodeId) { toggleCollapse(nodeId) {
var tree = this.state.tree; const tree = this.state.tree;
var index = tree.getIndex(nodeId); const index = tree.getIndex(nodeId);
var node = index.node; const node = index.node;
node.collapsed = !node.collapsed; node.collapsed = !node.collapsed;
tree.updateNodesPosition(); tree.updateNodesPosition();
this.setState({ this.setState({
tree: tree tree,
}); });
if(this.props.onCollapse) this.props.onCollapse(node.id, node.collapsed); if (this.props.onCollapse) this.props.onCollapse(node.id, node.collapsed);
}, },
// buildTreeNumbering(tree) { // buildTreeNumbering(tree) {

View File

@ -1,7 +1,7 @@
let constants; let constants;
constants = { constants = {
API_USER_AGENT: `Atlas`, API_USER_AGENT: 'Atlas',
API_BASE_URL: '/api', API_BASE_URL: '/api',
}; };

View File

@ -46,63 +46,83 @@ function requireAuth(nextState, replace) {
} }
} }
render(( render(
<div style={{ display: 'flex', flex: 1, height: '100%' }}> <div style={{ display: 'flex', flex: 1, height: '100%' }}>
<Provider { ...stores }> <Provider {...stores}>
<Offline> <Offline>
<Router history={ History }> <Router history={History}>
<Route path="/" component={ Application }> <Route path="/" component={Application}>
<IndexRoute component={ Home } /> <IndexRoute component={Home} />
<Route path="/dashboard" component={ Dashboard } onEnter={ requireAuth } /> <Route
<Route path="/collections/:id" component={ Atlas } onEnter={ requireAuth } /> path="/dashboard"
component={Dashboard}
onEnter={requireAuth}
/>
<Route
path="/collections/:id"
component={Atlas}
onEnter={requireAuth}
/>
<Route <Route
path="/collections/:id/new" path="/collections/:id/new"
component={ DocumentEdit } component={DocumentEdit}
onEnter={ requireAuth } onEnter={requireAuth}
newDocument newDocument
/> />
<Route path="/d/:id" component={ DocumentScene } onEnter={ requireAuth } /> <Route
<Route path="/d/:id/edit" component={ DocumentEdit } onEnter={ requireAuth } /> path="/d/:id"
component={DocumentScene}
onEnter={requireAuth}
/>
<Route
path="/d/:id/edit"
component={DocumentEdit}
onEnter={requireAuth}
/>
<Route <Route
path="/d/:id/new" path="/d/:id/new"
component={ DocumentEdit } component={DocumentEdit}
onEnter={ requireAuth } onEnter={requireAuth}
newChildDocument newChildDocument
/> />
<Route path="/search" component={ Search } onEnter={ requireAuth } /> <Route path="/search" component={Search} onEnter={requireAuth} />
<Route path="/settings" component={ Settings } onEnter={ requireAuth } /> <Route
path="/settings"
component={Settings}
onEnter={requireAuth}
/>
<Route path="/auth/slack" component={ SlackAuth } /> <Route path="/auth/slack" component={SlackAuth} />
<Route <Route
path="/auth/slack/commands" path="/auth/slack/commands"
component={ SlackAuth } component={SlackAuth}
apiPath="/auth.slackCommands" apiPath="/auth.slackCommands"
/> />
<Route path="/auth/error" component={ ErrorAuth } /> <Route path="/auth/error" component={ErrorAuth} />
<Flatpage <Flatpage
path="/keyboard-shortcuts" path="/keyboard-shortcuts"
component={ Flatpage } component={Flatpage}
title="Keyboard shortcuts" title="Keyboard shortcuts"
content={ flatpages.keyboard } content={flatpages.keyboard}
/> />
<Flatpage <Flatpage
path="/developers" path="/developers"
component={ Flatpage } component={Flatpage}
title="API" title="API"
content={ flatpages.api } content={flatpages.api}
/> />
<Route path="/404" component={Error404} />
<Route path="/404" component={ Error404 } /> <Route path="*" component={Search} sceneType="notFound" />
<Route path="*" component={ Search } sceneType="notFound" />
</Route> </Route>
</Router> </Router>
</Offline> </Offline>
</Provider> </Provider>
{ __DEV__ && <DevTools position={{ bottom: 0, right: 0 }} /> } {__DEV__ && <DevTools position={{ bottom: 0, right: 0 }} />}
</div> </div>,
), document.getElementById('root')); document.getElementById('root')
);

View File

@ -22,7 +22,7 @@ import styles from './Atlas.scss';
class Atlas extends React.Component { class Atlas extends React.Component {
static propTypes = { static propTypes = {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
} };
componentDidMount = () => { componentDidMount = () => {
const { id } = this.props.params; const { id } = this.props.params;
@ -32,21 +32,21 @@ class Atlas extends React.Component {
browserHistory.replace(data.navigationTree.url); browserHistory.replace(data.navigationTree.url);
} }
}); });
} };
componentWillReceiveProps = (nextProps) => { componentWillReceiveProps = nextProps => {
const key = nextProps.keydown.event; const key = nextProps.keydown.event;
if (key) { if (key) {
if (key.key === 'c') { if (key.key === 'c') {
_.defer(this.onCreate); _.defer(this.onCreate);
} }
} }
} };
onCreate = (event) => { onCreate = event => {
if (event) event.preventDefault(); if (event) event.preventDefault();
browserHistory.push(`${store.collection.url}/new`); browserHistory.push(`${store.collection.url}/new`);
} };
render() { render() {
const collection = store.collection; const collection = store.collection;
@ -58,47 +58,44 @@ class Atlas extends React.Component {
if (collection) { if (collection) {
actions = ( actions = (
<Flex> <Flex>
<DropdownMenu label={ <MoreIcon /> } > <DropdownMenu label={<MoreIcon />}>
<MenuItem onClick={ this.onCreate }> <MenuItem onClick={this.onCreate}>
New document New document
</MenuItem> </MenuItem>
</DropdownMenu> </DropdownMenu>
</Flex> </Flex>
); );
title = <Title>{ collection.name }</Title>; title = <Title>{collection.name}</Title>;
titleText = collection.name; titleText = collection.name;
} }
return ( return (
<Layout <Layout actions={actions} title={title} titleText={titleText}>
actions={ actions }
title={ title }
titleText={ titleText }
>
<CenteredContent> <CenteredContent>
<ReactCSSTransitionGroup <ReactCSSTransitionGroup
transitionName="fadeIn" transitionName="fadeIn"
transitionAppear transitionAppear
transitionAppearTimeout={ 0 } transitionAppearTimeout={0}
transitionEnterTimeout={ 0 } transitionEnterTimeout={0}
transitionLeaveTimeout={ 0 } transitionLeaveTimeout={0}
> >
{ store.isFetching ? ( {store.isFetching
<AtlasPreviewLoading /> ? <AtlasPreviewLoading />
) : ( : <div className={styles.container}>
<div className={ styles.container }> <div className={styles.atlasDetails}>
<div className={ styles.atlasDetails }> <h2>{collection.name}</h2>
<h2>{ collection.name }</h2>
<blockquote> <blockquote>
{ collection.description } {collection.description}
</blockquote> </blockquote>
</div> </div>
<Divider /> <Divider />
<DocumentList documents={ collection.recentDocuments } preview /> <DocumentList
</div> documents={collection.recentDocuments}
) } preview
/>
</div>}
</ReactCSSTransitionGroup> </ReactCSSTransitionGroup>
</CenteredContent> </CenteredContent>
</Layout> </Layout>

View File

@ -18,10 +18,10 @@ const store = new class AtlasStore {
this.collection = data; this.collection = data;
successCallback(data); successCallback(data);
} catch (e) { } catch (e) {
console.error("Something went wrong"); console.error('Something went wrong');
} }
this.isFetching = false; this.isFetching = false;
} };
}(); }();
export default store; export default store;

View File

@ -18,7 +18,7 @@ class DashboardStore {
runInAction('fetchCollections', () => { runInAction('fetchCollections', () => {
this.collections = data; this.collections = data;
this.pagination = pagination; this.pagination = pagination;
data.forEach((collection) => cacheResponse(collection.recentDocuments)); data.forEach(collection => cacheResponse(collection.recentDocuments));
}); });
// If only one collection, visit it automatically // If only one collection, visit it automatically
@ -29,7 +29,7 @@ class DashboardStore {
console.error('Something went wrong'); console.error('Something went wrong');
} }
this.isFetching = false; this.isFetching = false;
} };
constructor(options) { constructor(options) {
this.team = options.team; this.team = options.team;

View File

@ -3,9 +3,7 @@ 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 DocumentEditStore, { import DocumentEditStore, { DOCUMENT_EDIT_SETTINGS } from './DocumentEditStore';
DOCUMENT_EDIT_SETTINGS,
} from './DocumentEditStore';
import Switch from 'components/Switch'; import Switch from 'components/Switch';
import Layout, { Title, HeaderAction, SaveAction } from 'components/Layout'; import Layout, { Title, HeaderAction, SaveAction } from 'components/Layout';
@ -20,9 +18,13 @@ const DISREGARD_CHANGES = `You have unsaved changes.
Are you sure you want to disgard them?`; Are you sure you want to disgard them?`;
@keydown([ @keydown([
'cmd+enter', 'ctrl+enter', 'cmd+enter',
'cmd+esc', 'ctrl+esc', 'ctrl+enter',
'cmd+shift+p', 'ctrl+shift+p']) 'cmd+esc',
'ctrl+esc',
'cmd+shift+p',
'ctrl+shift+p',
])
@withRouter @withRouter
@observer @observer
class DocumentEdit extends Component { class DocumentEdit extends Component {
@ -30,7 +32,7 @@ class DocumentEdit extends Component {
route: React.PropTypes.object.isRequired, route: React.PropTypes.object.isRequired,
router: React.PropTypes.object.isRequired, router: React.PropTypes.object.isRequired,
params: React.PropTypes.object, params: React.PropTypes.object,
} };
constructor(props) { constructor(props) {
super(props); super(props);
@ -41,7 +43,7 @@ class DocumentEdit extends Component {
state = { state = {
scrollTop: 0, scrollTop: 0,
} };
componentDidMount = () => { componentDidMount = () => {
if (this.props.route.newDocument) { if (this.props.route.newDocument) {
@ -58,8 +60,7 @@ class DocumentEdit extends Component {
} }
// Load editor async // Load editor async
EditorLoader() EditorLoader().then(({ Editor }) => {
.then(({ Editor }) => {
this.setState({ Editor }); this.setState({ Editor });
}); });
@ -70,9 +71,9 @@ class DocumentEdit extends Component {
} }
return null; return null;
}); });
} };
componentWillReceiveProps = (nextProps) => { componentWillReceiveProps = nextProps => {
const key = nextProps.keydown.event; const key = nextProps.keydown.event;
if (key) { if (key) {
@ -91,7 +92,7 @@ class DocumentEdit extends Component {
this.store.togglePreview(); this.store.togglePreview();
} }
} }
} };
onSave = () => { onSave = () => {
// if (this.props.title.length === 0) { // if (this.props.title.length === 0) {
@ -103,45 +104,46 @@ class DocumentEdit extends Component {
} else { } else {
this.store.updateDocument(); this.store.updateDocument();
} }
} };
onCancel = () => { onCancel = () => {
browserHistory.goBack(); browserHistory.goBack();
} };
onScroll = (scrollTop) => { onScroll = scrollTop => {
this.setState({ this.setState({
scrollTop, scrollTop,
}); });
} };
render() { render() {
let title = ( const title = (
<Title <Title
truncate={ 60 } truncate={60}
placeholder={ !this.store.isFetching && 'Untitled document' } placeholder={!this.store.isFetching && 'Untitled document'}
> >
{ this.store.title } {this.store.title}
</Title> </Title>
); );
let titleText = this.store.title; const titleText = this.store.title;
let isNew = this.props.route.newDocument || this.props.route.newChildDocument; const isNew =
this.props.route.newDocument || this.props.route.newChildDocument;
const actions = ( const actions = (
<Flex> <Flex>
<HeaderAction> <HeaderAction>
<SaveAction <SaveAction
onClick={ this.onSave } onClick={this.onSave}
disabled={ this.store.isSaving } disabled={this.store.isSaving}
isNew={ isNew } isNew={isNew}
/> />
</HeaderAction> </HeaderAction>
<DropdownMenu label={ <MoreIcon /> }> <DropdownMenu label={<MoreIcon />}>
<MenuItem onClick={ this.store.togglePreview }> <MenuItem onClick={this.store.togglePreview}>
Preview <Switch checked={ this.store.preview } /> Preview <Switch checked={this.store.preview} />
</MenuItem> </MenuItem>
<MenuItem onClick={ this.onCancel }> <MenuItem onClick={this.onCancel}>
Cancel Cancel
</MenuItem> </MenuItem>
</DropdownMenu> </DropdownMenu>
@ -150,27 +152,25 @@ class DocumentEdit extends Component {
return ( return (
<Layout <Layout
actions={ actions } actions={actions}
title={ title } title={title}
titleText={ titleText } titleText={titleText}
fixed fixed
loading={ this.store.isSaving || this.store.isUploading } loading={this.store.isSaving || this.store.isUploading}
search={ false } search={false}
> >
{ (this.store.isFetching || !('Editor' in this.state)) ? ( {this.store.isFetching || !('Editor' in this.state)
<CenteredContent> ? <CenteredContent>
<AtlasPreviewLoading /> <AtlasPreviewLoading />
</CenteredContent> </CenteredContent>
) : ( : <this.state.Editor
<this.state.Editor store={this.store}
store={ this.store } scrollTop={this.state.scrollTop}
scrollTop={ this.state.scrollTop } onScroll={this.onScroll}
onScroll={ this.onScroll } onSave={this.onSave}
onSave={ this.onSave } onCancel={this.onCancel}
onCancel={ this.onCancel } togglePreview={this.togglePreview}
togglePreview={ this.togglePreview } />}
/>
) }
</Layout> </Layout>
); );
} }

View File

@ -5,7 +5,7 @@ import emojify from 'utils/emojify';
const DOCUMENT_EDIT_SETTINGS = 'DOCUMENT_EDIT_SETTINGS'; const DOCUMENT_EDIT_SETTINGS = 'DOCUMENT_EDIT_SETTINGS';
const parseHeader = (text) => { const parseHeader = text => {
const firstLine = text.split(/\r?\n/)[0]; const firstLine = text.split(/\r?\n/)[0];
if (firstLine) { if (firstLine) {
const match = firstLine.match(/^#+ +(.*)$/); const match = firstLine.match(/^#+ +(.*)$/);
@ -40,9 +40,13 @@ class DocumentEditStore {
this.isFetching = true; this.isFetching = true;
try { try {
const data = await client.get('/documents.info', { const data = await client.get(
'/documents.info',
{
id: this.documentId, id: this.documentId,
}, { cache: true }); },
{ cache: true }
);
if (this.newChildDocument) { if (this.newChildDocument) {
this.parentDocument = data.data; this.parentDocument = data.data;
} else { } else {
@ -54,7 +58,7 @@ class DocumentEditStore {
console.error('Something went wrong'); console.error('Something went wrong');
} }
this.isFetching = false; this.isFetching = false;
} };
@action saveDocument = async () => { @action saveDocument = async () => {
if (this.isSaving) return; if (this.isSaving) return;
@ -62,12 +66,16 @@ class DocumentEditStore {
this.isSaving = true; this.isSaving = true;
try { try {
const data = await client.post('/documents.create', { const data = await client.post(
'/documents.create',
{
parentDocument: this.parentDocument && this.parentDocument.id, parentDocument: this.parentDocument && this.parentDocument.id,
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; const { url } = data.data;
this.hasPendingChanges = false; this.hasPendingChanges = false;
@ -76,7 +84,7 @@ class DocumentEditStore {
console.error('Something went wrong'); console.error('Something went wrong');
} }
this.isSaving = false; this.isSaving = false;
} };
@action updateDocument = async () => { @action updateDocument = async () => {
if (this.isSaving) return; if (this.isSaving) return;
@ -84,11 +92,15 @@ class DocumentEditStore {
this.isSaving = true; this.isSaving = true;
try { try {
const data = await client.post('/documents.update', { const data = await client.post(
'/documents.update',
{
id: this.documentId, id: this.documentId,
title: this.title || 'Untitled document', title: this.title || 'Untitled document',
text: this.text, text: this.text,
}, { cache: true }); },
{ cache: true }
);
const { url } = data.data; const { url } = data.data;
this.hasPendingChanges = false; this.hasPendingChanges = false;
@ -97,35 +109,35 @@ class DocumentEditStore {
console.error('Something went wrong'); console.error('Something went wrong');
} }
this.isSaving = false; this.isSaving = false;
} };
@action updateText = (text) => { @action updateText = text => {
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 => {
this.title = title; this.title = title;
} };
@action replaceText = (args) => { @action replaceText = args => {
this.text = this.text.replace(args.original, args.new); this.text = this.text.replace(args.original, args.new);
this.hasPendingChanges = true; this.hasPendingChanges = true;
} };
@action togglePreview = () => { @action togglePreview = () => {
this.preview = !this.preview; this.preview = !this.preview;
} };
@action reset = () => { @action reset = () => {
this.title = 'Lets start with a title'; this.title = 'Lets start with a title';
this.text = '# Lets start with a title\n\nAnd continue from there...'; this.text = '# Lets start with a title\n\nAnd continue from there...';
} };
@action toggleUploadingIndicator = () => { @action toggleUploadingIndicator = () => {
this.isUploading = !this.isUploading; this.isUploading = !this.isUploading;
} };
// Generic // Generic
@ -133,7 +145,7 @@ class DocumentEditStore {
localStorage[DOCUMENT_EDIT_SETTINGS] = JSON.stringify({ localStorage[DOCUMENT_EDIT_SETTINGS] = JSON.stringify({
preview: toJS(this.preview), preview: toJS(this.preview),
}); });
} };
constructor(settings) { constructor(settings) {
// Rehydrate settings // Rehydrate settings
@ -148,6 +160,4 @@ class DocumentEditStore {
} }
export default DocumentEditStore; export default DocumentEditStore;
export { export { DOCUMENT_EDIT_SETTINGS };
DOCUMENT_EDIT_SETTINGS,
};

View File

@ -8,33 +8,28 @@ import EditorPane from './EditorPane';
import styles from '../DocumentEdit.scss'; import styles from '../DocumentEdit.scss';
const Editor = observer((props) => { const Editor = observer(props => {
const store = props.store; const store = props.store;
return ( return (
<div className={ styles.container }> <div className={styles.container}>
<EditorPane <EditorPane fullWidth={!store.preview} onScroll={props.onScroll}>
fullWidth={ !store.preview }
onScroll={ props.onScroll }
>
<MarkdownEditor <MarkdownEditor
onChange={ store.updateText } onChange={store.updateText}
text={ store.text } text={store.text}
replaceText={ store.replaceText } replaceText={store.replaceText}
preview={ store.preview } preview={store.preview}
onSave={ props.onSave } onSave={props.onSave}
onCancel={ props.onCancel } onCancel={props.onCancel}
togglePreview={ props.togglePreview } togglePreview={props.togglePreview}
toggleUploadingIndicator={ store.toggleUploadingIndicator } toggleUploadingIndicator={store.toggleUploadingIndicator}
/> />
</EditorPane> </EditorPane>
{ store.preview ? ( {store.preview
<EditorPane ? <EditorPane scrollTop={props.scrollTop}>
scrollTop={ props.scrollTop } <Preview html={convertToMarkdown(store.text)} />
>
<Preview html={ convertToMarkdown(store.text) } />
</EditorPane> </EditorPane>
) : null } : null}
</div> </div>
); );
}); });

View File

@ -10,32 +10,31 @@ class EditorPane extends React.Component {
onScroll: React.PropTypes.func.isRequired, onScroll: React.PropTypes.func.isRequired,
scrollTop: React.PropTypes.number, scrollTop: React.PropTypes.number,
fullWidth: React.PropTypes.bool, fullWidth: React.PropTypes.bool,
} };
componentWillReceiveProps = (nextProps) => {
componentWillReceiveProps = nextProps => {
if (nextProps.scrollTop) { if (nextProps.scrollTop) {
this.scrollToPosition(nextProps.scrollTop) this.scrollToPosition(nextProps.scrollTop);
}
} }
};
componentDidMount = () => { componentDidMount = () => {
this.refs.pane.addEventListener('scroll', this.handleScroll); this.refs.pane.addEventListener('scroll', this.handleScroll);
} };
componentWillUnmount = () => { componentWillUnmount = () => {
this.refs.pane.removeEventListener('scroll', this.handleScroll); this.refs.pane.removeEventListener('scroll', this.handleScroll);
} };
handleScroll = (e) => { handleScroll = e => {
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(element.scrollTop / contentEl.offsetHeight); this.props.onScroll(element.scrollTop / contentEl.offsetHeight);
}, 50); }, 50);
} };
scrollToPosition = (percentage) => { scrollToPosition = percentage => {
const contentEl = this.refs.content; const contentEl = this.refs.content;
// Push to edges // Push to edges
@ -43,20 +42,20 @@ class EditorPane extends React.Component {
if (percentage > 0.99) percentage = 100; if (percentage > 0.99) percentage = 100;
this.refs.pane.scrollTop = percentage * contentEl.offsetHeight; this.refs.pane.scrollTop = percentage * contentEl.offsetHeight;
} };
render() { render() {
return ( return (
<div <div
className={ cx(styles.editorPane, { fullWidth: this.props.fullWidth }) } className={cx(styles.editorPane, { fullWidth: this.props.fullWidth })}
ref="pane" ref="pane"
> >
<div ref="content" className={ styles.paneContent }> <div ref="content" className={styles.paneContent}>
{ this.props.children } {this.props.children}
</div> </div>
</div> </div>
); );
} }
}; }
export default EditorPane; export default EditorPane;

View File

@ -6,10 +6,10 @@ 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) => { const Preview = props => {
return ( return (
<div className={ cx(styles.container) }> <div className={cx(styles.container)}>
<DocumentHtml html={ props.html } /> <DocumentHtml html={props.html} />
</div> </div>
); );
}; };

View File

@ -1,5 +1,12 @@
import _ from 'lodash'; import _ from 'lodash';
import { observable, action, computed, runInAction, toJS, autorunAsync } from 'mobx'; import {
observable,
action,
computed,
runInAction,
toJS,
autorunAsync,
} from 'mobx';
import { client } from 'utils/ApiClient'; import { client } from 'utils/ApiClient';
import { browserHistory } from 'react-router'; import { browserHistory } from 'react-router';
@ -19,15 +26,14 @@ class DocumentSceneStore {
/* Computed */ /* Computed */
@computed get isCollection() { @computed get isCollection() {
return this.document && return this.document && this.document.collection.type === 'atlas';
this.document.collection.type === 'atlas';
} }
@computed get collectionTree() { @computed get collectionTree() {
if (!this.document || this.document.collection.type !== 'atlas') return; if (!this.document || this.document.collection.type !== 'atlas') return;
const tree = this.document.collection.navigationTree; const tree = this.document.collection.navigationTree;
const collapseNodes = (node) => { const collapseNodes = node => {
node.collapsed = this.collapsedNodes.includes(node.id); node.collapsed = this.collapsedNodes.includes(node.id);
node.children = node.children.map(childNode => { node.children = node.children.map(childNode => {
return collapseNodes(childNode); return collapseNodes(childNode);
@ -69,7 +75,7 @@ class DocumentSceneStore {
} }
this.isFetching = false; this.isFetching = false;
this.updatingContent = false; this.updatingContent = false;
} };
@action deleteDocument = async () => { @action deleteDocument = async () => {
this.isFetching = true; this.isFetching = true;
@ -81,9 +87,9 @@ class DocumentSceneStore {
console.error('Something went wrong'); console.error('Something went wrong');
} }
this.isFetching = false; this.isFetching = false;
} };
@action updateNavigationTree = async (tree) => { @action updateNavigationTree = async tree => {
// Only update when tree changes // Only update when tree changes
if (_.isEqual(toJS(tree), toJS(this.document.collection.navigationTree))) { if (_.isEqual(toJS(tree), toJS(this.document.collection.navigationTree))) {
return true; return true;
@ -104,15 +110,15 @@ class DocumentSceneStore {
console.error('Something went wrong'); console.error('Something went wrong');
} }
this.updatingStructure = false; this.updatingStructure = false;
} };
@action onNodeCollapse = (nodeId) => { @action onNodeCollapse = nodeId => {
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 {
this.collapsedNodes.push(nodeId); this.collapsedNodes.push(nodeId);
} }
} };
// General // General
@ -120,7 +126,7 @@ class DocumentSceneStore {
localStorage[DOCUMENT_PREFERENCES] = JSON.stringify({ localStorage[DOCUMENT_PREFERENCES] = JSON.stringify({
collapsedNodes: toJS(this.collapsedNodes), collapsedNodes: toJS(this.collapsedNodes),
}); });
} };
constructor(settings, options) { constructor(settings, options) {
// Rehydrate settings // Rehydrate settings
@ -136,6 +142,4 @@ class DocumentSceneStore {
} }
export default DocumentSceneStore; export default DocumentSceneStore;
export { export { DOCUMENT_PREFERENCES };
DOCUMENT_PREFERENCES,
};

View File

@ -12,8 +12,7 @@ const cx = classNames.bind(styles);
import SidebarStore from './SidebarStore'; import SidebarStore from './SidebarStore';
@observer @observer class Sidebar extends React.Component {
class Sidebar extends React.Component {
static store; static store;
static propTypes = { static propTypes = {
@ -22,7 +21,7 @@ class Sidebar extends React.Component {
navigationTree: PropTypes.object.isRequired, navigationTree: PropTypes.object.isRequired,
onNavigationUpdate: PropTypes.func.isRequired, onNavigationUpdate: PropTypes.func.isRequired,
onNodeCollapse: PropTypes.func.isRequired, onNodeCollapse: PropTypes.func.isRequired,
} };
constructor(props) { constructor(props) {
super(props); super(props);
@ -30,49 +29,47 @@ class Sidebar extends React.Component {
this.store = new SidebarStore(); this.store = new SidebarStore();
} }
toggleEdit = (e) => { toggleEdit = e => {
e.preventDefault(); e.preventDefault();
this.store.toggleEdit(); this.store.toggleEdit();
} };
render() { render() {
return ( return (
<Flex> <Flex>
{ this.props.open && ( {this.props.open &&
<Flex column className={ cx(styles.sidebar) }> <Flex column className={cx(styles.sidebar)}>
<Flex auto className={ cx(styles.content) }> <Flex auto className={cx(styles.content)}>
<Tree <Tree
paddingLeft={ 10 } paddingLeft={10}
tree={ this.props.navigationTree } tree={this.props.navigationTree}
allowUpdates={ this.store.isEditing } allowUpdates={this.store.isEditing}
onChange={ this.props.onNavigationUpdate } onChange={this.props.onNavigationUpdate}
onCollapse={ this.props.onNodeCollapse } onCollapse={this.props.onNodeCollapse}
/> />
</Flex> </Flex>
<Flex auto className={ styles.actions }> <Flex auto className={styles.actions}>
{ this.store.isEditing && ( {this.store.isEditing &&
<span className={ styles.action }> <span className={styles.action}>
Drag & drop to organize <Separator />&nbsp; Drag & drop to organize <Separator />&nbsp;
</span> </span>}
) }
<span <span
role="button" role="button"
onClick={ this.toggleEdit } onClick={this.toggleEdit}
className={ cx(styles.action, { active: this.store.isEditing }) } className={cx(styles.action, { active: this.store.isEditing })}
> >
{ !this.store.isEditing ? 'Organize documents' : 'Done' } {!this.store.isEditing ? 'Organize documents' : 'Done'}
</span> </span>
</Flex> </Flex>
</Flex> </Flex>}
) }
<div <div
onClick={ this.props.onToggle } onClick={this.props.onToggle}
className={ cx(styles.sidebarToggle, { active: this.store.isEditing }) } className={cx(styles.sidebarToggle, { active: this.store.isEditing })}
title="Toggle sidebar (Cmd+/)" title="Toggle sidebar (Cmd+/)"
> >
<img <img
src={ require("assets/icons/menu.svg") } src={require('assets/icons/menu.svg')}
className={ styles.menuIcon } className={styles.menuIcon}
alt="Menu" alt="Menu"
/> />
</div> </div>

View File

@ -7,7 +7,7 @@ class SidebarStore {
@action toggleEdit = () => { @action toggleEdit = () => {
this.isEditing = !this.isEditing; this.isEditing = !this.isEditing;
} };
} }
export default SidebarStore; export default SidebarStore;

View File

@ -5,8 +5,8 @@ import styles from './Separator.scss';
class Separator extends React.Component { class Separator extends React.Component {
render() { render() {
return ( return (
<span className={ styles.separator }> <span className={styles.separator}>
&middot; ·
</span> </span>
); );
} }

View File

@ -7,9 +7,7 @@ import CenteredContent from 'components/CenteredContent';
class Error404 extends React.Component { class Error404 extends React.Component {
render() { render() {
return ( return (
<Layout <Layout titleText="Not Found">
titleText="Not Found"
>
<CenteredContent> <CenteredContent>
<h1>Not Found</h1> <h1>Not Found</h1>

View File

@ -7,13 +7,13 @@ import CenteredContent from 'components/CenteredContent';
class ErrorAuth extends React.Component { class ErrorAuth extends React.Component {
render() { render() {
return ( return (
<Layout <Layout titleText="Not Found">
titleText="Not Found"
>
<CenteredContent> <CenteredContent>
<h1>Authentication failed</h1> <h1>Authentication failed</h1>
<p>We were unable to log you in. <Link to="/">Please try again.</Link></p> <p>
We were unable to log you in. <Link to="/">Please try again.</Link>
</p>
</CenteredContent> </CenteredContent>
</Layout> </Layout>
); );

View File

@ -7,23 +7,18 @@ import { DocumentHtml } from 'components/Document';
import { convertToMarkdown } from 'utils/markdown'; import { convertToMarkdown } from 'utils/markdown';
@observer @observer class Flatpage extends React.Component {
class Flatpage extends React.Component {
static propTypes = { static propTypes = {
route: PropTypes.object, route: PropTypes.object,
} };
render() { render() {
const { title, content } = this.props.route; const { title, content } = this.props.route;
return ( return (
<Layout <Layout title={<Title>{title}</Title>} titleText={title} search={false}>
title={ <Title>{ title }</Title> }
titleText={ title }
search={ false }
>
<CenteredContent> <CenteredContent>
<DocumentHtml html={ convertToMarkdown(content) } /> <DocumentHtml html={convertToMarkdown(content)} />
</CenteredContent> </CenteredContent>
</Layout> </Layout>
); );

View File

@ -1,25 +1,43 @@
import React from 'react'; import React from 'react';
import { Frame } from 'react-keyframes'; import { Frame } from 'react-keyframes';
let frames = []; const frames = [];
const p = (node) => frames.push(node); const p = node => frames.push(node);
const E = (props) => { const E = props => {
return (<Frame duration={props.duration || 300} component='div'>{ props.children }</Frame>); return (
<Frame duration={props.duration || 300} component="div">
{props.children}
</Frame>
);
}; };
const line1 = (<p>Hi there,</p>); const line1 = <p>Hi there,</p>;
const line2 = (<p>We're excited to share what were building.</p>); const line2 = <p>We're excited to share what were building.</p>;
const line3 = (<p>We <strong>**love**</strong> Markdown,</p>); const line3 = <p>We <strong>**love**</strong> Markdown,</p>;
const line4 = (<p>but we also get that it's not for everyone.</p>); const line4 = <p>but we also get that it's not for everyone.</p>;
const line5 = (<p>Together with you,</p>); const line5 = <p>Together with you,</p>;
const line6 = (<p>we want to build the best place to</p>); const line6 = <p>we want to build the best place to</p>;
const line7 = (<p>share ideas,</p>); const line7 = <p>share ideas,</p>;
const line8 = (<p>tell stories,</p>); const line8 = <p>tell stories,</p>;
const line9 = (<p>and build knowledge.</p>); const line9 = <p>and build knowledge.</p>;
const line10 = (<p>We're just getting started.</p>); const line10 = <p>We're just getting started.</p>;
const line11 = (<p>Welcome to Atlas.</p>); const line11 = <p>Welcome to Atlas.</p>;
p(<E>{line1}{line2}{line3}{line4}{line5}{line6}{line7}{line8}{line9}{line10}{line11}</E>); p(
<E>
{line1}
{line2}
{line3}
{line4}
{line5}
{line6}
{line7}
{line8}
{line9}
{line10}
{line11}
</E>
);
// Hmms leaving this here for now, would be nice to something // Hmms leaving this here for now, would be nice to something

View File

@ -12,12 +12,11 @@ import styles from './Search.scss';
import SearchStore from './SearchStore'; import SearchStore from './SearchStore';
@observer @observer class Search extends React.Component {
class Search extends React.Component {
static propTypes = { static propTypes = {
route: PropTypes.object.isRequired, route: PropTypes.object.isRequired,
routeParams: PropTypes.object.isRequired, routeParams: PropTypes.object.isRequired,
} };
constructor(props) { constructor(props) {
super(props); super(props);
@ -31,7 +30,7 @@ class Search extends React.Component {
searchTerm = searchTerm.split(/[\s-]+/gi).join(' '); searchTerm = searchTerm.split(/[\s-]+/gi).join(' ');
this.store.search(searchTerm); this.store.search(searchTerm);
} }
} };
get viewNotFound() { get viewNotFound() {
const { sceneType } = this.props.route; const { sceneType } = this.props.route;
@ -39,7 +38,7 @@ class Search extends React.Component {
} }
render() { render() {
const search = _.debounce((searchTerm) => { const search = _.debounce(searchTerm => {
this.store.search(searchTerm); this.store.search(searchTerm);
}, 250); }, 250);
const title = ( const title = (
@ -50,35 +49,37 @@ class Search extends React.Component {
return ( return (
<Layout <Layout
title={ title } title={title}
titleText="Search" titleText="Search"
search={ false } search={false}
loading={ this.store.isFetching } loading={this.store.isFetching}
> >
<CenteredContent> <CenteredContent>
{ this.viewNotFound && ( {this.viewNotFound &&
<div> <div>
<h1>Not Found</h1> <h1>Not Found</h1>
<p>We're unable to find the page you're accessing.</p> <p>We're unable to find the page you're accessing.</p>
<hr /> <hr />
</div> </div>}
) }
<Flex column auto> <Flex column auto>
<Flex auto> <Flex auto>
<img <img
src={ require('assets/icons/search.svg') } src={require('assets/icons/search.svg')}
className={ styles.icon } className={styles.icon}
alt="Search" alt="Search"
/> />
<SearchField <SearchField
searchTerm={ this.store.searchTerm } searchTerm={this.store.searchTerm}
onChange={ search } onChange={search}
/> />
</Flex> </Flex>
{ this.store.documents && this.store.documents.map((document) => { {this.store.documents &&
return (<DocumentPreview key={ document.id } document={ document } />); this.store.documents.map(document => {
}) } return (
<DocumentPreview key={document.id} document={document} />
);
})}
</Flex> </Flex>
</CenteredContent> </CenteredContent>
</Layout> </Layout>

View File

@ -11,7 +11,7 @@ class SearchStore {
/* Actions */ /* Actions */
@action search = async (query) => { @action search = async query => {
this.searchTerm = query; this.searchTerm = query;
this.isFetching = true; this.isFetching = true;
@ -24,14 +24,14 @@ class SearchStore {
this.pagination = pagination; this.pagination = pagination;
}); });
} catch (e) { } catch (e) {
console.error("Something went wrong"); console.error('Something went wrong');
} }
} else { } else {
this.documents = null; this.documents = null;
} }
this.isFetching = false; this.isFetching = false;
} };
} }
export default SearchStore; export default SearchStore;

View File

@ -3,22 +3,21 @@ import { observer } from 'mobx-react';
import styles from './SearchField.scss'; import styles from './SearchField.scss';
@observer @observer class SearchField extends React.Component {
class SearchField extends React.Component {
static propTypes = { static propTypes = {
onChange: PropTypes.func, onChange: PropTypes.func,
} };
onChange = (event) => { onChange = event => {
this.props.onChange(event.currentTarget.value); this.props.onChange(event.currentTarget.value);
} };
render() { render() {
return ( return (
<div className={ styles.container }> <div className={styles.container}>
<input <input
onChange={ this.onChange } onChange={this.onChange}
className={ styles.field } className={styles.field}
placeholder="Search" placeholder="Search"
autoFocus autoFocus
/> />

View File

@ -12,8 +12,7 @@ import styles from './Settings.scss';
import SettingsStore from './SettingsStore'; import SettingsStore from './SettingsStore';
@observer @observer class Settings extends React.Component {
class Settings extends React.Component {
static store; static store;
constructor(props) { constructor(props) {
@ -21,10 +20,10 @@ class Settings extends React.Component {
this.store = new SettingsStore(); this.store = new SettingsStore();
} }
onKeyCreate = (e) => { onKeyCreate = e => {
e.preventDefault(); e.preventDefault();
this.store.createApiKey(); this.store.createApiKey();
} };
render() { render() {
const title = ( const title = (
@ -37,48 +36,52 @@ class Settings extends React.Component {
return ( return (
<Layout <Layout
title={ title } title={title}
titleText="Settings" titleText="Settings"
search={ false } search={false}
loading={ this.store.isFetching } loading={this.store.isFetching}
> >
<CenteredContent> <CenteredContent>
{ showSlackSettings && ( {showSlackSettings &&
<div className={ styles.section }> <div className={styles.section}>
<h2 className={ styles.sectionHeader }>Slack</h2> <h2 className={styles.sectionHeader}>Slack</h2>
<p> <p>
Connect Atlas to your Slack to instantly search for your documents Connect Atlas to your Slack to instantly search for your documents
using <code>/atlas</code> command. using <code>/atlas</code> command.
</p> </p>
<SlackAuthLink <SlackAuthLink
scopes={ ['commands'] } scopes={['commands']}
redirectUri={ `${URL}/auth/slack/commands` } redirectUri={`${URL}/auth/slack/commands`}
> >
<img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /> <img
alt="Add to Slack"
height="40"
width="139"
src="https://platform.slack-edge.com/img/add_to_slack.png"
srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x"
/>
</SlackAuthLink> </SlackAuthLink>
</div> </div>}
) }
<div className={ styles.section }> <div className={styles.section}>
<h2 className={ styles.sectionHeader }>API access</h2> <h2 className={styles.sectionHeader}>API access</h2>
<p> <p>
Create API tokens to hack on your Atlas. Create API tokens to hack on your Atlas.
Learn more in <a href>API documentation</a>. Learn more in <a href>API documentation</a>.
</p> </p>
{ this.store.apiKeys && ( {this.store.apiKeys &&
<table className={ styles.apiKeyTable }> <table className={styles.apiKeyTable}>
{ this.store.apiKeys.map(key => ( {this.store.apiKeys.map(key => (
<ApiKeyRow <ApiKeyRow
id={ key.id } id={key.id}
name={ key.name } name={key.name}
secret={ key.secret } secret={key.secret}
onDelete={ this.store.deleteApiKey } onDelete={this.store.deleteApiKey}
/> />
)) } ))}
</table> </table>}
) }
<div> <div>
<InlineForm <InlineForm
@ -86,11 +89,11 @@ class Settings extends React.Component {
buttonLabel="Create token" buttonLabel="Create token"
label="InlineForm" label="InlineForm"
name="inline_form" name="inline_form"
value={ this.store.keyName } value={this.store.keyName}
onChange={ this.store.setKeyName } onChange={this.store.setKeyName}
onClick={ this.onKeyCreate } onClick={this.onKeyCreate}
style={{ width: '100%' }} style={{ width: '100%' }}
disabled={ this.store.isFetching } disabled={this.store.isFetching}
/> />
</div> </div>

View File

@ -17,10 +17,10 @@ class SearchStore {
this.apiKeys = data; this.apiKeys = data;
}); });
} catch (e) { } catch (e) {
console.error("Something went wrong"); console.error('Something went wrong');
} }
this.isFetching = false; this.isFetching = false;
} };
@action createApiKey = async () => { @action createApiKey = async () => {
this.isFetching = true; this.isFetching = true;
@ -35,12 +35,12 @@ class SearchStore {
this.keyName = ''; this.keyName = '';
}); });
} catch (e) { } catch (e) {
console.error("Something went wrong"); console.error('Something went wrong');
} }
this.isFetching = false; this.isFetching = false;
} };
@action deleteApiKey = async (id) => { @action deleteApiKey = async id => {
this.isFetching = true; this.isFetching = true;
try { try {
@ -51,14 +51,14 @@ class SearchStore {
this.fetchApiKeys(); this.fetchApiKeys();
}); });
} catch (e) { } catch (e) {
console.error("Something went wrong"); console.error('Something went wrong');
} }
this.isFetching = false; this.isFetching = false;
} };
@action setKeyName = (value) => { @action setKeyName = value => {
this.keyName = value.target.value; this.keyName = value.target.value;
} };
constructor() { constructor() {
this.fetchApiKeys(); this.fetchApiKeys();

View File

@ -10,37 +10,34 @@ class ApiKeyRow extends React.Component {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
secret: PropTypes.string.isRequired, secret: PropTypes.string.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
} };
state = { state = {
disabled: false, disabled: false,
} };
onClick = () => { onClick = () => {
this.props.onDelete(this.props.id); this.props.onDelete(this.props.id);
this.setState({ disabled: true }); this.setState({ disabled: true });
} };
render() { render() {
const { const { name, secret } = this.props;
name,
secret,
} = this.props;
const { const { disabled } = this.state;
disabled,
} = this.state;
return ( return (
<tr> <tr>
<td>{ name }</td> <td>{name}</td>
<td><code>{ secret }</code></td> <td><code>{secret}</code></td>
<td> <td>
<span <span
role="button" role="button"
onClick={ this.onClick } onClick={this.onClick}
className={ cx(styles.deleteAction, { disabled }) } className={cx(styles.deleteAction, { disabled })}
>Delete</span> >
Delete
</span>
</td> </td>
</tr> </tr>
); );

View File

@ -31,7 +31,6 @@ class SlackAuth extends React.Component {
browserHistory.replace('/dashboard'); browserHistory.replace('/dashboard');
} catch (e) { } catch (e) {
browserHistory.push('/auth-error'); browserHistory.push('/auth-error');
return;
} }
} else { } else {
// Regular Slack authentication // Regular Slack authentication

View File

@ -1,6 +1,6 @@
export default (type, ...argNames) => { export default (type, ...argNames) => {
return function(...args) { return function(...args) {
let action = { type }; const action = { type };
argNames.forEach((arg, index) => { argNames.forEach((arg, index) => {
action[argNames[index]] = args[index]; action[argNames[index]] = args[index];
}); });

View File

@ -1,5 +1,5 @@
{ {
"name": "BeautifulAtlas", "name": "Atlas",
"private": true, "private": true,
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@ -10,7 +10,6 @@
"start": "node index.js", "start": "node index.js",
"dev": "cross-env NODE_ENV=development DEBUG=sql,cache,presenters ./node_modules/.bin/nodemon --watch server index.js", "dev": "cross-env NODE_ENV=development DEBUG=sql,cache,presenters ./node_modules/.bin/nodemon --watch server index.js",
"lint": "eslint frontend", "lint": "eslint frontend",
"prettier": "prettier --single-quote --trailing-comma es5 --write frontend/**/*.js server/**/*.js",
"deploy": "git push heroku master", "deploy": "git push heroku master",
"heroku-postbuild": "npm run build && npm run sequelize db:migrate", "heroku-postbuild": "npm run build && npm run sequelize db:migrate",
"sequelize": "./node_modules/.bin/sequelize", "sequelize": "./node_modules/.bin/sequelize",
@ -21,7 +20,7 @@
}, },
"lint-staged": { "lint-staged": {
"*.js": [ "*.js": [
"yarn prettier", "eslint --fix",
"git add" "git add"
] ]
}, },
@ -51,7 +50,7 @@
] ]
}, },
"engines": { "engines": {
"node": "6.x" "node": ">= 7.6"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -79,10 +78,12 @@
"dotenv": "^4.0.0", "dotenv": "^4.0.0",
"emoji-name-map": "1.1.2", "emoji-name-map": "1.1.2",
"eslint": "^3.19.0", "eslint": "^3.19.0",
"eslint-config-airbnb": "^14.1.0", "eslint-config-react-app": "^0.6.2",
"eslint-import-resolver-webpack": "^0.3.1", "eslint-import-resolver-webpack": "^0.3.1",
"eslint-plugin-flowtype": "^2.32.1",
"eslint-plugin-import": "^2.2.0", "eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-prettier": "^2.0.1",
"eslint-plugin-react": "^6.10.3", "eslint-plugin-react": "^6.10.3",
"exports-loader": "0.6.3", "exports-loader": "0.6.3",
"extract-text-webpack-plugin": "1.0.1", "extract-text-webpack-plugin": "1.0.1",
@ -155,8 +156,6 @@
"devDependencies": { "devDependencies": {
"babel-jest": "^15.0.0", "babel-jest": "^15.0.0",
"enzyme": "^2.4.1", "enzyme": "^2.4.1",
"eslint-config-prettier": "^1.7.0",
"eslint-plugin-prettier": "^2.0.1",
"fetch-test-server": "^1.1.0", "fetch-test-server": "^1.1.0",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"ignore-loader": "0.1.1", "ignore-loader": "0.1.1",

View File

@ -2640,21 +2640,9 @@ escope@^3.6.0:
esrecurse "^4.1.0" esrecurse "^4.1.0"
estraverse "^4.1.1" estraverse "^4.1.1"
eslint-config-airbnb-base@^11.1.0: eslint-config-react-app@^0.6.2:
version "11.1.3" version "0.6.2"
resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.1.3.tgz#0e8db71514fa36b977fbcf977c01edcf863e0cf0" resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-0.6.2.tgz#ee535cbaaf9e3576ea16b99afe720353d8730ec0"
eslint-config-airbnb@^14.1.0:
version "14.1.0"
resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-14.1.0.tgz#355d290040bbf8e00bf8b4b19f4b70cbe7c2317f"
dependencies:
eslint-config-airbnb-base "^11.1.0"
eslint-config-prettier@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-1.7.0.tgz#cda3ce22df1e852daa9370f1f3446e8b8a02ce44"
dependencies:
get-stdin "^5.0.1"
eslint-import-resolver-node@^0.2.0: eslint-import-resolver-node@^0.2.0:
version "0.2.3" version "0.2.3"
@ -2684,6 +2672,12 @@ eslint-module-utils@^2.0.0:
debug "2.2.0" debug "2.2.0"
pkg-dir "^1.0.0" pkg-dir "^1.0.0"
eslint-plugin-flowtype@^2.32.1:
version "2.32.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.32.1.tgz#bbee185dedf97e5f63ec975cdcddd199bd2a2501"
dependencies:
lodash "^4.15.0"
eslint-plugin-import@^2.2.0: eslint-plugin-import@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz#72ba306fad305d67c4816348a4699a4229ac8b4e" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz#72ba306fad305d67c4816348a4699a4229ac8b4e"
@ -3230,7 +3224,7 @@ get-caller-file@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
get-stdin@5.0.1, get-stdin@^5.0.1: get-stdin@5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
@ -5259,7 +5253,7 @@ lodash@4.12.0:
version "4.12.0" version "4.12.0"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.12.0.tgz#2bd6dc46a040f59e686c972ed21d93dc59053258" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.12.0.tgz#2bd6dc46a040f59e686c972ed21d93dc59053258"
lodash@^4.0.0, lodash@^4.1.0, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.6.1: lodash@^4.0.0, lodash@^4.1.0, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.6.1:
version "4.17.4" version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"