Merge pull request #86 from jorilallo/jori/layout-changes
Layout changes
This commit is contained in:
commit
1fa473b271
|
@ -99,7 +99,8 @@ export default class MarkdownEditor extends Component {
|
|||
render = () => {
|
||||
return (
|
||||
<span>
|
||||
<ClickablePadding onClick={this.focusAtStart} />
|
||||
{!this.props.readOnly &&
|
||||
<ClickablePadding onClick={this.focusAtStart} />}
|
||||
<Toolbar state={this.state.state} onChange={this.onChange} />
|
||||
<Editor
|
||||
ref={ref => (this.editor = ref)}
|
||||
|
@ -114,7 +115,8 @@ export default class MarkdownEditor extends Component {
|
|||
onSave={this.props.onSave}
|
||||
readOnly={this.props.readOnly}
|
||||
/>
|
||||
<ClickablePadding onClick={this.focusAtEnd} grow />
|
||||
{!this.props.readOnly &&
|
||||
<ClickablePadding onClick={this.focusAtEnd} grow />}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,23 +6,31 @@ import styled from 'styled-components';
|
|||
import { observer, inject } from 'mobx-react';
|
||||
import _ from 'lodash';
|
||||
import keydown from 'react-keydown';
|
||||
import searchIcon from 'assets/icons/search.svg';
|
||||
import { Flex } from 'reflexbox';
|
||||
import { textColor, headerHeight } from 'styles/constants.scss';
|
||||
import { textColor } from 'styles/constants.scss';
|
||||
|
||||
import DropdownMenu, { MenuItem } from 'components/DropdownMenu';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
|
||||
import SidebarCollection from './components/SidebarCollection';
|
||||
import SidebarCollectionList from './components/SidebarCollectionList';
|
||||
import SidebarLink from './components/SidebarLink';
|
||||
|
||||
import UserStore from 'stores/UserStore';
|
||||
import AuthStore from 'stores/AuthStore';
|
||||
import UiStore from 'stores/UiStore';
|
||||
import CollectionsStore from 'stores/CollectionsStore';
|
||||
|
||||
type Props = {
|
||||
history: Object,
|
||||
collections: CollectionsStore,
|
||||
children?: ?React.Element<any>,
|
||||
actions?: ?React.Element<any>,
|
||||
title?: ?React.Element<any>,
|
||||
loading?: boolean,
|
||||
user: UserStore,
|
||||
auth: AuthStore,
|
||||
ui: UiStore,
|
||||
search: ?boolean,
|
||||
notifications?: React.Element<any>,
|
||||
};
|
||||
|
@ -36,13 +44,13 @@ type Props = {
|
|||
|
||||
@keydown(['/', 't'])
|
||||
search() {
|
||||
if (this.props.auth.isAuthenticated)
|
||||
if (this.props.auth.authenticated)
|
||||
_.defer(() => this.props.history.push('/search'));
|
||||
}
|
||||
|
||||
@keydown(['d'])
|
||||
dashboard() {
|
||||
if (this.props.auth.isAuthenticated)
|
||||
if (this.props.auth.authenticated)
|
||||
_.defer(() => this.props.history.push('/'));
|
||||
}
|
||||
|
||||
|
@ -51,7 +59,7 @@ type Props = {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { auth, user } = this.props;
|
||||
const { user, auth, ui, collections } = this.props;
|
||||
|
||||
return (
|
||||
<Container column auto>
|
||||
|
@ -69,29 +77,14 @@ type Props = {
|
|||
|
||||
{this.props.notifications}
|
||||
|
||||
<Header>
|
||||
<Flex align="center">
|
||||
<LogoLink to="/">Atlas</LogoLink>
|
||||
<Title>
|
||||
{this.props.title}
|
||||
</Title>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex>
|
||||
<Flex align="center">
|
||||
{this.props.actions}
|
||||
</Flex>
|
||||
<Flex auto>
|
||||
{auth.authenticated &&
|
||||
user &&
|
||||
<Flex>
|
||||
{this.props.search &&
|
||||
<Flex>
|
||||
<Link to="/search">
|
||||
<Search title="Search (/)">
|
||||
<SearchIcon src={searchIcon} alt="Search" />
|
||||
</Search>
|
||||
</Link>
|
||||
</Flex>}
|
||||
<Sidebar column editMode={ui.editMode}>
|
||||
<Header justify="space-between">
|
||||
<Flex align="center">
|
||||
<LogoLink to="/">Atlas</LogoLink>
|
||||
</Flex>
|
||||
<DropdownMenu label={<Avatar src={user.user.avatarUrl} />}>
|
||||
<MenuLink to="/settings">
|
||||
<MenuItem>Settings</MenuItem>
|
||||
|
@ -106,41 +99,43 @@ type Props = {
|
|||
</MenuLink>
|
||||
<MenuItem onClick={this.handleLogout}>Logout</MenuItem>
|
||||
</DropdownMenu>
|
||||
</Flex>}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Header>
|
||||
|
||||
<Content auto justify="center">
|
||||
<Flex column>
|
||||
<LinkSection>
|
||||
<SidebarLink to="/search">Search</SidebarLink>
|
||||
</LinkSection>
|
||||
<LinkSection>
|
||||
<SidebarLink to="/dashboard">Dashboard</SidebarLink>
|
||||
<SidebarLink to="/starred">Starred</SidebarLink>
|
||||
</LinkSection>
|
||||
<LinkSection>
|
||||
{ui.activeCollection
|
||||
? <SidebarCollection
|
||||
collection={collections.getById(ui.activeCollection)}
|
||||
/>
|
||||
: <SidebarCollectionList />}
|
||||
</LinkSection>
|
||||
</Flex>
|
||||
</Sidebar>}
|
||||
|
||||
<Content auto justify="center" editMode={ui.editMode}>
|
||||
{this.props.children}
|
||||
</Content>
|
||||
</Flex>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Container = styled(Flex)`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const Header = styled(Flex)`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
padding: 0 20px;
|
||||
|
||||
z-index: 1;
|
||||
background: #fff;
|
||||
height: ${headerHeight};
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
`;
|
||||
|
||||
const LogoLink = styled(Link)`
|
||||
margin-top: 5px;
|
||||
font-family: 'Atlas Grotesk';
|
||||
font-weight: bold;
|
||||
color: ${textColor};
|
||||
|
@ -148,28 +143,6 @@ const LogoLink = styled(Link)`
|
|||
font-size: 16px;
|
||||
`;
|
||||
|
||||
const Title = styled.span`
|
||||
color: #ccc;
|
||||
|
||||
a {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: ${textColor};
|
||||
}
|
||||
`;
|
||||
|
||||
const Search = styled(Flex)`
|
||||
margin: 0 5px;
|
||||
padding: 15px 5px 0 5px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const SearchIcon = styled.img`
|
||||
height: 20px;
|
||||
`;
|
||||
|
||||
const Avatar = styled.img`
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
@ -181,8 +154,31 @@ const MenuLink = styled(Link)`
|
|||
`;
|
||||
|
||||
const Content = styled(Flex)`
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: ${props => (props.editMode ? 0 : '250px')};
|
||||
transition: left 200ms ease-in-out;
|
||||
`;
|
||||
|
||||
export default withRouter(inject('user', 'auth')(Layout));
|
||||
const Sidebar = styled(Flex)`
|
||||
width: 250px;
|
||||
margin-left: ${props => (props.editMode ? '-250px' : 0)};
|
||||
padding: 10px 20px;
|
||||
background: rgba(250, 251, 252, 0.71);
|
||||
border-right: 1px solid #eceff3;
|
||||
transition: margin-left 200ms ease-in-out;
|
||||
`;
|
||||
|
||||
const Header = styled(Flex)`
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const LinkSection = styled(Flex)`
|
||||
margin-bottom: 20px;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export default withRouter(inject('user', 'auth', 'ui', 'collections')(Layout));
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Flex } from 'reflexbox';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import SidebarLink from '../SidebarLink';
|
||||
|
||||
import Collection from 'models/Collection';
|
||||
|
||||
type Props = {
|
||||
collection: Collection,
|
||||
};
|
||||
|
||||
const SidebarCollection = ({ collection }: Props) => {
|
||||
if (collection) {
|
||||
return (
|
||||
<Flex column>
|
||||
<Header>{collection.name}</Header>
|
||||
{collection.documents.map(document => (
|
||||
<SidebarLink key={document.id} to={document.url}>
|
||||
{document.title}
|
||||
</SidebarLink>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const Header = styled(Flex)`
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
color: #9FA6AB;
|
||||
letter-spacing: 0.04em;
|
||||
`;
|
||||
|
||||
export default observer(SidebarCollection);
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
import SidebarCollection from './SidebarCollection';
|
||||
export default SidebarCollection;
|
|
@ -0,0 +1,36 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { observer, inject } from 'mobx-react';
|
||||
import { Flex } from 'reflexbox';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import SidebarLink from '../SidebarLink';
|
||||
|
||||
import CollectionsStore from 'stores/CollectionsStore';
|
||||
|
||||
type Props = {
|
||||
collections: CollectionsStore,
|
||||
};
|
||||
|
||||
const SidebarCollectionList = observer(({ collections }: Props) => {
|
||||
return (
|
||||
<Flex column>
|
||||
<Header>Collections</Header>
|
||||
{collections.data.map(collection => (
|
||||
<SidebarLink key={collection.id} to={collection.url}>
|
||||
{collection.name}
|
||||
</SidebarLink>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
const Header = styled(Flex)`
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
color: #9FA6AB;
|
||||
letter-spacing: 0.04em;
|
||||
`;
|
||||
|
||||
export default inject('collections')(SidebarCollectionList);
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
import SidebarCollectionList from './SidebarCollectionList';
|
||||
export default SidebarCollectionList;
|
|
@ -0,0 +1,26 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { Flex } from 'reflexbox';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const activeStyle = {
|
||||
color: '#000000',
|
||||
};
|
||||
|
||||
const SidebarLink = observer(props => (
|
||||
<LinkContainer>
|
||||
<NavLink {...props} activeStyle={activeStyle} />
|
||||
</LinkContainer>
|
||||
));
|
||||
|
||||
const LinkContainer = styled(Flex)`
|
||||
padding: 5px 0;
|
||||
|
||||
a {
|
||||
color: #848484;
|
||||
}
|
||||
`;
|
||||
|
||||
export default SidebarLink;
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
import SidebarLink from './SidebarLink';
|
||||
export default SidebarLink;
|
|
@ -1,85 +0,0 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import { Flex } from 'reflexbox';
|
||||
import Tree from 'components/Tree';
|
||||
import Separator from './components/Separator';
|
||||
|
||||
import styles from './Sidebar.scss';
|
||||
import classNames from 'classnames/bind';
|
||||
const cx = classNames.bind(styles);
|
||||
|
||||
import SidebarStore from './SidebarStore';
|
||||
|
||||
type Props = {
|
||||
open?: boolean,
|
||||
onToggle: Function,
|
||||
navigationTree: Object,
|
||||
onNavigationUpdate: Function,
|
||||
onNodeCollapse: Function,
|
||||
history: Object,
|
||||
};
|
||||
|
||||
@observer class Sidebar extends React.Component {
|
||||
props: Props;
|
||||
store: SidebarStore;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.store = new SidebarStore();
|
||||
}
|
||||
|
||||
toggleEdit = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
this.store.toggleEdit();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Flex>
|
||||
{this.props.open &&
|
||||
<Flex column className={cx(styles.sidebar)}>
|
||||
<Flex auto className={cx(styles.content)}>
|
||||
<Tree
|
||||
paddingLeft={10}
|
||||
tree={this.props.navigationTree}
|
||||
allowUpdates={this.store.isEditing}
|
||||
onChange={this.props.onNavigationUpdate}
|
||||
onCollapse={this.props.onNodeCollapse}
|
||||
history={this.props.history}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex auto className={styles.actions}>
|
||||
{this.store.isEditing &&
|
||||
<span className={styles.action}>
|
||||
Drag & drop to organize <Separator />
|
||||
</span>}
|
||||
<span
|
||||
role="button"
|
||||
onClick={this.toggleEdit}
|
||||
className={cx(styles.action, { active: this.store.isEditing })}
|
||||
>
|
||||
{!this.store.isEditing ? 'Organize documents' : 'Done'}
|
||||
</span>
|
||||
</Flex>
|
||||
</Flex>}
|
||||
<div
|
||||
onClick={this.props.onToggle}
|
||||
className={cx(styles.sidebarToggle, { active: this.store.isEditing })}
|
||||
title="Toggle sidebar (Cmd+/)"
|
||||
>
|
||||
<img
|
||||
src={require('assets/icons/menu.svg')}
|
||||
className={styles.menuIcon}
|
||||
alt="Menu"
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Sidebar);
|
|
@ -1,56 +0,0 @@
|
|||
@import '~styles/constants.scss';
|
||||
|
||||
.sidebar {
|
||||
position: relative;
|
||||
width: 250px;
|
||||
border-right: 1px solid #eee;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.sidebarToggle {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 60px;
|
||||
cursor: pointer;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.menuIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menuIcon {
|
||||
margin-top: 18px;
|
||||
height: 28px;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px 20px 20px 5px;
|
||||
}
|
||||
|
||||
.tree {
|
||||
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
padding: 10px 20px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.action {
|
||||
color: $gray;
|
||||
|
||||
&.active {
|
||||
color: $textColor;
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// @flow
|
||||
import { observable, action } from 'mobx';
|
||||
|
||||
class SidebarStore {
|
||||
@observable isEditing = false;
|
||||
|
||||
/* Actions */
|
||||
|
||||
@action toggleEdit = () => {
|
||||
this.isEditing = !this.isEditing;
|
||||
};
|
||||
}
|
||||
|
||||
export default SidebarStore;
|
|
@ -1,16 +0,0 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import styles from './Separator.scss';
|
||||
|
||||
class Separator extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<span className={styles.separator}>
|
||||
·
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Separator;
|
|
@ -1,6 +0,0 @@
|
|||
@import '~styles/constants.scss';
|
||||
|
||||
.separator {
|
||||
padding: 0 10px;
|
||||
color: $lightGray;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
// @flow
|
||||
import Separator from './Separator';
|
||||
export default Separator;
|
|
@ -1,3 +0,0 @@
|
|||
// @flow
|
||||
import Sidebar from './Sidebar';
|
||||
export default Sidebar;
|
|
@ -1,139 +0,0 @@
|
|||
/* eslint-disable */
|
||||
import React from 'react';
|
||||
|
||||
import styles from './Tree.scss';
|
||||
import classNames from 'classnames/bind';
|
||||
const cx = classNames.bind(styles);
|
||||
|
||||
class Node extends React.Component {
|
||||
displayName: 'UITreeNode';
|
||||
|
||||
renderCollapse = () => {
|
||||
const index = this.props.index;
|
||||
|
||||
if (index.children && index.children.length) {
|
||||
const collapsed = index.node.collapsed;
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cx(
|
||||
styles.collapse,
|
||||
collapsed ? styles.caretRight : styles.caretDown
|
||||
)}
|
||||
onMouseDown={function(e) {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onClick={this.handleCollapse}
|
||||
>
|
||||
<img alt="Expand" src={require('./assets/chevron.svg')} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
renderChildren = () => {
|
||||
const index = this.props.index;
|
||||
const tree = this.props.tree;
|
||||
const dragging = this.props.dragging;
|
||||
|
||||
if (index.children && index.children.length) {
|
||||
const childrenStyles = {};
|
||||
|
||||
if (!this.props.rootNode) {
|
||||
if (index.node.collapsed) childrenStyles.display = 'none';
|
||||
childrenStyles.paddingLeft = `${this.props.paddingLeft}px`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.children} style={childrenStyles}>
|
||||
{index.children.map(child => {
|
||||
const childIndex = tree.getIndex(child);
|
||||
return (
|
||||
<Node
|
||||
tree={tree}
|
||||
index={childIndex}
|
||||
key={childIndex.id}
|
||||
dragging={dragging}
|
||||
allowUpdates={this.props.allowUpdates}
|
||||
paddingLeft={this.props.paddingLeft}
|
||||
onCollapse={this.props.onCollapse}
|
||||
onDragStart={this.props.onDragStart}
|
||||
history={this.props.history}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
isModifying = () => {
|
||||
return this.props.allowUpdates && !this.props.rootNode;
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
const index = this.props.index;
|
||||
const node = index.node;
|
||||
if (!this.isModifying()) this.props.history.push(node.url);
|
||||
};
|
||||
|
||||
render() {
|
||||
const index = this.props.index;
|
||||
const dragging = this.props.dragging;
|
||||
const node = index.node;
|
||||
const style = {};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(styles.node, {
|
||||
placeholder: index.id === dragging,
|
||||
rootNode: this.props.rootNode,
|
||||
})}
|
||||
style={style}
|
||||
>
|
||||
<div
|
||||
className={styles.inner}
|
||||
ref="inner"
|
||||
onMouseDown={
|
||||
this.props.rootNode || !this.props.allowUpdates
|
||||
? e => e.stopPropagation()
|
||||
: this.handleMouseDown
|
||||
}
|
||||
>
|
||||
{!this.props.rootNode && this.renderCollapse()}
|
||||
<span
|
||||
className={cx(styles.nodeLabel, {
|
||||
rootLabel: this.props.rootNode,
|
||||
isModifying: this.isModifying(),
|
||||
})}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
{node.title}
|
||||
</span>
|
||||
</div>
|
||||
{this.renderChildren()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleCollapse = e => {
|
||||
e.stopPropagation();
|
||||
const nodeId = this.props.index.id;
|
||||
if (this.props.onCollapse) this.props.onCollapse(nodeId);
|
||||
};
|
||||
|
||||
handleMouseDown = e => {
|
||||
const nodeId = this.props.index.id;
|
||||
const dom = this.refs.inner;
|
||||
|
||||
if (this.props.onDragStart) {
|
||||
this.props.onDragStart(nodeId, dom, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Node;
|
|
@ -1,74 +0,0 @@
|
|||
/* eslint-disable */
|
||||
const Tree = require('js-tree');
|
||||
const proto = Tree.prototype;
|
||||
|
||||
proto.updateNodesPosition = function() {
|
||||
let top = 1;
|
||||
let left = 1;
|
||||
const root = this.getIndex(1);
|
||||
const self = this;
|
||||
|
||||
root.top = top++;
|
||||
root.left = left++;
|
||||
|
||||
if (root.children && root.children.length) {
|
||||
walk(root.children, root, left, root.node.collapsed);
|
||||
}
|
||||
|
||||
function walk(children, parent, left, collapsed) {
|
||||
let height = 1;
|
||||
children.forEach(id => {
|
||||
const node = self.getIndex(id);
|
||||
if (collapsed) {
|
||||
node.top = null;
|
||||
node.left = null;
|
||||
} else {
|
||||
node.top = top++;
|
||||
node.left = left;
|
||||
}
|
||||
|
||||
if (node.children && node.children.length) {
|
||||
height += walk(
|
||||
node.children,
|
||||
node,
|
||||
left + 1,
|
||||
collapsed || node.node.collapsed
|
||||
);
|
||||
} else {
|
||||
node.height = 1;
|
||||
height += 1;
|
||||
}
|
||||
});
|
||||
|
||||
if (parent.node.collapsed) parent.height = 1;
|
||||
else parent.height = height;
|
||||
return parent.height;
|
||||
}
|
||||
};
|
||||
|
||||
proto.move = function(fromId, toId, placement) {
|
||||
if (fromId === toId || toId === 1) return;
|
||||
|
||||
const obj = this.remove(fromId);
|
||||
let index = null;
|
||||
|
||||
if (placement === 'before') index = this.insertBefore(obj, toId);
|
||||
else if (placement === 'after') index = this.insertAfter(obj, toId);
|
||||
else if (placement === 'prepend') index = this.prepend(obj, toId);
|
||||
else if (placement === 'append') index = this.append(obj, toId);
|
||||
|
||||
// todo: perf
|
||||
this.updateNodesPosition();
|
||||
return index;
|
||||
};
|
||||
|
||||
proto.getNodeByTop = function(top) {
|
||||
const indexes = this.indexes;
|
||||
for (const id in indexes) {
|
||||
if (indexes.hasOwnProperty(id)) {
|
||||
if (indexes[id].top === top) return indexes[id];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Tree;
|
|
@ -1,87 +0,0 @@
|
|||
@mixin no-select {
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tree {
|
||||
@include no-select;
|
||||
padding: 20px 20px 20px 5px;
|
||||
overflow: scroll;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 40px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
position: absolute;
|
||||
opacity: 0.8;
|
||||
@include no-select;
|
||||
}
|
||||
|
||||
.node {
|
||||
&.placeholder > * {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&.placeholder {
|
||||
border: 1px dashed #ccc;
|
||||
}
|
||||
|
||||
.inner {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
cursor: pointer;
|
||||
|
||||
width: 20px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.caretRight {
|
||||
margin-top: 3px;
|
||||
margin-left: -3px;
|
||||
}
|
||||
|
||||
.caretDown {
|
||||
transform: rotate(90deg);
|
||||
margin-left: -4px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.node {
|
||||
&.placeholder {
|
||||
border: 1px dashed #1385e5;
|
||||
}
|
||||
|
||||
.nodeLabel {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 4px 5px 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&.isModifying {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
background-color: #31363F;
|
||||
}
|
||||
}
|
||||
|
||||
.rootLabel {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
|
@ -1,275 +0,0 @@
|
|||
/* eslint-disable */
|
||||
const React = require('react');
|
||||
const Tree = require('./Tree');
|
||||
const Node = require('./Node');
|
||||
|
||||
import styles from './Tree.scss';
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'UITree',
|
||||
|
||||
propTypes: {
|
||||
tree: React.PropTypes.object.isRequired,
|
||||
paddingLeft: React.PropTypes.number,
|
||||
onCollapse: React.PropTypes.func,
|
||||
allowUpdates: React.PropTypes.bool,
|
||||
history: React.PropTypes.object,
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
paddingLeft: 20,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return this.init(this.props);
|
||||
},
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this._updated) this.setState(this.init(nextProps));
|
||||
else this._updated = false;
|
||||
},
|
||||
|
||||
init(props) {
|
||||
const tree = new Tree(props.tree);
|
||||
tree.isNodeCollapsed = props.isNodeCollapsed;
|
||||
tree.changeNodeCollapsed = props.changeNodeCollapsed;
|
||||
tree.updateNodesPosition();
|
||||
|
||||
return {
|
||||
tree,
|
||||
dragging: {
|
||||
id: null,
|
||||
x: null,
|
||||
y: null,
|
||||
w: null,
|
||||
h: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
getDraggingDom() {
|
||||
const tree = this.state.tree;
|
||||
const dragging = this.state.dragging;
|
||||
|
||||
if (dragging && dragging.id) {
|
||||
const draggingIndex = tree.getIndex(dragging.id);
|
||||
const draggingStyles = {
|
||||
top: dragging.y,
|
||||
left: dragging.x,
|
||||
width: dragging.w,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.draggable} style={draggingStyles}>
|
||||
<Node
|
||||
tree={tree}
|
||||
index={draggingIndex}
|
||||
paddingLeft={this.props.paddingLeft}
|
||||
allowUpdates={this.props.allowUpdates}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
render() {
|
||||
const tree = this.state.tree;
|
||||
const dragging = this.state.dragging;
|
||||
const draggingDom = this.getDraggingDom();
|
||||
|
||||
return (
|
||||
<div className={styles.tree}>
|
||||
{draggingDom}
|
||||
<Node
|
||||
rootNode
|
||||
tree={tree}
|
||||
index={tree.getIndex(1)}
|
||||
key={1}
|
||||
paddingLeft={this.props.paddingLeft}
|
||||
allowUpdates={this.props.allowUpdates}
|
||||
onDragStart={this.dragStart}
|
||||
onCollapse={this.toggleCollapse}
|
||||
dragging={dragging && dragging.id}
|
||||
history={this.props.history}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
dragStart(id, dom, e) {
|
||||
this.dragging = {
|
||||
id,
|
||||
w: dom.offsetWidth,
|
||||
h: dom.offsetHeight,
|
||||
x: dom.offsetLeft,
|
||||
y: dom.offsetTop,
|
||||
};
|
||||
|
||||
this._startX = dom.offsetLeft;
|
||||
this._startY = dom.offsetTop;
|
||||
this._offsetX = e.clientX;
|
||||
this._offsetY = e.clientY;
|
||||
this._start = true;
|
||||
|
||||
window.addEventListener('mousemove', this.drag);
|
||||
window.addEventListener('mouseup', this.dragEnd);
|
||||
},
|
||||
|
||||
// oh
|
||||
drag(e) {
|
||||
if (this._start) {
|
||||
this.setState({
|
||||
dragging: this.dragging,
|
||||
});
|
||||
this._start = false;
|
||||
}
|
||||
|
||||
const tree = this.state.tree;
|
||||
const dragging = this.state.dragging;
|
||||
const paddingLeft = this.props.paddingLeft;
|
||||
let newIndex = null;
|
||||
let index = tree.getIndex(dragging.id);
|
||||
const collapsed = index.node.collapsed;
|
||||
|
||||
const _startX = this._startX;
|
||||
const _startY = this._startY;
|
||||
const _offsetX = this._offsetX;
|
||||
const _offsetY = this._offsetY;
|
||||
|
||||
const pos = {
|
||||
x: _startX + e.clientX - _offsetX,
|
||||
y: _startY + e.clientY - _offsetY,
|
||||
};
|
||||
dragging.x = pos.x;
|
||||
dragging.y = pos.y;
|
||||
|
||||
const diffX = dragging.x - paddingLeft / 2 - (index.left - 2) * paddingLeft;
|
||||
const diffY = dragging.y - dragging.h / 2 - (index.top - 2) * dragging.h;
|
||||
|
||||
if (diffX < 0) {
|
||||
// left
|
||||
if (index.parent && !index.next) {
|
||||
newIndex = tree.move(index.id, index.parent, 'after');
|
||||
}
|
||||
} else if (diffX > paddingLeft) {
|
||||
// right
|
||||
if (index.prev) {
|
||||
const prevNode = tree.getIndex(index.prev).node;
|
||||
if (!prevNode.collapsed && !prevNode.leaf) {
|
||||
newIndex = tree.move(index.id, index.prev, 'append');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newIndex) {
|
||||
index = newIndex;
|
||||
newIndex.node.collapsed = collapsed;
|
||||
dragging.id = newIndex.id;
|
||||
}
|
||||
|
||||
if (diffY < 0) {
|
||||
// up
|
||||
const above = tree.getNodeByTop(index.top - 1);
|
||||
newIndex = tree.move(index.id, above.id, 'before');
|
||||
} else if (diffY > dragging.h) {
|
||||
// down
|
||||
if (index.next) {
|
||||
let below = tree.getIndex(index.next);
|
||||
if (below.children && below.children.length && !below.node.collapsed) {
|
||||
newIndex = tree.move(index.id, index.next, 'prepend');
|
||||
} else {
|
||||
newIndex = tree.move(index.id, index.next, 'after');
|
||||
}
|
||||
} else {
|
||||
let below = tree.getNodeByTop(index.top + index.height);
|
||||
if (below && below.parent !== index.id) {
|
||||
if (below.children && below.children.length) {
|
||||
newIndex = tree.move(index.id, below.id, 'prepend');
|
||||
} else {
|
||||
newIndex = tree.move(index.id, below.id, 'after');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newIndex) {
|
||||
newIndex.node.collapsed = collapsed;
|
||||
dragging.id = newIndex.id;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
tree,
|
||||
dragging,
|
||||
});
|
||||
},
|
||||
|
||||
dragEnd() {
|
||||
this.setState({
|
||||
dragging: {
|
||||
id: null,
|
||||
x: null,
|
||||
y: null,
|
||||
w: null,
|
||||
h: null,
|
||||
},
|
||||
});
|
||||
|
||||
this.change(this.state.tree);
|
||||
window.removeEventListener('mousemove', this.drag);
|
||||
window.removeEventListener('mouseup', this.dragEnd);
|
||||
},
|
||||
|
||||
change(tree) {
|
||||
this._updated = true;
|
||||
if (this.props.onChange) this.props.onChange(tree.obj);
|
||||
},
|
||||
|
||||
toggleCollapse(nodeId) {
|
||||
const tree = this.state.tree;
|
||||
const index = tree.getIndex(nodeId);
|
||||
const node = index.node;
|
||||
node.collapsed = !node.collapsed;
|
||||
tree.updateNodesPosition();
|
||||
|
||||
this.setState({
|
||||
tree,
|
||||
});
|
||||
|
||||
if (this.props.onCollapse) this.props.onCollapse(node.id, node.collapsed);
|
||||
},
|
||||
|
||||
// buildTreeNumbering(tree) {
|
||||
// const numberBuilder = (index, node, parentNumbering) => {
|
||||
// let numbering = parentNumbering ? `${parentNumbering}.${index}` : index;
|
||||
// let children;
|
||||
// if (node.children) {
|
||||
// children = node.children.map((child, childIndex) => {
|
||||
// return numberBuilder(childIndex+1, child, numbering);
|
||||
// });
|
||||
// }
|
||||
|
||||
// const data = {
|
||||
// module: {
|
||||
// ...node.module,
|
||||
// index: numbering,
|
||||
// }
|
||||
// }
|
||||
// if (children) {
|
||||
// data.children = children;
|
||||
// }
|
||||
|
||||
// return data;
|
||||
// };
|
||||
|
||||
// const newTree = {...tree};
|
||||
// newTree.children = [];
|
||||
// tree.children.forEach((child, index) => {
|
||||
// newTree.children.push(numberBuilder(index+1, child));
|
||||
// })
|
||||
// return newTree;
|
||||
// }
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 30" x="0px" y="0px"><path d="M8.59,18.16L14.25,12.5L8.59,6.84L7.89,7.55L12.84,12.5L7.89,17.45L8.59,18.16Z"/></svg>
|
Before Width: | Height: | Size: 227 B |
|
@ -1,3 +0,0 @@
|
|||
// @flow
|
||||
import UiTree from './UiTree';
|
||||
export default UiTree;
|
|
@ -31,6 +31,8 @@ import Flatpage from 'scenes/Flatpage';
|
|||
import ErrorAuth from 'scenes/ErrorAuth';
|
||||
import Error404 from 'scenes/Error404';
|
||||
|
||||
import Layout from 'components/Layout';
|
||||
|
||||
import flatpages from 'static/flatpages';
|
||||
|
||||
let DevTools;
|
||||
|
@ -94,10 +96,12 @@ render(
|
|||
<Route exact path="/auth/error" component={ErrorAuth} />
|
||||
|
||||
<Auth>
|
||||
<Layout>
|
||||
<Switch>
|
||||
<Route exact path="/dashboard" component={Dashboard} />
|
||||
<Route exact path="/collections/:id" component={Collection} />
|
||||
<Route exact path="/d/:id" component={Document} />
|
||||
|
||||
<Route exact path="/d/:id/:edit" component={Document} />
|
||||
<Route
|
||||
exact
|
||||
|
@ -120,6 +124,7 @@ render(
|
|||
<Route path="/404" component={Error404} />
|
||||
<Route component={notFoundSearch} />
|
||||
</Switch>
|
||||
</Layout>
|
||||
</Auth>
|
||||
</Switch>
|
||||
</Router>
|
||||
|
|
|
@ -3,11 +3,10 @@ import React from 'react';
|
|||
import { observer, inject } from 'mobx-react';
|
||||
import { Redirect } from 'react-router';
|
||||
import _ from 'lodash';
|
||||
import { notFoundUrl } from 'utils/routeHelpers';
|
||||
|
||||
import CollectionsStore from 'stores/CollectionsStore';
|
||||
import CollectionStore from './CollectionStore';
|
||||
|
||||
import Layout from 'components/Layout';
|
||||
import CenteredContent from 'components/CenteredContent';
|
||||
import PreviewLoading from 'components/PreviewLoading';
|
||||
|
||||
|
@ -16,50 +15,26 @@ type Props = {
|
|||
match: Object,
|
||||
};
|
||||
|
||||
type State = {
|
||||
redirectUrl: ?string,
|
||||
};
|
||||
|
||||
@observer class Collection extends React.Component {
|
||||
props: Props;
|
||||
state: State;
|
||||
store: CollectionStore;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
redirectUrl: null,
|
||||
};
|
||||
this.store = new CollectionStore();
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
const { id } = this.props.match.params;
|
||||
this.props.collections
|
||||
.getById(id)
|
||||
.then(collection => {
|
||||
if (collection.type !== 'atlas')
|
||||
throw new Error('TODO code up non-atlas collections');
|
||||
|
||||
this.setState({
|
||||
redirectUrl: collection.documents[0].url,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
redirectUrl: notFoundUrl(),
|
||||
});
|
||||
});
|
||||
this.store.fetchCollection(id);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Layout>
|
||||
{this.state.redirectUrl && <Redirect to={this.state.redirectUrl} />}
|
||||
|
||||
<CenteredContent>
|
||||
return this.store.redirectUrl
|
||||
? <Redirect to={this.store.redirectUrl} />
|
||||
: <CenteredContent>
|
||||
<PreviewLoading />
|
||||
</CenteredContent>
|
||||
</Layout>
|
||||
);
|
||||
</CenteredContent>;
|
||||
}
|
||||
}
|
||||
export default inject('collections')(Collection);
|
||||
|
|
|
@ -1,37 +1,30 @@
|
|||
// @flow
|
||||
import { observable, action, computed } from 'mobx';
|
||||
import { observable, action } from 'mobx';
|
||||
import invariant from 'invariant';
|
||||
import { client } from 'utils/ApiClient';
|
||||
import Collection from 'models/Collection';
|
||||
|
||||
const store = new class AtlasStore {
|
||||
@observable collection: ?(Collection & { recentDocuments?: Object[] });
|
||||
import { notFoundUrl } from 'utils/routeHelpers';
|
||||
|
||||
class CollectionStore {
|
||||
@observable redirectUrl: ?string;
|
||||
@observable isFetching = true;
|
||||
|
||||
/* Computed */
|
||||
|
||||
@computed get isLoaded(): boolean {
|
||||
return !this.isFetching && !!this.collection;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
|
||||
@action fetchCollection = async (id: string, successCallback: Function) => {
|
||||
@action fetchCollection = async (id: string) => {
|
||||
this.isFetching = true;
|
||||
this.collection = null;
|
||||
|
||||
try {
|
||||
const res = await client.get('/collections.info', { id });
|
||||
invariant(res && res.data, 'Data should be available');
|
||||
const { data } = res;
|
||||
this.collection = new Collection(data);
|
||||
successCallback(data);
|
||||
|
||||
if (data.type === 'atlas') this.redirectUrl = data.documents[0].url;
|
||||
else throw new Error('TODO code up non-atlas collections');
|
||||
} catch (e) {
|
||||
console.error('Something went wrong');
|
||||
this.redirectUrl = notFoundUrl();
|
||||
}
|
||||
this.isFetching = false;
|
||||
};
|
||||
}();
|
||||
}
|
||||
|
||||
export default store;
|
||||
export default CollectionStore;
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Flex } from 'reflexbox';
|
|||
|
||||
import CollectionsStore from 'stores/CollectionsStore';
|
||||
|
||||
import Layout from 'components/Layout';
|
||||
import Collection from 'components/Collection';
|
||||
import PreviewLoading from 'components/PreviewLoading';
|
||||
import CenteredContent from 'components/CenteredContent';
|
||||
|
@ -21,7 +20,6 @@ type Props = {
|
|||
const { collections } = this.props;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<CenteredContent>
|
||||
<Flex column auto>
|
||||
{!collections.isLoaded
|
||||
|
@ -31,7 +29,6 @@ type Props = {
|
|||
))}
|
||||
</Flex>
|
||||
</CenteredContent>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
import React, { Component } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import styled from 'styled-components';
|
||||
import { observer } from 'mobx-react';
|
||||
import { observer, inject } from 'mobx-react';
|
||||
import { withRouter, Prompt } from 'react-router';
|
||||
import { Flex } from 'reflexbox';
|
||||
|
||||
import UiStore from 'stores/UiStore';
|
||||
|
||||
import DocumentStore from './DocumentStore';
|
||||
import Breadcrumbs from './components/Breadcrumbs';
|
||||
import Menu from './components/Menu';
|
||||
import Editor from 'components/Editor';
|
||||
import Layout, { HeaderAction, SaveAction } from 'components/Layout';
|
||||
import { HeaderAction, SaveAction } from 'components/Layout';
|
||||
import PublishingInfo from 'components/PublishingInfo';
|
||||
import PreviewLoading from 'components/PreviewLoading';
|
||||
import CenteredContent from 'components/CenteredContent';
|
||||
|
@ -21,25 +22,12 @@ You have unsaved changes.
|
|||
Are you sure you want to discard them?
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
position: relative;
|
||||
font-weight: 400;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
padding: 0 3em;
|
||||
width: 50em;
|
||||
`;
|
||||
|
||||
const Meta = styled.div`
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
match: Object,
|
||||
history: Object,
|
||||
keydown: Object,
|
||||
newChildDocument?: boolean,
|
||||
ui: UiStore,
|
||||
};
|
||||
|
||||
@observer class Document extends Component {
|
||||
|
@ -48,10 +36,13 @@ type Props = {
|
|||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.store = new DocumentStore({ history: this.props.history });
|
||||
this.store = new DocumentStore({
|
||||
history: this.props.history,
|
||||
ui: props.ui,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
componentDidMount() {
|
||||
if (this.props.newDocument) {
|
||||
this.store.collectionId = this.props.match.params.id;
|
||||
this.store.newDocument = true;
|
||||
|
@ -67,19 +58,25 @@ type Props = {
|
|||
this.store.newDocument = false;
|
||||
this.store.fetchDocument();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmout() {
|
||||
this.props.ui.clearActiveCollection();
|
||||
}
|
||||
|
||||
onEdit = () => {
|
||||
const url = `${this.store.document.url}/edit`;
|
||||
this.props.history.push(url);
|
||||
this.props.ui.enableEditMode();
|
||||
};
|
||||
|
||||
onSave = (options: { redirect?: boolean } = {}) => {
|
||||
onSave = async (options: { redirect?: boolean } = {}) => {
|
||||
if (this.store.newDocument || this.store.newChildDocument) {
|
||||
this.store.saveDocument(options);
|
||||
await this.store.saveDocument(options);
|
||||
} else {
|
||||
this.store.updateDocument(options);
|
||||
await this.store.updateDocument(options);
|
||||
}
|
||||
this.props.ui.disableEditMode();
|
||||
};
|
||||
|
||||
onImageUploadStart = () => {
|
||||
|
@ -97,12 +94,12 @@ type Props = {
|
|||
render() {
|
||||
const isNew = this.props.newDocument || this.props.newChildDocument;
|
||||
const isEditing = this.props.match.params.edit;
|
||||
const title = (
|
||||
/*const title = (
|
||||
<Breadcrumbs
|
||||
document={this.store.document}
|
||||
pathToDocument={this.store.pathToDocument}
|
||||
/>
|
||||
);
|
||||
);*/
|
||||
|
||||
const titleText = this.store.document && get(this.store, 'document.title');
|
||||
|
||||
|
@ -117,36 +114,34 @@ type Props = {
|
|||
/>
|
||||
: <a onClick={this.onEdit}>Edit</a>}
|
||||
</HeaderAction>
|
||||
<Menu store={this.store} document={this.store.document} />
|
||||
{!isEditing &&
|
||||
<Menu store={this.store} document={this.store.document} />}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout
|
||||
actions={actions}
|
||||
title={title}
|
||||
loading={this.store.isSaving || this.store.isUploading}
|
||||
search={false}
|
||||
fixed
|
||||
>
|
||||
<Container>
|
||||
<Actions>{actions}</Actions>
|
||||
<PagePadding auto justify="center">
|
||||
<PageTitle title={titleText} />
|
||||
<Prompt when={this.store.hasPendingChanges} message={DISCARD_CHANGES} />
|
||||
<Prompt
|
||||
when={this.store.hasPendingChanges}
|
||||
message={DISCARD_CHANGES}
|
||||
/>
|
||||
{this.store.isFetching &&
|
||||
<CenteredContent>
|
||||
<PreviewLoading />
|
||||
</CenteredContent>}
|
||||
{this.store.document &&
|
||||
<Container>
|
||||
<DocumentContainer>
|
||||
{!isEditing &&
|
||||
<Meta>
|
||||
<PublishingInfo
|
||||
collaborators={this.store.document.collaborators}
|
||||
createdAt={this.store.document.createdAt}
|
||||
createdBy={this.store.document.createdBy}
|
||||
updatedAt={this.store.document.updatedAt}
|
||||
updatedBy={this.store.document.updatedBy}
|
||||
/>
|
||||
</Meta>}
|
||||
/>}
|
||||
<Editor
|
||||
text={this.store.document.text}
|
||||
onImageUploadStart={this.onImageUploadStart}
|
||||
|
@ -156,10 +151,34 @@ type Props = {
|
|||
onCancel={this.onCancel}
|
||||
readOnly={!isEditing}
|
||||
/>
|
||||
</Container>}
|
||||
</Layout>
|
||||
</DocumentContainer>}
|
||||
</PagePadding>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Document);
|
||||
const Container = styled(Flex)`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const PagePadding = styled(Flex)`
|
||||
padding: 80px 20px;
|
||||
`;
|
||||
|
||||
const Actions = styled(Flex)`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 20px;
|
||||
`;
|
||||
|
||||
const DocumentContainer = styled.div`
|
||||
font-weight: 400;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
padding: 0 3em;
|
||||
width: 50em;
|
||||
`;
|
||||
|
||||
export default withRouter(inject('ui')(Document));
|
||||
|
|
|
@ -2,23 +2,20 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Layout from 'components/Layout';
|
||||
import CenteredContent from 'components/CenteredContent';
|
||||
import PageTitle from 'components/PageTitle';
|
||||
|
||||
class Error404 extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Layout>
|
||||
<PageTitle title="Not found" />
|
||||
<CenteredContent>
|
||||
<PageTitle title="Not found" />
|
||||
<h1>Not Found</h1>
|
||||
|
||||
<p>We're unable to find the page you're accessing.</p>
|
||||
|
||||
<p>Maybe you want to try <Link to="/search">search</Link> instead?</p>
|
||||
</CenteredContent>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,23 +2,20 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Layout from 'components/Layout';
|
||||
import CenteredContent from 'components/CenteredContent';
|
||||
import PageTitle from 'components/PageTitle';
|
||||
|
||||
class ErrorAuth extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Layout>
|
||||
<PageTitle title="Authentication error" />
|
||||
<CenteredContent>
|
||||
<PageTitle title="Authentication error" />
|
||||
<h1>Authentication failed</h1>
|
||||
|
||||
<p>
|
||||
We were unable to log you in. <Link to="/">Please try again.</Link>
|
||||
</p>
|
||||
</CenteredContent>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import styled from 'styled-components';
|
|||
|
||||
import AuthStore from 'stores/AuthStore';
|
||||
|
||||
import Layout from 'components/Layout';
|
||||
import CenteredContent from 'components/CenteredContent';
|
||||
import SlackAuthLink from 'components/SlackAuthLink';
|
||||
import Alert from 'components/Alert';
|
||||
|
@ -40,7 +39,6 @@ type Props = {
|
|||
|
||||
return (
|
||||
<Flex auto>
|
||||
<Layout notifications={this.notifications}>
|
||||
{this.props.auth.authenticated && <Redirect to="/dashboard" />}
|
||||
|
||||
<CenteredContent>
|
||||
|
@ -63,7 +61,6 @@ type Props = {
|
|||
</SlackAuthLink>
|
||||
</div>
|
||||
</CenteredContent>
|
||||
</Layout>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
|
|||
import SearchField from './components/SearchField';
|
||||
import SearchStore from './SearchStore';
|
||||
|
||||
import Layout, { Title } from 'components/Layout';
|
||||
import CenteredContent from 'components/CenteredContent';
|
||||
import DocumentPreview from 'components/DocumentPreview';
|
||||
import PageTitle from 'components/PageTitle';
|
||||
|
@ -83,13 +82,11 @@ const ResultsWrapper = styled(Flex)`
|
|||
|
||||
render() {
|
||||
const query = this.props.match.params.query;
|
||||
const title = <Title content="Search" />;
|
||||
const hasResults = this.store.documents.length > 0;
|
||||
|
||||
return (
|
||||
<Layout title={title} search={false} loading={this.store.isFetching}>
|
||||
<PageTitle title="Search" />
|
||||
<Container auto>
|
||||
<PageTitle title="Search" />
|
||||
{this.props.notFound &&
|
||||
<div>
|
||||
<h1>Not Found</h1>
|
||||
|
@ -117,7 +114,6 @@ const ResultsWrapper = styled(Flex)`
|
|||
</ArrowKeyNavigation>
|
||||
</ResultsWrapper>
|
||||
</Container>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import ApiKeyRow from './components/ApiKeyRow';
|
|||
import styles from './Settings.scss';
|
||||
import SettingsStore from './SettingsStore';
|
||||
|
||||
import Layout, { Title } from 'components/Layout';
|
||||
import CenteredContent from 'components/CenteredContent';
|
||||
import SlackAuthLink from 'components/SlackAuthLink';
|
||||
import PageTitle from 'components/PageTitle';
|
||||
|
@ -22,13 +21,11 @@ import PageTitle from 'components/PageTitle';
|
|||
}
|
||||
|
||||
render() {
|
||||
const title = <Title content="Settings" />;
|
||||
const showSlackSettings = DEPLOYMENT === 'hosted';
|
||||
|
||||
return (
|
||||
<Layout title={title} search={false} loading={this.store.isFetching}>
|
||||
<PageTitle title="Settings" />
|
||||
<CenteredContent>
|
||||
<PageTitle title="Settings" />
|
||||
{showSlackSettings &&
|
||||
<div className={styles.section}>
|
||||
<h2 className={styles.sectionHeader}>Slack</h2>
|
||||
|
@ -87,7 +84,6 @@ import PageTitle from 'components/PageTitle';
|
|||
</div>
|
||||
</div>
|
||||
</CenteredContent>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,8 +38,7 @@ class CollectionsStore {
|
|||
}
|
||||
};
|
||||
|
||||
@action getById = async (id: string): Promise<Collection> => {
|
||||
if (!this.isLoaded) await this.fetch();
|
||||
getById = (id: string): Collection => {
|
||||
return _.find(this.data, { id });
|
||||
};
|
||||
|
||||
|
|
|
@ -1,33 +1,26 @@
|
|||
// @flow
|
||||
import { observable, action, computed, autorunAsync } from 'mobx';
|
||||
|
||||
const UI_STORE = 'UI_STORE';
|
||||
import { observable, action } from 'mobx';
|
||||
|
||||
class UiStore {
|
||||
@observable sidebar: boolean = false;
|
||||
|
||||
/* Computed */
|
||||
|
||||
@computed get asJson(): string {
|
||||
return JSON.stringify({
|
||||
sidebar: this.sidebar,
|
||||
});
|
||||
}
|
||||
@observable activeCollection: ?string;
|
||||
@observable editMode: boolean = false;
|
||||
|
||||
/* Actions */
|
||||
|
||||
@action toggleSidebar = (): void => {
|
||||
this.sidebar = !this.sidebar;
|
||||
@action setActiveCollection = (id: string): void => {
|
||||
this.activeCollection = id;
|
||||
};
|
||||
|
||||
constructor() {
|
||||
// Rehydrate
|
||||
const data = JSON.parse(localStorage.getItem(UI_STORE) || '{}');
|
||||
this.sidebar = data.sidebar;
|
||||
@action clearActiveCollection = (): void => {
|
||||
this.activeCollection = null;
|
||||
};
|
||||
|
||||
autorunAsync(() => {
|
||||
localStorage.setItem(UI_STORE, this.asJson);
|
||||
});
|
||||
@action enableEditMode() {
|
||||
this.editMode = true;
|
||||
}
|
||||
|
||||
@action disableEditMode() {
|
||||
this.editMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,11 +20,11 @@ html, body, .viewport {
|
|||
}
|
||||
|
||||
body {
|
||||
font-family: 'Atlas Grotesk', -apple-system, 'Helvetica Neue', Helvetica, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
color: $textColor;
|
||||
color: #617180;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
|
@ -32,6 +32,10 @@ body {
|
|||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
|
@ -41,13 +45,13 @@ svg {
|
|||
max-height: 100%;
|
||||
}
|
||||
a {
|
||||
color: $actionColor;
|
||||
color: #005AA6;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
h1, h2, h3,
|
||||
h4, h5, h6 {
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
line-height: 1.25;
|
||||
margin-top: 1em;
|
||||
margin-bottom: .5em;
|
||||
|
|
|
@ -1,36 +1,19 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Atlas</title>
|
||||
<style>
|
||||
body, html {
|
||||
body,
|
||||
html {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#root {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 42px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style='display: flex; width: 100%; height: 100%;'>
|
||||
<div id="root">
|
||||
<div class="container">
|
||||
<div class="header"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>
|
Reference in New Issue