Fix prettier integration, format (#31)
* Fix prettier integration, format * Reformat again
This commit is contained in:
parent
51fade7439
commit
2095b3a874
28
.eslintrc
28
.eslintrc
|
@ -1,23 +1,16 @@
|
|||
{
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"prettier",
|
||||
"prettier/react"
|
||||
],
|
||||
"parser": "babel-eslint",
|
||||
"extends": ["react-app", "plugin:import/errors", "plugin:import/warnings"],
|
||||
"plugins": [
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"arrow-body-style":[0, "as-needed"], // fix `this` shortcut on ES6 classes
|
||||
"react/jsx-no-bind": 0, // Makes difficult to pass args to prop functions
|
||||
"react/jsx-curly-spacing": [2, "always", {"spacing": {
|
||||
"objectLiterals": "never"
|
||||
}}], // Spaces inside curlies, except prop={{}}
|
||||
"react/prefer-stateless-function": 0, // Don't prefer stateless components
|
||||
"no-else-return": 0,
|
||||
"new-cap": 0,
|
||||
"no-param-reassign": 0,
|
||||
no-unused-vars: ["error", {
|
||||
"argsIgnorePattern": "^_",
|
||||
}],
|
||||
"import/order": "warn",
|
||||
// Prettier automatically uses the least amount of parens possible, so this
|
||||
// does more harm than good.
|
||||
"no-mixed-operators": "off",
|
||||
// Enforce that code is formatted with prettier.
|
||||
"prettier/prettier": ["error", {"trailingComma": "es5", "singleQuote": true}]
|
||||
},
|
||||
"settings" : {
|
||||
"import/resolver": {
|
||||
|
@ -34,7 +27,6 @@
|
|||
SLACK_KEY: true,
|
||||
SLACK_REDIRECT_URI: true,
|
||||
DEPLOYMENT: true,
|
||||
|
||||
afterAll: true
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
|
||||
import { Flex } from 'reflexbox';
|
||||
|
||||
import styles from './Alert.scss';
|
||||
import classNames from 'classnames/bind';
|
||||
import styles from './Alert.scss';
|
||||
|
||||
const cx = classNames.bind(styles);
|
||||
|
||||
class Alert extends React.Component {
|
||||
|
@ -12,9 +11,8 @@ class Alert extends React.Component {
|
|||
danger: PropTypes.bool,
|
||||
warning: PropTypes.bool,
|
||||
success: PropTypes.bool,
|
||||
info: PropTypes.bool,
|
||||
offline: PropTypes.bool,
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let alertType;
|
||||
|
@ -25,8 +23,12 @@ class Alert extends React.Component {
|
|||
if (!alertType) alertType = 'info'; // default
|
||||
|
||||
return (
|
||||
<Flex align="center" justify="center" className={ cx(styles.container, styles[alertType]) }>
|
||||
{ this.props.children }
|
||||
<Flex
|
||||
align="center"
|
||||
justify="center"
|
||||
className={cx(styles.container, styles[alertType])}
|
||||
>
|
||||
{this.props.children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,29 +8,29 @@ import styles from './AtlasPreview.scss';
|
|||
// import classNames from 'classnames/bind';
|
||||
// const cx = classNames.bind(styles);
|
||||
|
||||
@observer
|
||||
class AtlasPreview extends React.Component {
|
||||
@observer class AtlasPreview extends React.Component {
|
||||
static propTypes = {
|
||||
data: React.PropTypes.object.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const data = this.props.data;
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<h2><Link to={ data.url } className={ styles.atlasLink }>{ data.name }</Link></h2>
|
||||
{ data.recentDocuments.length > 0 ?
|
||||
data.recentDocuments.map(document => {
|
||||
return (
|
||||
<DocumentLink document={ document } key={ document.id } />
|
||||
);
|
||||
})
|
||||
: (
|
||||
<div className={ styles.description }>
|
||||
No documents. Why not <Link to={ `${data.url}/new` }>create one</Link>?
|
||||
</div>
|
||||
) }
|
||||
<div className={styles.container}>
|
||||
<h2>
|
||||
<Link to={data.url} className={styles.atlasLink}>{data.name}</Link>
|
||||
</h2>
|
||||
{data.recentDocuments.length > 0
|
||||
? data.recentDocuments.map(document => {
|
||||
return <DocumentLink document={document} key={document.id} />;
|
||||
})
|
||||
: <div className={styles.description}>
|
||||
No documents. Why not
|
||||
{' '}
|
||||
<Link to={`${data.url}/new`}>create one</Link>
|
||||
?
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import React from 'react';
|
||||
import { observer } from "mobx-react"
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import moment from 'moment';
|
||||
import Link from 'react-router/lib/Link';
|
||||
|
||||
import styles from './DocumentLink.scss';
|
||||
|
||||
const DocumentLink = observer((props) => {
|
||||
const DocumentLink = observer(props => {
|
||||
return (
|
||||
<Link to={ props.document.url } className={ styles.link }>
|
||||
<h3 className={ styles.title }>{ props.document.title }</h3>
|
||||
<span className={ styles.timestamp }>{ moment(props.document.updatedAt).fromNow() }</span>
|
||||
<Link to={props.document.url} className={styles.link}>
|
||||
<h3 className={styles.title}>{props.document.title}</h3>
|
||||
<span className={styles.timestamp}>
|
||||
{moment(props.document.updatedAt).fromNow()}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import DocumentLink from './DocumentLink';
|
||||
export default DocumentLink;
|
||||
export default DocumentLink;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import AtlasPreview from './AtlasPreview';
|
||||
export default AtlasPreview;
|
||||
export default AtlasPreview;
|
||||
|
|
|
@ -7,37 +7,40 @@ const cx = classNames.bind(styles);
|
|||
|
||||
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 (
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName="fadeIn"
|
||||
transitionAppear
|
||||
transitionEnter
|
||||
transitionLeave
|
||||
transitionAppearTimeout={ 0 }
|
||||
transitionEnterTimeout={ 0 }
|
||||
transitionLeaveTimeout={ 0 }
|
||||
transitionAppearTimeout={0}
|
||||
transitionEnterTimeout={0}
|
||||
transitionLeaveTimeout={0}
|
||||
>
|
||||
<div>
|
||||
<div className={ cx(styles.container, styles.animated) }>
|
||||
<div className={cx(styles.container, styles.animated)}>
|
||||
<div
|
||||
className={ cx(styles.mask, styles.header) }
|
||||
className={cx(styles.mask, styles.header)}
|
||||
style={{ width: randomValues[0] }}
|
||||
> </div>
|
||||
/>
|
||||
<div
|
||||
className={ cx(styles.mask, styles.bodyText) }
|
||||
className={cx(styles.mask, styles.bodyText)}
|
||||
style={{ width: randomValues[1] }}
|
||||
> </div>
|
||||
/>
|
||||
<div
|
||||
className={ cx(styles.mask, styles.bodyText) }
|
||||
className={cx(styles.mask, styles.bodyText)}
|
||||
style={{ width: randomValues[2] }}
|
||||
> </div>
|
||||
/>
|
||||
<div
|
||||
className={ cx(styles.mask, styles.bodyText) }
|
||||
className={cx(styles.mask, styles.bodyText)}
|
||||
style={{ width: randomValues[3] }}
|
||||
> </div>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ReactCSSTransitionGroup>
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import AtlasPreviewLoading from './AtlasPreviewLoading';
|
||||
export default AtlasPreviewLoading;
|
||||
export default AtlasPreviewLoading;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import Button from './Button';
|
||||
export default Button;
|
||||
export default Button;
|
||||
|
|
|
@ -2,15 +2,15 @@ import React from 'react';
|
|||
|
||||
import styles from './CenteredContent.scss';
|
||||
|
||||
const CenteredContent = (props) => {
|
||||
const CenteredContent = props => {
|
||||
const style = {
|
||||
maxWidth: props.maxWidth,
|
||||
...props.style,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={ styles.content } style={ style }>
|
||||
{ props.children }
|
||||
<div className={styles.content} style={style}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -24,4 +24,4 @@ CenteredContent.propTypes = {
|
|||
style: React.PropTypes.object,
|
||||
};
|
||||
|
||||
export default CenteredContent;
|
||||
export default CenteredContent;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import CenteredContent from './CenteredContent';
|
||||
export default CenteredContent;
|
||||
export default CenteredContent;
|
||||
|
|
|
@ -2,10 +2,8 @@ import React from 'react';
|
|||
|
||||
import styles from './Divider.scss';
|
||||
|
||||
const Divider = (props) => {
|
||||
return(
|
||||
<div className={ styles.divider }><span></span></div>
|
||||
);
|
||||
const Divider = props => {
|
||||
return <div className={styles.divider}><span /></div>;
|
||||
};
|
||||
|
||||
export default Divider;
|
||||
export default Divider;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import Divider from './Divider';
|
||||
export default Divider;
|
||||
export default Divider;
|
||||
|
|
|
@ -7,23 +7,22 @@ import DocumentHtml from './components/DocumentHtml';
|
|||
|
||||
import styles from './Document.scss';
|
||||
|
||||
@observer
|
||||
class Document extends React.Component {
|
||||
@observer class Document extends React.Component {
|
||||
static propTypes = {
|
||||
document: PropTypes.object.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<div className={styles.container}>
|
||||
<PublishingInfo
|
||||
createdAt={ this.props.document.createdAt }
|
||||
createdBy={ this.props.document.createdBy }
|
||||
updatedAt={ this.props.document.updatedAt }
|
||||
updatedBy={ this.props.document.updatedBy }
|
||||
collaborators={ toJS(this.props.document.collaborators) }
|
||||
createdAt={this.props.document.createdAt}
|
||||
createdBy={this.props.document.createdBy}
|
||||
updatedAt={this.props.document.updatedAt}
|
||||
updatedBy={this.props.document.updatedBy}
|
||||
collaborators={toJS(this.props.document.collaborators)}
|
||||
/>
|
||||
<DocumentHtml html={ this.props.document.html } />
|
||||
<DocumentHtml html={this.props.document.html} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,19 +4,18 @@ import { observer } from 'mobx-react';
|
|||
|
||||
import styles from './DocumentHtml.scss';
|
||||
|
||||
@observer
|
||||
class DocumentHtml extends React.Component {
|
||||
@observer class DocumentHtml extends React.Component {
|
||||
static propTypes = {
|
||||
html: PropTypes.string.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
this.setExternalLinks();
|
||||
}
|
||||
};
|
||||
|
||||
componentDidUpdate = () => {
|
||||
this.setExternalLinks();
|
||||
}
|
||||
};
|
||||
|
||||
setExternalLinks = () => {
|
||||
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
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={ styles.document }
|
||||
className={styles.document}
|
||||
dangerouslySetInnerHTML={{ __html: this.props.html }}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -2,6 +2,4 @@ import Document from './Document';
|
|||
import DocumentHtml from './components/DocumentHtml';
|
||||
|
||||
export default Document;
|
||||
export {
|
||||
DocumentHtml,
|
||||
};
|
||||
export { DocumentHtml };
|
||||
|
|
|
@ -8,22 +8,23 @@ import styles from './DocumentList.scss';
|
|||
class DocumentList extends React.Component {
|
||||
static propTypes = {
|
||||
documents: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{ this.props.documents && this.props.documents.map((document) => {
|
||||
return (
|
||||
<div>
|
||||
<DocumentPreview document={ document } />
|
||||
<Divider />
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
{this.props.documents &&
|
||||
this.props.documents.map(document => {
|
||||
return (
|
||||
<div>
|
||||
<DocumentPreview document={document} />
|
||||
<Divider />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default DocumentList;
|
||||
export default DocumentList;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import DocumentList from './DocumentList';
|
||||
export default DocumentList;
|
||||
export default DocumentList;
|
||||
|
|
|
@ -10,33 +10,29 @@ import styles from './DocumentPreview.scss';
|
|||
class Document extends React.Component {
|
||||
static propTypes = {
|
||||
document: React.PropTypes.object.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<div className={styles.container}>
|
||||
<PublishingInfo
|
||||
createdAt={ this.props.document.createdAt }
|
||||
createdBy={ this.props.document.createdBy }
|
||||
updatedAt={ this.props.document.updatedAt }
|
||||
updatedBy={ this.props.document.updatedBy }
|
||||
collaborators={ toJS(this.props.document.collaborators) }
|
||||
createdAt={this.props.document.createdAt}
|
||||
createdBy={this.props.document.createdBy}
|
||||
updatedAt={this.props.document.updatedAt}
|
||||
updatedBy={this.props.document.updatedBy}
|
||||
collaborators={toJS(this.props.document.collaborators)}
|
||||
/>
|
||||
|
||||
<Link
|
||||
to={ this.props.document.url }
|
||||
className={ styles.title }
|
||||
>
|
||||
<h2>{ this.props.document.title }</h2>
|
||||
<Link to={this.props.document.url} className={styles.title}>
|
||||
<h2>{this.props.document.title}</h2>
|
||||
</Link>
|
||||
|
||||
<div dangerouslySetInnerHTML={{ __html: this.props.document.preview }} />
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: this.props.document.preview }}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Link
|
||||
to={ this.props.document.url }
|
||||
className={ styles.continueLink }
|
||||
>
|
||||
<Link to={this.props.document.url} className={styles.continueLink}>
|
||||
Continue reading...
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import DocumentPreview from './DocumentPreview';
|
||||
export default DocumentPreview;
|
||||
export default DocumentPreview;
|
||||
|
|
|
@ -10,14 +10,13 @@ class MenuItem extends React.Component {
|
|||
} else {
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={ styles.menuItem }
|
||||
onClick={ this.onClick }
|
||||
>{ this.props.children }</div>
|
||||
<div className={styles.menuItem} onClick={this.onClick}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -34,46 +33,44 @@ class DropdownMenu extends React.Component {
|
|||
static propTypes = {
|
||||
label: React.PropTypes.node.isRequired,
|
||||
children: React.PropTypes.node.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
state = {
|
||||
menuVisible: false,
|
||||
}
|
||||
};
|
||||
|
||||
onMouseEnter = () => {
|
||||
this.setState({ menuVisible: true });
|
||||
}
|
||||
};
|
||||
|
||||
onMouseLeave = () => {
|
||||
this.setState({ menuVisible: false });
|
||||
}
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
this.setState({ menuVisible: !this.state.menuVisible });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={ styles.menuContainer }
|
||||
onMouseEnter={ this.onMouseEnter }
|
||||
onMouseLeave={ this.onMouseLeave }
|
||||
className={styles.menuContainer}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
>
|
||||
<div className={ styles.label } onClick={ this.onClick }>
|
||||
{ this.props.label }
|
||||
<div className={styles.label} onClick={this.onClick}>
|
||||
{this.props.label}
|
||||
</div>
|
||||
|
||||
{ this.state.menuVisible ? (
|
||||
<div className={ styles.menu }>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
) : null }
|
||||
{this.state.menuVisible
|
||||
? <div className={styles.menu}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default DropdownMenu;
|
||||
export {
|
||||
MenuItem,
|
||||
}
|
||||
export { MenuItem };
|
||||
|
|
|
@ -2,13 +2,8 @@ import React from 'react';
|
|||
|
||||
import styles from './MoreIcon.scss';
|
||||
|
||||
const MoreIcon = (props) => {
|
||||
return (
|
||||
<img
|
||||
src={ require("./assets/more.svg") }
|
||||
className={ styles.icon }
|
||||
/>
|
||||
);
|
||||
const MoreIcon = props => {
|
||||
return <img src={require('./assets/more.svg')} className={styles.icon} />;
|
||||
};
|
||||
|
||||
export default MoreIcon;
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import DropdownMenu, { MenuItem } from './DropdownMenu';
|
||||
import MoreIcon from './components/MoreIcon';
|
||||
export default DropdownMenu;
|
||||
export {
|
||||
MenuItem,
|
||||
MoreIcon,
|
||||
};
|
||||
export { MenuItem, MoreIcon };
|
||||
|
|
|
@ -5,10 +5,10 @@ export default ({ style = {}, className }) => {
|
|||
<span className={className}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={ style.width || 208 }
|
||||
height={ style.height || 128 }
|
||||
width={style.width || 208}
|
||||
height={style.height || 128}
|
||||
viewBox="0 0 208 128"
|
||||
color={ style.color }
|
||||
color={style.color}
|
||||
>
|
||||
<rect
|
||||
width="198"
|
||||
|
|
|
@ -2,12 +2,11 @@ import React from 'react';
|
|||
|
||||
import styles from './HeaderAction.scss';
|
||||
|
||||
const HeaderAction = (props) => {
|
||||
const HeaderAction = props => {
|
||||
return (
|
||||
<div
|
||||
onClick={ props.onClick }
|
||||
className={ styles.container }
|
||||
>{ props.children }</div>
|
||||
<div onClick={props.onClick} className={styles.container}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -15,4 +14,4 @@ HeaderAction.propTypes = {
|
|||
onClick: React.PropTypes.func,
|
||||
};
|
||||
|
||||
export default HeaderAction;
|
||||
export default HeaderAction;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import HeaderAction from './HeaderAction';
|
||||
export default HeaderAction;
|
||||
export default HeaderAction;
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
@observer
|
||||
class SaveAction extends React.Component {
|
||||
@observer class SaveAction extends React.Component {
|
||||
static propTypes = {
|
||||
onClick: React.PropTypes.func.isRequired,
|
||||
disabled: React.PropTypes.bool,
|
||||
isNew: React.PropTypes.bool,
|
||||
}
|
||||
};
|
||||
|
||||
onClick = (event) => {
|
||||
onClick = event => {
|
||||
if (this.props.disabled) return;
|
||||
|
||||
event.preventDefault();
|
||||
this.props.onClick();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { disabled, isNew } = this.props;
|
||||
|
@ -23,11 +22,11 @@ class SaveAction extends React.Component {
|
|||
<div>
|
||||
<a
|
||||
href
|
||||
onClick={ this.onClick }
|
||||
onClick={this.onClick}
|
||||
style={{ opacity: disabled ? 0.5 : 1 }}
|
||||
title="Save changes (Cmd+Enter)"
|
||||
>
|
||||
{ isNew ? 'Publish' : 'Save' }
|
||||
{isNew ? 'Publish' : 'Save'}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ class Title extends React.Component {
|
|||
children: React.PropTypes.string,
|
||||
truncate: React.PropTypes.number,
|
||||
placeholder: React.PropTypes.string,
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let title;
|
||||
|
@ -28,12 +28,12 @@ class Title extends React.Component {
|
|||
|
||||
return (
|
||||
<span>
|
||||
{ title && (<span> / </span>) }
|
||||
{title && <span> / </span>}
|
||||
<span
|
||||
title={ this.props.children }
|
||||
className={ cx(styles.title, { untitled: usePlaceholder }) }
|
||||
title={this.props.children}
|
||||
className={cx(styles.title, { untitled: usePlaceholder })}
|
||||
>
|
||||
{ title }
|
||||
{title}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -5,8 +5,4 @@ import SaveAction from './components/SaveAction';
|
|||
|
||||
export default Layout;
|
||||
|
||||
export {
|
||||
Title,
|
||||
HeaderAction,
|
||||
SaveAction,
|
||||
};
|
||||
export { Title, HeaderAction, SaveAction };
|
||||
|
|
|
@ -2,12 +2,12 @@ import React from 'react';
|
|||
|
||||
import styles from './LoadingIndicator.scss';
|
||||
|
||||
const LoadingIndicator = (props) => {
|
||||
const LoadingIndicator = props => {
|
||||
return (
|
||||
<div className={ styles.loading }>
|
||||
<div className={ styles.loader }></div>
|
||||
<div className={styles.loading}>
|
||||
<div className={styles.loader} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingIndicator;
|
||||
export default LoadingIndicator;
|
||||
|
|
|
@ -14,8 +14,7 @@ import './codemirror.scss';
|
|||
|
||||
import { client } from 'utils/ApiClient';
|
||||
|
||||
@observer
|
||||
class MarkdownEditor extends React.Component {
|
||||
@observer class MarkdownEditor extends React.Component {
|
||||
static propTypes = {
|
||||
text: React.PropTypes.string,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
|
@ -27,15 +26,15 @@ class MarkdownEditor extends React.Component {
|
|||
// re-render to help with CodeMirror focus issues
|
||||
preview: React.PropTypes.bool,
|
||||
toggleUploadingIndicator: React.PropTypes.func,
|
||||
}
|
||||
};
|
||||
|
||||
onChange = (newText) => {
|
||||
onChange = newText => {
|
||||
if (newText !== this.props.text) {
|
||||
this.props.onChange(newText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onDropAccepted = (files) => {
|
||||
onDropAccepted = files => {
|
||||
const file = files[0];
|
||||
const editor = this.getEditorInstance();
|
||||
|
||||
|
@ -56,67 +55,68 @@ class MarkdownEditor extends React.Component {
|
|||
}
|
||||
editor.setCursor(newCursorPositionLine, 0);
|
||||
|
||||
client.post('/user.s3Upload', {
|
||||
kind: file.type,
|
||||
size: file.size,
|
||||
filename: file.name,
|
||||
})
|
||||
.then(response => {
|
||||
const data = response.data;
|
||||
// Upload using FormData API
|
||||
const formData = new FormData();
|
||||
|
||||
for (const key in data.form) {
|
||||
formData.append(key, data.form[key]);
|
||||
}
|
||||
|
||||
if (file.blob) {
|
||||
formData.append('file', file.file);
|
||||
} else {
|
||||
formData.append('file', file);
|
||||
}
|
||||
|
||||
fetch(data.uploadUrl, {
|
||||
method: 'post',
|
||||
body: formData,
|
||||
client
|
||||
.post('/user.s3Upload', {
|
||||
kind: file.type,
|
||||
size: file.size,
|
||||
filename: file.name,
|
||||
})
|
||||
.then(_s3Response => {
|
||||
this.props.toggleUploadingIndicator();
|
||||
this.props.replaceText({
|
||||
original: pendingUploadTag,
|
||||
new: `![${file.name}](${data.asset.url})`,
|
||||
});
|
||||
editor.setCursor(newCursorPositionLine, 0);
|
||||
.then(response => {
|
||||
const data = response.data;
|
||||
// Upload using FormData API
|
||||
const formData = new FormData();
|
||||
|
||||
for (const key in data.form) {
|
||||
formData.append(key, data.form[key]);
|
||||
}
|
||||
|
||||
if (file.blob) {
|
||||
formData.append('file', file.file);
|
||||
} else {
|
||||
formData.append('file', file);
|
||||
}
|
||||
|
||||
fetch(data.uploadUrl, {
|
||||
method: 'post',
|
||||
body: formData,
|
||||
})
|
||||
.then(_s3Response => {
|
||||
this.props.toggleUploadingIndicator();
|
||||
this.props.replaceText({
|
||||
original: pendingUploadTag,
|
||||
new: `![${file.name}](${data.asset.url})`,
|
||||
});
|
||||
editor.setCursor(newCursorPositionLine, 0);
|
||||
})
|
||||
.catch(_err => {
|
||||
this.props.toggleUploadingIndicator();
|
||||
this.props.replaceText({
|
||||
original: pendingUploadTag,
|
||||
new: '',
|
||||
});
|
||||
editor.setCursor(newCursorPositionLine, 0);
|
||||
});
|
||||
})
|
||||
.catch(_err => {
|
||||
this.props.toggleUploadingIndicator();
|
||||
this.props.replaceText({
|
||||
original: pendingUploadTag,
|
||||
new: '',
|
||||
});
|
||||
editor.setCursor(newCursorPositionLine, 0);
|
||||
});
|
||||
})
|
||||
.catch(_err => {
|
||||
this.props.toggleUploadingIndicator();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onPaddingTopClick = () => {
|
||||
const cm = this.getEditorInstance();
|
||||
cm.setCursor(0, 0);
|
||||
cm.focus();
|
||||
}
|
||||
};
|
||||
|
||||
onPaddingBottomClick = () => {
|
||||
const cm = this.getEditorInstance();
|
||||
cm.setCursor(cm.lineCount(), 0);
|
||||
cm.focus();
|
||||
}
|
||||
};
|
||||
|
||||
getEditorInstance = () => {
|
||||
return this.refs.editor.getCodeMirror();
|
||||
}
|
||||
};
|
||||
|
||||
render = () => {
|
||||
const options = {
|
||||
|
@ -146,24 +146,24 @@ class MarkdownEditor extends React.Component {
|
|||
|
||||
return (
|
||||
<Dropzone
|
||||
onDropAccepted={ this.onDropAccepted }
|
||||
onDropAccepted={this.onDropAccepted}
|
||||
disableClick
|
||||
multiple={ false }
|
||||
multiple={false}
|
||||
accept="image/*"
|
||||
className={ styles.container }
|
||||
className={styles.container}
|
||||
>
|
||||
<ClickablePadding onClick={ this.onPaddingTopClick } />
|
||||
<ClickablePadding onClick={this.onPaddingTopClick} />
|
||||
<Codemirror
|
||||
value={ this.props.text }
|
||||
onChange={ this.onChange }
|
||||
options={ options }
|
||||
value={this.props.text}
|
||||
onChange={this.onChange}
|
||||
options={options}
|
||||
ref="editor"
|
||||
className={ styles.codeMirrorContainer }
|
||||
className={styles.codeMirrorContainer}
|
||||
/>
|
||||
<ClickablePadding onClick={ this.onPaddingBottomClick } />
|
||||
<ClickablePadding onClick={this.onPaddingBottomClick} />
|
||||
</Dropzone>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MarkdownEditor;
|
||||
|
|
|
@ -2,17 +2,12 @@ import React from 'react';
|
|||
|
||||
import styles from './ClickablePadding.scss';
|
||||
|
||||
const ClickablePadding = (props) => {
|
||||
return (
|
||||
<div
|
||||
className={ styles.container }
|
||||
onClick={ props.onClick }
|
||||
> </div>
|
||||
)
|
||||
const ClickablePadding = props => {
|
||||
return <div className={styles.container} onClick={props.onClick}> </div>;
|
||||
};
|
||||
|
||||
ClickablePadding.propTypes = {
|
||||
onClick: React.PropTypes.func,
|
||||
};
|
||||
|
||||
export default ClickablePadding;
|
||||
export default ClickablePadding;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import ClickablePadding from './ClickablePadding';
|
||||
export default ClickablePadding;
|
||||
export default ClickablePadding;
|
||||
|
|
|
@ -11,7 +11,7 @@ class Offline extends React.Component {
|
|||
|
||||
state = {
|
||||
offline: !navigator.onLine,
|
||||
}
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
|
@ -22,7 +22,7 @@ class Offline extends React.Component {
|
|||
componentDidMount = () => {
|
||||
window.addEventListener('offline', this.handleConnectionState);
|
||||
window.addEventListener('online', this.handleConnectionState);
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
window.removeEventListener('offline', this.handleConnectionState);
|
||||
|
@ -33,7 +33,7 @@ class Offline extends React.Component {
|
|||
this.setState({
|
||||
offline: !navigator.onLine,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return React.Children.only(this.props.children);
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import Offline from './Offline';
|
||||
import injectOffline from './injectOffline';
|
||||
|
||||
export {
|
||||
Offline,
|
||||
injectOffline,
|
||||
}
|
||||
export { Offline, injectOffline };
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const injectOffline = (WrappedComponent) => {
|
||||
const injectOffline = WrappedComponent => {
|
||||
return class OfflineWrapper extends React.Component {
|
||||
static contextTypes = {
|
||||
offline: React.PropTypes.bool,
|
||||
|
@ -11,7 +11,7 @@ const injectOffline = (WrappedComponent) => {
|
|||
offline: this.context.offline,
|
||||
};
|
||||
|
||||
return (<WrappedComponent { ...this.props } { ...newProps } />);
|
||||
return <WrappedComponent {...this.props} {...newProps} />;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -17,31 +17,35 @@ class PublishingInfo extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Flex align="center" className={ styles.user }>
|
||||
<Flex className={ styles.avatarLine }>
|
||||
{ this.props.collaborators.reverse().map(user => (
|
||||
<Flex align="center" className={styles.user}>
|
||||
<Flex className={styles.avatarLine}>
|
||||
{this.props.collaborators.reverse().map(user => (
|
||||
<Avatar
|
||||
key={ `avatar-${user.id}` }
|
||||
src={ user.avatarUrl }
|
||||
size={ 26 }
|
||||
key={`avatar-${user.id}`}
|
||||
src={user.avatarUrl}
|
||||
size={26}
|
||||
style={{
|
||||
marginRight: '-12px',
|
||||
border: '2px solid #FFFFFF',
|
||||
}}
|
||||
title={ user.name }
|
||||
title={user.name}
|
||||
/>
|
||||
)) }
|
||||
))}
|
||||
</Flex>
|
||||
<span className={ styles.userName }>
|
||||
{ this.props.createdBy.name } published { moment(this.props.createdAt).fromNow() }
|
||||
{ this.props.createdAt !== this.props.updatedAt ? (
|
||||
<span>
|
||||
and
|
||||
{ this.props.createdBy.id !== this.props.updatedBy.id &&
|
||||
` ${this.props.updatedBy.name} ` }
|
||||
modified { moment(this.props.updatedAt).fromNow() }
|
||||
</span>
|
||||
) : null }
|
||||
<span className={styles.userName}>
|
||||
{this.props.createdBy.name}
|
||||
{' '}
|
||||
published
|
||||
{' '}
|
||||
{moment(this.props.createdAt).fromNow()}
|
||||
{this.props.createdAt !== this.props.updatedAt
|
||||
? <span>
|
||||
and
|
||||
{this.props.createdBy.id !== this.props.updatedBy.id &&
|
||||
` ${this.props.updatedBy.name} `}
|
||||
modified {moment(this.props.updatedAt).fromNow()}
|
||||
</span>
|
||||
: null}
|
||||
</span>
|
||||
</Flex>
|
||||
);
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import PublishingInfo from './PublishingInfo';
|
||||
export default PublishingInfo;
|
||||
export default PublishingInfo;
|
||||
|
|
|
@ -6,52 +6,57 @@ import classNames from 'classnames/bind';
|
|||
const cx = classNames.bind(styles);
|
||||
|
||||
class Node extends React.Component {
|
||||
displayName: 'UITreeNode'
|
||||
displayName: 'UITreeNode';
|
||||
|
||||
renderCollapse = () => {
|
||||
var index = this.props.index;
|
||||
const index = this.props.index;
|
||||
|
||||
if(index.children && index.children.length) {
|
||||
var collapsed = index.node.collapsed;
|
||||
if (index.children && index.children.length) {
|
||||
const collapsed = index.node.collapsed;
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cx(styles.collapse, collapsed ? styles.caretRight : styles.caretDown)}
|
||||
onMouseDown={function(e) {e.stopPropagation()}}
|
||||
className={cx(
|
||||
styles.collapse,
|
||||
collapsed ? styles.caretRight : styles.caretDown
|
||||
)}
|
||||
onMouseDown={function(e) {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onClick={this.handleCollapse}
|
||||
>
|
||||
<img src={ require("./assets/chevron.svg") } />
|
||||
<img src={require('./assets/chevron.svg')} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
renderChildren = () => {
|
||||
var index = this.props.index;
|
||||
var tree = this.props.tree;
|
||||
var dragging = this.props.dragging;
|
||||
const index = this.props.index;
|
||||
const tree = this.props.tree;
|
||||
const dragging = this.props.dragging;
|
||||
|
||||
if(index.children && index.children.length) {
|
||||
var childrenStyles = {};
|
||||
if (index.children && index.children.length) {
|
||||
const childrenStyles = {};
|
||||
|
||||
if (!this.props.rootNode) {
|
||||
if(index.node.collapsed) childrenStyles.display = 'none';
|
||||
childrenStyles['paddingLeft'] = this.props.paddingLeft + 'px';
|
||||
if (index.node.collapsed) childrenStyles.display = 'none';
|
||||
childrenStyles.paddingLeft = `${this.props.paddingLeft}px`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.children } style={childrenStyles}>
|
||||
{index.children.map((child) => {
|
||||
var childIndex = tree.getIndex(child);
|
||||
<div className={styles.children} style={childrenStyles}>
|
||||
{index.children.map(child => {
|
||||
const childIndex = tree.getIndex(child);
|
||||
return (
|
||||
<Node
|
||||
tree={tree}
|
||||
index={childIndex}
|
||||
key={childIndex.id}
|
||||
dragging={dragging}
|
||||
allowUpdates={ this.props.allowUpdates }
|
||||
allowUpdates={this.props.allowUpdates}
|
||||
paddingLeft={this.props.paddingLeft}
|
||||
onCollapse={this.props.onCollapse}
|
||||
onDragStart={this.props.onDragStart}
|
||||
|
@ -63,18 +68,18 @@ class Node extends React.Component {
|
|||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
isModifying = () => {
|
||||
return this.props.allowUpdates && !this.props.rootNode;
|
||||
}
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
const index = this.props.index;
|
||||
const node = index.node;
|
||||
|
||||
if (!this.isModifying()) history.push(node.url);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const tree = this.props.tree;
|
||||
|
@ -84,24 +89,31 @@ class Node extends React.Component {
|
|||
const style = {};
|
||||
|
||||
return (
|
||||
<div className={cx(styles.node, {
|
||||
placeholder: index.id === dragging,
|
||||
rootNode: this.props.rootNode,
|
||||
})} style={style}>
|
||||
<div
|
||||
className={cx(styles.node, {
|
||||
placeholder: index.id === dragging,
|
||||
rootNode: this.props.rootNode,
|
||||
})}
|
||||
style={style}
|
||||
>
|
||||
<div
|
||||
className={ styles.inner }
|
||||
className={styles.inner}
|
||||
ref="inner"
|
||||
onMouseDown={this.props.rootNode || !this.props.allowUpdates ? (e) => e.stopPropagation() : this.handleMouseDown}
|
||||
onMouseDown={
|
||||
this.props.rootNode || !this.props.allowUpdates
|
||||
? e => e.stopPropagation()
|
||||
: this.handleMouseDown
|
||||
}
|
||||
>
|
||||
{ !this.props.rootNode && this.renderCollapse() }
|
||||
{!this.props.rootNode && this.renderCollapse()}
|
||||
<span
|
||||
className={ cx(styles.nodeLabel, {
|
||||
className={cx(styles.nodeLabel, {
|
||||
rootLabel: this.props.rootNode,
|
||||
isModifying: this.isModifying(),
|
||||
}) }
|
||||
onClick={ this.onClick }
|
||||
})}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
{ node.title }
|
||||
{node.title}
|
||||
</span>
|
||||
</div>
|
||||
{this.renderChildren()}
|
||||
|
@ -109,20 +121,20 @@ class Node extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
handleCollapse = (e) => {
|
||||
handleCollapse = e => {
|
||||
e.stopPropagation();
|
||||
var nodeId = this.props.index.id;
|
||||
if(this.props.onCollapse) this.props.onCollapse(nodeId);
|
||||
}
|
||||
const nodeId = this.props.index.id;
|
||||
if (this.props.onCollapse) this.props.onCollapse(nodeId);
|
||||
};
|
||||
|
||||
handleMouseDown = (e) => {
|
||||
var nodeId = this.props.index.id;
|
||||
var dom = this.refs.inner;
|
||||
handleMouseDown = e => {
|
||||
const nodeId = this.props.index.id;
|
||||
const dom = this.refs.inner;
|
||||
|
||||
if(this.props.onDragStart) {
|
||||
if (this.props.onDragStart) {
|
||||
this.props.onDragStart(nodeId, dom, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Node;
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
var Tree = require('js-tree');
|
||||
var proto = Tree.prototype;
|
||||
const Tree = require('js-tree');
|
||||
const proto = Tree.prototype;
|
||||
|
||||
proto.updateNodesPosition = function() {
|
||||
var top = 1;
|
||||
var left = 1;
|
||||
var root = this.getIndex(1);
|
||||
var self = this;
|
||||
let top = 1;
|
||||
let left = 1;
|
||||
const root = this.getIndex(1);
|
||||
const self = this;
|
||||
|
||||
root.top = top++;
|
||||
root.left = left++;
|
||||
|
||||
if(root.children && root.children.length) {
|
||||
if (root.children && root.children.length) {
|
||||
walk(root.children, root, left, root.node.collapsed);
|
||||
}
|
||||
|
||||
function walk(children, parent, left, collapsed) {
|
||||
var height = 1;
|
||||
children.forEach(function(id) {
|
||||
var node = self.getIndex(id);
|
||||
if(collapsed) {
|
||||
let height = 1;
|
||||
children.forEach(id => {
|
||||
const node = self.getIndex(id);
|
||||
if (collapsed) {
|
||||
node.top = null;
|
||||
node.left = null;
|
||||
} else {
|
||||
|
@ -26,30 +26,35 @@ proto.updateNodesPosition = function() {
|
|||
node.left = left;
|
||||
}
|
||||
|
||||
if(node.children && node.children.length) {
|
||||
height += walk(node.children, node, left+1, collapsed || node.node.collapsed);
|
||||
if (node.children && node.children.length) {
|
||||
height += walk(
|
||||
node.children,
|
||||
node,
|
||||
left + 1,
|
||||
collapsed || node.node.collapsed
|
||||
);
|
||||
} else {
|
||||
node.height = 1;
|
||||
height += 1;
|
||||
}
|
||||
});
|
||||
|
||||
if(parent.node.collapsed) parent.height = 1;
|
||||
if (parent.node.collapsed) parent.height = 1;
|
||||
else parent.height = height;
|
||||
return parent.height;
|
||||
}
|
||||
};
|
||||
|
||||
proto.move = function(fromId, toId, placement) {
|
||||
if(fromId === toId || toId === 1) return;
|
||||
if (fromId === toId || toId === 1) return;
|
||||
|
||||
var obj = this.remove(fromId);
|
||||
var index = null;
|
||||
const obj = this.remove(fromId);
|
||||
let index = null;
|
||||
|
||||
if(placement === 'before') index = this.insertBefore(obj, toId);
|
||||
else if(placement === 'after') index = this.insertAfter(obj, toId);
|
||||
else if(placement === 'prepend') index = this.prepend(obj, toId);
|
||||
else if(placement === 'append') index = this.append(obj, toId);
|
||||
if (placement === 'before') index = this.insertBefore(obj, toId);
|
||||
else if (placement === 'after') index = this.insertAfter(obj, toId);
|
||||
else if (placement === 'prepend') index = this.prepend(obj, toId);
|
||||
else if (placement === 'append') index = this.append(obj, toId);
|
||||
|
||||
// todo: perf
|
||||
this.updateNodesPosition();
|
||||
|
@ -57,12 +62,12 @@ proto.move = function(fromId, toId, placement) {
|
|||
};
|
||||
|
||||
proto.getNodeByTop = function(top) {
|
||||
var indexes = this.indexes;
|
||||
for(var id in indexes) {
|
||||
if(indexes.hasOwnProperty(id)) {
|
||||
if(indexes[id].top === top) return indexes[id];
|
||||
const indexes = this.indexes;
|
||||
for (const id in indexes) {
|
||||
if (indexes.hasOwnProperty(id)) {
|
||||
if (indexes[id].top === top) return indexes[id];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Tree;
|
||||
module.exports = Tree;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
var React = require('react');
|
||||
var Tree = require('./Tree');
|
||||
var Node = require('./Node');
|
||||
const React = require('react');
|
||||
const Tree = require('./Tree');
|
||||
const Node = require('./Node');
|
||||
|
||||
import styles from './Tree.scss';
|
||||
|
||||
|
@ -16,7 +16,7 @@ module.exports = React.createClass({
|
|||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
paddingLeft: 20
|
||||
paddingLeft: 20,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -25,47 +25,47 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(!this._updated) this.setState(this.init(nextProps));
|
||||
if (!this._updated) this.setState(this.init(nextProps));
|
||||
else this._updated = false;
|
||||
},
|
||||
|
||||
init(props) {
|
||||
var tree = new Tree(props.tree);
|
||||
const tree = new Tree(props.tree);
|
||||
tree.isNodeCollapsed = props.isNodeCollapsed;
|
||||
tree.changeNodeCollapsed = props.changeNodeCollapsed;
|
||||
tree.updateNodesPosition();
|
||||
|
||||
return {
|
||||
tree: tree,
|
||||
tree,
|
||||
dragging: {
|
||||
id: null,
|
||||
x: null,
|
||||
y: null,
|
||||
w: null,
|
||||
h: null
|
||||
}
|
||||
h: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
getDraggingDom() {
|
||||
var tree = this.state.tree;
|
||||
var dragging = this.state.dragging;
|
||||
const tree = this.state.tree;
|
||||
const dragging = this.state.dragging;
|
||||
|
||||
if(dragging && dragging.id) {
|
||||
var draggingIndex = tree.getIndex(dragging.id);
|
||||
var draggingStyles = {
|
||||
if (dragging && dragging.id) {
|
||||
const draggingIndex = tree.getIndex(dragging.id);
|
||||
const draggingStyles = {
|
||||
top: dragging.y,
|
||||
left: dragging.x,
|
||||
width: dragging.w
|
||||
width: dragging.w,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={ styles.draggable } style={ draggingStyles }>
|
||||
<div className={styles.draggable} style={draggingStyles}>
|
||||
<Node
|
||||
tree={ tree }
|
||||
index={ draggingIndex }
|
||||
paddingLeft={ this.props.paddingLeft }
|
||||
allowUpdates={ this.props.allowUpdates }
|
||||
tree={tree}
|
||||
index={draggingIndex}
|
||||
paddingLeft={this.props.paddingLeft}
|
||||
allowUpdates={this.props.allowUpdates}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -75,23 +75,23 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
render() {
|
||||
var tree = this.state.tree;
|
||||
var dragging = this.state.dragging;
|
||||
var draggingDom = this.getDraggingDom();
|
||||
const tree = this.state.tree;
|
||||
const dragging = this.state.dragging;
|
||||
const draggingDom = this.getDraggingDom();
|
||||
|
||||
return (
|
||||
<div className={ styles.tree }>
|
||||
{ draggingDom }
|
||||
<div className={styles.tree}>
|
||||
{draggingDom}
|
||||
<Node
|
||||
rootNode
|
||||
tree={ tree }
|
||||
index={ tree.getIndex(1) }
|
||||
key={ 1 }
|
||||
paddingLeft={ this.props.paddingLeft }
|
||||
allowUpdates={ this.props.allowUpdates }
|
||||
onDragStart={ this.dragStart }
|
||||
onCollapse={ this.toggleCollapse }
|
||||
dragging={ dragging && dragging.id }
|
||||
tree={tree}
|
||||
index={tree.getIndex(1)}
|
||||
key={1}
|
||||
paddingLeft={this.props.paddingLeft}
|
||||
allowUpdates={this.props.allowUpdates}
|
||||
onDragStart={this.dragStart}
|
||||
onCollapse={this.toggleCollapse}
|
||||
dragging={dragging && dragging.id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -99,11 +99,11 @@ module.exports = React.createClass({
|
|||
|
||||
dragStart(id, dom, e) {
|
||||
this.dragging = {
|
||||
id: id,
|
||||
id,
|
||||
w: dom.offsetWidth,
|
||||
h: dom.offsetHeight,
|
||||
x: dom.offsetLeft,
|
||||
y: dom.offsetTop
|
||||
y: dom.offsetTop,
|
||||
};
|
||||
|
||||
this._startX = dom.offsetLeft;
|
||||
|
@ -118,69 +118,73 @@ module.exports = React.createClass({
|
|||
|
||||
// oh
|
||||
drag(e) {
|
||||
if(this._start) {
|
||||
if (this._start) {
|
||||
this.setState({
|
||||
dragging: this.dragging
|
||||
dragging: this.dragging,
|
||||
});
|
||||
this._start = false;
|
||||
}
|
||||
|
||||
var tree = this.state.tree;
|
||||
var dragging = this.state.dragging;
|
||||
var paddingLeft = this.props.paddingLeft;
|
||||
var newIndex = null;
|
||||
var index = tree.getIndex(dragging.id);
|
||||
var collapsed = index.node.collapsed;
|
||||
const tree = this.state.tree;
|
||||
const dragging = this.state.dragging;
|
||||
const paddingLeft = this.props.paddingLeft;
|
||||
let newIndex = null;
|
||||
let index = tree.getIndex(dragging.id);
|
||||
const collapsed = index.node.collapsed;
|
||||
|
||||
var _startX = this._startX;
|
||||
var _startY = this._startY;
|
||||
var _offsetX = this._offsetX;
|
||||
var _offsetY = this._offsetY;
|
||||
const _startX = this._startX;
|
||||
const _startY = this._startY;
|
||||
const _offsetX = this._offsetX;
|
||||
const _offsetY = this._offsetY;
|
||||
|
||||
var pos = {
|
||||
const pos = {
|
||||
x: _startX + e.clientX - _offsetX,
|
||||
y: _startY + e.clientY - _offsetY
|
||||
y: _startY + e.clientY - _offsetY,
|
||||
};
|
||||
dragging.x = pos.x;
|
||||
dragging.y = pos.y;
|
||||
|
||||
var diffX = dragging.x - paddingLeft/2 - (index.left-2) * paddingLeft;
|
||||
var diffY = dragging.y - dragging.h/2 - (index.top-2) * dragging.h;
|
||||
const diffX = dragging.x - paddingLeft / 2 - (index.left - 2) * paddingLeft;
|
||||
const diffY = dragging.y - dragging.h / 2 - (index.top - 2) * dragging.h;
|
||||
|
||||
if(diffX < 0) { // left
|
||||
if(index.parent && !index.next) {
|
||||
if (diffX < 0) {
|
||||
// left
|
||||
if (index.parent && !index.next) {
|
||||
newIndex = tree.move(index.id, index.parent, 'after');
|
||||
}
|
||||
} else if(diffX > paddingLeft) { // right
|
||||
if(index.prev) {
|
||||
var prevNode = tree.getIndex(index.prev).node;
|
||||
if(!prevNode.collapsed && !prevNode.leaf) {
|
||||
} else if (diffX > paddingLeft) {
|
||||
// right
|
||||
if (index.prev) {
|
||||
const prevNode = tree.getIndex(index.prev).node;
|
||||
if (!prevNode.collapsed && !prevNode.leaf) {
|
||||
newIndex = tree.move(index.id, index.prev, 'append');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(newIndex) {
|
||||
if (newIndex) {
|
||||
index = newIndex;
|
||||
newIndex.node.collapsed = collapsed;
|
||||
dragging.id = newIndex.id;
|
||||
}
|
||||
|
||||
if(diffY < 0) { // up
|
||||
var above = tree.getNodeByTop(index.top-1);
|
||||
if (diffY < 0) {
|
||||
// up
|
||||
const above = tree.getNodeByTop(index.top - 1);
|
||||
newIndex = tree.move(index.id, above.id, 'before');
|
||||
} else if(diffY > dragging.h) { // down
|
||||
if(index.next) {
|
||||
} else if (diffY > dragging.h) {
|
||||
// down
|
||||
if (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');
|
||||
} else {
|
||||
newIndex = tree.move(index.id, index.next, 'after');
|
||||
}
|
||||
} else {
|
||||
var below = tree.getNodeByTop(index.top+index.height);
|
||||
if(below && below.parent !== index.id) {
|
||||
if(below.children && below.children.length) {
|
||||
var below = tree.getNodeByTop(index.top + index.height);
|
||||
if (below && below.parent !== index.id) {
|
||||
if (below.children && below.children.length) {
|
||||
newIndex = tree.move(index.id, below.id, 'prepend');
|
||||
} else {
|
||||
newIndex = tree.move(index.id, below.id, 'after');
|
||||
|
@ -189,14 +193,14 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
if(newIndex) {
|
||||
if (newIndex) {
|
||||
newIndex.node.collapsed = collapsed;
|
||||
dragging.id = newIndex.id;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
tree: tree,
|
||||
dragging: dragging
|
||||
tree,
|
||||
dragging,
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -207,8 +211,8 @@ module.exports = React.createClass({
|
|||
x: null,
|
||||
y: null,
|
||||
w: null,
|
||||
h: null
|
||||
}
|
||||
h: null,
|
||||
},
|
||||
});
|
||||
|
||||
this.change(this.state.tree);
|
||||
|
@ -218,21 +222,21 @@ module.exports = React.createClass({
|
|||
|
||||
change(tree) {
|
||||
this._updated = true;
|
||||
if(this.props.onChange) this.props.onChange(tree.obj);
|
||||
if (this.props.onChange) this.props.onChange(tree.obj);
|
||||
},
|
||||
|
||||
toggleCollapse(nodeId) {
|
||||
var tree = this.state.tree;
|
||||
var index = tree.getIndex(nodeId);
|
||||
var node = index.node;
|
||||
const tree = this.state.tree;
|
||||
const index = tree.getIndex(nodeId);
|
||||
const node = index.node;
|
||||
node.collapsed = !node.collapsed;
|
||||
tree.updateNodesPosition();
|
||||
|
||||
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) {
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import UiTree from './UiTree';
|
||||
export default UiTree;
|
||||
export default UiTree;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
let constants;
|
||||
|
||||
constants = {
|
||||
API_USER_AGENT: `Atlas`,
|
||||
API_USER_AGENT: 'Atlas',
|
||||
API_BASE_URL: '/api',
|
||||
};
|
||||
|
||||
|
|
|
@ -46,63 +46,83 @@ function requireAuth(nextState, replace) {
|
|||
}
|
||||
}
|
||||
|
||||
render((
|
||||
render(
|
||||
<div style={{ display: 'flex', flex: 1, height: '100%' }}>
|
||||
<Provider { ...stores }>
|
||||
<Provider {...stores}>
|
||||
<Offline>
|
||||
<Router history={ History }>
|
||||
<Route path="/" component={ Application }>
|
||||
<IndexRoute component={ Home } />
|
||||
<Router history={History}>
|
||||
<Route path="/" component={Application}>
|
||||
<IndexRoute component={Home} />
|
||||
|
||||
<Route path="/dashboard" component={ Dashboard } onEnter={ requireAuth } />
|
||||
<Route path="/collections/:id" component={ Atlas } onEnter={ requireAuth } />
|
||||
<Route
|
||||
path="/dashboard"
|
||||
component={Dashboard}
|
||||
onEnter={requireAuth}
|
||||
/>
|
||||
<Route
|
||||
path="/collections/:id"
|
||||
component={Atlas}
|
||||
onEnter={requireAuth}
|
||||
/>
|
||||
<Route
|
||||
path="/collections/:id/new"
|
||||
component={ DocumentEdit }
|
||||
onEnter={ requireAuth }
|
||||
component={DocumentEdit}
|
||||
onEnter={requireAuth}
|
||||
newDocument
|
||||
/>
|
||||
<Route path="/d/:id" component={ DocumentScene } onEnter={ requireAuth } />
|
||||
<Route path="/d/:id/edit" component={ DocumentEdit } onEnter={ requireAuth } />
|
||||
<Route
|
||||
path="/d/:id"
|
||||
component={DocumentScene}
|
||||
onEnter={requireAuth}
|
||||
/>
|
||||
<Route
|
||||
path="/d/:id/edit"
|
||||
component={DocumentEdit}
|
||||
onEnter={requireAuth}
|
||||
/>
|
||||
<Route
|
||||
path="/d/:id/new"
|
||||
component={ DocumentEdit }
|
||||
onEnter={ requireAuth }
|
||||
component={DocumentEdit}
|
||||
onEnter={requireAuth}
|
||||
newChildDocument
|
||||
/>
|
||||
|
||||
<Route path="/search" component={ Search } onEnter={ requireAuth } />
|
||||
<Route path="/settings" component={ Settings } onEnter={ requireAuth } />
|
||||
<Route path="/search" component={Search} onEnter={requireAuth} />
|
||||
<Route
|
||||
path="/settings"
|
||||
component={Settings}
|
||||
onEnter={requireAuth}
|
||||
/>
|
||||
|
||||
<Route path="/auth/slack" component={ SlackAuth } />
|
||||
<Route path="/auth/slack" component={SlackAuth} />
|
||||
<Route
|
||||
path="/auth/slack/commands"
|
||||
component={ SlackAuth }
|
||||
component={SlackAuth}
|
||||
apiPath="/auth.slackCommands"
|
||||
/>
|
||||
<Route path="/auth/error" component={ ErrorAuth } />
|
||||
<Route path="/auth/error" component={ErrorAuth} />
|
||||
|
||||
<Flatpage
|
||||
path="/keyboard-shortcuts"
|
||||
component={ Flatpage }
|
||||
component={Flatpage}
|
||||
title="Keyboard shortcuts"
|
||||
content={ flatpages.keyboard }
|
||||
content={flatpages.keyboard}
|
||||
/>
|
||||
|
||||
<Flatpage
|
||||
path="/developers"
|
||||
component={ Flatpage }
|
||||
component={Flatpage}
|
||||
title="API"
|
||||
content={ flatpages.api }
|
||||
content={flatpages.api}
|
||||
/>
|
||||
|
||||
|
||||
<Route path="/404" component={ Error404 } />
|
||||
<Route path="*" component={ Search } sceneType="notFound" />
|
||||
<Route path="/404" component={Error404} />
|
||||
<Route path="*" component={Search} sceneType="notFound" />
|
||||
</Route>
|
||||
</Router>
|
||||
</Offline>
|
||||
</Provider>
|
||||
{ __DEV__ && <DevTools position={{ bottom: 0, right: 0 }} /> }
|
||||
</div>
|
||||
), document.getElementById('root'));
|
||||
{__DEV__ && <DevTools position={{ bottom: 0, right: 0 }} />}
|
||||
</div>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
|
|
@ -22,7 +22,7 @@ import styles from './Atlas.scss';
|
|||
class Atlas extends React.Component {
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
const { id } = this.props.params;
|
||||
|
@ -32,21 +32,21 @@ class Atlas extends React.Component {
|
|||
browserHistory.replace(data.navigationTree.url);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentWillReceiveProps = (nextProps) => {
|
||||
componentWillReceiveProps = nextProps => {
|
||||
const key = nextProps.keydown.event;
|
||||
if (key) {
|
||||
if (key.key === 'c') {
|
||||
_.defer(this.onCreate);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onCreate = (event) => {
|
||||
onCreate = event => {
|
||||
if (event) event.preventDefault();
|
||||
browserHistory.push(`${store.collection.url}/new`);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const collection = store.collection;
|
||||
|
@ -58,47 +58,44 @@ class Atlas extends React.Component {
|
|||
if (collection) {
|
||||
actions = (
|
||||
<Flex>
|
||||
<DropdownMenu label={ <MoreIcon /> } >
|
||||
<MenuItem onClick={ this.onCreate }>
|
||||
<DropdownMenu label={<MoreIcon />}>
|
||||
<MenuItem onClick={this.onCreate}>
|
||||
New document
|
||||
</MenuItem>
|
||||
</DropdownMenu>
|
||||
</Flex>
|
||||
);
|
||||
title = <Title>{ collection.name }</Title>;
|
||||
title = <Title>{collection.name}</Title>;
|
||||
titleText = collection.name;
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout
|
||||
actions={ actions }
|
||||
title={ title }
|
||||
titleText={ titleText }
|
||||
>
|
||||
<Layout actions={actions} title={title} titleText={titleText}>
|
||||
<CenteredContent>
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName="fadeIn"
|
||||
transitionAppear
|
||||
transitionAppearTimeout={ 0 }
|
||||
transitionEnterTimeout={ 0 }
|
||||
transitionLeaveTimeout={ 0 }
|
||||
transitionAppearTimeout={0}
|
||||
transitionEnterTimeout={0}
|
||||
transitionLeaveTimeout={0}
|
||||
>
|
||||
{ store.isFetching ? (
|
||||
<AtlasPreviewLoading />
|
||||
) : (
|
||||
<div className={ styles.container }>
|
||||
<div className={ styles.atlasDetails }>
|
||||
<h2>{ collection.name }</h2>
|
||||
<blockquote>
|
||||
{ collection.description }
|
||||
</blockquote>
|
||||
</div>
|
||||
{store.isFetching
|
||||
? <AtlasPreviewLoading />
|
||||
: <div className={styles.container}>
|
||||
<div className={styles.atlasDetails}>
|
||||
<h2>{collection.name}</h2>
|
||||
<blockquote>
|
||||
{collection.description}
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
<Divider />
|
||||
|
||||
<DocumentList documents={ collection.recentDocuments } preview />
|
||||
</div>
|
||||
) }
|
||||
<DocumentList
|
||||
documents={collection.recentDocuments}
|
||||
preview
|
||||
/>
|
||||
</div>}
|
||||
</ReactCSSTransitionGroup>
|
||||
</CenteredContent>
|
||||
</Layout>
|
||||
|
|
|
@ -18,10 +18,10 @@ const store = new class AtlasStore {
|
|||
this.collection = data;
|
||||
successCallback(data);
|
||||
} catch (e) {
|
||||
console.error("Something went wrong");
|
||||
console.error('Something went wrong');
|
||||
}
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
}();
|
||||
|
||||
export default store;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import Atlas from './Atlas';
|
||||
export default Atlas;
|
||||
export default Atlas;
|
||||
|
|
|
@ -18,7 +18,7 @@ class DashboardStore {
|
|||
runInAction('fetchCollections', () => {
|
||||
this.collections = data;
|
||||
this.pagination = pagination;
|
||||
data.forEach((collection) => cacheResponse(collection.recentDocuments));
|
||||
data.forEach(collection => cacheResponse(collection.recentDocuments));
|
||||
});
|
||||
|
||||
// If only one collection, visit it automatically
|
||||
|
@ -29,7 +29,7 @@ class DashboardStore {
|
|||
console.error('Something went wrong');
|
||||
}
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
|
||||
constructor(options) {
|
||||
this.team = options.team;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import Dashboard from './Dashboard';
|
||||
export default Dashboard;
|
||||
export default Dashboard;
|
||||
|
|
|
@ -3,9 +3,7 @@ import { observer } from 'mobx-react';
|
|||
import { browserHistory, withRouter } from 'react-router';
|
||||
import keydown from 'react-keydown';
|
||||
|
||||
import DocumentEditStore, {
|
||||
DOCUMENT_EDIT_SETTINGS,
|
||||
} from './DocumentEditStore';
|
||||
import DocumentEditStore, { DOCUMENT_EDIT_SETTINGS } from './DocumentEditStore';
|
||||
|
||||
import Switch from 'components/Switch';
|
||||
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?`;
|
||||
|
||||
@keydown([
|
||||
'cmd+enter', 'ctrl+enter',
|
||||
'cmd+esc', 'ctrl+esc',
|
||||
'cmd+shift+p', 'ctrl+shift+p'])
|
||||
'cmd+enter',
|
||||
'ctrl+enter',
|
||||
'cmd+esc',
|
||||
'ctrl+esc',
|
||||
'cmd+shift+p',
|
||||
'ctrl+shift+p',
|
||||
])
|
||||
@withRouter
|
||||
@observer
|
||||
class DocumentEdit extends Component {
|
||||
|
@ -30,7 +32,7 @@ class DocumentEdit extends Component {
|
|||
route: React.PropTypes.object.isRequired,
|
||||
router: React.PropTypes.object.isRequired,
|
||||
params: React.PropTypes.object,
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -41,7 +43,7 @@ class DocumentEdit extends Component {
|
|||
|
||||
state = {
|
||||
scrollTop: 0,
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
if (this.props.route.newDocument) {
|
||||
|
@ -58,8 +60,7 @@ class DocumentEdit extends Component {
|
|||
}
|
||||
|
||||
// Load editor async
|
||||
EditorLoader()
|
||||
.then(({ Editor }) => {
|
||||
EditorLoader().then(({ Editor }) => {
|
||||
this.setState({ Editor });
|
||||
});
|
||||
|
||||
|
@ -70,9 +71,9 @@ class DocumentEdit extends Component {
|
|||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentWillReceiveProps = (nextProps) => {
|
||||
componentWillReceiveProps = nextProps => {
|
||||
const key = nextProps.keydown.event;
|
||||
|
||||
if (key) {
|
||||
|
@ -91,7 +92,7 @@ class DocumentEdit extends Component {
|
|||
this.store.togglePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onSave = () => {
|
||||
// if (this.props.title.length === 0) {
|
||||
|
@ -103,45 +104,46 @@ class DocumentEdit extends Component {
|
|||
} else {
|
||||
this.store.updateDocument();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onCancel = () => {
|
||||
browserHistory.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
onScroll = (scrollTop) => {
|
||||
onScroll = scrollTop => {
|
||||
this.setState({
|
||||
scrollTop,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let title = (
|
||||
const title = (
|
||||
<Title
|
||||
truncate={ 60 }
|
||||
placeholder={ !this.store.isFetching && 'Untitled document' }
|
||||
truncate={60}
|
||||
placeholder={!this.store.isFetching && 'Untitled document'}
|
||||
>
|
||||
{ this.store.title }
|
||||
{this.store.title}
|
||||
</Title>
|
||||
);
|
||||
|
||||
let titleText = this.store.title;
|
||||
let isNew = this.props.route.newDocument || this.props.route.newChildDocument;
|
||||
const titleText = this.store.title;
|
||||
const isNew =
|
||||
this.props.route.newDocument || this.props.route.newChildDocument;
|
||||
|
||||
const actions = (
|
||||
<Flex>
|
||||
<HeaderAction>
|
||||
<SaveAction
|
||||
onClick={ this.onSave }
|
||||
disabled={ this.store.isSaving }
|
||||
isNew={ isNew }
|
||||
onClick={this.onSave}
|
||||
disabled={this.store.isSaving}
|
||||
isNew={isNew}
|
||||
/>
|
||||
</HeaderAction>
|
||||
<DropdownMenu label={ <MoreIcon /> }>
|
||||
<MenuItem onClick={ this.store.togglePreview }>
|
||||
Preview <Switch checked={ this.store.preview } />
|
||||
<DropdownMenu label={<MoreIcon />}>
|
||||
<MenuItem onClick={this.store.togglePreview}>
|
||||
Preview <Switch checked={this.store.preview} />
|
||||
</MenuItem>
|
||||
<MenuItem onClick={ this.onCancel }>
|
||||
<MenuItem onClick={this.onCancel}>
|
||||
Cancel
|
||||
</MenuItem>
|
||||
</DropdownMenu>
|
||||
|
@ -150,27 +152,25 @@ class DocumentEdit extends Component {
|
|||
|
||||
return (
|
||||
<Layout
|
||||
actions={ actions }
|
||||
title={ title }
|
||||
titleText={ titleText }
|
||||
actions={actions}
|
||||
title={title}
|
||||
titleText={titleText}
|
||||
fixed
|
||||
loading={ this.store.isSaving || this.store.isUploading }
|
||||
search={ false }
|
||||
loading={this.store.isSaving || this.store.isUploading}
|
||||
search={false}
|
||||
>
|
||||
{ (this.store.isFetching || !('Editor' in this.state)) ? (
|
||||
<CenteredContent>
|
||||
<AtlasPreviewLoading />
|
||||
</CenteredContent>
|
||||
) : (
|
||||
<this.state.Editor
|
||||
store={ this.store }
|
||||
scrollTop={ this.state.scrollTop }
|
||||
onScroll={ this.onScroll }
|
||||
onSave={ this.onSave }
|
||||
onCancel={ this.onCancel }
|
||||
togglePreview={ this.togglePreview }
|
||||
/>
|
||||
) }
|
||||
{this.store.isFetching || !('Editor' in this.state)
|
||||
? <CenteredContent>
|
||||
<AtlasPreviewLoading />
|
||||
</CenteredContent>
|
||||
: <this.state.Editor
|
||||
store={this.store}
|
||||
scrollTop={this.state.scrollTop}
|
||||
onScroll={this.onScroll}
|
||||
onSave={this.onSave}
|
||||
onCancel={this.onCancel}
|
||||
togglePreview={this.togglePreview}
|
||||
/>}
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import emojify from 'utils/emojify';
|
|||
|
||||
const DOCUMENT_EDIT_SETTINGS = 'DOCUMENT_EDIT_SETTINGS';
|
||||
|
||||
const parseHeader = (text) => {
|
||||
const parseHeader = text => {
|
||||
const firstLine = text.split(/\r?\n/)[0];
|
||||
if (firstLine) {
|
||||
const match = firstLine.match(/^#+ +(.*)$/);
|
||||
|
@ -40,9 +40,13 @@ class DocumentEditStore {
|
|||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const data = await client.get('/documents.info', {
|
||||
id: this.documentId,
|
||||
}, { cache: true });
|
||||
const data = await client.get(
|
||||
'/documents.info',
|
||||
{
|
||||
id: this.documentId,
|
||||
},
|
||||
{ cache: true }
|
||||
);
|
||||
if (this.newChildDocument) {
|
||||
this.parentDocument = data.data;
|
||||
} else {
|
||||
|
@ -54,7 +58,7 @@ class DocumentEditStore {
|
|||
console.error('Something went wrong');
|
||||
}
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
|
||||
@action saveDocument = async () => {
|
||||
if (this.isSaving) return;
|
||||
|
@ -62,12 +66,16 @@ class DocumentEditStore {
|
|||
this.isSaving = true;
|
||||
|
||||
try {
|
||||
const data = await client.post('/documents.create', {
|
||||
parentDocument: this.parentDocument && this.parentDocument.id,
|
||||
collection: this.collectionId || this.parentDocument.collection.id,
|
||||
title: this.title || 'Untitled document',
|
||||
text: this.text,
|
||||
}, { cache: true });
|
||||
const data = await client.post(
|
||||
'/documents.create',
|
||||
{
|
||||
parentDocument: this.parentDocument && this.parentDocument.id,
|
||||
collection: this.collectionId || this.parentDocument.collection.id,
|
||||
title: this.title || 'Untitled document',
|
||||
text: this.text,
|
||||
},
|
||||
{ cache: true }
|
||||
);
|
||||
const { url } = data.data;
|
||||
|
||||
this.hasPendingChanges = false;
|
||||
|
@ -76,7 +84,7 @@ class DocumentEditStore {
|
|||
console.error('Something went wrong');
|
||||
}
|
||||
this.isSaving = false;
|
||||
}
|
||||
};
|
||||
|
||||
@action updateDocument = async () => {
|
||||
if (this.isSaving) return;
|
||||
|
@ -84,11 +92,15 @@ class DocumentEditStore {
|
|||
this.isSaving = true;
|
||||
|
||||
try {
|
||||
const data = await client.post('/documents.update', {
|
||||
id: this.documentId,
|
||||
title: this.title || 'Untitled document',
|
||||
text: this.text,
|
||||
}, { cache: true });
|
||||
const data = await client.post(
|
||||
'/documents.update',
|
||||
{
|
||||
id: this.documentId,
|
||||
title: this.title || 'Untitled document',
|
||||
text: this.text,
|
||||
},
|
||||
{ cache: true }
|
||||
);
|
||||
const { url } = data.data;
|
||||
|
||||
this.hasPendingChanges = false;
|
||||
|
@ -97,35 +109,35 @@ class DocumentEditStore {
|
|||
console.error('Something went wrong');
|
||||
}
|
||||
this.isSaving = false;
|
||||
}
|
||||
};
|
||||
|
||||
@action updateText = (text) => {
|
||||
@action updateText = text => {
|
||||
this.text = text;
|
||||
this.title = parseHeader(text);
|
||||
this.hasPendingChanges = true;
|
||||
}
|
||||
};
|
||||
|
||||
@action updateTitle = (title) => {
|
||||
@action updateTitle = title => {
|
||||
this.title = title;
|
||||
}
|
||||
};
|
||||
|
||||
@action replaceText = (args) => {
|
||||
@action replaceText = args => {
|
||||
this.text = this.text.replace(args.original, args.new);
|
||||
this.hasPendingChanges = true;
|
||||
}
|
||||
};
|
||||
|
||||
@action togglePreview = () => {
|
||||
this.preview = !this.preview;
|
||||
}
|
||||
};
|
||||
|
||||
@action reset = () => {
|
||||
this.title = 'Lets start with a title';
|
||||
this.text = '# Lets start with a title\n\nAnd continue from there...';
|
||||
}
|
||||
};
|
||||
|
||||
@action toggleUploadingIndicator = () => {
|
||||
this.isUploading = !this.isUploading;
|
||||
}
|
||||
};
|
||||
|
||||
// Generic
|
||||
|
||||
|
@ -133,7 +145,7 @@ class DocumentEditStore {
|
|||
localStorage[DOCUMENT_EDIT_SETTINGS] = JSON.stringify({
|
||||
preview: toJS(this.preview),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
constructor(settings) {
|
||||
// Rehydrate settings
|
||||
|
@ -148,6 +160,4 @@ class DocumentEditStore {
|
|||
}
|
||||
|
||||
export default DocumentEditStore;
|
||||
export {
|
||||
DOCUMENT_EDIT_SETTINGS,
|
||||
};
|
||||
export { DOCUMENT_EDIT_SETTINGS };
|
||||
|
|
|
@ -8,33 +8,28 @@ import EditorPane from './EditorPane';
|
|||
|
||||
import styles from '../DocumentEdit.scss';
|
||||
|
||||
const Editor = observer((props) => {
|
||||
const Editor = observer(props => {
|
||||
const store = props.store;
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<EditorPane
|
||||
fullWidth={ !store.preview }
|
||||
onScroll={ props.onScroll }
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<EditorPane fullWidth={!store.preview} onScroll={props.onScroll}>
|
||||
<MarkdownEditor
|
||||
onChange={ store.updateText }
|
||||
text={ store.text }
|
||||
replaceText={ store.replaceText }
|
||||
preview={ store.preview }
|
||||
onSave={ props.onSave }
|
||||
onCancel={ props.onCancel }
|
||||
togglePreview={ props.togglePreview }
|
||||
toggleUploadingIndicator={ store.toggleUploadingIndicator }
|
||||
onChange={store.updateText}
|
||||
text={store.text}
|
||||
replaceText={store.replaceText}
|
||||
preview={store.preview}
|
||||
onSave={props.onSave}
|
||||
onCancel={props.onCancel}
|
||||
togglePreview={props.togglePreview}
|
||||
toggleUploadingIndicator={store.toggleUploadingIndicator}
|
||||
/>
|
||||
</EditorPane>
|
||||
{ store.preview ? (
|
||||
<EditorPane
|
||||
scrollTop={ props.scrollTop }
|
||||
>
|
||||
<Preview html={ convertToMarkdown(store.text) } />
|
||||
</EditorPane>
|
||||
) : null }
|
||||
{store.preview
|
||||
? <EditorPane scrollTop={props.scrollTop}>
|
||||
<Preview html={convertToMarkdown(store.text)} />
|
||||
</EditorPane>
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -10,32 +10,31 @@ class EditorPane extends React.Component {
|
|||
onScroll: React.PropTypes.func.isRequired,
|
||||
scrollTop: React.PropTypes.number,
|
||||
fullWidth: React.PropTypes.bool,
|
||||
}
|
||||
|
||||
componentWillReceiveProps = (nextProps) => {
|
||||
};
|
||||
|
||||
componentWillReceiveProps = nextProps => {
|
||||
if (nextProps.scrollTop) {
|
||||
this.scrollToPosition(nextProps.scrollTop)
|
||||
this.scrollToPosition(nextProps.scrollTop);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
this.refs.pane.addEventListener('scroll', this.handleScroll);
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
this.refs.pane.removeEventListener('scroll', this.handleScroll);
|
||||
}
|
||||
};
|
||||
|
||||
handleScroll = (e) => {
|
||||
handleScroll = e => {
|
||||
setTimeout(() => {
|
||||
const element = this.refs.pane;
|
||||
const contentEl = this.refs.content;
|
||||
this.props.onScroll(element.scrollTop / contentEl.offsetHeight);
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
|
||||
scrollToPosition = (percentage) => {
|
||||
scrollToPosition = percentage => {
|
||||
const contentEl = this.refs.content;
|
||||
|
||||
// Push to edges
|
||||
|
@ -43,20 +42,20 @@ class EditorPane extends React.Component {
|
|||
if (percentage > 0.99) percentage = 100;
|
||||
|
||||
this.refs.pane.scrollTop = percentage * contentEl.offsetHeight;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={ cx(styles.editorPane, { fullWidth: this.props.fullWidth }) }
|
||||
className={cx(styles.editorPane, { fullWidth: this.props.fullWidth })}
|
||||
ref="pane"
|
||||
>
|
||||
<div ref="content" className={ styles.paneContent }>
|
||||
{ this.props.children }
|
||||
<div ref="content" className={styles.paneContent}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default EditorPane;
|
||||
|
|
|
@ -6,10 +6,10 @@ import styles from './Preview.scss';
|
|||
import classNames from 'classnames/bind';
|
||||
const cx = classNames.bind(styles);
|
||||
|
||||
const Preview = (props) => {
|
||||
const Preview = props => {
|
||||
return (
|
||||
<div className={ cx(styles.container) }>
|
||||
<DocumentHtml html={ props.html } />
|
||||
<div className={cx(styles.container)}>
|
||||
<DocumentHtml html={props.html} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
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 { browserHistory } from 'react-router';
|
||||
|
||||
|
@ -19,15 +26,14 @@ class DocumentSceneStore {
|
|||
/* Computed */
|
||||
|
||||
@computed get isCollection() {
|
||||
return this.document &&
|
||||
this.document.collection.type === 'atlas';
|
||||
return this.document && this.document.collection.type === 'atlas';
|
||||
}
|
||||
|
||||
@computed get collectionTree() {
|
||||
if (!this.document || this.document.collection.type !== 'atlas') return;
|
||||
const tree = this.document.collection.navigationTree;
|
||||
|
||||
const collapseNodes = (node) => {
|
||||
const collapseNodes = node => {
|
||||
node.collapsed = this.collapsedNodes.includes(node.id);
|
||||
node.children = node.children.map(childNode => {
|
||||
return collapseNodes(childNode);
|
||||
|
@ -69,7 +75,7 @@ class DocumentSceneStore {
|
|||
}
|
||||
this.isFetching = false;
|
||||
this.updatingContent = false;
|
||||
}
|
||||
};
|
||||
|
||||
@action deleteDocument = async () => {
|
||||
this.isFetching = true;
|
||||
|
@ -81,9 +87,9 @@ class DocumentSceneStore {
|
|||
console.error('Something went wrong');
|
||||
}
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
|
||||
@action updateNavigationTree = async (tree) => {
|
||||
@action updateNavigationTree = async tree => {
|
||||
// Only update when tree changes
|
||||
if (_.isEqual(toJS(tree), toJS(this.document.collection.navigationTree))) {
|
||||
return true;
|
||||
|
@ -104,15 +110,15 @@ class DocumentSceneStore {
|
|||
console.error('Something went wrong');
|
||||
}
|
||||
this.updatingStructure = false;
|
||||
}
|
||||
};
|
||||
|
||||
@action onNodeCollapse = (nodeId) => {
|
||||
@action onNodeCollapse = nodeId => {
|
||||
if (_.indexOf(this.collapsedNodes, nodeId) >= 0) {
|
||||
this.collapsedNodes = _.without(this.collapsedNodes, nodeId);
|
||||
} else {
|
||||
this.collapsedNodes.push(nodeId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// General
|
||||
|
||||
|
@ -120,7 +126,7 @@ class DocumentSceneStore {
|
|||
localStorage[DOCUMENT_PREFERENCES] = JSON.stringify({
|
||||
collapsedNodes: toJS(this.collapsedNodes),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
constructor(settings, options) {
|
||||
// Rehydrate settings
|
||||
|
@ -136,6 +142,4 @@ class DocumentSceneStore {
|
|||
}
|
||||
|
||||
export default DocumentSceneStore;
|
||||
export {
|
||||
DOCUMENT_PREFERENCES,
|
||||
};
|
||||
export { DOCUMENT_PREFERENCES };
|
||||
|
|
|
@ -12,8 +12,7 @@ const cx = classNames.bind(styles);
|
|||
|
||||
import SidebarStore from './SidebarStore';
|
||||
|
||||
@observer
|
||||
class Sidebar extends React.Component {
|
||||
@observer class Sidebar extends React.Component {
|
||||
static store;
|
||||
|
||||
static propTypes = {
|
||||
|
@ -22,7 +21,7 @@ class Sidebar extends React.Component {
|
|||
navigationTree: PropTypes.object.isRequired,
|
||||
onNavigationUpdate: PropTypes.func.isRequired,
|
||||
onNodeCollapse: PropTypes.func.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -30,49 +29,47 @@ class Sidebar extends React.Component {
|
|||
this.store = new SidebarStore();
|
||||
}
|
||||
|
||||
toggleEdit = (e) => {
|
||||
toggleEdit = e => {
|
||||
e.preventDefault();
|
||||
this.store.toggleEdit();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Flex>
|
||||
{ this.props.open && (
|
||||
<Flex column className={ cx(styles.sidebar) }>
|
||||
<Flex auto className={ cx(styles.content) }>
|
||||
{this.props.open &&
|
||||
<Flex column className={cx(styles.sidebar)}>
|
||||
<Flex auto className={cx(styles.content)}>
|
||||
<Tree
|
||||
paddingLeft={ 10 }
|
||||
tree={ this.props.navigationTree }
|
||||
allowUpdates={ this.store.isEditing }
|
||||
onChange={ this.props.onNavigationUpdate }
|
||||
onCollapse={ this.props.onNodeCollapse }
|
||||
paddingLeft={10}
|
||||
tree={this.props.navigationTree}
|
||||
allowUpdates={this.store.isEditing}
|
||||
onChange={this.props.onNavigationUpdate}
|
||||
onCollapse={this.props.onNodeCollapse}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex auto className={ styles.actions }>
|
||||
{ this.store.isEditing && (
|
||||
<span className={ styles.action }>
|
||||
<Flex auto className={styles.actions}>
|
||||
{this.store.isEditing &&
|
||||
<span className={styles.action}>
|
||||
Drag & drop to organize <Separator />
|
||||
</span>
|
||||
) }
|
||||
</span>}
|
||||
<span
|
||||
role="button"
|
||||
onClick={ this.toggleEdit }
|
||||
className={ cx(styles.action, { active: this.store.isEditing }) }
|
||||
onClick={this.toggleEdit}
|
||||
className={cx(styles.action, { active: this.store.isEditing })}
|
||||
>
|
||||
{ !this.store.isEditing ? 'Organize documents' : 'Done' }
|
||||
{!this.store.isEditing ? 'Organize documents' : 'Done'}
|
||||
</span>
|
||||
</Flex>
|
||||
</Flex>
|
||||
) }
|
||||
</Flex>}
|
||||
<div
|
||||
onClick={ this.props.onToggle }
|
||||
className={ cx(styles.sidebarToggle, { active: this.store.isEditing }) }
|
||||
onClick={this.props.onToggle}
|
||||
className={cx(styles.sidebarToggle, { active: this.store.isEditing })}
|
||||
title="Toggle sidebar (Cmd+/)"
|
||||
>
|
||||
<img
|
||||
src={ require("assets/icons/menu.svg") }
|
||||
className={ styles.menuIcon }
|
||||
src={require('assets/icons/menu.svg')}
|
||||
className={styles.menuIcon}
|
||||
alt="Menu"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@ class SidebarStore {
|
|||
|
||||
@action toggleEdit = () => {
|
||||
this.isEditing = !this.isEditing;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default SidebarStore;
|
||||
|
|
|
@ -5,8 +5,8 @@ import styles from './Separator.scss';
|
|||
class Separator extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<span className={ styles.separator }>
|
||||
·
|
||||
<span className={styles.separator}>
|
||||
·
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import DocumentScene from './DocumentScene';
|
||||
export default DocumentScene;
|
||||
export default DocumentScene;
|
||||
|
|
|
@ -7,9 +7,7 @@ import CenteredContent from 'components/CenteredContent';
|
|||
class Error404 extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Layout
|
||||
titleText="Not Found"
|
||||
>
|
||||
<Layout titleText="Not Found">
|
||||
<CenteredContent>
|
||||
<h1>Not Found</h1>
|
||||
|
||||
|
|
|
@ -7,13 +7,13 @@ import CenteredContent from 'components/CenteredContent';
|
|||
class ErrorAuth extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Layout
|
||||
titleText="Not Found"
|
||||
>
|
||||
<Layout titleText="Not Found">
|
||||
<CenteredContent>
|
||||
<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>
|
||||
</Layout>
|
||||
);
|
||||
|
|
|
@ -7,23 +7,18 @@ import { DocumentHtml } from 'components/Document';
|
|||
|
||||
import { convertToMarkdown } from 'utils/markdown';
|
||||
|
||||
@observer
|
||||
class Flatpage extends React.Component {
|
||||
@observer class Flatpage extends React.Component {
|
||||
static propTypes = {
|
||||
route: PropTypes.object,
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { title, content } = this.props.route;
|
||||
|
||||
return (
|
||||
<Layout
|
||||
title={ <Title>{ title }</Title> }
|
||||
titleText={ title }
|
||||
search={ false }
|
||||
>
|
||||
<Layout title={<Title>{title}</Title>} titleText={title} search={false}>
|
||||
<CenteredContent>
|
||||
<DocumentHtml html={ convertToMarkdown(content) } />
|
||||
<DocumentHtml html={convertToMarkdown(content)} />
|
||||
</CenteredContent>
|
||||
</Layout>
|
||||
);
|
||||
|
|
|
@ -1,26 +1,44 @@
|
|||
import React from 'react';
|
||||
import { Frame } from 'react-keyframes';
|
||||
|
||||
let frames = [];
|
||||
const p = (node) => frames.push(node);
|
||||
const E = (props) => {
|
||||
return (<Frame duration={props.duration || 300} component='div'>{ props.children }</Frame>);
|
||||
const frames = [];
|
||||
const p = node => frames.push(node);
|
||||
const E = props => {
|
||||
return (
|
||||
<Frame duration={props.duration || 300} component="div">
|
||||
{props.children}
|
||||
</Frame>
|
||||
);
|
||||
};
|
||||
|
||||
const line1 = (<p>Hi there,</p>);
|
||||
const line2 = (<p>We're excited to share what we’re building.</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 line5 = (<p>Together with you,</p>);
|
||||
const line6 = (<p>we want to build the best place to</p>);
|
||||
const line7 = (<p>share ideas,</p>);
|
||||
const line8 = (<p>tell stories,</p>);
|
||||
const line9 = (<p>and build knowledge.</p>);
|
||||
const line10 = (<p>We're just getting started.</p>);
|
||||
const line11 = (<p>Welcome to Atlas.</p>);
|
||||
const line1 = <p>Hi there,</p>;
|
||||
const line2 = <p>We're excited to share what we’re building.</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 line5 = <p>Together with you,</p>;
|
||||
const line6 = <p>we want to build the best place to</p>;
|
||||
const line7 = <p>share ideas,</p>;
|
||||
const line8 = <p>tell stories,</p>;
|
||||
const line9 = <p>and build knowledge.</p>;
|
||||
const line10 = <p>We're just getting started.</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
|
||||
|
||||
export default frames;
|
||||
export default frames;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import Home from './Home';
|
||||
export default Home;
|
||||
export default Home;
|
||||
|
|
|
@ -12,12 +12,11 @@ import styles from './Search.scss';
|
|||
|
||||
import SearchStore from './SearchStore';
|
||||
|
||||
@observer
|
||||
class Search extends React.Component {
|
||||
@observer class Search extends React.Component {
|
||||
static propTypes = {
|
||||
route: PropTypes.object.isRequired,
|
||||
routeParams: PropTypes.object.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -31,7 +30,7 @@ class Search extends React.Component {
|
|||
searchTerm = searchTerm.split(/[\s-]+/gi).join(' ');
|
||||
this.store.search(searchTerm);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
get viewNotFound() {
|
||||
const { sceneType } = this.props.route;
|
||||
|
@ -39,7 +38,7 @@ class Search extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const search = _.debounce((searchTerm) => {
|
||||
const search = _.debounce(searchTerm => {
|
||||
this.store.search(searchTerm);
|
||||
}, 250);
|
||||
const title = (
|
||||
|
@ -50,35 +49,37 @@ class Search extends React.Component {
|
|||
|
||||
return (
|
||||
<Layout
|
||||
title={ title }
|
||||
title={title}
|
||||
titleText="Search"
|
||||
search={ false }
|
||||
loading={ this.store.isFetching }
|
||||
search={false}
|
||||
loading={this.store.isFetching}
|
||||
>
|
||||
<CenteredContent>
|
||||
{ this.viewNotFound && (
|
||||
{this.viewNotFound &&
|
||||
<div>
|
||||
<h1>Not Found</h1>
|
||||
<p>We're unable to find the page you're accessing.</p>
|
||||
<hr />
|
||||
</div>
|
||||
) }
|
||||
</div>}
|
||||
|
||||
<Flex column auto>
|
||||
<Flex auto>
|
||||
<img
|
||||
src={ require('assets/icons/search.svg') }
|
||||
className={ styles.icon }
|
||||
src={require('assets/icons/search.svg')}
|
||||
className={styles.icon}
|
||||
alt="Search"
|
||||
/>
|
||||
<SearchField
|
||||
searchTerm={ this.store.searchTerm }
|
||||
onChange={ search }
|
||||
searchTerm={this.store.searchTerm}
|
||||
onChange={search}
|
||||
/>
|
||||
</Flex>
|
||||
{ this.store.documents && this.store.documents.map((document) => {
|
||||
return (<DocumentPreview key={ document.id } document={ document } />);
|
||||
}) }
|
||||
{this.store.documents &&
|
||||
this.store.documents.map(document => {
|
||||
return (
|
||||
<DocumentPreview key={document.id} document={document} />
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</CenteredContent>
|
||||
</Layout>
|
||||
|
|
|
@ -11,7 +11,7 @@ class SearchStore {
|
|||
|
||||
/* Actions */
|
||||
|
||||
@action search = async (query) => {
|
||||
@action search = async query => {
|
||||
this.searchTerm = query;
|
||||
this.isFetching = true;
|
||||
|
||||
|
@ -24,14 +24,14 @@ class SearchStore {
|
|||
this.pagination = pagination;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Something went wrong");
|
||||
console.error('Something went wrong');
|
||||
}
|
||||
} else {
|
||||
this.documents = null;
|
||||
}
|
||||
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default SearchStore;
|
||||
|
|
|
@ -3,22 +3,21 @@ import { observer } from 'mobx-react';
|
|||
|
||||
import styles from './SearchField.scss';
|
||||
|
||||
@observer
|
||||
class SearchField extends React.Component {
|
||||
@observer class SearchField extends React.Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
}
|
||||
};
|
||||
|
||||
onChange = (event) => {
|
||||
onChange = event => {
|
||||
this.props.onChange(event.currentTarget.value);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<div className={styles.container}>
|
||||
<input
|
||||
onChange={ this.onChange }
|
||||
className={ styles.field }
|
||||
onChange={this.onChange}
|
||||
className={styles.field}
|
||||
placeholder="Search"
|
||||
autoFocus
|
||||
/>
|
||||
|
|
|
@ -12,8 +12,7 @@ import styles from './Settings.scss';
|
|||
|
||||
import SettingsStore from './SettingsStore';
|
||||
|
||||
@observer
|
||||
class Settings extends React.Component {
|
||||
@observer class Settings extends React.Component {
|
||||
static store;
|
||||
|
||||
constructor(props) {
|
||||
|
@ -21,10 +20,10 @@ class Settings extends React.Component {
|
|||
this.store = new SettingsStore();
|
||||
}
|
||||
|
||||
onKeyCreate = (e) => {
|
||||
onKeyCreate = e => {
|
||||
e.preventDefault();
|
||||
this.store.createApiKey();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const title = (
|
||||
|
@ -37,48 +36,52 @@ class Settings extends React.Component {
|
|||
|
||||
return (
|
||||
<Layout
|
||||
title={ title }
|
||||
title={title}
|
||||
titleText="Settings"
|
||||
search={ false }
|
||||
loading={ this.store.isFetching }
|
||||
search={false}
|
||||
loading={this.store.isFetching}
|
||||
>
|
||||
<CenteredContent>
|
||||
{ showSlackSettings && (
|
||||
<div className={ styles.section }>
|
||||
<h2 className={ styles.sectionHeader }>Slack</h2>
|
||||
{showSlackSettings &&
|
||||
<div className={styles.section}>
|
||||
<h2 className={styles.sectionHeader}>Slack</h2>
|
||||
<p>
|
||||
Connect Atlas to your Slack to instantly search for your documents
|
||||
using <code>/atlas</code> command.
|
||||
</p>
|
||||
|
||||
<SlackAuthLink
|
||||
scopes={ ['commands'] }
|
||||
redirectUri={ `${URL}/auth/slack/commands` }
|
||||
scopes={['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>
|
||||
</div>
|
||||
) }
|
||||
</div>}
|
||||
|
||||
<div className={ styles.section }>
|
||||
<h2 className={ styles.sectionHeader }>API access</h2>
|
||||
<div className={styles.section}>
|
||||
<h2 className={styles.sectionHeader}>API access</h2>
|
||||
<p>
|
||||
Create API tokens to hack on your Atlas.
|
||||
Learn more in <a href>API documentation</a>.
|
||||
</p>
|
||||
|
||||
{ this.store.apiKeys && (
|
||||
<table className={ styles.apiKeyTable }>
|
||||
{ this.store.apiKeys.map(key => (
|
||||
{this.store.apiKeys &&
|
||||
<table className={styles.apiKeyTable}>
|
||||
{this.store.apiKeys.map(key => (
|
||||
<ApiKeyRow
|
||||
id={ key.id }
|
||||
name={ key.name }
|
||||
secret={ key.secret }
|
||||
onDelete={ this.store.deleteApiKey }
|
||||
id={key.id}
|
||||
name={key.name}
|
||||
secret={key.secret}
|
||||
onDelete={this.store.deleteApiKey}
|
||||
/>
|
||||
)) }
|
||||
</table>
|
||||
) }
|
||||
))}
|
||||
</table>}
|
||||
|
||||
<div>
|
||||
<InlineForm
|
||||
|
@ -86,11 +89,11 @@ class Settings extends React.Component {
|
|||
buttonLabel="Create token"
|
||||
label="InlineForm"
|
||||
name="inline_form"
|
||||
value={ this.store.keyName }
|
||||
onChange={ this.store.setKeyName }
|
||||
onClick={ this.onKeyCreate }
|
||||
value={this.store.keyName}
|
||||
onChange={this.store.setKeyName}
|
||||
onClick={this.onKeyCreate}
|
||||
style={{ width: '100%' }}
|
||||
disabled={ this.store.isFetching }
|
||||
disabled={this.store.isFetching}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -17,10 +17,10 @@ class SearchStore {
|
|||
this.apiKeys = data;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Something went wrong");
|
||||
console.error('Something went wrong');
|
||||
}
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
|
||||
@action createApiKey = async () => {
|
||||
this.isFetching = true;
|
||||
|
@ -35,12 +35,12 @@ class SearchStore {
|
|||
this.keyName = '';
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Something went wrong");
|
||||
console.error('Something went wrong');
|
||||
}
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
|
||||
@action deleteApiKey = async (id) => {
|
||||
@action deleteApiKey = async id => {
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
|
@ -51,14 +51,14 @@ class SearchStore {
|
|||
this.fetchApiKeys();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Something went wrong");
|
||||
console.error('Something went wrong');
|
||||
}
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
|
||||
@action setKeyName = (value) => {
|
||||
@action setKeyName = value => {
|
||||
this.keyName = value.target.value;
|
||||
}
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.fetchApiKeys();
|
||||
|
|
|
@ -10,37 +10,34 @@ class ApiKeyRow extends React.Component {
|
|||
name: PropTypes.string.isRequired,
|
||||
secret: PropTypes.string.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
state = {
|
||||
disabled: false,
|
||||
}
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
this.props.onDelete(this.props.id);
|
||||
this.setState({ disabled: true });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
secret,
|
||||
} = this.props;
|
||||
const { name, secret } = this.props;
|
||||
|
||||
const {
|
||||
disabled,
|
||||
} = this.state;
|
||||
const { disabled } = this.state;
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{ name }</td>
|
||||
<td><code>{ secret }</code></td>
|
||||
<td>{name}</td>
|
||||
<td><code>{secret}</code></td>
|
||||
<td>
|
||||
<span
|
||||
role="button"
|
||||
onClick={ this.onClick }
|
||||
className={ cx(styles.deleteAction, { disabled }) }
|
||||
>Delete</span>
|
||||
onClick={this.onClick}
|
||||
className={cx(styles.deleteAction, { disabled })}
|
||||
>
|
||||
Delete
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
|
@ -31,7 +31,6 @@ class SlackAuth extends React.Component {
|
|||
browserHistory.replace('/dashboard');
|
||||
} catch (e) {
|
||||
browserHistory.push('/auth-error');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Regular Slack authentication
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default (type, ...argNames) => {
|
||||
return function(...args) {
|
||||
let action = { type };
|
||||
const action = { type };
|
||||
argNames.forEach((arg, index) => {
|
||||
action[argNames[index]] = args[index];
|
||||
});
|
||||
|
|
13
package.json
13
package.json
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "BeautifulAtlas",
|
||||
"name": "Atlas",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -10,7 +10,6 @@
|
|||
"start": "node index.js",
|
||||
"dev": "cross-env NODE_ENV=development DEBUG=sql,cache,presenters ./node_modules/.bin/nodemon --watch server index.js",
|
||||
"lint": "eslint frontend",
|
||||
"prettier": "prettier --single-quote --trailing-comma es5 --write frontend/**/*.js server/**/*.js",
|
||||
"deploy": "git push heroku master",
|
||||
"heroku-postbuild": "npm run build && npm run sequelize db:migrate",
|
||||
"sequelize": "./node_modules/.bin/sequelize",
|
||||
|
@ -21,7 +20,7 @@
|
|||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"yarn prettier",
|
||||
"eslint --fix",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
|
@ -51,7 +50,7 @@
|
|||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": "6.x"
|
||||
"node": ">= 7.6"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -79,10 +78,12 @@
|
|||
"dotenv": "^4.0.0",
|
||||
"emoji-name-map": "1.1.2",
|
||||
"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-plugin-flowtype": "^2.32.1",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-jsx-a11y": "^4.0.0",
|
||||
"eslint-plugin-prettier": "^2.0.1",
|
||||
"eslint-plugin-react": "^6.10.3",
|
||||
"exports-loader": "0.6.3",
|
||||
"extract-text-webpack-plugin": "1.0.1",
|
||||
|
@ -155,8 +156,6 @@
|
|||
"devDependencies": {
|
||||
"babel-jest": "^15.0.0",
|
||||
"enzyme": "^2.4.1",
|
||||
"eslint-config-prettier": "^1.7.0",
|
||||
"eslint-plugin-prettier": "^2.0.1",
|
||||
"fetch-test-server": "^1.1.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"ignore-loader": "0.1.1",
|
||||
|
|
28
yarn.lock
28
yarn.lock
|
@ -2640,21 +2640,9 @@ escope@^3.6.0:
|
|||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-config-airbnb-base@^11.1.0:
|
||||
version "11.1.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.1.3.tgz#0e8db71514fa36b977fbcf977c01edcf863e0cf0"
|
||||
|
||||
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-config-react-app@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-0.6.2.tgz#ee535cbaaf9e3576ea16b99afe720353d8730ec0"
|
||||
|
||||
eslint-import-resolver-node@^0.2.0:
|
||||
version "0.2.3"
|
||||
|
@ -2684,6 +2672,12 @@ eslint-module-utils@^2.0.0:
|
|||
debug "2.2.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:
|
||||
version "2.2.0"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||
|
||||
|
|
Reference in New Issue