feat: Keyboard shortcut reference inside editor

This commit is contained in:
Tom Moor
2019-08-08 21:13:58 -07:00
parent 789a1acea1
commit a26ae119fe
7 changed files with 167 additions and 57 deletions

View File

@ -21,6 +21,8 @@ import Sidebar from 'components/Sidebar';
import SettingsSidebar from 'components/Sidebar/Settings'; import SettingsSidebar from 'components/Sidebar/Settings';
import Modals from 'components/Modals'; import Modals from 'components/Modals';
import DocumentHistory from 'components/DocumentHistory'; import DocumentHistory from 'components/DocumentHistory';
import Modal from 'components/Modal';
import KeyboardShortcuts from 'scenes/KeyboardShortcuts';
import ErrorSuspended from 'scenes/ErrorSuspended'; import ErrorSuspended from 'scenes/ErrorSuspended';
import AuthStore from 'stores/AuthStore'; import AuthStore from 'stores/AuthStore';
import UiStore from 'stores/UiStore'; import UiStore from 'stores/UiStore';
@ -41,6 +43,7 @@ type Props = {
class Layout extends React.Component<Props> { class Layout extends React.Component<Props> {
scrollable: ?HTMLDivElement; scrollable: ?HTMLDivElement;
@observable redirectTo: ?string; @observable redirectTo: ?string;
@observable keyboardShortcutsOpen: boolean = false;
componentWillMount() { componentWillMount() {
this.updateBackground(); this.updateBackground();
@ -54,6 +57,15 @@ class Layout extends React.Component<Props> {
} }
} }
@keydown('shift+/')
handleOpenKeyboardShortcuts() {
this.keyboardShortcutsOpen = true;
}
handleCloseKeyboardShortcuts = () => {
this.keyboardShortcutsOpen = false;
};
updateBackground() { updateBackground() {
// ensure the wider page color always matches the theme // ensure the wider page color always matches the theme
window.document.body.style.background = this.props.theme.background; window.document.body.style.background = this.props.theme.background;
@ -71,11 +83,6 @@ class Layout extends React.Component<Props> {
this.redirectTo = homeUrl(); this.redirectTo = homeUrl();
} }
@keydown('shift+/')
openKeyboardShortcuts() {
this.props.ui.setActiveModal('keyboard-shortcuts');
}
render() { render() {
const { auth, ui } = this.props; const { auth, ui } = this.props;
const { user, team } = auth; const { user, team } = auth;
@ -118,6 +125,13 @@ class Layout extends React.Component<Props> {
</Switch> </Switch>
</Container> </Container>
<Modals ui={ui} /> <Modals ui={ui} />
<Modal
isOpen={this.keyboardShortcutsOpen}
onRequestClose={this.handleCloseKeyboardShortcuts}
title="Keyboard shortcuts"
>
<KeyboardShortcuts />
</Modal>
<GlobalStyles /> <GlobalStyles />
</Container> </Container>
); );

View File

@ -9,7 +9,6 @@ import CollectionDelete from 'scenes/CollectionDelete';
import CollectionExport from 'scenes/CollectionExport'; import CollectionExport from 'scenes/CollectionExport';
import DocumentDelete from 'scenes/DocumentDelete'; import DocumentDelete from 'scenes/DocumentDelete';
import DocumentShare from 'scenes/DocumentShare'; import DocumentShare from 'scenes/DocumentShare';
import KeyboardShortcuts from 'scenes/KeyboardShortcuts';
type Props = { type Props = {
ui: UiStore, ui: UiStore,
@ -55,9 +54,6 @@ class Modals extends React.Component<Props> {
<Modal name="document-delete" title="Delete document"> <Modal name="document-delete" title="Delete document">
<DocumentDelete onSubmit={this.handleClose} /> <DocumentDelete onSubmit={this.handleClose} />
</Modal> </Modal>
<Modal name="keyboard-shortcuts" title="Keyboard shortcuts">
<KeyboardShortcuts />
</Modal>
</span> </span>
); );
} }

View File

@ -9,6 +9,7 @@ type Props = {
children: React.Node, children: React.Node,
delay?: number, delay?: number,
className?: string, className?: string,
block?: boolean,
}; };
class Tooltip extends React.Component<Props> { class Tooltip extends React.Component<Props> {
@ -17,7 +18,15 @@ class Tooltip extends React.Component<Props> {
} }
render() { render() {
const { tooltip, delay = 50, children, className, ...rest } = this.props; const {
tooltip,
delay = 50,
children,
block,
className,
...rest
} = this.props;
const Component = block ? 'div' : 'span';
return ( return (
<StyledTippy <StyledTippy
@ -30,7 +39,7 @@ class Tooltip extends React.Component<Props> {
inertia inertia
{...rest} {...rest}
> >
<span className={className}>{children}</span> <Component className={className}>{children}</Component>
</StyledTippy> </StyledTippy>
); );
} }

View File

@ -1,6 +1,7 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { observable } from 'mobx';
import { inject, observer } from 'mobx-react'; import { inject, observer } from 'mobx-react';
import { MoonIcon } from 'outline-icons'; import { MoonIcon } from 'outline-icons';
import styled, { withTheme } from 'styled-components'; import styled, { withTheme } from 'styled-components';
@ -8,6 +9,8 @@ import UiStore from 'stores/UiStore';
import AuthStore from 'stores/AuthStore'; import AuthStore from 'stores/AuthStore';
import Flex from 'shared/components/Flex'; import Flex from 'shared/components/Flex';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
import Modal from 'components/Modal';
import KeyboardShortcuts from 'scenes/KeyboardShortcuts';
import { import {
developers, developers,
changelog, changelog,
@ -26,57 +29,74 @@ type Props = {
@observer @observer
class AccountMenu extends React.Component<Props> { class AccountMenu extends React.Component<Props> {
handleOpenKeyboardShortcuts = () => { @observable keyboardShortcutsOpen: boolean = false;
this.props.ui.setActiveModal('keyboard-shortcuts');
};
handleLogout = () => { handleLogout = () => {
this.props.auth.logout(); this.props.auth.logout();
}; };
handleOpenKeyboardShortcuts = () => {
this.keyboardShortcutsOpen = true;
};
handleCloseKeyboardShortcuts = () => {
this.keyboardShortcutsOpen = false;
};
render() { render() {
const { ui, theme } = this.props; const { ui, theme } = this.props;
const isLightTheme = ui.theme === 'light'; const isLightTheme = ui.theme === 'light';
return ( return (
<DropdownMenu <React.Fragment>
style={{ marginRight: 10, marginTop: -10 }} <Modal
label={this.props.label} isOpen={this.keyboardShortcutsOpen}
> onRequestClose={this.handleCloseKeyboardShortcuts}
<DropdownMenuItem as={Link} to={settings()}> title="Keyboard shortcuts"
Settings >
</DropdownMenuItem> <KeyboardShortcuts />
<DropdownMenuItem onClick={this.handleOpenKeyboardShortcuts}> </Modal>
Keyboard shortcuts <DropdownMenu
</DropdownMenuItem> style={{ marginRight: 10, marginTop: -10 }}
<DropdownMenuItem href={developers()} target="_blank"> label={this.props.label}
API documentation >
</DropdownMenuItem> <DropdownMenuItem as={Link} to={settings()}>
<hr /> Settings
<DropdownMenuItem href={changelog()} target="_blank"> </DropdownMenuItem>
Changelog <DropdownMenuItem onClick={this.handleOpenKeyboardShortcuts}>
</DropdownMenuItem> Keyboard shortcuts
<DropdownMenuItem href={spectrumUrl()} target="_blank"> </DropdownMenuItem>
Community <DropdownMenuItem href={developers()} target="_blank">
</DropdownMenuItem> API documentation
<DropdownMenuItem href={mailToUrl()} target="_blank"> </DropdownMenuItem>
Send us feedback <hr />
</DropdownMenuItem> <DropdownMenuItem href={changelog()} target="_blank">
<DropdownMenuItem href={githubIssuesUrl()} target="_blank"> Changelog
Report a bug </DropdownMenuItem>
</DropdownMenuItem> <DropdownMenuItem href={spectrumUrl()} target="_blank">
<hr /> Community
<DropdownMenuItem onClick={ui.toggleDarkMode}> </DropdownMenuItem>
<NightMode justify="space-between"> <DropdownMenuItem href={mailToUrl()} target="_blank">
Night Mode{' '} Send us feedback
<MoonIcon </DropdownMenuItem>
color={isLightTheme ? theme.textSecondary : theme.primary} <DropdownMenuItem href={githubIssuesUrl()} target="_blank">
/> Report a bug
</NightMode> </DropdownMenuItem>
</DropdownMenuItem> <hr />
<hr /> <DropdownMenuItem onClick={ui.toggleDarkMode}>
<DropdownMenuItem onClick={this.handleLogout}>Log out</DropdownMenuItem> <NightMode justify="space-between">
</DropdownMenu> Night Mode{' '}
<MoonIcon
color={isLightTheme ? theme.textSecondary : theme.primary}
/>
</NightMode>
</DropdownMenuItem>
<hr />
<DropdownMenuItem onClick={this.handleLogout}>
Log out
</DropdownMenuItem>
</DropdownMenu>
</React.Fragment>
); );
} }
} }

View File

@ -22,6 +22,7 @@ import { emojiToUrl } from 'utils/emoji';
import Header from './components/Header'; import Header from './components/Header';
import DocumentMove from './components/DocumentMove'; import DocumentMove from './components/DocumentMove';
import Branding from './components/Branding'; import Branding from './components/Branding';
import KeyboardShortcuts from './components/KeyboardShortcuts';
import Backlinks from './components/Backlinks'; import Backlinks from './components/Backlinks';
import ErrorBoundary from 'components/ErrorBoundary'; import ErrorBoundary from 'components/ErrorBoundary';
import LoadingPlaceholder from 'components/LoadingPlaceholder'; import LoadingPlaceholder from 'components/LoadingPlaceholder';
@ -426,7 +427,7 @@ class DocumentScene extends React.Component<Props> {
</MaxWidth> </MaxWidth>
</Container> </Container>
</Container> </Container>
{isShare && <Branding />} {isShare ? <Branding /> : <KeyboardShortcuts />}
</ErrorBoundary> </ErrorBoundary>
); );
} }

View File

@ -0,0 +1,72 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import { KeyboardIcon } from 'outline-icons';
import Modal from 'components/Modal';
import Tooltip from 'components/Tooltip';
import KeyboardShortcuts from 'scenes/KeyboardShortcuts';
type Props = {};
@observer
class KeyboardShortcutsButton extends React.Component<Props> {
@observable keyboardShortcutsOpen: boolean = false;
handleOpenKeyboardShortcuts = () => {
this.keyboardShortcutsOpen = true;
};
handleCloseKeyboardShortcuts = () => {
this.keyboardShortcutsOpen = false;
};
render() {
return (
<React.Fragment>
<Modal
isOpen={this.keyboardShortcutsOpen}
onRequestClose={this.handleCloseKeyboardShortcuts}
title="Keyboard shortcuts"
>
<KeyboardShortcuts />
</Modal>
<Button onClick={this.handleOpenKeyboardShortcuts}>
<Tooltip tooltip="Keyboard shortcuts" placement="left" block>
<KeyboardIcon />
</Tooltip>
</Button>
</React.Fragment>
);
}
}
const Button = styled.button`
display: none;
position: fixed;
bottom: 0;
right: 0;
margin: 24px;
width: 24px;
height: 24px;
opacity: 0.8;
background: none;
line-height: 0;
border: 0;
&:hover {
opacity: 1;
}
${breakpoint('tablet')`
display: block;
`};
@media print {
display: none;
}
`;
export default KeyboardShortcutsButton;

View File

@ -10,10 +10,8 @@ function KeyboardShortcuts() {
return ( return (
<Flex column> <Flex column>
<HelpText> <HelpText>
Outline is designed to be super fast and easy to use. All of your usual Outline is designed to be fast and easy to use. All of your usual
keyboard shortcuts work here, and there keyboard shortcuts work here, and theres Markdown too.
{"'"}
s Markdown too.
</HelpText> </HelpText>
<h2>Navigation</h2> <h2>Navigation</h2>