Upgrade prettier
This commit is contained in:
@ -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',
|
||||||
|
@ -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 = {
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -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`
|
||||||
|
@ -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}
|
||||||
|
@ -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}>
|
in <strong>{collection.name}</strong>
|
||||||
{' '}
|
</span>
|
||||||
modified
|
)}
|
||||||
{' '}
|
|
||||||
{moment(updatedAt).fromNow()}
|
|
||||||
</Modified>
|
|
||||||
</span>}
|
|
||||||
{collection && <span> in <strong>{collection.name}</strong></span>}
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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)`
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -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;
|
||||||
`}
|
`};
|
||||||
`;
|
`;
|
||||||
|
@ -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;
|
||||||
|
@ -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};
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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};
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
`;
|
`;
|
||||||
|
@ -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%;
|
||||||
|
@ -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;
|
||||||
`}
|
`};
|
||||||
`;
|
`;
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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;
|
||||||
`;
|
`;
|
||||||
|
@ -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;
|
||||||
|
@ -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;'};
|
||||||
`;
|
`;
|
||||||
|
@ -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;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -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();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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}
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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 = {
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
@ -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 {
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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'}
|
||||||
|
@ -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 = '';
|
||||||
|
@ -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"
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>></Key> <Key>Space</Key></Keys>
|
<Keys>
|
||||||
|
<Key>></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;
|
||||||
|
@ -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>We’re unable to find the page you’re accessing.</p>
|
<p>We’re unable to find the page you’re 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}
|
||||||
|
@ -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)`
|
||||||
|
@ -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;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user