Upgrade prettier

This commit is contained in:
Tom Moor
2017-11-10 14:14:30 -08:00
parent c737b613e4
commit ab13f51d5d
79 changed files with 780 additions and 533 deletions

View File

@ -10,7 +10,8 @@ type Props = {
type?: 'info' | 'success' | 'warning' | 'danger' | 'offline', type?: 'info' | 'success' | 'warning' | 'danger' | 'offline',
}; };
@observer class Alert extends React.Component { @observer
class Alert extends React.Component {
props: Props; props: Props;
defaultProps = { defaultProps = {
type: 'info', type: 'info',

View File

@ -29,35 +29,35 @@ const RealButton = styled.button`
svg { svg {
position: relative; position: relative;
top: .05em; top: 0.05em;
} }
${props => props.light && ` ${props =>
props.light &&
`
color: ${color.text}; color: ${color.text};
background: ${lighten(0.08, color.slateLight)}; background: ${lighten(0.08, color.slateLight)};
&:hover { &:hover {
background: ${color.slateLight}; background: ${color.slateLight};
} }
`} `} ${props =>
props.neutral &&
${props => props.neutral && ` `
background: ${color.slate}; background: ${color.slate};
&:hover { &:hover {
background: ${darken(0.05, color.slate)}; background: ${darken(0.05, color.slate)};
} }
`} `} ${props =>
props.danger &&
${props => props.danger && ` `
background: ${color.danger}; background: ${color.danger};
&:hover { &:hover {
background: ${darken(0.05, color.danger)}; background: ${darken(0.05, color.danger)};
} }
`} `} &:disabled {
&:disabled {
background: ${color.slateLight}; background: ${color.slateLight};
cursor: default; cursor: default;
} }
@ -68,7 +68,7 @@ const Label = styled.span`
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
${props => props.hasIcon && 'padding-left: 2px;'} ${props => props.hasIcon && 'padding-left: 2px;'};
`; `;
const Inner = styled.span` const Inner = styled.span`
@ -78,7 +78,9 @@ const Inner = styled.span`
justify-content: center; justify-content: center;
align-items: center; align-items: center;
${props => props.hasIcon && (props.small ? 'padding-left: 6px;' : 'padding-left: 10px;')} ${props =>
props.hasIcon &&
(props.small ? 'padding-left: 6px;' : 'padding-left: 10px;')};
`; `;
export type Props = { export type Props = {

View File

@ -19,9 +19,7 @@ const Content = styled.div`
const CenteredContent = ({ children, ...rest }: Props) => { const CenteredContent = ({ children, ...rest }: Props) => {
return ( return (
<Container {...rest}> <Container {...rest}>
<Content> <Content>{children}</Content>
{children}
</Content>
</Container> </Container>
); );
}; };

View File

@ -25,7 +25,8 @@ type Props = {
value?: string, value?: string,
}; };
@observer class ColorPicker extends React.Component { @observer
class ColorPicker extends React.Component {
props: Props; props: Props;
@observable selectedColor: string = colors[0]; @observable selectedColor: string = colors[0];
@ -52,26 +53,30 @@ type Props = {
); );
}; };
@computed get customColor(): string { @computed
get customColor(): string {
return this.customColorValue && return this.customColorValue &&
validateColorHex(`#${this.customColorValue}`) validateColorHex(`#${this.customColorValue}`)
? `#${this.customColorValue}` ? `#${this.customColorValue}`
: colors[0]; : colors[0];
} }
@action setColor = (color: string) => { @action
setColor = (color: string) => {
this.selectedColor = color; this.selectedColor = color;
this.customColorSelected = false; this.customColorSelected = false;
this.fireCallback(); this.fireCallback();
}; };
@action focusOnCustomColor = (event: SyntheticEvent) => { @action
focusOnCustomColor = (event: SyntheticEvent) => {
this.selectedColor = ''; this.selectedColor = '';
this.customColorSelected = true; this.customColorSelected = true;
this.fireCallback(); this.fireCallback();
}; };
@action setCustomColor = (event: SyntheticEvent) => { @action
setCustomColor = (event: SyntheticEvent) => {
let target = event.target; let target = event.target;
if (target instanceof HTMLInputElement) { if (target instanceof HTMLInputElement) {
const color = target.value; const color = target.value;
@ -137,9 +142,7 @@ const SwatchOutset = styled(Flex)`
border: 2px solid ${({ active, color }) => (active ? color : 'transparent')}; border: 2px solid ${({ active, color }) => (active ? color : 'transparent')};
border-radius: 2px; border-radius: 2px;
background: ${({ color }) => color}; background: ${({ color }) => color};
${({ onClick }) => onClick && `cursor: pointer;`} ${({ onClick }) => onClick && `cursor: pointer;`} &:last-child {
&:last-child {
margin-right: 0; margin-right: 0;
} }
`; `;

View File

@ -4,7 +4,11 @@ import styled from 'styled-components';
import Flex from 'shared/components/Flex'; import Flex from 'shared/components/Flex';
const Divider = () => { const Divider = () => {
return <Flex auto justify="center"><Content /></Flex>; return (
<Flex auto justify="center">
<Content />
</Flex>
);
}; };
const Content = styled.span` const Content = styled.span`

View File

@ -49,7 +49,7 @@ const DocumentLink = styled(Link)`
outline: none; outline: none;
${StyledStar} { ${StyledStar} {
opacity: .5; opacity: 0.5;
&:hover { &:hover {
opacity: 1; opacity: 1;
@ -63,11 +63,12 @@ const DocumentLink = styled(Link)`
h3 { h3 {
margin-top: 0; margin-top: 0;
margin-bottom: .25em; margin-bottom: 0.25em;
} }
`; `;
@observer class DocumentPreview extends Component { @observer
class DocumentPreview extends Component {
props: Props; props: Props;
star = (ev: SyntheticEvent) => { star = (ev: SyntheticEvent) => {
@ -89,13 +90,15 @@ const DocumentLink = styled(Link)`
<DocumentLink to={document.url} innerRef={innerRef} {...rest}> <DocumentLink to={document.url} innerRef={innerRef} {...rest}>
<h3> <h3>
{document.title} {document.title}
{document.starred {document.starred ? (
? <a onClick={this.unstar}> <a onClick={this.unstar}>
<StyledStar solid /> <StyledStar solid />
</a> </a>
: <a onClick={this.star}> ) : (
<StyledStar /> <a onClick={this.star}>
</a>} <StyledStar />
</a>
)}
</h3> </h3>
<PublishingInfo <PublishingInfo
document={document} document={document}

View File

@ -36,24 +36,24 @@ class PublishingInfo extends Component {
return ( return (
<Container align="center"> <Container align="center">
{createdAt === updatedAt {createdAt === updatedAt ? (
? <span> <span>
{createdBy.name} {createdBy.name} published {moment(createdAt).fromNow()}
</span>
) : (
<span>
{updatedBy.name}
<Modified highlight={modifiedSinceViewed}>
{' '} {' '}
published modified {moment(updatedAt).fromNow()}
{' '} </Modified>
{moment(createdAt).fromNow()} </span>
</span> )}
: <span> {collection && (
{updatedBy.name} <span>
<Modified highlight={modifiedSinceViewed}> &nbsp;in <strong>{collection.name}</strong>
{' '} </span>
modified )}
{' '}
{moment(updatedAt).fromNow()}
</Modified>
</span>}
{collection && <span>&nbsp;in <strong>{collection.name}</strong></span>}
</Container> </Container>
); );
} }

View File

@ -14,7 +14,8 @@ class DocumentViewersStore {
@observable viewers: Array<View>; @observable viewers: Array<View>;
@observable isFetching: boolean; @observable isFetching: boolean;
@action fetchViewers = async () => { @action
fetchViewers = async () => {
this.isFetching = true; this.isFetching = true;
try { try {

View File

@ -25,7 +25,8 @@ type Props = {
count: number, count: number,
}; };
@observer class DocumentViews extends Component { @observer
class DocumentViews extends Component {
anchor: HTMLElement; anchor: HTMLElement;
store: DocumentViewersStore; store: DocumentViewersStore;
props: Props; props: Props;
@ -55,19 +56,16 @@ type Props = {
return ( return (
<Container align="center"> <Container align="center">
<a ref={this.setRef} onClick={this.openPopover}> <a ref={this.setRef} onClick={this.openPopover}>
Viewed Viewed {this.props.count} {this.props.count === 1 ? 'time' : 'times'}
{' '}
{this.props.count}
{' '}
{this.props.count === 1 ? 'time' : 'times'}
</a> </a>
{this.state.opened && {this.state.opened && (
<Popover anchor={this.anchor} onClose={this.closePopover}> <Popover anchor={this.anchor} onClose={this.closePopover}>
<DocumentViewers <DocumentViewers
onMount={this.store.fetchViewers} onMount={this.store.fetchViewers}
viewers={this.store.viewers} viewers={this.store.viewers}
/> />
</Popover>} </Popover>
)}
</Container> </Container>
); );
} }

View File

@ -40,8 +40,7 @@ class DocumentViewers extends Component {
{map(this.props.viewers, view => ( {map(this.props.viewers, view => (
<li key={view.user.id}> <li key={view.user.id}>
<Flex align="center"> <Flex align="center">
<Avatar src={view.user.avatarUrl} /> <Avatar src={view.user.avatarUrl} />{' '}
{' '}
<UserName>{view.user.name}</UserName> <UserName>{view.user.name}</UserName>
</Flex> </Flex>
</li> </li>

View File

@ -17,7 +17,8 @@ type Props = {
style?: Object, style?: Object,
}; };
@observer class DropdownMenu extends Component { @observer
class DropdownMenu extends Component {
props: Props; props: Props;
@observable top: number; @observable top: number;
@observable left: number; @observable left: number;
@ -93,7 +94,8 @@ const Menu = styled.div`
border-radius: 2px; border-radius: 2px;
min-width: 160px; min-width: 160px;
overflow: hidden; overflow: hidden;
box-shadow: 0 0 0 1px rgba(0,0,0,.05), 0 4px 8px rgba(0,0,0,.08), 0 2px 4px rgba(0,0,0,.08); box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 8px rgba(0, 0, 0, 0.08),
0 2px 4px rgba(0, 0, 0, 0.08);
`; `;
export default DropdownMenu; export default DropdownMenu;

View File

@ -11,11 +11,7 @@ const DropdownMenuItem = ({
onClick?: SyntheticEvent => void, onClick?: SyntheticEvent => void,
children?: React.Element<any>, children?: React.Element<any>,
}) => { }) => {
return ( return <MenuItem onClick={onClick}>{children}</MenuItem>;
<MenuItem onClick={onClick}>
{children}
</MenuItem>
);
}; };
const MenuItem = styled(Flex)` const MenuItem = styled(Flex)`

View File

@ -34,7 +34,8 @@ type KeyData = {
key: string, key: string,
}; };
@observer class MarkdownEditor extends Component { @observer
class MarkdownEditor extends Component {
props: Props; props: Props;
editor: EditorType; editor: EditorType;
schema: Object; schema: Object;
@ -193,14 +194,16 @@ type KeyData = {
<MaxWidth column auto> <MaxWidth column auto>
<Header onClick={this.focusAtStart} readOnly={readOnly} /> <Header onClick={this.focusAtStart} readOnly={readOnly} />
<Contents state={this.editorState} /> <Contents state={this.editorState} />
{!readOnly && {!readOnly && (
<Toolbar state={this.editorState} onChange={this.onChange} />} <Toolbar state={this.editorState} onChange={this.onChange} />
{!readOnly && )}
{!readOnly && (
<BlockInsert <BlockInsert
state={this.editorState} state={this.editorState}
onChange={this.onChange} onChange={this.onChange}
onInsertImage={this.insertImageFile} onInsertImage={this.insertImageFile}
/>} />
)}
<StyledEditor <StyledEditor
innerRef={ref => (this.editor = ref)} innerRef={ref => (this.editor = ref)}
placeholder="Start with a title…" placeholder="Start with a title…"
@ -234,7 +237,7 @@ const Header = styled(Flex)`
height: 60px; height: 60px;
flex-shrink: 0; flex-shrink: 0;
align-items: flex-end; align-items: flex-end;
${({ readOnly }) => !readOnly && 'cursor: text;'} ${({ readOnly }) => !readOnly && 'cursor: text;'};
`; `;
const StyledEditor = styled(Editor)` const StyledEditor = styled(Editor)`
@ -326,7 +329,8 @@ const StyledEditor = styled(Editor)`
padding: 5px 20px 5px 0; padding: 5px 20px 5px 0;
} }
b, strong { b,
strong {
font-weight: 600; font-weight: 600;
} }
`; `;

View File

@ -126,20 +126,23 @@ const Trigger = styled.div`
z-index: 1; z-index: 1;
opacity: 0; opacity: 0;
background-color: ${color.white}; background-color: ${color.white};
transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275), transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275); transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
line-height: 0; line-height: 0;
margin-left: -10px; margin-left: -10px;
box-shadow: inset 0 0 0 2px ${color.slate}; box-shadow: inset 0 0 0 2px ${color.slate};
border-radius: 100%; border-radius: 100%;
transform: scale(.9); transform: scale(0.9);
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: ${color.smokeDark}; background-color: ${color.smokeDark};
} }
${({ active }) => active && ` ${({ active }) =>
active &&
`
transform: scale(1); transform: scale(1);
opacity: .9; opacity: .9;
`} `};
`; `;

View File

@ -16,7 +16,7 @@ const Container = styled.div`
padding-top: 50px; padding-top: 50px;
cursor: ${({ onClick }) => (onClick ? 'text' : 'default')}; cursor: ${({ onClick }) => (onClick ? 'text' : 'default')};
${({ grow }) => grow && `flex-grow: 1;`} ${({ grow }) => grow && `flex-grow: 1;`};
`; `;
export default ClickablePadding; export default ClickablePadding;

View File

@ -12,16 +12,14 @@ export default function Code({ children, node, readOnly, attributes }: Props) {
<Container {...attributes}> <Container {...attributes}>
{readOnly && <CopyButton text={node.text} />} {readOnly && <CopyButton text={node.text} />}
<Pre className={`language-${language}`}> <Pre className={`language-${language}`}>
<code className={`language-${language}`}> <code className={`language-${language}`}>{children}</code>
{children}
</code>
</Pre> </Pre>
</Container> </Container>
); );
} }
const Pre = styled.pre` const Pre = styled.pre`
padding: .5em 1em; padding: 0.5em 1em;
background: ${color.smokeLight}; background: ${color.smokeLight};
border-radius: 4px; border-radius: 4px;
border: 1px solid ${color.smokeDark}; border: 1px solid ${color.smokeDark};

View File

@ -12,7 +12,8 @@ type Props = {
state: State, state: State,
}; };
@observer class Contents extends Component { @observer
class Contents extends Component {
props: Props; props: Props;
@observable activeHeading: ?string; @observable activeHeading: ?string;

View File

@ -6,7 +6,8 @@ import { color } from 'shared/styles/constants';
import styled from 'styled-components'; import styled from 'styled-components';
import CopyToClipboard from 'components/CopyToClipboard'; import CopyToClipboard from 'components/CopyToClipboard';
@observer class CopyButton extends Component { @observer
class CopyButton extends Component {
@observable copied: boolean = false; @observable copied: boolean = false;
copiedTimeout: ?number; copiedTimeout: ?number;

View File

@ -41,26 +41,29 @@ function Heading(props: Props) {
return ( return (
<Component {...attributes} id={slugish}> <Component {...attributes} id={slugish}>
<Wrapper hasEmoji={startsWithEmojiAndSpace}> <Wrapper hasEmoji={startsWithEmojiAndSpace}>{children}</Wrapper>
{children} {showPlaceholder && (
</Wrapper>
{showPlaceholder &&
<Placeholder contentEditable={false}> <Placeholder contentEditable={false}>
{editor.props.placeholder} {editor.props.placeholder}
</Placeholder>} </Placeholder>
{showHash && <Anchor name={slugish} href={`#${slugish}`}>#</Anchor>} )}
{showHash && (
<Anchor name={slugish} href={`#${slugish}`}>
#
</Anchor>
)}
</Component> </Component>
); );
} }
const Wrapper = styled.div` const Wrapper = styled.div`
display: inline; display: inline;
margin-left: ${(props: Props) => (props.hasEmoji ? '-1.2em' : 0)} margin-left: ${(props: Props) => (props.hasEmoji ? '-1.2em' : 0)};
`; `;
const Anchor = styled.a` const Anchor = styled.a`
visibility: hidden; visibility: hidden;
padding-left: .25em; padding-left: 0.25em;
color: #dedede; color: #dedede;
&:hover { &:hover {

View File

@ -11,10 +11,11 @@ function HorizontalRule(props: Props) {
} }
const StyledHr = styled.hr` const StyledHr = styled.hr`
padding-top: .75em; padding-top: 0.75em;
margin: 0; margin: 0;
border: 0; border: 0;
border-bottom: 1px solid ${props => (props.active ? color.slate : color.slateLight)}; border-bottom: 1px solid
${props => (props.active ? color.slate : color.slateLight)};
`; `;
export default HorizontalRule; export default HorizontalRule;

View File

@ -35,26 +35,28 @@ class Image extends Component {
return ( return (
<CenteredImage> <CenteredImage>
{!readOnly {!readOnly ? (
? <StyledImg <StyledImg
{...attributes} {...attributes}
src={src} src={src}
alt={caption} alt={caption}
active={active} active={active}
loading={loading} loading={loading}
/> />
: <ImageZoom ) : (
image={{ <ImageZoom
src, image={{
alt: caption, src,
style: { alt: caption,
maxWidth: '100%', style: {
}, maxWidth: '100%',
...attributes, },
}} ...attributes,
shouldRespectMaxDimension }}
/>} shouldRespectMaxDimension
{showCaption && />
)}
{showCaption && (
<Caption <Caption
type="text" type="text"
placeholder="Write a caption" placeholder="Write a caption"
@ -64,7 +66,8 @@ class Image extends Component {
contentEditable={false} contentEditable={false}
disabled={readOnly} disabled={readOnly}
tabIndex={-1} tabIndex={-1}
/>} />
)}
</CenteredImage> </CenteredImage>
); );
} }

View File

@ -3,7 +3,7 @@ import styled from 'styled-components';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
const InlineCode = styled.code` const InlineCode = styled.code`
padding: .25em; padding: 0.25em;
background: ${color.smoke}; background: ${color.smoke};
border-radius: 4px; border-radius: 4px;
border: 1px solid ${color.smokeDark}; border: 1px solid ${color.smokeDark};

View File

@ -31,8 +31,16 @@ export default function Link({ attributes, node, children, readOnly }: Props) {
const path = getPathFromUrl(href); const path = getPathFromUrl(href);
if (isOutlineUrl(href) && readOnly) { if (isOutlineUrl(href) && readOnly) {
return <InternalLink {...attributes} to={path}>{children}</InternalLink>; return (
<InternalLink {...attributes} to={path}>
{children}
</InternalLink>
);
} else { } else {
return <a {...attributes} href={href} target="_blank">{children}</a>; return (
<a {...attributes} href={href} target="_blank">
{children}
</a>
);
} }
} }

View File

@ -25,10 +25,11 @@ export default function Link({
return ( return (
<p {...attributes}> <p {...attributes}>
{children} {children}
{showPlaceholder && {showPlaceholder && (
<Placeholder contentEditable={false}> <Placeholder contentEditable={false}>
{editor.props.bodyPlaceholder} {editor.props.bodyPlaceholder}
</Placeholder>} </Placeholder>
)}
</p> </p>
); );
} }

View File

@ -7,5 +7,5 @@ export default styled.span`
visibility: hidden; visibility: hidden;
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
color: #B1BECC; color: #b1becc;
`; `;

View File

@ -170,7 +170,7 @@ const Bar = styled(Flex)`
&:before, &:before,
&:after { &:after {
content: ""; content: '';
position: absolute; position: absolute;
left: -100%; left: -100%;
width: 100%; width: 100%;

View File

@ -99,7 +99,9 @@ export default class Toolbar extends Component {
const left = const left =
rect.left + window.scrollX - this.menu.offsetWidth / 2 + rect.width / 2; rect.left + window.scrollX - this.menu.offsetWidth / 2 + rect.width / 2;
data.top = `${Math.round(rect.top + window.scrollY - this.menu.offsetHeight)}px`; data.top = `${Math.round(
rect.top + window.scrollY - this.menu.offsetHeight
)}px`;
data.left = `${Math.round(Math.max(padding, left))}px`; data.left = `${Math.round(Math.max(padding, left))}px`;
this.setState(data); this.setState(data);
} }
@ -120,17 +122,15 @@ export default class Toolbar extends Component {
return ( return (
<Portal> <Portal>
<Menu active={this.state.active} innerRef={this.setRef} style={style}> <Menu active={this.state.active} innerRef={this.setRef} style={style}>
{link && {link && (
<LinkToolbar <LinkToolbar {...this.props} link={link} onBlur={this.handleBlur} />
{...this.props} )}
link={link} {!link && (
onBlur={this.handleBlur}
/>}
{!link &&
<FormattingToolbar <FormattingToolbar
onCreateLink={this.handleFocus} onCreateLink={this.handleFocus}
{...this.props} {...this.props}
/>} />
)}
</Menu> </Menu>
</Portal> </Portal>
); );
@ -144,16 +144,19 @@ const Menu = styled.div`
top: -10000px; top: -10000px;
left: -10000px; left: -10000px;
opacity: 0; opacity: 0;
background-color: #2F3336; background-color: #2f3336;
border-radius: 4px; border-radius: 4px;
transform: scale(.95); transform: scale(0.95);
transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275), transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275); transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
line-height: 0; line-height: 0;
height: 40px; height: 40px;
min-width: 260px; min-width: 260px;
${({ active }) => active && ` ${({ active }) =>
active &&
`
transform: translateY(-6px) scale(1); transform: translateY(-6px) scale(1);
opacity: 1; opacity: 1;
`} `};
`; `;

View File

@ -14,7 +14,9 @@ type Props = {
function DocumentResult({ document, ...rest }: Props) { function DocumentResult({ document, ...rest }: Props) {
return ( return (
<ListItem {...rest} href=""> <ListItem {...rest} href="">
<i><NextIcon light /></i> <i>
<NextIcon light />
</i>
{document.title} {document.title}
</ListItem> </ListItem>
); );

View File

@ -42,7 +42,10 @@ class FormattingToolbar extends Component {
ev.preventDefault(); ev.preventDefault();
let { state } = this.props; let { state } = this.props;
state = state.transform().toggleMark(type).apply(); state = state
.transform()
.toggleMark(type)
.apply();
this.props.onChange(state); this.props.onChange(state);
}; };
@ -50,7 +53,10 @@ class FormattingToolbar extends Component {
ev.preventDefault(); ev.preventDefault();
let { state } = this.props; let { state } = this.props;
state = state.transform().setBlock(type).apply(); state = state
.transform()
.setBlock(type)
.apply();
this.props.onChange(state); this.props.onChange(state);
}; };
@ -59,7 +65,10 @@ class FormattingToolbar extends Component {
ev.stopPropagation(); ev.stopPropagation();
let { state } = this.props; let { state } = this.props;
const data = { href: '' }; const data = { href: '' };
state = state.transform().wrapInline({ type: 'link', data }).apply(); state = state
.transform()
.wrapInline({ type: 'link', data })
.apply();
this.props.onChange(state); this.props.onChange(state);
this.props.onCreateLink(); this.props.onCreateLink();
}; };
@ -109,8 +118,8 @@ class FormattingToolbar extends Component {
const Separator = styled.div` const Separator = styled.div`
height: 100%; height: 100%;
width: 1px; width: 1px;
background: #FFF; background: #fff;
opacity: .2; opacity: 0.2;
display: inline-block; display: inline-block;
margin-left: 10px; margin-left: 10px;
`; `;

View File

@ -39,7 +39,8 @@ class LinkToolbar extends Component {
this.isEditing = !!this.props.link.data.get('href'); this.isEditing = !!this.props.link.data.get('href');
} }
@action search = async () => { @action
search = async () => {
this.isFetching = true; this.isFetching = true;
if (this.searchTerm) { if (this.searchTerm) {
@ -144,15 +145,16 @@ class LinkToolbar extends Component {
onChange={this.onChange} onChange={this.onChange}
autoFocus autoFocus
/> />
{this.isEditing && {this.isEditing && (
<ToolbarButton onMouseDown={this.openLink}> <ToolbarButton onMouseDown={this.openLink}>
<OpenIcon light /> <OpenIcon light />
</ToolbarButton>} </ToolbarButton>
)}
<ToolbarButton onMouseDown={this.removeLink}> <ToolbarButton onMouseDown={this.removeLink}>
{this.isEditing ? <TrashIcon light /> : <CloseIcon light />} {this.isEditing ? <TrashIcon light /> : <CloseIcon light />}
</ToolbarButton> </ToolbarButton>
</LinkEditor> </LinkEditor>
{hasResults && {hasResults && (
<SearchResults> <SearchResults>
<ArrowKeyNavigation <ArrowKeyNavigation
mode={ArrowKeyNavigation.mode.VERTICAL} mode={ArrowKeyNavigation.mode.VERTICAL}
@ -165,7 +167,8 @@ class LinkToolbar extends Component {
return ( return (
<DocumentResult <DocumentResult
innerRef={ref => innerRef={ref =>
index === 0 && this.setFirstDocumentRef(ref)} index === 0 && this.setFirstDocumentRef(ref)
}
document={document} document={document}
key={document.id} key={document.id}
onClick={ev => this.selectDocument(ev, document)} onClick={ev => this.selectDocument(ev, document)}
@ -173,14 +176,15 @@ class LinkToolbar extends Component {
); );
})} })}
</ArrowKeyNavigation> </ArrowKeyNavigation>
</SearchResults>} </SearchResults>
)}
</span> </span>
); );
} }
} }
const SearchResults = styled.div` const SearchResults = styled.div`
background: #2F3336; background: #2f3336;
position: absolute; position: absolute;
top: 100%; top: 100%;
width: 100%; width: 100%;
@ -199,7 +203,7 @@ const LinkEditor = styled(Flex)`
const Input = styled.input` const Input = styled.input`
font-size: 15px; font-size: 15px;
background: rgba(255,255,255,.1); background: rgba(255, 255, 255, 0.1);
border-radius: 2px; border-radius: 2px;
padding: 4px 8px; padding: 4px 8px;
border: 0; border: 0;

View File

@ -12,7 +12,7 @@ export default styled.button`
background: none; background: none;
transition: opacity 100ms ease-in-out; transition: opacity 100ms ease-in-out;
padding: 0; padding: 0;
opacity: .7; opacity: 0.7;
&:first-child { &:first-child {
margin-left: 0; margin-left: 0;
@ -22,5 +22,5 @@ export default styled.button`
opacity: 1; opacity: 1;
} }
${({ active }) => active && 'opacity: 1;'} ${({ active }) => active && 'opacity: 1;'};
`; `;

View File

@ -37,7 +37,10 @@ export default function KeyboardShortcuts() {
const firstNode = state.document.nodes.first(); const firstNode = state.document.nodes.first();
if (firstNode === state.startBlock) return; if (firstNode === state.startBlock) return;
state = state.transform().toggleMark(type).apply(); state = state
.transform()
.toggleMark(type)
.apply();
return state; return state;
}, },
}; };

View File

@ -64,7 +64,10 @@ export default function MarkdownShortcuts() {
} }
} }
state = transform.extendToStartOf(startBlock).delete().apply(); state = transform
.extendToStartOf(startBlock)
.delete()
.apply();
return state; return state;
} }
@ -113,7 +116,10 @@ export default function MarkdownShortcuts() {
lastCodeTagIndex - shortcut.length lastCodeTagIndex - shortcut.length
); );
transform.addMark(mark); transform.addMark(mark);
state = transform.collapseToEnd().removeMark(mark).apply(); state = transform
.collapseToEnd()
.removeMark(mark)
.apply();
return state; return state;
} }
} }
@ -212,7 +218,11 @@ export default function MarkdownShortcuts() {
onTab(ev: SyntheticEvent, state: Object) { onTab(ev: SyntheticEvent, state: Object) {
if (state.startBlock.type === 'heading1') { if (state.startBlock.type === 'heading1') {
ev.preventDefault(); ev.preventDefault();
return state.transform().splitBlock().setBlock('paragraph').apply(); return state
.transform()
.splitBlock()
.setBlock('paragraph')
.apply();
} }
}, },
@ -253,7 +263,11 @@ export default function MarkdownShortcuts() {
} }
ev.preventDefault(); ev.preventDefault();
return state.transform().splitBlock().setBlock('paragraph').apply(); return state
.transform()
.splitBlock()
.setBlock('paragraph')
.apply();
}, },
/** /**

View File

@ -9,12 +9,14 @@ export default function CheckboxIcon({
}: Props & { checked: boolean }) { }: Props & { checked: boolean }) {
return ( return (
<Icon {...rest}> <Icon {...rest}>
{checked {checked ? (
? <path d="M8,5 L16,5 L16,5 C17.6568542,5 19,6.34314575 19,8 L19,16 C19,17.6568542 17.6568542,19 16,19 L8,19 L8,19 C6.34314575,19 5,17.6568542 5,16 L5,8 L5,8 C5,6.34314575 6.34314575,5 8,5 L8,5 Z M10.958729,12.8883948 L9.26824635,10.8598156 C8.91468227,10.4355387 8.28411757,10.3782146 7.85984067,10.7317787 C7.43556378,11.0853428 7.37823971,11.7159075 7.73180379,12.1401844 L10.2318038,15.1401844 C10.6450125,15.6360348 11.4127535,15.616362 11.8000251,15.1 L16.3000251,9.1 C16.6313959,8.6581722 16.5418529,8.03137085 16.1000251,7.7 C15.6581973,7.36862915 15.0313959,7.4581722 14.7000251,7.9 L10.958729,12.8883948 Z" /> <path d="M8,5 L16,5 L16,5 C17.6568542,5 19,6.34314575 19,8 L19,16 C19,17.6568542 17.6568542,19 16,19 L8,19 L8,19 C6.34314575,19 5,17.6568542 5,16 L5,8 L5,8 C5,6.34314575 6.34314575,5 8,5 L8,5 Z M10.958729,12.8883948 L9.26824635,10.8598156 C8.91468227,10.4355387 8.28411757,10.3782146 7.85984067,10.7317787 C7.43556378,11.0853428 7.37823971,11.7159075 7.73180379,12.1401844 L10.2318038,15.1401844 C10.6450125,15.6360348 11.4127535,15.616362 11.8000251,15.1 L16.3000251,9.1 C16.6313959,8.6581722 16.5418529,8.03137085 16.1000251,7.7 C15.6581973,7.36862915 15.0313959,7.4581722 14.7000251,7.9 L10.958729,12.8883948 Z" />
: <path ) : (
d="M8,5 L16,5 L16,5 C17.6568542,5 19,6.34314575 19,8 L19,16 C19,17.6568542 17.6568542,19 16,19 L8,19 L8,19 C6.34314575,19 5,17.6568542 5,16 L5,8 L5,8 C5,6.34314575 6.34314575,5 8,5 L8,5 Z M8,7 C7.44771525,7 7,7.44771525 7,8 L7,16 C7,16.5522847 7.44771525,17 8,17 L16,17 C16.5522847,17 17,16.5522847 17,16 L17,8 C17,7.44771525 16.5522847,7 16,7 L8,7 Z" <path
id="path-1" d="M8,5 L16,5 L16,5 C17.6568542,5 19,6.34314575 19,8 L19,16 C19,17.6568542 17.6568542,19 16,19 L8,19 L8,19 C6.34314575,19 5,17.6568542 5,16 L5,8 L5,8 C5,6.34314575 6.34314575,5 8,5 L8,5 Z M8,7 C7.44771525,7 7,7.44771525 7,8 L7,16 C7,16.5522847 7.44771525,17 8,17 L16,17 C16.5522847,17 17,16.5522847 17,16 L17,8 C17,7.44771525 16.5522847,7 16,7 L8,7 Z"
/>} id="path-1"
/>
)}
</Icon> </Icon>
); );
} }

View File

@ -9,9 +9,11 @@ export default function CollectionIcon({
}: Props & { expanded: boolean }) { }: Props & { expanded: boolean }) {
return ( return (
<Icon {...rest}> <Icon {...rest}>
{expanded {expanded ? (
? <path d="M16.701875,4.16415178 L17,4.14285714 C18.1045695,4.06395932 19,5.02334914 19,6.28571429 L19,17.7142857 C19,18.9766509 18.1045695,19.9360407 17,19.8571429 L16.701875,19.8358482 C16.8928984,19.371917 17,18.8348314 17,18.25 L17,5.75 C17,5.16516859 16.8928984,4.62808299 16.701875,4.16415178 Z M14,3.36363636 C15.1045695,3.16280555 16,4.15126779 16,5.57142857 L16,18.4285714 C16,19.8487322 15.1045695,20.8371945 14,20.6363636 L7,19.3636364 C5.8954305,19.1628055 5,18.1045695 5,17 L5,7 C5,5.8954305 5.8954305,4.83719445 7,4.63636364 L14,3.36363636 Z M7.5,6.67532468 C7.22385763,6.71118732 7,6.97574633 7,7.26623377 L7,16.7337662 C7,17.0242537 7.22385763,17.2888127 7.5,17.3246753 L8.5,17.4545455 C8.77614237,17.4904081 9,17.272365 9,16.9675325 L9,7.03246753 C9,6.72763504 8.77614237,6.5095919 8.5,6.54545455 L7.5,6.67532468 Z" /> <path d="M16.701875,4.16415178 L17,4.14285714 C18.1045695,4.06395932 19,5.02334914 19,6.28571429 L19,17.7142857 C19,18.9766509 18.1045695,19.9360407 17,19.8571429 L16.701875,19.8358482 C16.8928984,19.371917 17,18.8348314 17,18.25 L17,5.75 C17,5.16516859 16.8928984,4.62808299 16.701875,4.16415178 Z M14,3.36363636 C15.1045695,3.16280555 16,4.15126779 16,5.57142857 L16,18.4285714 C16,19.8487322 15.1045695,20.8371945 14,20.6363636 L7,19.3636364 C5.8954305,19.1628055 5,18.1045695 5,17 L5,7 C5,5.8954305 5.8954305,4.83719445 7,4.63636364 L14,3.36363636 Z M7.5,6.67532468 C7.22385763,6.71118732 7,6.97574633 7,7.26623377 L7,16.7337662 C7,17.0242537 7.22385763,17.2888127 7.5,17.3246753 L8.5,17.4545455 C8.77614237,17.4904081 9,17.272365 9,16.9675325 L9,7.03246753 C9,6.72763504 8.77614237,6.5095919 8.5,6.54545455 L7.5,6.67532468 Z" />
: <path d="M7,4 L17,4 C18.1045695,4 19,4.8954305 19,6 L19,18 C19,19.1045695 18.1045695,20 17,20 L7,20 C5.8954305,20 5,19.1045695 5,18 L5,6 L5,6 C5,4.8954305 5.8954305,4 7,4 L7,4 Z M7.5,6 C7.22385763,6 7,6.22385763 7,6.5 L7,17.5 C7,17.7761424 7.22385763,18 7.5,18 L8.5,18 C8.77614237,18 9,17.7761424 9,17.5 L9,6.5 C9,6.22385763 8.77614237,6 8.5,6 L7.5,6 Z" />} ) : (
<path d="M7,4 L17,4 C18.1045695,4 19,4.8954305 19,6 L19,18 C19,19.1045695 18.1045695,20 17,20 L7,20 C5.8954305,20 5,19.1045695 5,18 L5,6 L5,6 C5,4.8954305 5.8954305,4 7,4 L7,4 Z M7.5,6 C7.22385763,6 7,6.22385763 7,6.5 L7,17.5 C7,17.7761424 7.22385763,18 7.5,18 L8.5,18 C8.77614237,18 9,17.7761424 9,17.5 L9,6.5 C9,6.22385763 8.77614237,6 8.5,6 L7.5,6 Z" />
)}
</Icon> </Icon>
); );
} }

View File

@ -28,9 +28,7 @@ const RealInput = styled.input`
} }
`; `;
const Wrapper = styled.div` const Wrapper = styled.div``;
`;
export const Outline = styled(Flex)` export const Outline = styled(Flex)`
display: flex; display: flex;
@ -44,7 +42,7 @@ export const Outline = styled(Flex)`
font-weight: normal; font-weight: normal;
&:focus { &:focus {
border-color: ${color.slate} border-color: ${color.slate};
} }
`; `;

View File

@ -5,7 +5,8 @@ import { color } from 'shared/styles/constants';
const Key = styled.kbd` const Key = styled.kbd`
display: inline-block; display: inline-block;
padding: 4px 6px; padding: 4px 6px;
font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; font: 11px 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier,
monospace;
line-height: 10px; line-height: 10px;
color: ${color.text}; color: ${color.text};
vertical-align: middle; vertical-align: middle;

View File

@ -22,7 +22,7 @@ export const Label = styled(Flex)`
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 500;
text-transform: uppercase; text-transform: uppercase;
color: #9FA6AB; color: #9fa6ab;
letter-spacing: 0.04em; letter-spacing: 0.04em;
`; `;

View File

@ -43,7 +43,8 @@ type Props = {
notifications?: React.Element<any>, notifications?: React.Element<any>,
}; };
@observer class Layout extends React.Component { @observer
class Layout extends React.Component {
props: Props; props: Props;
scrollable: ?HTMLDivElement; scrollable: ?HTMLDivElement;
@ -117,41 +118,42 @@ type Props = {
<Flex auto> <Flex auto>
{auth.authenticated && {auth.authenticated &&
user && user &&
team && team && (
<Sidebar column editMode={ui.editMode}> <Sidebar column editMode={ui.editMode}>
<AccountMenu <AccountMenu
label={ label={
<HeaderBlock user={user} team={team}> <HeaderBlock user={user} team={team}>
<Avatar src={user.avatarUrl} /> <Avatar src={user.avatarUrl} />
</HeaderBlock> </HeaderBlock>
} }
/> />
<Flex auto column> <Flex auto column>
<Scrollable innerRef={this.setScrollableRef}> <Scrollable innerRef={this.setScrollableRef}>
<LinkSection> <LinkSection>
<SidebarLink to="/dashboard" icon={<HomeIcon />}> <SidebarLink to="/dashboard" icon={<HomeIcon />}>
Home Home
</SidebarLink> </SidebarLink>
<SidebarLink to="/search" icon={<SearchIcon />}> <SidebarLink to="/search" icon={<SearchIcon />}>
Search Search
</SidebarLink> </SidebarLink>
<SidebarLink to="/starred" icon={<StarredIcon />}> <SidebarLink to="/starred" icon={<StarredIcon />}>
Starred Starred
</SidebarLink> </SidebarLink>
</LinkSection> </LinkSection>
<LinkSection> <LinkSection>
<SidebarCollections <SidebarCollections
history={this.props.history} history={this.props.history}
location={this.props.location} location={this.props.location}
activeDocument={documents.active} activeDocument={documents.active}
onCreateCollection={this.handleCreateCollection} onCreateCollection={this.handleCreateCollection}
activeDocumentRef={this.scrollToActiveDocument} activeDocumentRef={this.scrollToActiveDocument}
/> />
</LinkSection> </LinkSection>
</Scrollable> </Scrollable>
</Flex> </Flex>
</Sidebar>} </Sidebar>
)}
<Content auto justify="center" editMode={ui.editMode}> <Content auto justify="center" editMode={ui.editMode}>
{this.props.children} {this.props.children}

View File

@ -43,14 +43,14 @@ const Header = styled(Flex)`
&:active, &:active,
&:hover { &:hover {
background: rgba(0,0,0,.05); background: rgba(0, 0, 0, 0.05);
} }
&::after { &::after {
content: ""; content: '';
left: 24px; left: 24px;
right: 24px; right: 24px;
background: rgba(0,0,0,.075); background: rgba(0, 0, 0, 0.075);
height: 1px; height: 1px;
position: absolute; position: absolute;
bottom: 0; bottom: 0;

View File

@ -10,7 +10,8 @@ import DocumentDelete from 'scenes/DocumentDelete';
import KeyboardShortcuts from 'scenes/KeyboardShortcuts'; import KeyboardShortcuts from 'scenes/KeyboardShortcuts';
import Settings from 'scenes/Settings'; import Settings from 'scenes/Settings';
@observer class Modals extends Component { @observer
class Modals extends Component {
props: { props: {
ui: UiStore, ui: UiStore,
}; };

View File

@ -31,7 +31,8 @@ type Props = {
ui: UiStore, ui: UiStore,
}; };
@observer class SidebarCollections extends Component { @observer
class SidebarCollections extends Component {
props: Props; props: Props;
render() { render() {
@ -61,13 +62,14 @@ type Props = {
/> />
))} ))}
{collections.isLoaded && {collections.isLoaded && (
<SidebarLink <SidebarLink
onClick={this.props.onCreateCollection} onClick={this.props.onCreateCollection}
icon={<PlusIcon />} icon={<PlusIcon />}
> >
New collection New collection
</SidebarLink>} </SidebarLink>
)}
</Flex> </Flex>
); );
} }
@ -82,7 +84,8 @@ type CollectionLinkProps = {
prefetchDocument: (id: string) => Promise<void>, prefetchDocument: (id: string) => Promise<void>,
}; };
@observer class CollectionLink extends Component { @observer
class CollectionLink extends Component {
props: CollectionLinkProps; props: CollectionLinkProps;
dropzoneRef; dropzoneRef;
@ -133,7 +136,7 @@ type CollectionLinkProps = {
</CollectionAction> </CollectionAction>
</Flex> </Flex>
{expanded && {expanded && (
<Children column> <Children column>
{collection.documents.map(document => ( {collection.documents.map(document => (
<DocumentLink <DocumentLink
@ -146,7 +149,8 @@ type CollectionLinkProps = {
depth={0} depth={0}
/> />
))} ))}
</Children>} </Children>
)}
</SidebarLink> </SidebarLink>
</StyledDropToImport> </StyledDropToImport>
); );
@ -172,11 +176,13 @@ const DocumentLink = observer(
}: DocumentLinkProps) => { }: DocumentLinkProps) => {
const isActiveDocument = const isActiveDocument =
activeDocument && activeDocument.id === document.id; activeDocument && activeDocument.id === document.id;
const showChildren = !!(activeDocument && const showChildren = !!(
activeDocument &&
(activeDocument.pathToDocument (activeDocument.pathToDocument
.map(entry => entry.id) .map(entry => entry.id)
.includes(document.id) || .includes(document.id) ||
isActiveDocument)); isActiveDocument)
);
const handleMouseEnter = (event: SyntheticEvent) => { const handleMouseEnter = (event: SyntheticEvent) => {
event.stopPropagation(); event.stopPropagation();
@ -200,20 +206,22 @@ const DocumentLink = observer(
to={document.url} to={document.url}
expand={showChildren} expand={showChildren}
expandedContent={ expandedContent={
document.children.length document.children.length ? (
? <Children column> <Children column>
{document.children.map(childDocument => ( {document.children.map(childDocument => (
<DocumentLink <DocumentLink
key={childDocument.id} key={childDocument.id}
history={history} history={history}
document={childDocument} document={childDocument}
activeDocument={activeDocument} activeDocument={activeDocument}
prefetchDocument={prefetchDocument} prefetchDocument={prefetchDocument}
depth={depth + 1} depth={depth + 1}
/> />
))} ))}
</Children> </Children>
: undefined ) : (
undefined
)
} }
> >
{document.title} {document.title}
@ -228,10 +236,14 @@ const CollectionAction = styled.a`
position: absolute; position: absolute;
right: 0; right: 0;
color: ${color.slate}; color: ${color.slate};
svg { opacity: .75; } svg {
opacity: 0.75;
}
&:hover { &:hover {
svg { opacity: 1; } svg {
opacity: 1;
}
} }
`; `;

View File

@ -17,7 +17,7 @@ const StyledGoTo = styled(CollapsedIcon)`
margin-bottom: -4px; margin-bottom: -4px;
margin-left: 1px; margin-left: 1px;
margin-right: -3px; margin-right: -3px;
${({ expanded }) => !expanded && 'transform: rotate(-90deg);'} ${({ expanded }) => !expanded && 'transform: rotate(-90deg);'};
`; `;
const IconWrapper = styled.span` const IconWrapper = styled.span`
@ -55,7 +55,8 @@ type Props = {
iconColor?: string, iconColor?: string,
}; };
@observer class SidebarLink extends Component { @observer
class SidebarLink extends Component {
props: Props; props: Props;
@observable expanded: boolean = false; @observable expanded: boolean = false;
@ -67,13 +68,15 @@ type Props = {
if (nextProps.expand) this.handleExpand(); if (nextProps.expand) this.handleExpand();
} }
@action handleClick = (event: SyntheticEvent) => { @action
handleClick = (event: SyntheticEvent) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.expanded = !this.expanded; this.expanded = !this.expanded;
}; };
@action handleExpand = () => { @action
handleExpand = () => {
this.expanded = true; this.expanded = true;
}; };
@ -91,8 +94,9 @@ type Props = {
exact exact
> >
{icon && <IconWrapper>{icon}</IconWrapper>} {icon && <IconWrapper>{icon}</IconWrapper>}
{expandedContent && {expandedContent && (
<StyledGoTo expanded={this.expanded} onClick={this.handleClick} />} <StyledGoTo expanded={this.expanded} onClick={this.handleClick} />
)}
<Content onClick={this.handleExpand}>{children}</Content> <Content onClick={this.handleExpand}>{children}</Content>
</Component> </Component>
{this.expanded && expandedContent} {this.expanded && expandedContent}

View File

@ -2,7 +2,8 @@
import React from 'react'; import React from 'react';
import { inject, observer } from 'mobx-react'; import { inject, observer } from 'mobx-react';
@observer class LoadingIndicator extends React.Component { @observer
class LoadingIndicator extends React.Component {
componentDidMount() { componentDidMount() {
this.props.ui.enableProgressBar(); this.props.ui.enableProgressBar();
} }

View File

@ -20,7 +20,7 @@ const Container = styled.div`
top: 0; top: 0;
z-index: 9999; z-index: 9999;
background-color: #03A9F4; background-color: #03a9f4;
width: 100%; width: 100%;
animation: ${loadingFrame} 4s ease-in-out infinite; animation: ${loadingFrame} 4s ease-in-out infinite;
animation-delay: 250ms; animation-delay: 250ms;
@ -30,7 +30,7 @@ const Container = styled.div`
const Loader = styled.div` const Loader = styled.div`
width: 100%; width: 100%;
height: 2px; height: 2px;
background-color: #03A9F4; background-color: #03a9f4;
`; `;
export default LoadingIndicatorBar; export default LoadingIndicatorBar;

View File

@ -43,7 +43,9 @@ const Modal = ({
> >
<Content column> <Content column>
{title && <h1>{title}</h1>} {title && <h1>{title}</h1>}
<Close onClick={onRequestClose}><CloseIcon size={32} /></Close> <Close onClick={onRequestClose}>
<CloseIcon size={32} />
</Close>
{children} {children}
</Content> </Content>
</StyledModal> </StyledModal>
@ -79,7 +81,7 @@ const Close = styled.a`
position: fixed; position: fixed;
top: 3rem; top: 3rem;
right: 3rem; right: 3rem;
opacity: .5; opacity: 0.5;
color: ${color.text}; color: ${color.text};
&:hover { &:hover {

View File

@ -7,7 +7,9 @@ type Props = {
}; };
const PageTitle = ({ title }: Props) => ( const PageTitle = ({ title }: Props) => (
<Helmet><title>{`${title} - Outline`}</title></Helmet> <Helmet>
<title>{`${title} - Outline`}</title>
</Helmet>
); );
export default PageTitle; export default PageTitle;

View File

@ -30,18 +30,19 @@ const StyledPopover = styled(BoundlessPopover)`
position: absolute; position: absolute;
polygon:first-child { polygon:first-child {
fill: rgba(0,0,0,.075); fill: rgba(0, 0, 0, 0.075);
} }
polygon { polygon {
fill: #FFF; fill: #fff;
} }
} }
`; `;
const Dialog = styled.div` const Dialog = styled.div`
outline: none; outline: none;
background: #FFF; background: #fff;
box-shadow: 0 0 0 1px rgba(0,0,0,.05), 0 8px 16px rgba(0,0,0,.1), 0 2px 4px rgba(0,0,0,.1); box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 8px 16px rgba(0, 0, 0, 0.1),
0 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 4px; border-radius: 4px;
line-height: 1.5; line-height: 1.5;
padding: 16px; padding: 16px;

View File

@ -10,7 +10,8 @@ type Props = {
redirectUri: string, redirectUri: string,
}; };
@observer class SlackAuthLink extends React.Component { @observer
class SlackAuthLink extends React.Component {
props: Props; props: Props;
static defaultProps = { static defaultProps = {

View File

@ -5,7 +5,8 @@ import styled from 'styled-components';
import { layout } from 'shared/styles/constants'; import { layout } from 'shared/styles/constants';
import Toast from './components/Toast'; import Toast from './components/Toast';
@observer class Toasts extends Component { @observer
class Toasts extends Component {
handleClose = index => { handleClose = index => {
this.props.errors.remove(index); this.props.errors.remove(index);
}; };

View File

@ -82,9 +82,7 @@ const Auth = ({ children }: AuthProps) => {
return ( return (
<Flex auto> <Flex auto>
<Provider {...authenticatedStores}> <Provider {...authenticatedStores}>{children}</Provider>
{children}
</Provider>
</Flex> </Flex>
); );
} else { } else {

View File

@ -6,7 +6,8 @@ import UiStore from 'stores/UiStore';
import AuthStore from 'stores/AuthStore'; import AuthStore from 'stores/AuthStore';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
@observer class AccountMenu extends Component { @observer
class AccountMenu extends Component {
props: { props: {
label?: React$Element<any>, label?: React$Element<any>,
history: Object, history: Object,
@ -42,9 +43,7 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
<Link to="/developers"> <Link to="/developers">
<DropdownMenuItem>API documentation</DropdownMenuItem> <DropdownMenuItem>API documentation</DropdownMenuItem>
</Link> </Link>
<DropdownMenuItem onClick={this.handleLogout}> <DropdownMenuItem onClick={this.handleLogout}>Logout</DropdownMenuItem>
Logout
</DropdownMenuItem>
</DropdownMenu> </DropdownMenu>
); );
} }

View File

@ -8,7 +8,8 @@ import MoreIcon from 'components/Icon/MoreIcon';
import Flex from 'shared/components/Flex'; import Flex from 'shared/components/Flex';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
@observer class CollectionMenu extends Component { @observer
class CollectionMenu extends Component {
props: { props: {
label?: React$Element<*>, label?: React$Element<*>,
onOpen?: () => void, onOpen?: () => void,
@ -44,7 +45,7 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
onOpen={onOpen} onOpen={onOpen}
onClose={onClose} onClose={onClose}
> >
{collection && {collection && (
<Flex column> <Flex column>
<DropdownMenuItem onClick={this.onNewDocument}> <DropdownMenuItem onClick={this.onNewDocument}>
New document New document
@ -53,9 +54,11 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
Import document Import document
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={this.onEdit}>Edit</DropdownMenuItem> <DropdownMenuItem onClick={this.onEdit}>Edit</DropdownMenuItem>
</Flex>} </Flex>
{allowDelete && )}
<DropdownMenuItem onClick={this.onDelete}>Delete</DropdownMenuItem>} {allowDelete && (
<DropdownMenuItem onClick={this.onDelete}>Delete</DropdownMenuItem>
)}
</DropdownMenu> </DropdownMenu>
); );
} }

View File

@ -8,7 +8,8 @@ import MoreIcon from 'components/Icon/MoreIcon';
import { documentMoveUrl } from 'utils/routeHelpers'; import { documentMoveUrl } from 'utils/routeHelpers';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
@observer class DocumentMenu extends Component { @observer
class DocumentMenu extends Component {
props: { props: {
ui: UiStore, ui: UiStore,
label?: React$Element<any>, label?: React$Element<any>,
@ -50,11 +51,13 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
return ( return (
<DropdownMenu label={label || <MoreIcon />}> <DropdownMenu label={label || <MoreIcon />}>
{document.starred {document.starred ? (
? <DropdownMenuItem onClick={this.handleUnstar}> <DropdownMenuItem onClick={this.handleUnstar}>
Unstar Unstar
</DropdownMenuItem> </DropdownMenuItem>
: <DropdownMenuItem onClick={this.handleStar}>Star</DropdownMenuItem>} ) : (
<DropdownMenuItem onClick={this.handleStar}>Star</DropdownMenuItem>
)}
<DropdownMenuItem <DropdownMenuItem
onClick={this.handleNewChild} onClick={this.handleNewChild}
title="Create a new child document for the current document" title="Create a new child document for the current document"
@ -65,10 +68,11 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
Download Download
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={this.handleMove}>Move</DropdownMenuItem> <DropdownMenuItem onClick={this.handleMove}>Move</DropdownMenuItem>
{allowDelete && {allowDelete && (
<DropdownMenuItem onClick={this.handleDelete}> <DropdownMenuItem onClick={this.handleDelete}>
Delete Delete
</DropdownMenuItem>} </DropdownMenuItem>
)}
</DropdownMenu> </DropdownMenu>
); );
} }

View File

@ -27,19 +27,22 @@ class Collection extends BaseModel {
/* Computed */ /* Computed */
@computed get entryUrl(): string { @computed
get entryUrl(): string {
return this.type === 'atlas' && this.documents.length > 0 return this.type === 'atlas' && this.documents.length > 0
? this.documents[0].url ? this.documents[0].url
: this.url; : this.url;
} }
@computed get allowDelete(): boolean { @computed
get allowDelete(): boolean {
return true; return true;
} }
/* Actions */ /* Actions */
@action fetch = async () => { @action
fetch = async () => {
try { try {
const res = await client.post('/collections.info', { id: this.id }); const res = await client.post('/collections.info', { id: this.id });
invariant(res && res.data, 'API response should be available'); invariant(res && res.data, 'API response should be available');
@ -54,7 +57,8 @@ class Collection extends BaseModel {
return this; return this;
}; };
@action save = async () => { @action
save = async () => {
if (this.isSaving) return this; if (this.isSaving) return this;
this.isSaving = true; this.isSaving = true;
@ -89,7 +93,8 @@ class Collection extends BaseModel {
return true; return true;
}; };
@action delete = async () => { @action
delete = async () => {
try { try {
await client.post('/collections.delete', { id: this.id }); await client.post('/collections.delete', { id: this.id });
this.emit('collections.delete', { this.emit('collections.delete', {
@ -102,7 +107,8 @@ class Collection extends BaseModel {
return false; return false;
}; };
@action updateData(data: Object = {}) { @action
updateData(data: Object = {}) {
this.data = data; this.data = data;
extendObservable(this, data); extendObservable(this, data);
} }

View File

@ -43,11 +43,13 @@ class Document extends BaseModel {
/* Computed */ /* Computed */
@computed get modifiedSinceViewed(): boolean { @computed
get modifiedSinceViewed(): boolean {
return !!this.lastViewedAt && this.lastViewedAt < this.updatedAt; return !!this.lastViewedAt && this.lastViewedAt < this.updatedAt;
} }
@computed get pathToDocument(): Array<{ id: string, title: string }> { @computed
get pathToDocument(): Array<{ id: string, title: string }> {
let path; let path;
const traveler = (nodes, previousPath) => { const traveler = (nodes, previousPath) => {
nodes.forEach(childNode => { nodes.forEach(childNode => {
@ -76,16 +78,19 @@ class Document extends BaseModel {
return []; return [];
} }
@computed get isEmpty(): boolean { @computed
get isEmpty(): boolean {
// Check if the document title has been modified and user generated content exists // Check if the document title has been modified and user generated content exists
return this.text.replace(new RegExp(`^#$`), '').trim().length === 0; return this.text.replace(new RegExp(`^#$`), '').trim().length === 0;
} }
@computed get allowSave(): boolean { @computed
get allowSave(): boolean {
return !this.isEmpty && !this.isSaving; return !this.isEmpty && !this.isSaving;
} }
@computed get allowDelete(): boolean { @computed
get allowDelete(): boolean {
const collection = this.collection; const collection = this.collection;
return ( return (
collection && collection &&
@ -95,7 +100,8 @@ class Document extends BaseModel {
); );
} }
@computed get parentDocumentId(): ?string { @computed
get parentDocumentId(): ?string {
return this.pathToDocument.length > 1 return this.pathToDocument.length > 1
? this.pathToDocument[this.pathToDocument.length - 2].id ? this.pathToDocument[this.pathToDocument.length - 2].id
: null; : null;
@ -103,7 +109,8 @@ class Document extends BaseModel {
/* Actions */ /* Actions */
@action star = async () => { @action
star = async () => {
this.starred = true; this.starred = true;
try { try {
await client.post('/documents.star', { id: this.id }); await client.post('/documents.star', { id: this.id });
@ -113,7 +120,8 @@ class Document extends BaseModel {
} }
}; };
@action unstar = async () => { @action
unstar = async () => {
this.starred = false; this.starred = false;
try { try {
await client.post('/documents.unstar', { id: this.id }); await client.post('/documents.unstar', { id: this.id });
@ -123,7 +131,8 @@ class Document extends BaseModel {
} }
}; };
@action view = async () => { @action
view = async () => {
this.views++; this.views++;
try { try {
await client.post('/views.create', { id: this.id }); await client.post('/views.create', { id: this.id });
@ -132,7 +141,8 @@ class Document extends BaseModel {
} }
}; };
@action fetch = async () => { @action
fetch = async () => {
try { try {
const res = await client.post('/documents.info', { id: this.id }); const res = await client.post('/documents.info', { id: this.id });
invariant(res && res.data, 'Document API response should be available'); invariant(res && res.data, 'Document API response should be available');
@ -145,7 +155,8 @@ class Document extends BaseModel {
} }
}; };
@action save = async () => { @action
save = async () => {
if (this.isSaving) return this; if (this.isSaving) return this;
this.isSaving = true; this.isSaving = true;
@ -196,7 +207,8 @@ class Document extends BaseModel {
return this; return this;
}; };
@action move = async (parentDocumentId: ?string) => { @action
move = async (parentDocumentId: ?string) => {
try { try {
const res = await client.post('/documents.move', { const res = await client.post('/documents.move', {
id: this.id, id: this.id,
@ -214,7 +226,8 @@ class Document extends BaseModel {
return; return;
}; };
@action delete = async () => { @action
delete = async () => {
try { try {
await client.post('/documents.delete', { id: this.id }); await client.post('/documents.delete', { id: this.id });
this.emit('documents.delete', { this.emit('documents.delete', {
@ -232,7 +245,9 @@ class Document extends BaseModel {
const a = window.document.createElement('a'); const a = window.document.createElement('a');
a.textContent = 'download'; a.textContent = 'download';
a.download = `${this.title}.md`; a.download = `${this.title}.md`;
a.href = `data:text/markdown;charset=UTF-8,${encodeURIComponent(this.text)}`; a.href = `data:text/markdown;charset=UTF-8,${encodeURIComponent(
this.text
)}`;
a.click(); a.click();
} }

View File

@ -20,7 +20,8 @@ type Props = {
match: Object, match: Object,
}; };
@observer class CollectionScene extends Component { @observer
class CollectionScene extends Component {
props: Props; props: Props;
@observable collection: ?Collection; @observable collection: ?Collection;
@observable isFetching = true; @observable isFetching = true;
@ -56,11 +57,8 @@ type Props = {
<NewDocumentContainer auto column justify="center"> <NewDocumentContainer auto column justify="center">
<h1>Create a document</h1> <h1>Create a document</h1>
<HelpText> <HelpText>
Publish your first document to start building the Publish your first document to start building the{' '}
{' '} <strong>{this.collection.name}</strong> collection.
<strong>{this.collection.name}</strong>
{' '}
collection.
</HelpText> </HelpText>
<Action> <Action>
<Link to={newDocumentUrl(this.collection)}> <Link to={newDocumentUrl(this.collection)}>
@ -76,9 +74,11 @@ type Props = {
return ( return (
<CenteredContent> <CenteredContent>
{this.isFetching {this.isFetching ? (
? <LoadingListPlaceholder /> <LoadingListPlaceholder />
: this.renderEmptyCollection()} ) : (
this.renderEmptyCollection()
)}
</CenteredContent> </CenteredContent>
); );
} }

View File

@ -17,7 +17,8 @@ type Props = {
onSubmit: () => void, onSubmit: () => void,
}; };
@observer class CollectionDelete extends Component { @observer
class CollectionDelete extends Component {
props: Props; props: Props;
@observable isDeleting: boolean; @observable isDeleting: boolean;
@ -42,12 +43,9 @@ type Props = {
<Flex column> <Flex column>
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<HelpText> <HelpText>
Are you sure? Deleting the Are you sure? Deleting the <strong>{collection.name}</strong>{' '}
{' '} collection is permanant and will also delete all of the documents
<strong>{collection.name}</strong> within it, so be careful with that.
{' '}
collection is permanant and will also delete all of the documents within
it, so be careful with that.
</HelpText> </HelpText>
<Button type="submit" danger> <Button type="submit" danger>
{this.isDeleting ? 'Deleting…' : 'Delete'} {this.isDeleting ? 'Deleting…' : 'Delete'}

View File

@ -16,7 +16,8 @@ type Props = {
onSubmit: () => void, onSubmit: () => void,
}; };
@observer class CollectionEdit extends Component { @observer
class CollectionEdit extends Component {
props: Props; props: Props;
@observable name: string; @observable name: string;
@observable color: string = ''; @observable color: string = '';

View File

@ -17,7 +17,8 @@ type Props = {
onSubmit: () => void, onSubmit: () => void,
}; };
@observer class CollectionNew extends Component { @observer
class CollectionNew extends Component {
props: Props; props: Props;
@observable collection: Collection; @observable collection: Collection;
@observable name: string = ''; @observable name: string = '';
@ -56,8 +57,9 @@ type Props = {
return ( return (
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<HelpText> <HelpText>
Collections are for grouping your Outline. They work best when organized Collections are for grouping your Outline. They work best when
around a topic or internal team Product or Engineering for example. organized around a topic or internal team Product or Engineering for
example.
</HelpText> </HelpText>
<Input <Input
type="text" type="text"

View File

@ -15,7 +15,7 @@ const Subheading = styled.h3`
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
text-transform: uppercase; text-transform: uppercase;
color: #9FA6AB; color: #9fa6ab;
letter-spacing: 0.04em; letter-spacing: 0.04em;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
padding-bottom: 10px; padding-bottom: 10px;
@ -26,7 +26,8 @@ type Props = {
documents: DocumentsStore, documents: DocumentsStore,
}; };
@observer class Dashboard extends Component { @observer
class Dashboard extends Component {
props: Props; props: Props;
@observable isLoaded: boolean = false; @observable isLoaded: boolean = false;
@ -53,20 +54,24 @@ type Props = {
<CenteredContent> <CenteredContent>
<PageTitle title="Home" /> <PageTitle title="Home" />
<h1>Home</h1> <h1>Home</h1>
{showContent {showContent ? (
? <Flex column> <Flex column>
{recentlyViewedLoaded && {recentlyViewedLoaded && (
<Flex column> <Flex column>
<Subheading>Recently viewed</Subheading> <Subheading>Recently viewed</Subheading>
<DocumentList documents={documents.recentlyViewed} /> <DocumentList documents={documents.recentlyViewed} />
</Flex>} </Flex>
{recentlyEditedLoaded && )}
<Flex column> {recentlyEditedLoaded && (
<Subheading>Recently edited</Subheading> <Flex column>
<DocumentList documents={documents.recentlyEdited} /> <Subheading>Recently edited</Subheading>
</Flex>} <DocumentList documents={documents.recentlyEdited} />
</Flex> </Flex>
: <ListPlaceholder count={5} />} )}
</Flex>
) : (
<ListPlaceholder count={5} />
)}
</CenteredContent> </CenteredContent>
); );
} }

View File

@ -51,7 +51,8 @@ type Props = {
ui: UiStore, ui: UiStore,
}; };
@observer class DocumentScene extends Component { @observer
class DocumentScene extends Component {
props: Props; props: Props;
savedTimeout: number; savedTimeout: number;
@ -219,66 +220,74 @@ type Props = {
{isMoving && document && <DocumentMove document={document} />} {isMoving && document && <DocumentMove document={document} />}
{titleText && <PageTitle title={titleText} />} {titleText && <PageTitle title={titleText} />}
{this.isLoading && <LoadingIndicator />} {this.isLoading && <LoadingIndicator />}
{isFetching && {isFetching && (
<CenteredContent> <CenteredContent>
<LoadingState /> <LoadingState />
</CenteredContent>} </CenteredContent>
)}
{!isFetching && {!isFetching &&
document && document && (
<Flex justify="center" auto> <Flex justify="center" auto>
<Prompt <Prompt
when={document.hasPendingChanges} when={document.hasPendingChanges}
message={DISCARD_CHANGES} message={DISCARD_CHANGES}
/> />
<Editor <Editor
key={document.id} key={document.id}
text={document.text} text={document.text}
emoji={document.emoji} emoji={document.emoji}
onImageUploadStart={this.onImageUploadStart} onImageUploadStart={this.onImageUploadStart}
onImageUploadStop={this.onImageUploadStop} onImageUploadStop={this.onImageUploadStop}
onChange={this.onChange} onChange={this.onChange}
onSave={this.onSave} onSave={this.onSave}
onCancel={this.onDiscard} onCancel={this.onDiscard}
readOnly={!this.isEditing} readOnly={!this.isEditing}
/> />
<Meta align="center" justify="flex-end" readOnly={!this.isEditing}> <Meta
<Flex align="center"> align="center"
{!isNew && justify="flex-end"
!this.isEditing && readOnly={!this.isEditing}
<Collaborators document={document} />} >
<HeaderAction> <Flex align="center">
{this.isEditing {!isNew &&
? <SaveAction !this.isEditing && <Collaborators document={document} />}
<HeaderAction>
{this.isEditing ? (
<SaveAction
isSaving={this.isSaving} isSaving={this.isSaving}
onClick={this.onSave.bind(this, true)} onClick={this.onSave.bind(this, true)}
disabled={ disabled={
!(this.document && this.document.allowSave) || !(this.document && this.document.allowSave) ||
this.isSaving this.isSaving
} }
isNew={!!isNew} isNew={!!isNew}
/> />
: <a onClick={this.onClickEdit}> ) : (
Edit <a onClick={this.onClickEdit}>Edit</a>
</a>} )}
</HeaderAction> </HeaderAction>
{this.isEditing && {this.isEditing && (
<HeaderAction>
<a onClick={this.onDiscard}>Discard</a>
</HeaderAction>
)}
{!this.isEditing && (
<HeaderAction>
<DocumentMenu document={document} />
</HeaderAction>
)}
{!this.isEditing && <Separator />}
<HeaderAction> <HeaderAction>
<a onClick={this.onDiscard}>Discard</a> {!this.isEditing && (
</HeaderAction>} <a onClick={this.onClickNew}>
{!this.isEditing && <NewDocumentIcon />
<HeaderAction> </a>
<DocumentMenu document={document} /> )}
</HeaderAction>} </HeaderAction>
{!this.isEditing && <Separator />} </Flex>
<HeaderAction> </Meta>
{!this.isEditing && </Flex>
<a onClick={this.onClickNew}> )}
<NewDocumentIcon />
</a>}
</HeaderAction>
</Flex>
</Meta>
</Flex>}
</Container> </Container>
); );
} }

View File

@ -28,14 +28,16 @@ type Props = {
collections: CollectionsStore, collections: CollectionsStore,
}; };
@observer class DocumentMove extends Component { @observer
class DocumentMove extends Component {
props: Props; props: Props;
firstDocument: HTMLElement; firstDocument: HTMLElement;
@observable searchTerm: ?string; @observable searchTerm: ?string;
@observable isSaving: boolean; @observable isSaving: boolean;
@computed get searchIndex() { @computed
get searchIndex() {
const { document, collections } = this.props; const { document, collections } = this.props;
const paths = collections.pathsToDocuments; const paths = collections.pathsToDocuments;
const index = new Search('id'); const index = new Search('id');
@ -54,7 +56,8 @@ type Props = {
return index; return index;
} }
@computed get results(): DocumentPath[] { @computed
get results(): DocumentPath[] {
const { document, collections } = this.props; const { document, collections } = this.props;
let results = []; let results = [];
@ -134,43 +137,46 @@ type Props = {
return ( return (
<Modal isOpen onRequestClose={this.handleClose} title="Move document"> <Modal isOpen onRequestClose={this.handleClose} title="Move document">
{document && {document &&
collections.isLoaded && collections.isLoaded && (
<Flex column> <Flex column>
<Section> <Section>
<Labeled label="Current location"> <Labeled label="Current location">
{this.renderPathToCurrentDocument()} {this.renderPathToCurrentDocument()}
</Labeled> </Labeled>
</Section> </Section>
<Section column> <Section column>
<Labeled label="Choose a new location"> <Labeled label="Choose a new location">
<Input <Input
type="text" type="text"
placeholder="Filter by document name…" placeholder="Filter by document name…"
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
onChange={this.handleFilter} onChange={this.handleFilter}
required required
autoFocus autoFocus
/> />
</Labeled> </Labeled>
<Flex column> <Flex column>
<StyledArrowKeyNavigation <StyledArrowKeyNavigation
mode={ArrowKeyNavigation.mode.VERTICAL} mode={ArrowKeyNavigation.mode.VERTICAL}
defaultActiveChildIndex={0} defaultActiveChildIndex={0}
> >
{this.results.map((result, index) => ( {this.results.map((result, index) => (
<PathToDocument <PathToDocument
key={result.id} key={result.id}
result={result} result={result}
document={document} document={document}
ref={ref => index === 0 && this.setFirstDocumentRef(ref)} ref={ref =>
onSuccess={this.handleClose} index === 0 && this.setFirstDocumentRef(ref)
/> }
))} onSuccess={this.handleClose}
</StyledArrowKeyNavigation> />
</Flex> ))}
</Section> </StyledArrowKeyNavigation>
</Flex>} </Flex>
</Section>
</Flex>
)}
</Modal> </Modal>
); );
} }

View File

@ -19,8 +19,7 @@ const ResultWrapper = styled.div`
cursor: default; cursor: default;
`; `;
const StyledGoToIcon = styled(GoToIcon)` const StyledGoToIcon = styled(GoToIcon)``;
`;
const ResultWrapperLink = ResultWrapper.withComponent('a').extend` const ResultWrapperLink = ResultWrapper.withComponent('a').extend`
height: 32px; height: 32px;
@ -50,7 +49,8 @@ type Props = {
ref?: Function, ref?: Function,
}; };
@observer class PathToDocument extends React.Component { @observer
class PathToDocument extends React.Component {
props: Props; props: Props;
handleClick = async (ev: SyntheticEvent) => { handleClick = async (ev: SyntheticEvent) => {
@ -83,12 +83,12 @@ type Props = {
{result.path {result.path
.map(doc => <span key={doc.id}>{doc.title}</span>) .map(doc => <span key={doc.id}>{doc.title}</span>)
.reduce((prev, curr) => [prev, <StyledGoToIcon />, curr])} .reduce((prev, curr) => [prev, <StyledGoToIcon />, curr])}
{document && {document && (
<Flex> <Flex>
{' '} {' '}
<StyledGoToIcon /> <StyledGoToIcon /> {document.title}
{' '}{document.title} </Flex>
</Flex>} )}
</Component> </Component>
); );
} }

View File

@ -16,7 +16,8 @@ type Props = {
onSubmit: () => void, onSubmit: () => void,
}; };
@observer class DocumentDelete extends Component { @observer
class DocumentDelete extends Component {
props: Props; props: Props;
@observable isDeleting: boolean; @observable isDeleting: boolean;
@ -41,10 +42,7 @@ type Props = {
<Flex column> <Flex column>
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<HelpText> <HelpText>
Are you sure? Deleting the Are you sure? Deleting the <strong>{document.title}</strong>{' '}
{' '}
<strong>{document.title}</strong>
{' '}
document is permanant and will also delete all of its history. document is permanant and will also delete all of its history.
</HelpText> </HelpText>
<Button type="submit" danger> <Button type="submit" danger>

View File

@ -14,7 +14,9 @@ class Error404 extends React.Component {
<p>We're unable to find the page you're accessing.</p> <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> <p>
Maybe you want to try <Link to="/search">search</Link> instead?
</p>
</CenteredContent> </CenteredContent>
); );
} }

View File

@ -9,75 +9,123 @@ function KeyboardShortcuts() {
return ( return (
<Flex column> <Flex column>
<HelpText> <HelpText>
Outline is designed to be super fast and easy to use. Outline is designed to be super fast and easy to use. All of your usual
All of your usual keyboard shortcuts work here, and there keyboard shortcuts work here, and there
{"'"} {"'"}
s Markdown too. s Markdown too.
</HelpText> </HelpText>
<h2>Navigation</h2> <h2>Navigation</h2>
<List> <List>
<Keys><Key>e</Key></Keys> <Keys>
<Key>e</Key>
</Keys>
<Label>Edit current document</Label> <Label>Edit current document</Label>
<Keys><Key>m</Key></Keys> <Keys>
<Key>m</Key>
</Keys>
<Label>Move current document</Label> <Label>Move current document</Label>
<Keys><Key>/</Key> or <Key>t</Key></Keys> <Keys>
<Key>/</Key> or <Key>t</Key>
</Keys>
<Label>Jump to search</Label> <Label>Jump to search</Label>
<Keys><Key>d</Key></Keys> <Keys>
<Key>d</Key>
</Keys>
<Label>Jump to dashboard</Label> <Label>Jump to dashboard</Label>
<Keys><Key></Key> + <Key>/</Key></Keys> <Keys>
<Key></Key> + <Key>/</Key>
</Keys>
<Label>Open this guide</Label> <Label>Open this guide</Label>
</List> </List>
<h2>Editor</h2> <h2>Editor</h2>
<List> <List>
<Keys><Key></Key> + <Key>Enter</Key></Keys> <Keys>
<Key></Key> + <Key>Enter</Key>
</Keys>
<Label>Save and exit document edit mode</Label> <Label>Save and exit document edit mode</Label>
<Keys><Key></Key> + <Key>S</Key></Keys> <Keys>
<Key></Key> + <Key>S</Key>
</Keys>
<Label>Save document and continue editing</Label> <Label>Save document and continue editing</Label>
<Keys><Key></Key> + <Key>Esc</Key></Keys> <Keys>
<Key></Key> + <Key>Esc</Key>
</Keys>
<Label>Cancel editing</Label> <Label>Cancel editing</Label>
<Keys><Key></Key> + <Key>b</Key></Keys> <Keys>
<Key></Key> + <Key>b</Key>
</Keys>
<Label>Bold</Label> <Label>Bold</Label>
<Keys><Key></Key> + <Key>i</Key></Keys> <Keys>
<Key></Key> + <Key>i</Key>
</Keys>
<Label>Italic</Label> <Label>Italic</Label>
<Keys><Key></Key> + <Key>u</Key></Keys> <Keys>
<Key></Key> + <Key>u</Key>
</Keys>
<Label>Underline</Label> <Label>Underline</Label>
<Keys><Key></Key> + <Key>d</Key></Keys> <Keys>
<Key></Key> + <Key>d</Key>
</Keys>
<Label>Strikethrough</Label> <Label>Strikethrough</Label>
<Keys><Key></Key> + <Key>k</Key></Keys> <Keys>
<Key></Key> + <Key>k</Key>
</Keys>
<Label>Link</Label> <Label>Link</Label>
<Keys><Key></Key> + <Key>z</Key></Keys> <Keys>
<Key></Key> + <Key>z</Key>
</Keys>
<Label>Undo</Label> <Label>Undo</Label>
<Keys><Key></Key> + <Key>Shift</Key> + <Key>z</Key></Keys> <Keys>
<Key></Key> + <Key>Shift</Key> + <Key>z</Key>
</Keys>
<Label>Redo</Label> <Label>Redo</Label>
</List> </List>
<h2>Markdown</h2> <h2>Markdown</h2>
<List> <List>
<Keys><Key>#</Key> <Key>Space</Key></Keys> <Keys>
<Key>#</Key> <Key>Space</Key>
</Keys>
<Label>Large header</Label> <Label>Large header</Label>
<Keys><Key>##</Key> <Key>Space</Key></Keys> <Keys>
<Key>##</Key> <Key>Space</Key>
</Keys>
<Label>Medium header</Label> <Label>Medium header</Label>
<Keys><Key>###</Key> <Key>Space</Key></Keys> <Keys>
<Key>###</Key> <Key>Space</Key>
</Keys>
<Label>Small header</Label> <Label>Small header</Label>
<Keys><Key>1.</Key> <Key>Space</Key></Keys> <Keys>
<Key>1.</Key> <Key>Space</Key>
</Keys>
<Label>Numbered list</Label> <Label>Numbered list</Label>
<Keys><Key>-</Key> <Key>Space</Key></Keys> <Keys>
<Key>-</Key> <Key>Space</Key>
</Keys>
<Label>Bulleted list</Label> <Label>Bulleted list</Label>
<Keys><Key>[ ]</Key> <Key>Space</Key></Keys> <Keys>
<Key>[ ]</Key> <Key>Space</Key>
</Keys>
<Label>Todo list</Label> <Label>Todo list</Label>
<Keys><Key>&gt;</Key> <Key>Space</Key></Keys> <Keys>
<Key>&gt;</Key> <Key>Space</Key>
</Keys>
<Label>Blockquote</Label> <Label>Blockquote</Label>
<Keys><Key>---</Key></Keys> <Keys>
<Key>---</Key>
</Keys>
<Label>Horizontal divider</Label> <Label>Horizontal divider</Label>
<Keys><Key>{'```'}</Key></Keys> <Keys>
<Key>{'```'}</Key>
</Keys>
<Label>Code block</Label> <Label>Code block</Label>
<Keys>_italic_</Keys> <Keys>_italic_</Keys>
@ -97,21 +145,21 @@ const List = styled.dl`
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
padding: 0; padding: 0;
margin: 0 margin: 0;
`; `;
const Keys = styled.dt` const Keys = styled.dt`
float: left; float: left;
width: 25%; width: 25%;
padding: 0 0 4px; padding: 0 0 4px;
margin: 0 margin: 0;
`; `;
const Label = styled.dd` const Label = styled.dd`
float: left; float: left;
width: 75%; width: 75%;
padding: 0 0 4px; padding: 0 0 4px;
margin: 0 margin: 0;
`; `;
export default KeyboardShortcuts; export default KeyboardShortcuts;

View File

@ -54,7 +54,8 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
flex: 1; flex: 1;
`; `;
@observer class Search extends React.Component { @observer
class Search extends React.Component {
firstDocument: HTMLElement; firstDocument: HTMLElement;
props: Props; props: Props;
@ -99,7 +100,8 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
this.search(this.props.match.params.query); this.search(this.props.match.params.query);
}, 250); }, 250);
@action search = async (query: string) => { @action
search = async (query: string) => {
this.searchTerm = query; this.searchTerm = query;
this.isFetching = true; this.isFetching = true;
@ -141,11 +143,12 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
<Container auto> <Container auto>
<PageTitle title={this.title} /> <PageTitle title={this.title} />
{this.isFetching && <LoadingIndicator />} {this.isFetching && <LoadingIndicator />}
{notFound && {notFound && (
<div> <div>
<h1>Not Found</h1> <h1>Not Found</h1>
<p>Were unable to find the page youre accessing.</p> <p>Were unable to find the page youre accessing.</p>
</div>} </div>
)}
<ResultsWrapper pinToTop={hasResults} column auto> <ResultsWrapper pinToTop={hasResults} column auto>
<SearchField <SearchField
searchTerm={this.searchTerm} searchTerm={this.searchTerm}
@ -165,7 +168,8 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
return ( return (
<DocumentPreview <DocumentPreview
innerRef={ref => innerRef={ref =>
index === 0 && this.setFirstDocumentRef(ref)} index === 0 && this.setFirstDocumentRef(ref)
}
key={documentId} key={documentId}
document={document} document={document}
highlight={this.searchTerm} highlight={this.searchTerm}

View File

@ -53,10 +53,18 @@ const StyledInput = styled.input`
outline: none; outline: none;
border: 0; border: 0;
::-webkit-input-placeholder { color: ${color.slateLight}; } ::-webkit-input-placeholder {
:-moz-placeholder { color: ${color.slateLight}; } color: ${color.slateLight};
::-moz-placeholder { color: ${color.slateLight}; } }
:-ms-input-placeholder { color: ${color.slateLight}; } :-moz-placeholder {
color: ${color.slateLight};
}
::-moz-placeholder {
color: ${color.slateLight};
}
:-ms-input-placeholder {
color: ${color.slateLight};
}
`; `;
const StyledIcon = styled(SearchIcon)` const StyledIcon = styled(SearchIcon)`

View File

@ -14,7 +14,8 @@ import HelpText from 'components/HelpText';
import { Label } from 'components/Labeled'; import { Label } from 'components/Labeled';
import SlackAuthLink from 'components/SlackAuthLink'; import SlackAuthLink from 'components/SlackAuthLink';
@observer class Settings extends React.Component { @observer
class Settings extends React.Component {
store: SettingsStore; store: SettingsStore;
constructor() { constructor() {
@ -27,12 +28,12 @@ import SlackAuthLink from 'components/SlackAuthLink';
return ( return (
<Flex column> <Flex column>
{showSlackSettings && {showSlackSettings && (
<Section> <Section>
<SectionLabel>Slack</SectionLabel> <SectionLabel>Slack</SectionLabel>
<HelpText> <HelpText>
Connect Outline to your Slack to instantly search for your documents Connect Outline to your Slack to instantly search for your
using <Code>/outline</Code> command. documents using <Code>/outline</Code> command.
</HelpText> </HelpText>
<SlackAuthLink <SlackAuthLink
@ -47,16 +48,17 @@ import SlackAuthLink from 'components/SlackAuthLink';
srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" 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>
</Section>} </Section>
)}
<Section> <Section>
<SectionLabel>API Access</SectionLabel> <SectionLabel>API Access</SectionLabel>
<HelpText> <HelpText>
Create API tokens to hack on your Outline. Create API tokens to hack on your Outline. Learn more in{' '}
Learn more in <Link to="/developers">API documentation</Link>. <Link to="/developers">API documentation</Link>.
</HelpText> </HelpText>
{this.store.apiKeys && {this.store.apiKeys && (
<Table> <Table>
<tbody> <tbody>
{this.store.apiKeys && {this.store.apiKeys &&
@ -70,7 +72,8 @@ import SlackAuthLink from 'components/SlackAuthLink';
/> />
))} ))}
</tbody> </tbody>
</Table>} </Table>
)}
<InlineForm <InlineForm
placeholder="Token name" placeholder="Token name"
buttonLabel="Create token" buttonLabel="Create token"
@ -162,13 +165,13 @@ const Table = styled.table`
const SectionLabel = styled(Label)` const SectionLabel = styled(Label)`
padding-bottom: 12px; padding-bottom: 12px;
margin-bottom: 20px; margin-bottom: 20px;
border-bottom: 1px solid #EAEBEA; border-bottom: 1px solid #eaebea;
`; `;
const Code = styled.code` const Code = styled.code`
padding: 4px 6px; padding: 4px 6px;
margin: 0 2px; margin: 0 2px;
background: #EAEBEA; background: #eaebea;
border-radius: 4px; border-radius: 4px;
`; `;

View File

@ -10,7 +10,8 @@ class SearchStore {
@observable isFetching: boolean = false; @observable isFetching: boolean = false;
@action fetchApiKeys = async () => { @action
fetchApiKeys = async () => {
this.isFetching = true; this.isFetching = true;
try { try {
@ -27,7 +28,8 @@ class SearchStore {
this.isFetching = false; this.isFetching = false;
}; };
@action createApiKey = async () => { @action
createApiKey = async () => {
this.isFetching = true; this.isFetching = true;
try { try {
@ -46,7 +48,8 @@ class SearchStore {
this.isFetching = false; this.isFetching = false;
}; };
@action deleteApiKey = async (id: string) => { @action
deleteApiKey = async (id: string) => {
this.isFetching = true; this.isFetching = true;
try { try {
@ -62,7 +65,8 @@ class SearchStore {
this.isFetching = false; this.isFetching = false;
}; };
@action setKeyName = (value: SyntheticInputEvent) => { @action
setKeyName = (value: SyntheticInputEvent) => {
this.keyName = value.target.value; this.keyName = value.target.value;
}; };

View File

@ -12,7 +12,8 @@ type Props = {
onDelete: Function, onDelete: Function,
}; };
@observer class ApiKeyRow extends React.Component { @observer
class ApiKeyRow extends React.Component {
props: Props; props: Props;
@observable disabled: boolean; @observable disabled: boolean;
@ -28,7 +29,9 @@ type Props = {
return ( return (
<tr> <tr>
<td>{name}</td> <td>{name}</td>
<td><code>{secret}</code></td> <td>
<code>{secret}</code>
</td>
<td> <td>
<Action role="button" onClick={this.onClick} disabled={disabled}> <Action role="button" onClick={this.onClick} disabled={disabled}>
Action Action

View File

@ -15,7 +15,8 @@ type Props = {
location: Location, location: Location,
}; };
@observer class SlackAuth extends React.Component { @observer
class SlackAuth extends React.Component {
props: Props; props: Props;
@observable redirectTo: string; @observable redirectTo: string;

View File

@ -8,7 +8,8 @@ import PageTitle from 'components/PageTitle';
import DocumentList from 'components/DocumentList'; import DocumentList from 'components/DocumentList';
import DocumentsStore from 'stores/DocumentsStore'; import DocumentsStore from 'stores/DocumentsStore';
@observer class Starred extends Component { @observer
class Starred extends Component {
props: { props: {
documents: DocumentsStore, documents: DocumentsStore,
}; };

View File

@ -16,11 +16,13 @@ class AuthStore {
/* Computed */ /* Computed */
@computed get authenticated(): boolean { @computed
get authenticated(): boolean {
return !!this.token; return !!this.token;
} }
@computed get asJson(): string { @computed
get asJson(): string {
return JSON.stringify({ return JSON.stringify({
user: this.user, user: this.user,
team: this.team, team: this.team,
@ -31,19 +33,24 @@ class AuthStore {
/* Actions */ /* Actions */
@action logout = () => { @action
logout = () => {
this.user = null; this.user = null;
this.token = null; this.token = null;
Cookie.remove('loggedIn', { path: '/' }); Cookie.remove('loggedIn', { path: '/' });
}; };
@action getOauthState = () => { @action
const state = Math.random().toString(36).substring(7); getOauthState = () => {
const state = Math.random()
.toString(36)
.substring(7);
this.oauthState = state; this.oauthState = state;
return this.oauthState; return this.oauthState;
}; };
@action authWithSlack = async (code: string, state: string) => { @action
authWithSlack = async (code: string, state: string) => {
if (state !== this.oauthState) { if (state !== this.oauthState) {
return { return {
success: false, success: false,

View File

@ -43,20 +43,23 @@ class CollectionsStore {
cache: CacheStore; cache: CacheStore;
ui: UiStore; ui: UiStore;
@computed get active(): ?Collection { @computed
get active(): ?Collection {
return this.ui.activeCollectionId return this.ui.activeCollectionId
? this.getById(this.ui.activeCollectionId) ? this.getById(this.ui.activeCollectionId)
: undefined; : undefined;
} }
@computed get orderedData(): Collection[] { @computed
get orderedData(): Collection[] {
return _.sortBy(this.data, 'name'); return _.sortBy(this.data, 'name');
} }
/** /**
* List of paths to each of the documents, where paths are composed of id and title/name pairs * List of paths to each of the documents, where paths are composed of id and title/name pairs
*/ */
@computed get pathsToDocuments(): Array<DocumentPath> { @computed
get pathsToDocuments(): Array<DocumentPath> {
let results = []; let results = [];
const travelDocuments = (documentList, path) => const travelDocuments = (documentList, path) =>
documentList.forEach(document => { documentList.forEach(document => {
@ -95,7 +98,8 @@ class CollectionsStore {
/* Actions */ /* Actions */
@action fetchAll = async (): Promise<*> => { @action
fetchAll = async (): Promise<*> => {
try { try {
const res = await this.client.post('/collections.list', { const res = await this.client.post('/collections.list', {
id: this.teamId, id: this.teamId,
@ -111,7 +115,8 @@ class CollectionsStore {
} }
}; };
@action fetchById = async (id: string): Promise<?Collection> => { @action
fetchById = async (id: string): Promise<?Collection> => {
let collection = this.getById(id); let collection = this.getById(id);
if (!collection) { if (!collection) {
try { try {
@ -133,11 +138,13 @@ class CollectionsStore {
return collection; return collection;
}; };
@action add = (collection: Collection): void => { @action
add = (collection: Collection): void => {
this.data.push(collection); this.data.push(collection);
}; };
@action remove = (id: string): void => { @action
remove = (id: string): void => {
this.data.splice(this.data.indexOf(id), 1); this.data.splice(this.data.indexOf(id), 1);
}; };

View File

@ -37,7 +37,8 @@ class DocumentsStore extends BaseStore {
/* Computed */ /* Computed */
@computed get recentlyViewed(): Array<Document> { @computed
get recentlyViewed(): Array<Document> {
return _.take( return _.take(
_.filter(this.data.values(), ({ id }) => _.filter(this.data.values(), ({ id }) =>
this.recentlyViewedIds.includes(id) this.recentlyViewedIds.includes(id)
@ -46,15 +47,18 @@ class DocumentsStore extends BaseStore {
); );
} }
@computed get recentlyEdited(): Array<Document> { @computed
get recentlyEdited(): Array<Document> {
return _.take(_.orderBy(this.data.values(), 'updatedAt', 'desc'), 5); return _.take(_.orderBy(this.data.values(), 'updatedAt', 'desc'), 5);
} }
@computed get starred(): Array<Document> { @computed
get starred(): Array<Document> {
return _.filter(this.data.values(), 'starred'); return _.filter(this.data.values(), 'starred');
} }
@computed get active(): ?Document { @computed
get active(): ?Document {
return this.ui.activeDocumentId return this.ui.activeDocumentId
? this.getById(this.ui.activeDocumentId) ? this.getById(this.ui.activeDocumentId)
: undefined; : undefined;
@ -62,10 +66,8 @@ class DocumentsStore extends BaseStore {
/* Actions */ /* Actions */
@action fetchAll = async ( @action
request: string = 'list', fetchAll = async (request: string = 'list', options: ?Object): Promise<*> => {
options: ?Object
): Promise<*> => {
this.isFetching = true; this.isFetching = true;
try { try {
@ -86,11 +88,13 @@ class DocumentsStore extends BaseStore {
} }
}; };
@action fetchRecentlyModified = async (options: ?Object): Promise<*> => { @action
fetchRecentlyModified = async (options: ?Object): Promise<*> => {
return await this.fetchAll('list', options); return await this.fetchAll('list', options);
}; };
@action fetchRecentlyViewed = async (options: ?Object): Promise<*> => { @action
fetchRecentlyViewed = async (options: ?Object): Promise<*> => {
const data = await this.fetchAll('viewed', options); const data = await this.fetchAll('viewed', options);
runInAction('DocumentsStore#fetchRecentlyViewed', () => { runInAction('DocumentsStore#fetchRecentlyViewed', () => {
@ -99,11 +103,13 @@ class DocumentsStore extends BaseStore {
return data; return data;
}; };
@action fetchStarred = async (): Promise<*> => { @action
fetchStarred = async (): Promise<*> => {
await this.fetchAll('starred'); await this.fetchAll('starred');
}; };
@action search = async (query: string): Promise<*> => { @action
search = async (query: string): Promise<*> => {
const res = await client.get('/documents.search', { query }); const res = await client.get('/documents.search', { query });
invariant(res && res.data, 'res or res.data missing'); invariant(res && res.data, 'res or res.data missing');
const { data } = res; const { data } = res;
@ -111,11 +117,13 @@ class DocumentsStore extends BaseStore {
return data.map(documentData => documentData.id); return data.map(documentData => documentData.id);
}; };
@action prefetchDocument = async (id: string) => { @action
prefetchDocument = async (id: string) => {
if (!this.getById(id)) this.fetch(id, true); if (!this.getById(id)) this.fetch(id, true);
}; };
@action fetch = async (id: string, prefetch?: boolean): Promise<*> => { @action
fetch = async (id: string, prefetch?: boolean): Promise<*> => {
if (!prefetch) this.isFetching = true; if (!prefetch) this.isFetching = true;
try { try {
@ -137,11 +145,13 @@ class DocumentsStore extends BaseStore {
} }
}; };
@action add = (document: Document): void => { @action
add = (document: Document): void => {
this.data.set(document.id, document); this.data.set(document.id, document);
}; };
@action remove = (id: string): void => { @action
remove = (id: string): void => {
this.data.delete(id); this.data.delete(id);
}; };

View File

@ -6,11 +6,13 @@ class ErrorsStore {
/* Actions */ /* Actions */
@action add = (message: string): void => { @action
add = (message: string): void => {
this.data.push(message); this.data.push(message);
}; };
@action remove = (index: number): void => { @action
remove = (index: number): void => {
this.data.splice(index, 1); this.data.splice(index, 1);
}; };
} }

View File

@ -11,39 +11,47 @@ class UiStore {
@observable editMode: boolean = false; @observable editMode: boolean = false;
/* Actions */ /* Actions */
@action setActiveModal = (name: string, props: ?Object): void => { @action
setActiveModal = (name: string, props: ?Object): void => {
this.activeModalName = name; this.activeModalName = name;
this.activeModalProps = props; this.activeModalProps = props;
}; };
@action clearActiveModal = (): void => { @action
clearActiveModal = (): void => {
this.activeModalName = undefined; this.activeModalName = undefined;
this.activeModalProps = undefined; this.activeModalProps = undefined;
}; };
@action setActiveDocument = (document: Document): void => { @action
setActiveDocument = (document: Document): void => {
this.activeDocumentId = document.id; this.activeDocumentId = document.id;
this.activeCollectionId = document.collection.id; this.activeCollectionId = document.collection.id;
}; };
@action clearActiveDocument = (): void => { @action
clearActiveDocument = (): void => {
this.activeDocumentId = undefined; this.activeDocumentId = undefined;
this.activeCollectionId = undefined; this.activeCollectionId = undefined;
}; };
@action enableEditMode() { @action
enableEditMode() {
this.editMode = true; this.editMode = true;
} }
@action disableEditMode() { @action
disableEditMode() {
this.editMode = false; this.editMode = false;
} }
@action enableProgressBar() { @action
enableProgressBar() {
this.progressBarVisible = true; this.progressBarVisible = true;
} }
@action disableProgressBar() { @action
disableProgressBar() {
this.progressBarVisible = false; this.progressBarVisible = false;
} }
} }