diff --git a/frontend/assets/icons/menu.svg b/frontend/assets/icons/menu.svg deleted file mode 100644 index 4523b5a1b..000000000 --- a/frontend/assets/icons/menu.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/assets/icons/search.svg b/frontend/assets/icons/search.svg deleted file mode 100644 index f974b540a..000000000 --- a/frontend/assets/icons/search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/components/Button/Button.js b/frontend/components/Button/Button.js index d0b3afffd..20ecb553c 100644 --- a/frontend/components/Button/Button.js +++ b/frontend/components/Button/Button.js @@ -1,17 +1,18 @@ // @flow import React from 'react'; import styled from 'styled-components'; -import { size, color } from 'styles/constants'; -import { darken } from 'polished'; +import { color } from 'styles/constants'; +import { darken, lighten } from 'polished'; const RealButton = styled.button` display: inline-block; - margin: 0 ${size.medium} ${size.large} 0; + margin: 0; padding: 0; border: 0; - background: ${props => (props.neutral ? color.slate : props.danger ? color.danger : color.primary)}; + background: ${color.primary}; color: ${color.white}; border-radius: 4px; + font-size: 15px; min-width: 32px; min-height: 32px; text-decoration: none; @@ -23,30 +24,70 @@ const RealButton = styled.button` border: 0; } &:hover { - background: ${props => darken(0.05, props.neutral ? color.slate : props.danger ? color.danger : color.primary)}; + background: ${darken(0.05, color.primary)}; } + + svg { + position: relative; + top: .05em; + } + + ${props => props.light && ` + color: ${color.text}; + background: ${lighten(0.08, color.slateLight)}; + + &:hover { + background: ${color.slateLight}; + } + `} + + ${props => props.neutral && ` + background: ${color.slate}; + + &:hover { + background: ${darken(0.05, color.slate)}; + } + `} + + ${props => props.danger && ` + background: ${color.danger}; + + &:hover { + background: ${darken(0.05, color.danger)}; + } + `} + &:disabled { background: ${color.slateLight}; } `; const Label = styled.span` - padding: 4px 16px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - font-weight: 500; + + ${props => props.hasIcon && 'padding-left: 2px;'} `; const Inner = styled.span` + padding: 4px 16px; display: flex; line-height: 28px; justify-content: center; + align-items: center; + + ${props => props.small && ` + padding: 1px 10px; + `} + + ${props => props.hasIcon && (props.small ? 'padding-left: 6px;' : 'padding-left: 10px;')} `; export type Props = { type?: string, value?: string, + small?: boolean, icon?: React$Element, className?: string, children?: React$Element, @@ -56,6 +97,7 @@ export default function Button({ type = 'text', icon, children, + small, value, ...rest }: Props) { @@ -64,9 +106,9 @@ export default function Button({ return ( - + {hasIcon && icon} - {hasText && } + {hasText && } ); diff --git a/frontend/components/CenteredContent/CenteredContent.js b/frontend/components/CenteredContent/CenteredContent.js index cfc4fe08b..b9f15975a 100644 --- a/frontend/components/CenteredContent/CenteredContent.js +++ b/frontend/components/CenteredContent/CenteredContent.js @@ -12,7 +12,7 @@ const Container = styled.div` `; const Content = styled.div` - max-width: 740px; + max-width: 50em; margin: 0 auto; `; diff --git a/frontend/components/Collaborators/Collaborators.js b/frontend/components/Collaborators/Collaborators.js index 487921fdd..ae62fd1b4 100644 --- a/frontend/components/Collaborators/Collaborators.js +++ b/frontend/components/Collaborators/Collaborators.js @@ -36,7 +36,6 @@ const Collaborators = function({ document }: { document: Document }) { const Avatars = styled(Flex)` flex-direction: row-reverse; - margin-right: 10px; height: 26px; `; diff --git a/frontend/components/DocumentPreview/DocumentPreview.js b/frontend/components/DocumentPreview/DocumentPreview.js index 91d8bff18..f82bb1424 100644 --- a/frontend/components/DocumentPreview/DocumentPreview.js +++ b/frontend/components/DocumentPreview/DocumentPreview.js @@ -5,8 +5,8 @@ import { Link } from 'react-router-dom'; import Document from 'models/Document'; import styled from 'styled-components'; import { color } from 'styles/constants'; +import Icon from 'components/Icon'; import PublishingInfo from './components/PublishingInfo'; -import StarIcon from 'components/Icon/StarIcon'; type Props = { document: Document, @@ -15,17 +15,13 @@ type Props = { innerRef?: Function, }; -const StyledStar = styled(StarIcon)` - top: 2px; - position: relative; +const StyledStar = styled(Icon).attrs({ + type: 'Star', + color: color.text, +})` margin-left: 4px; opacity: ${props => (props.solid ? '1 !important' : 0)}; transition: opacity 100ms ease-in-out; - - svg { - width: 1em; - height: 1em; - } `; const DocumentLink = styled(Link)` @@ -46,7 +42,7 @@ const DocumentLink = styled(Link)` outline: none; ${StyledStar} { - opacity: .25; + opacity: .5; &:hover { opacity: 1; @@ -87,8 +83,12 @@ const DocumentLink = styled(Link)`

{document.title} {document.starred - ? - : } + ? + + + : + + }

{ type DropdownMenuProps = { label: React.Element, children?: React.Element, + style?: Object, }; @observer class DropdownMenu extends React.Component { @@ -42,7 +43,7 @@ type DropdownMenuProps = { {this.menuOpen && - + {this.props.children} } @@ -63,11 +64,8 @@ const Label = styled(Flex).attrs({ justify: 'center', align: 'center', })` - cursor: pointer; z-index: 1000; - - min-height: 43px; - margin: 0 5px; + cursor: pointer; `; const MenuContainer = styled.div` diff --git a/frontend/components/DropdownMenu/components/MoreIcon/MoreIcon.js b/frontend/components/DropdownMenu/components/MoreIcon/MoreIcon.js deleted file mode 100644 index 29c7af452..000000000 --- a/frontend/components/DropdownMenu/components/MoreIcon/MoreIcon.js +++ /dev/null @@ -1,16 +0,0 @@ -// @flow -import React from 'react'; - -import styles from './MoreIcon.scss'; - -const MoreIcon = () => { - return ( - More - ); -}; - -export default MoreIcon; diff --git a/frontend/components/DropdownMenu/components/MoreIcon/MoreIcon.scss b/frontend/components/DropdownMenu/components/MoreIcon/MoreIcon.scss deleted file mode 100644 index 328840769..000000000 --- a/frontend/components/DropdownMenu/components/MoreIcon/MoreIcon.scss +++ /dev/null @@ -1,4 +0,0 @@ -.icon { - width: 21px; - margin-top: 6px; -} diff --git a/frontend/components/DropdownMenu/components/MoreIcon/assets/more.svg b/frontend/components/DropdownMenu/components/MoreIcon/assets/more.svg deleted file mode 100644 index f6e12e971..000000000 --- a/frontend/components/DropdownMenu/components/MoreIcon/assets/more.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/components/DropdownMenu/components/MoreIcon/index.js b/frontend/components/DropdownMenu/components/MoreIcon/index.js deleted file mode 100644 index a4d1358e6..000000000 --- a/frontend/components/DropdownMenu/components/MoreIcon/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import MoreIcon from './MoreIcon'; -export default MoreIcon; diff --git a/frontend/components/DropdownMenu/index.js b/frontend/components/DropdownMenu/index.js index fd1562125..11e84f9cb 100644 --- a/frontend/components/DropdownMenu/index.js +++ b/frontend/components/DropdownMenu/index.js @@ -1,4 +1,2 @@ // @flow -import { DropdownMenu, DropdownMenuItem } from './DropdownMenu'; -import MoreIcon from './components/MoreIcon'; -export { DropdownMenu, DropdownMenuItem, MoreIcon }; +export { DropdownMenu, DropdownMenuItem } from './DropdownMenu'; diff --git a/frontend/components/Editor/Editor.scss b/frontend/components/Editor/Editor.scss index a7694603d..7508c6021 100644 --- a/frontend/components/Editor/Editor.scss +++ b/frontend/components/Editor/Editor.scss @@ -1,9 +1,9 @@ .editor { font-weight: 400; font-size: 1em; - line-height: 1.5em; + line-height: 1.7em; width: 100%; - color: #1b2631; + color: #1b2830; h1, h2, diff --git a/frontend/components/Editor/components/Toolbar/components/LinkToolbar.js b/frontend/components/Editor/components/Toolbar/components/LinkToolbar.js index c02f727fe..badee913d 100644 --- a/frontend/components/Editor/components/Toolbar/components/LinkToolbar.js +++ b/frontend/components/Editor/components/Toolbar/components/LinkToolbar.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import type { State } from '../../../types'; import keydown from 'react-keydown'; import styles from '../Toolbar.scss'; -import CloseIcon from 'components/Icon/CloseIcon'; +import Icon from 'components/Icon'; @keydown export default class LinkToolbar extends Component { @@ -58,7 +58,7 @@ export default class LinkToolbar extends Component { autoFocus /> ); diff --git a/frontend/components/Icon/AddIcon.js b/frontend/components/Icon/AddIcon.js deleted file mode 100644 index 688202cfc..000000000 --- a/frontend/components/Icon/AddIcon.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import React from 'react'; -import Icon from './Icon'; -import type { Props } from './Icon'; - -export default function AddIcon(props: Props) { - return ( - - - - - - - ); -} diff --git a/frontend/components/Icon/CheckIcon.js b/frontend/components/Icon/CheckIcon.js deleted file mode 100644 index ab6ea35e3..000000000 --- a/frontend/components/Icon/CheckIcon.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import React from 'react'; -import Icon from './Icon'; -import type { Props } from './Icon'; - -export default function CheckIcon(props: Props) { - return ( - - - - - - - ); -} diff --git a/frontend/components/Icon/CloseIcon.js b/frontend/components/Icon/CloseIcon.js deleted file mode 100644 index c93cc6bd3..000000000 --- a/frontend/components/Icon/CloseIcon.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import React from 'react'; -import Icon from './Icon'; -import type { Props } from './Icon'; - -export default function CloseIcon(props: Props) { - return ( - - - - - - - ); -} diff --git a/frontend/components/Icon/Icon.js b/frontend/components/Icon/Icon.js index 11e26a2d3..44811b1ec 100644 --- a/frontend/components/Icon/Icon.js +++ b/frontend/components/Icon/Icon.js @@ -1,9 +1,11 @@ // @flow import React from 'react'; import styled from 'styled-components'; +import * as Icons from 'react-feather'; export type Props = { className?: string, + type?: string, light?: boolean, }; @@ -11,16 +13,40 @@ type BaseProps = { children?: React$Element, }; -export default function Icon({ children, ...rest }: Props & BaseProps) { +export default function Icon({ + children, + light, + type, + ...rest +}: Props & BaseProps) { + if (type) { + children = React.createElement(Icons[type], { + size: '1em', + color: light ? '#FFFFFF' : undefined, + ...rest, + }); + + return ( + + {children} + + ); + } + return ( - + {children} ); } +const FeatherWrapper = styled.span` + position: relative; + top: .1em; +`; + const Wrapper = styled.span` svg { - fill: ${props => (props.light ? '#fff' : '#000')}; + fill: ${props => (props.light ? '#FFF' : '#000')} } `; diff --git a/frontend/components/Icon/Icon.scss b/frontend/components/Icon/Icon.scss deleted file mode 100644 index 7fd441749..000000000 --- a/frontend/components/Icon/Icon.scss +++ /dev/null @@ -1,9 +0,0 @@ -.icon { - -} - -.light { - svg { - fill: #fff; - } -} diff --git a/frontend/components/Icon/MoreIcon.js b/frontend/components/Icon/MoreIcon.js deleted file mode 100644 index 5e8126ff0..000000000 --- a/frontend/components/Icon/MoreIcon.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import React from 'react'; -import Icon from './Icon'; -import type { Props } from './Icon'; - -export default function MoreIcon(props: Props) { - return ( - - - - - - - ); -} diff --git a/frontend/components/Icon/QuoteIcon.js b/frontend/components/Icon/QuoteIcon.js deleted file mode 100644 index 6b80497a6..000000000 --- a/frontend/components/Icon/QuoteIcon.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import React from 'react'; -import Icon from './Icon'; -import type { Props } from './Icon'; - -export default function QuoteIcon(props: Props) { - return ( - - - - - - - ); -} diff --git a/frontend/components/Icon/StarIcon.js b/frontend/components/Icon/StarIcon.js deleted file mode 100644 index 9939785e9..000000000 --- a/frontend/components/Icon/StarIcon.js +++ /dev/null @@ -1,43 +0,0 @@ -// @flow -import React from 'react'; -import Icon from './Icon'; -import type { Props } from './Icon'; - -export default function StarIcon(props: Props & { solid?: boolean }) { - let icon; - - if (props.solid) { - icon = ( - - - - - - ); - } else { - icon = ( - - - - - ); - } - - return ( - - {icon} - - ); -} diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index 9679eb833..485926455 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -10,14 +10,13 @@ import keydown from 'react-keydown'; import Flex from 'components/Flex'; import { color, layout } from 'styles/constants'; import { documentEditUrl, homeUrl, searchUrl } from 'utils/routeHelpers'; - import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; + +import Avatar from 'components/Avatar'; import { LoadingIndicatorBar } from 'components/LoadingIndicator'; import Scrollable from 'components/Scrollable'; -import Avatar from 'components/Avatar'; import Modal from 'components/Modal'; -import AddIcon from 'components/Icon/AddIcon'; -import MoreIcon from 'components/Icon/MoreIcon'; +import Icon from 'components/Icon'; import CollectionNew from 'scenes/CollectionNew'; import CollectionEdit from 'scenes/CollectionEdit'; import KeyboardShortcuts from 'scenes/KeyboardShortcuts'; @@ -26,8 +25,8 @@ import Settings from 'scenes/Settings'; import SidebarCollection from './components/SidebarCollection'; import SidebarCollectionList from './components/SidebarCollectionList'; import SidebarLink from './components/SidebarLink'; +import HeaderBlock from './components/HeaderBlock'; -import UserStore from 'stores/UserStore'; import AuthStore from 'stores/AuthStore'; import UiStore from 'stores/UiStore'; import CollectionsStore from 'stores/CollectionsStore'; @@ -40,7 +39,6 @@ type Props = { children?: ?React.Element, actions?: ?React.Element, title?: ?React.Element, - user: UserStore, auth: AuthStore, ui: UiStore, search: ?boolean, @@ -108,7 +106,8 @@ type Props = { }; render() { - const { user, auth, documents, collections, history, ui } = this.props; + const { auth, documents, collections, history, ui } = this.props; + const { user, team } = auth; return ( @@ -129,43 +128,50 @@ type Props = { {auth.authenticated && user && + team && -
- - Atlas - - }> - - Settings - - - Keyboard shortcuts - - - API - - - Logout - - -
+ + + + } + > + + Settings + + + Keyboard shortcuts + + + API + + + Logout + + - Search - - - Home - Starred + + Home + + + Search + + + Starred + {collections.active ? - + : - + } {collections.active ? (props.editMode ? 0 : layout.sidebarWidth)}; + transition: margin-left 200ms ease-in-out; `; const MenuLink = styled(Link)` color: ${color.text}; `; -const Content = styled(Flex)` - overflow: scroll; - position: absolute; +const Sidebar = styled(Flex)` + position: fixed; top: 0; bottom: 0; - right: 0; - left: ${props => (props.editMode ? 0 : layout.sidebarWidth)}; - transition: left 200ms ease-in-out; -`; - -const Sidebar = styled(Flex)` + left: ${props => (props.editMode ? `-${layout.sidebarWidth}` : 0)}; width: ${layout.sidebarWidth}; - margin-left: ${props => (props.editMode ? `-${layout.sidebarWidth}` : 0)}; - background: rgba(250, 251, 252, 0.71); - border-right: 1px solid #eceff3; - transition: margin-left 200ms ease-in-out; -`; - -const Header = styled(Flex)` - flex-shrink: 0; - padding: ${layout.padding}; - padding-bottom: 10px; + background: ${color.smoke}; + transition: left 200ms ease-in-out; `; const LinkSection = styled(Flex)` flex-direction: column; - padding: 10px 0; + margin: 24px 0; position: relative; `; diff --git a/frontend/components/Layout/components/HeaderAction/HeaderAction.js b/frontend/components/Layout/components/HeaderAction/HeaderAction.js deleted file mode 100644 index c49806c4a..000000000 --- a/frontend/components/Layout/components/HeaderAction/HeaderAction.js +++ /dev/null @@ -1,16 +0,0 @@ -// @flow -import React from 'react'; - -import styles from './HeaderAction.scss'; - -type Props = { onClick?: ?Function, children?: ?React.Element }; - -const HeaderAction = (props: Props) => { - return ( -
- {props.children} -
- ); -}; - -export default HeaderAction; diff --git a/frontend/components/Layout/components/HeaderAction/HeaderAction.scss b/frontend/components/Layout/components/HeaderAction/HeaderAction.scss deleted file mode 100644 index cc5013907..000000000 --- a/frontend/components/Layout/components/HeaderAction/HeaderAction.scss +++ /dev/null @@ -1,11 +0,0 @@ -.container { - display: flex; - justify-content: center; - align-items: center; - min-height: 43px; - padding: 0 0.5rem; - - a { - color: #171B35; - } -} diff --git a/frontend/components/Layout/components/HeaderAction/index.js b/frontend/components/Layout/components/HeaderAction/index.js deleted file mode 100644 index e231db11a..000000000 --- a/frontend/components/Layout/components/HeaderAction/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import HeaderAction from './HeaderAction'; -export default HeaderAction; diff --git a/frontend/components/Layout/components/HeaderBlock.js b/frontend/components/Layout/components/HeaderBlock.js new file mode 100644 index 000000000..1038738e4 --- /dev/null +++ b/frontend/components/Layout/components/HeaderBlock.js @@ -0,0 +1,61 @@ +// @flow +import React from 'react'; +import styled from 'styled-components'; +import { color, layout } from 'styles/constants'; +import type { User, Team } from 'types'; +import Flex from 'components/Flex'; + +type Props = { + user: User, + team: Team, + children?: React$Element, +}; + +function HeaderBlock({ user, team, children }: Props) { + return ( +
+ + {team.name} + {user.name} + + {children} +
+ ); +} + +const UserName = styled.div` + font-size: 13px; +`; + +const TeamName = styled.div` + font-family: 'Atlas Grotesk'; + font-weight: bold; + color: ${color.text}; + text-decoration: none; + font-size: 16px; +`; + +const Header = styled(Flex)` + flex-shrink: 0; + padding: ${layout.padding}; + position: relative; + cursor: pointer; + width: 100%; + + &:active, + &:hover { + background: rgba(0,0,0,.05); + } + + &::after { + content: ""; + left: ${layout.hpadding}; + right: ${layout.hpadding}; + background: rgba(0,0,0,.075); + height: 1px; + position: absolute; + bottom: 0; + } +`; + +export default HeaderBlock; diff --git a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js index 5f46528e3..9d1cefdda 100644 --- a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js +++ b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js @@ -3,7 +3,7 @@ import React from 'react'; import { observer } from 'mobx-react'; import Flex from 'components/Flex'; import styled from 'styled-components'; -import { layout } from 'styles/constants'; +import { color, layout } from 'styles/constants'; import SidebarLink from '../SidebarLink'; import DropToImport from 'components/DropToImport'; @@ -18,8 +18,8 @@ type Props = { }; const activeStyle = { - color: '#000', - background: '#E1E1E1', + color: color.black, + background: color.slateDark, }; @observer class SidebarCollection extends React.Component { @@ -72,7 +72,7 @@ const Header = styled(Flex)` font-size: 11px; font-weight: 500; text-transform: uppercase; - color: #9FA6AB; + color: ${color.slate}; letter-spacing: 0.04em; padding: 0 ${layout.hpadding}; `; diff --git a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js index c672ae420..9318e4370 100644 --- a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js +++ b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js @@ -3,7 +3,7 @@ import React from 'react'; import { observer, inject } from 'mobx-react'; import Flex from 'components/Flex'; import styled from 'styled-components'; -import { layout } from 'styles/constants'; +import { color, layout, fontWeight } from 'styles/constants'; import SidebarLink from '../SidebarLink'; import DropToImport from 'components/DropToImport'; @@ -16,8 +16,8 @@ type Props = { }; const activeStyle = { - color: '#000', - background: '#E1E1E1', + color: color.black, + background: color.slateDark, }; const SidebarCollectionList = observer(({ history, collections }: Props) => { @@ -42,9 +42,9 @@ const SidebarCollectionList = observer(({ history, collections }: Props) => { const Header = styled(Flex)` font-size: 11px; - font-weight: 500; + font-weight: ${fontWeight.semiBold}; text-transform: uppercase; - color: #9FA6AB; + color: ${color.slate}; letter-spacing: 0.04em; padding: 0 ${layout.hpadding}; `; diff --git a/frontend/components/Layout/components/SidebarLink/SidebarLink.js b/frontend/components/Layout/components/SidebarLink/SidebarLink.js index 7df81e86c..cb6f2b0e1 100644 --- a/frontend/components/Layout/components/SidebarLink/SidebarLink.js +++ b/frontend/components/Layout/components/SidebarLink/SidebarLink.js @@ -1,25 +1,28 @@ // @flow import React from 'react'; import { NavLink } from 'react-router-dom'; -import { layout, color } from 'styles/constants'; -import { darken } from 'polished'; +import { layout, color, fontWeight } from 'styles/constants'; import styled from 'styled-components'; const activeStyle = { - color: '#000000', + color: color.black, + fontWeight: fontWeight.semiBold, }; function SidebarLink(props: Object) { - return ; + return ; } const StyledNavLink = styled(NavLink)` display: block; - padding: 5px ${layout.hpadding}; + overflow: hidden; + text-overflow: ellipsis; + margin: 5px ${layout.hpadding}; color: ${color.slateDark}; + font-size: 15px; &:hover { - color: ${darken(0.1, color.slateDark)}; + color: ${color.text}; } `; diff --git a/frontend/components/Layout/index.js b/frontend/components/Layout/index.js index 38c7a426f..fb7b105d1 100644 --- a/frontend/components/Layout/index.js +++ b/frontend/components/Layout/index.js @@ -1,8 +1,7 @@ // @flow import Layout from './Layout'; import Title from './components/Title'; -import HeaderAction from './components/HeaderAction'; export default Layout; -export { Title, HeaderAction }; +export { Title }; diff --git a/frontend/components/Modal/Modal.js b/frontend/components/Modal/Modal.js index 66110f0ec..cde113089 100644 --- a/frontend/components/Modal/Modal.js +++ b/frontend/components/Modal/Modal.js @@ -2,9 +2,9 @@ import React from 'react'; import styled from 'styled-components'; import ReactModal from 'react-modal'; +import { color } from 'styles/constants'; import { fadeAndScaleIn } from 'styles/animations'; - -import CloseIcon from 'components/Icon/CloseIcon'; +import Icon from 'components/Icon'; import Flex from 'components/Flex'; type Props = { @@ -32,7 +32,7 @@ const Modal = ({ > {title &&

{title}

} - + {children}
@@ -68,6 +68,7 @@ const Close = styled.a` top: 3rem; right: 3rem; opacity: .5; + color: ${color.text}; &:hover { opacity: 1; diff --git a/frontend/index.js b/frontend/index.js index d47f3774f..b783b4e76 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -51,23 +51,22 @@ type AuthProps = { }; const Auth = ({ children }: AuthProps) => { - if (stores.auth.authenticated && stores.auth.team) { + if (stores.auth.authenticated && stores.auth.team && stores.auth.user) { // Only initialize stores once. Kept in global scope // because otherwise they will get overriden on route // change if (!authenticatedStores) { // Stores for authenticated user - const user = stores.auth.getUserStore(); - const cache = new CacheStore(user.user.id); + const { user, team } = stores.auth; + const cache = new CacheStore(user.id); authenticatedStores = { - user, documents: new DocumentsStore({ ui: stores.ui, cache, }), collections: new CollectionsStore({ ui: stores.ui, - teamId: user.team.id, + teamId: team.id, cache, }), }; diff --git a/frontend/scenes/Dashboard/Dashboard.js b/frontend/scenes/Dashboard/Dashboard.js index da95a0f2b..f8adf4b03 100644 --- a/frontend/scenes/Dashboard/Dashboard.js +++ b/frontend/scenes/Dashboard/Dashboard.js @@ -28,8 +28,8 @@ type Props = { props: Props; componentDidMount() { - this.props.documents.fetchAll(); - this.props.documents.fetchRecentlyViewed(); + this.props.documents.fetchRecentlyModified({ limit: 5 }); + this.props.documents.fetchRecentlyViewed({ limit: 5 }); } get showPlaceholder() { diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index dbdb66f39..aec4fc2b1 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -5,7 +5,7 @@ import styled from 'styled-components'; import { observer, inject } from 'mobx-react'; import { withRouter, Prompt } from 'react-router'; import Flex from 'components/Flex'; -import { layout } from 'styles/constants'; +import { color, layout } from 'styles/constants'; import Document from 'models/Document'; import UiStore from 'stores/UiStore'; @@ -15,7 +15,6 @@ import SaveAction from './components/SaveAction'; import LoadingPlaceholder from 'components/LoadingPlaceholder'; import Editor from 'components/Editor'; import DropToImport from 'components/DropToImport'; -import { HeaderAction } from 'components/Layout'; import LoadingIndicator from 'components/LoadingIndicator'; import Collaborators from 'components/Collaborators'; import CenteredContent from 'components/CenteredContent'; @@ -110,6 +109,11 @@ type Props = { this.props.history.push(url); }; + onClickNew = () => { + if (!this.document) return; + this.props.history.push(`${this.document.collection.url}/new`); + }; + onSave = async (redirect: boolean = false) => { if (this.document && !this.document.allowSave) return; let document = this.document; @@ -230,13 +234,22 @@ type Props = { } isNew={!!isNew} /> - : Edit} + : + Edit + } + + + {isEditing + ? Cancel + : } + + {!isEditing && } + + {!isEditing && + + New + } - {isEditing && - - Cancel - } - {!isEditing && } @@ -246,6 +259,32 @@ type Props = { } } +const Separator = styled.div` + margin-left: 12px; + width: 1px; + height: 20px; + background: ${color.slateLight}; +`; + +const HeaderAction = styled(Flex)` + justify-content: center; + align-items: center; + min-height: 43px; + color: ${color.text}; + padding: 0 0 0 14px; + + a, + svg { + color: ${color.text}; + opacity: .8; + transition: opacity 100ms ease-in-out; + + &:hover { + opacity: 1; + } + } +`; + const DropHere = styled(Flex)` pointer-events: none; position: fixed; diff --git a/frontend/scenes/Document/components/Menu.js b/frontend/scenes/Document/components/Menu.js index ae315d82c..9bae6f0b8 100644 --- a/frontend/scenes/Document/components/Menu.js +++ b/frontend/scenes/Document/components/Menu.js @@ -5,11 +5,8 @@ import get from 'lodash/get'; import { withRouter } from 'react-router-dom'; import { observer } from 'mobx-react'; import Document from 'models/Document'; -import { - DropdownMenu, - DropdownMenuItem, - MoreIcon, -} from 'components/DropdownMenu'; +import Icon from 'components/Icon'; +import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; type Props = { history: Object, @@ -65,7 +62,7 @@ type Props = { collection.documents.length > 1; return ( - }> + }> {collection &&
diff --git a/frontend/scenes/Search/components/SearchField/SearchField.js b/frontend/scenes/Search/components/SearchField/SearchField.js index 785a3a49f..6b2f6577d 100644 --- a/frontend/scenes/Search/components/SearchField/SearchField.js +++ b/frontend/scenes/Search/components/SearchField/SearchField.js @@ -1,8 +1,9 @@ // @flow import React, { Component } from 'react'; +import Icon from 'components/Icon'; import Flex from 'components/Flex'; +import { color } from 'styles/constants'; import styled from 'styled-components'; -import searchImg from 'assets/icons/search.svg'; const Field = styled.input` width: 100%; @@ -12,17 +13,10 @@ const Field = styled.input` outline: none; border: 0; - ::-webkit-input-placeholder { color: #ccc; } - :-moz-placeholder { color: #ccc; } - ::-moz-placeholder { color: #ccc; } - :-ms-input-placeholder { color: #ccc; } -`; - -const Icon = styled.img` - width: 38px; - margin-bottom: -5px; - margin-right: 10px; - opacity: 0.15; + ::-webkit-input-placeholder { color: ${color.slate}; } + :-moz-placeholder { color: ${color.slate}; } + ::-moz-placeholder { color: ${color.slate}; } + :-ms-input-placeholder { color: ${color.slate}; } `; class SearchField extends Component { @@ -45,13 +39,18 @@ class SearchField extends Component { render() { return ( - - + + diff --git a/frontend/stores/AuthStore.js b/frontend/stores/AuthStore.js index dcc879c41..f5b77ecbe 100644 --- a/frontend/stores/AuthStore.js +++ b/frontend/stores/AuthStore.js @@ -2,7 +2,6 @@ import { observable, action, computed, autorunAsync } from 'mobx'; import invariant from 'invariant'; import { client } from 'utils/ApiClient'; -import UserStore from 'stores/UserStore'; import type { User, Team } from 'types'; const AUTH_STORE = 'AUTH_STORE'; @@ -72,17 +71,6 @@ class AuthStore { }; }; - getUserStore(): UserStore { - invariant( - this.user && this.team, - 'Tried to create a user store without data' - ); - return new UserStore({ - user: this.user, - team: this.team, - }); - } - constructor() { // Rehydrate const data = JSON.parse(localStorage.getItem(AUTH_STORE) || '{}'); diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js index 3d839a1c9..444a47bbf 100644 --- a/frontend/stores/DocumentsStore.js +++ b/frontend/stores/DocumentsStore.js @@ -38,14 +38,17 @@ class DocumentsStore extends BaseStore { /* Computed */ @computed get recentlyViewed(): Array { - return _.filter(this.data.values(), ({ id }) => - this.recentlyViewedIds.includes(id) + return _.take( + _.filter(this.data.values(), ({ id }) => + this.recentlyViewedIds.includes(id) + ), + 5 ); } @computed get recentlyEdited(): Array { // $FlowIssue - return this.data.values(); + return _.take(this.data.values(), 5); } @computed get starred(): Array { @@ -60,11 +63,14 @@ class DocumentsStore extends BaseStore { /* Actions */ - @action fetchAll = async (request: string = 'list'): Promise<*> => { + @action fetchAll = async ( + request: string = 'list', + options: ?Object + ): Promise<*> => { this.isFetching = true; try { - const res = await client.post(`/documents.${request}`); + const res = await client.post(`/documents.${request}`, options); invariant(res && res.data, 'Document list not available'); const { data } = res; runInAction('DocumentsStore#fetchAll', () => { @@ -81,12 +87,17 @@ class DocumentsStore extends BaseStore { } }; - @action fetchRecentlyViewed = async (): Promise<*> => { - const data = await this.fetchAll('viewed'); + @action fetchRecentlyModified = async (options: ?Object): Promise<*> => { + return this.fetchAll('list', options); + }; + + @action fetchRecentlyViewed = async (options: ?Object): Promise<*> => { + const data = await this.fetchAll('viewed', options); runInAction('DocumentsStore#fetchRecentlyViewed', () => { this.recentlyViewedIds = _.map(data, 'id'); }); + return data; }; @action fetchStarred = async (): Promise<*> => { diff --git a/frontend/stores/UserStore.js b/frontend/stores/UserStore.js deleted file mode 100644 index 7d368c934..000000000 --- a/frontend/stores/UserStore.js +++ /dev/null @@ -1,32 +0,0 @@ -// @flow -import { observable, computed } from 'mobx'; -import type { User, Team } from 'types'; - -type Options = { - user: User, - team: Team, -}; - -class UserStore { - @observable user: User; - @observable team: Team; - - @observable isLoading: boolean = false; - - /* Computed */ - - @computed get asJson(): string { - return JSON.stringify({ - user: this.user, - team: this.team, - }); - } - - constructor(options: Options) { - // Rehydrate - this.user = options.user; - this.team = options.team; - } -} - -export default UserStore; diff --git a/frontend/styles/base.scss b/frontend/styles/base.scss index 3fcc4acd2..1e745df7f 100644 --- a/frontend/styles/base.scss +++ b/frontend/styles/base.scss @@ -16,22 +16,18 @@ html, body, .viewport { width: 100%; + min-height: 100vh; margin: 0; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; - font-size: 15px; + font-size: 16px; line-height: 1.5; margin: 0; color: #617180; background-color: #fff; display: flex; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; diff --git a/frontend/styles/constants.js b/frontend/styles/constants.js index 56bf05aa1..bfdf50e50 100644 --- a/frontend/styles/constants.js +++ b/frontend/styles/constants.js @@ -31,7 +31,7 @@ export const fontWeight = { light: 300, regular: 400, medium: 500, - demiBold: 600, + semiBold: 600, bold: 700, heavy: 800, }; diff --git a/frontend/utils/ApiClient.js b/frontend/utils/ApiClient.js index 5abf98ef9..aff280e95 100644 --- a/frontend/utils/ApiClient.js +++ b/frontend/utils/ApiClient.js @@ -89,11 +89,11 @@ class ApiClient { }); }; - get = (path: string, data?: Object, options?: Object) => { + get = (path: string, data: ?Object, options?: Object) => { return this.fetch(path, 'GET', data, options); }; - post = (path: string, data?: Object, options?: Object) => { + post = (path: string, data: ?Object, options?: Object) => { return this.fetch(path, 'POST', data, options); }; diff --git a/package.json b/package.json index 64b20ba46..b9ca985eb 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,7 @@ "react-addons-css-transition-group": "15.3.2", "react-dom": "^15.6.1", "react-dropzone": "3.6.0", + "react-feather": "^1.0.7", "react-helmet": "3.1.0", "react-keydown": "^1.7.3", "react-modal": "^2.2.1", diff --git a/server/api/auth.js b/server/api/auth.js index 811055cf5..35ae88c14 100644 --- a/server/api/auth.js +++ b/server/api/auth.js @@ -1,11 +1,9 @@ // @flow import Router from 'koa-router'; -import apiError, { httpErrors } from '../errors'; -import fetch from 'isomorphic-fetch'; -import querystring from 'querystring'; - +import apiError from '../errors'; import { presentUser, presentTeam } from '../presenters'; import { User, Team } from '../models'; +import * as Slack from '../slack'; const router = new Router(); @@ -88,24 +86,7 @@ router.post('auth.slack', async ctx => { const { code } = ctx.body; ctx.assertPresent(code, 'code is required'); - const body = { - client_id: process.env.SLACK_KEY, - client_secret: process.env.SLACK_SECRET, - redirect_uri: `${process.env.URL || ''}/auth/slack`, - code, - }; - - let data; - try { - const response = await fetch( - `https://slack.com/api/oauth.access?${querystring.stringify(body)}` - ); - data = await response.json(); - } catch (e) { - throw httpErrors.BadRequest(); - } - - if (!data.ok) throw httpErrors.BadRequest(data.error); + const data = await Slack.oauthAccess(code); // Temp to block const allowedSlackDomains = (process.env.ALLOWED_SLACK_DOMAINS || '') @@ -118,22 +99,20 @@ router.post('auth.slack', async ctx => { ); } - // User let user = await User.findOne({ where: { slackId: data.user.id } }); - - // Team let team = await Team.findOne({ where: { slackId: data.team.id } }); const teamExisted = !!team; - if (!team) { + + if (team) { + team.name = data.team.name; + team.slackData = data.team; + await team.save(); + } else { team = await Team.create({ name: data.team.name, slackId: data.team.id, slackData: data.team, }); - } else { - team.name = data.team.name; - team.slackData = data.team; - team = await team.save(); } if (user) { @@ -143,7 +122,6 @@ router.post('auth.slack', async ctx => { } else { user = await User.create({ slackId: data.user.id, - username: data.user.name, name: data.user.name, email: data.user.email, teamId: team.id, @@ -169,24 +147,7 @@ router.post('auth.slackCommands', async ctx => { const { code } = ctx.body; ctx.assertPresent(code, 'code is required'); - const body = { - client_id: process.env.SLACK_KEY, - client_secret: process.env.SLACK_SECRET, - redirect_uri: `${process.env.URL || ''}/auth/slack/commands`, - code, - }; - - let data; - try { - const response = await fetch( - `https://slack.com/api/oauth.access?${querystring.stringify(body)}` - ); - data = await response.json(); - } catch (e) { - throw httpErrors.BadRequest(); - } - - if (!data.ok) throw httpErrors.BadRequest(data.error); + await Slack.oauthAccess(code, `${process.env.URL || ''}/auth/slack/commands`); }); export default router; diff --git a/server/api/middlewares/pagination.js b/server/api/middlewares/pagination.js index 57ebf03a7..d0e94048b 100644 --- a/server/api/middlewares/pagination.js +++ b/server/api/middlewares/pagination.js @@ -10,8 +10,9 @@ export default function pagination(options) { }; let query = ctx.request.query; - let limit = parseInt(query.limit, 10); - let offset = parseInt(query.offset, 10); + let body = ctx.request.body; + let limit = parseInt(query.limit || body.limit, 10); + let offset = parseInt(query.offset || body.offset, 10); limit = isNaN(limit) ? opts.defaultLimit : limit; offset = isNaN(offset) ? 0 : offset; diff --git a/server/migrations/20170904202454-allow-null-username.js b/server/migrations/20170904202454-allow-null-username.js new file mode 100644 index 000000000..399dc4e8c --- /dev/null +++ b/server/migrations/20170904202454-allow-null-username.js @@ -0,0 +1,15 @@ +module.exports = { + up: async function(queryInterface, Sequelize) { + await queryInterface.changeColumn('users', 'username', { + type: Sequelize.STRING, + allowNull: true, + }); + }, + + down: async function(queryInterface, Sequelize) { + await queryInterface.changeColumn('users', 'username', { + type: Sequelize.STRING, + allowNull: false, + }); + }, +}; diff --git a/server/slack.js b/server/slack.js new file mode 100644 index 000000000..62bb75eb5 --- /dev/null +++ b/server/slack.js @@ -0,0 +1,34 @@ +// @flow +import fetch from 'isomorphic-fetch'; +import querystring from 'querystring'; +import { httpErrors } from './errors'; + +const SLACK_API_URL = 'https://slack.com/api'; + +export async function request(endpoint: string, body: Object) { + let data; + try { + const response = await fetch( + `${SLACK_API_URL}/${endpoint}?${querystring.stringify(body)}` + ); + data = await response.json(); + } catch (e) { + throw httpErrors.BadRequest(); + } + console.log('DATA', data); + if (!data.ok) throw httpErrors.BadRequest(data.error); + + return data; +} + +export async function oauthAccess( + code: string, + redirect_uri: string = `${process.env.URL || ''}/auth/slack` +) { + return request('oauth.access', { + client_id: process.env.SLACK_KEY, + client_secret: process.env.SLACK_SECRET, + redirect_uri: `${process.env.URL || ''}/auth/slack`, + code, + }); +} diff --git a/server/static/dev.html b/server/static/dev.html index 1933c80b3..8eea6eee8 100644 --- a/server/static/dev.html +++ b/server/static/dev.html @@ -20,7 +20,7 @@ #root { flex: 1; - height: 100%; + min-height: 100vh; } @@ -30,4 +30,4 @@ - \ No newline at end of file + diff --git a/server/static/index.html b/server/static/index.html index 90df71b66..e79463d5b 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -18,7 +18,7 @@ #root { flex: 1; - height: 100%; + min-height: 100vh; } @@ -28,4 +28,4 @@ - \ No newline at end of file + diff --git a/yarn.lock b/yarn.lock index 104fa6ffb..7f54a6e27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7391,6 +7391,10 @@ react-dropzone@3.6.0: dependencies: attr-accept "^1.0.3" +react-feather@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-1.0.7.tgz#f2118f1d2402b0c1e6f23c732f9e7f9fd4ca61e2" + react-helmet@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-3.1.0.tgz#63486194682f33004826f3687dc49a138b557050"