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