feat: New keyboard shortcuts guide (#2051)
* feat: Add search * feat: New design for keyboard shortcuts guide feat: Include quick search fix: Add missing shortcuts * tweaks * fix: Two other spots that should trigger guide-style instead of modal * sink,lift -> indent,outdent * fix: Animation should slide out as well as in
This commit is contained in:
parent
50fdd73610
commit
2ffc0ae81c
|
@ -0,0 +1,112 @@
|
||||||
|
// @flow
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Dialog, DialogBackdrop, useDialogState } from "reakit/Dialog";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import Scrollable from "components/Scrollable";
|
||||||
|
import usePrevious from "hooks/usePrevious";
|
||||||
|
|
||||||
|
type Props = {|
|
||||||
|
children?: React.Node,
|
||||||
|
isOpen: boolean,
|
||||||
|
title?: string,
|
||||||
|
onRequestClose: () => void,
|
||||||
|
|};
|
||||||
|
|
||||||
|
const Guide = ({
|
||||||
|
children,
|
||||||
|
isOpen,
|
||||||
|
title = "Untitled",
|
||||||
|
onRequestClose,
|
||||||
|
...rest
|
||||||
|
}: Props) => {
|
||||||
|
const dialog = useDialogState({ animated: 250 });
|
||||||
|
const wasOpen = usePrevious(isOpen);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!wasOpen && isOpen) {
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
if (wasOpen && !isOpen) {
|
||||||
|
dialog.hide();
|
||||||
|
}
|
||||||
|
}, [dialog, wasOpen, isOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogBackdrop {...dialog}>
|
||||||
|
{(props) => (
|
||||||
|
<Backdrop {...props}>
|
||||||
|
<Dialog
|
||||||
|
{...dialog}
|
||||||
|
aria-label={title}
|
||||||
|
preventBodyScrollhideOnEsc
|
||||||
|
hide={onRequestClose}
|
||||||
|
>
|
||||||
|
{(props) => (
|
||||||
|
<Scene {...props} {...rest}>
|
||||||
|
<Content>
|
||||||
|
{title && <Header>{title}</Header>}
|
||||||
|
{children}
|
||||||
|
</Content>
|
||||||
|
</Scene>
|
||||||
|
)}
|
||||||
|
</Dialog>
|
||||||
|
</Backdrop>
|
||||||
|
)}
|
||||||
|
</DialogBackdrop>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = styled.h1`
|
||||||
|
font-size: 18px;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Backdrop = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: ${(props) => props.theme.backdrop} !important;
|
||||||
|
z-index: ${(props) => props.theme.depths.modalOverlay};
|
||||||
|
transition: opacity 200ms ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
&[data-enter] {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Scene = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: 12px;
|
||||||
|
z-index: ${(props) => props.theme.depths.modal};
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 350px;
|
||||||
|
background: ${(props) => props.theme.background};
|
||||||
|
transition: ${(props) => props.theme.backgroundTransition};
|
||||||
|
border-radius: 8px;
|
||||||
|
outline: none;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(16px);
|
||||||
|
transition: transform 250ms ease, opacity 250ms ease;
|
||||||
|
|
||||||
|
&[data-enter] {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0px);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Content = styled(Scrollable)`
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default observer(Guide);
|
|
@ -35,6 +35,10 @@ const RealInput = styled.input`
|
||||||
color: ${(props) => props.theme.placeholder};
|
color: ${(props) => props.theme.placeholder};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&::-webkit-search-cancel-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
${breakpoint("mobile", "tablet")`
|
${breakpoint("mobile", "tablet")`
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
`};
|
`};
|
||||||
|
|
|
@ -20,6 +20,9 @@ type Props = {
|
||||||
label?: string,
|
label?: string,
|
||||||
labelHidden?: boolean,
|
labelHidden?: boolean,
|
||||||
collectionId?: string,
|
collectionId?: string,
|
||||||
|
redirectDisabled?: boolean,
|
||||||
|
maxWidth?: string,
|
||||||
|
onChange: (event: SyntheticInputEvent<>) => mixed,
|
||||||
t: TFunction,
|
t: TFunction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,7 +59,7 @@ class InputSearch extends React.Component<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t } = this.props;
|
const { t, redirectDisabled, onChange } = this.props;
|
||||||
const { theme, placeholder = `${t("Search")}…` } = this.props;
|
const { theme, placeholder = `${t("Search")}…` } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -64,7 +67,8 @@ class InputSearch extends React.Component<Props> {
|
||||||
ref={(ref) => (this.input = ref)}
|
ref={(ref) => (this.input = ref)}
|
||||||
type="search"
|
type="search"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onInput={this.handleSearchInput}
|
onInput={redirectDisabled ? undefined : this.handleSearchInput}
|
||||||
|
onChange={onChange}
|
||||||
icon={
|
icon={
|
||||||
<SearchIcon
|
<SearchIcon
|
||||||
color={this.focused ? theme.inputBorderFocused : theme.inputBorder}
|
color={this.focused ? theme.inputBorderFocused : theme.inputBorder}
|
||||||
|
@ -72,6 +76,7 @@ class InputSearch extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
label={this.props.label}
|
label={this.props.label}
|
||||||
labelHidden={this.props.labelHidden}
|
labelHidden={this.props.labelHidden}
|
||||||
|
maxWidth={this.props.maxWidth}
|
||||||
onFocus={this.handleFocus}
|
onFocus={this.handleFocus}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
margin={0}
|
margin={0}
|
||||||
|
@ -81,7 +86,7 @@ class InputSearch extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputMaxWidth = styled(Input)`
|
const InputMaxWidth = styled(Input)`
|
||||||
max-width: 30vw;
|
max-width: ${(props) => props.maxWidth};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default withTranslation()<InputSearch>(
|
export default withTranslation()<InputSearch>(
|
||||||
|
|
|
@ -24,8 +24,8 @@ import KeyboardShortcuts from "scenes/KeyboardShortcuts";
|
||||||
import Button from "components/Button";
|
import Button from "components/Button";
|
||||||
import DocumentHistory from "components/DocumentHistory";
|
import DocumentHistory from "components/DocumentHistory";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
|
import Guide from "components/Guide";
|
||||||
import { LoadingIndicatorBar } from "components/LoadingIndicator";
|
import { LoadingIndicatorBar } from "components/LoadingIndicator";
|
||||||
import Modal from "components/Modal";
|
|
||||||
import Sidebar from "components/Sidebar";
|
import Sidebar from "components/Sidebar";
|
||||||
import SettingsSidebar from "components/Sidebar/Settings";
|
import SettingsSidebar from "components/Sidebar/Settings";
|
||||||
import SkipNavContent from "components/SkipNavContent";
|
import SkipNavContent from "components/SkipNavContent";
|
||||||
|
@ -161,13 +161,13 @@ class Layout extends React.Component<Props> {
|
||||||
/>
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Container>
|
</Container>
|
||||||
<Modal
|
<Guide
|
||||||
isOpen={this.keyboardShortcutsOpen}
|
isOpen={this.keyboardShortcutsOpen}
|
||||||
onRequestClose={this.handleCloseKeyboardShortcuts}
|
onRequestClose={this.handleCloseKeyboardShortcuts}
|
||||||
title={t("Keyboard shortcuts")}
|
title={t("Keyboard shortcuts")}
|
||||||
>
|
>
|
||||||
<KeyboardShortcuts />
|
<KeyboardShortcuts />
|
||||||
</Modal>
|
</Guide>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import ContextMenu from "components/ContextMenu";
|
||||||
import MenuItem, { MenuAnchor } from "components/ContextMenu/MenuItem";
|
import MenuItem, { MenuAnchor } from "components/ContextMenu/MenuItem";
|
||||||
import Separator from "components/ContextMenu/Separator";
|
import Separator from "components/ContextMenu/Separator";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
import Modal from "components/Modal";
|
import Guide from "components/Guide";
|
||||||
import usePrevious from "hooks/usePrevious";
|
import usePrevious from "hooks/usePrevious";
|
||||||
import useStores from "hooks/useStores";
|
import useStores from "hooks/useStores";
|
||||||
|
|
||||||
|
@ -90,13 +90,13 @@ function AccountMenu(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Guide
|
||||||
isOpen={keyboardShortcutsOpen}
|
isOpen={keyboardShortcutsOpen}
|
||||||
onRequestClose={() => setKeyboardShortcutsOpen(false)}
|
onRequestClose={() => setKeyboardShortcutsOpen(false)}
|
||||||
title={t("Keyboard shortcuts")}
|
title={t("Keyboard shortcuts")}
|
||||||
>
|
>
|
||||||
<KeyboardShortcuts />
|
<KeyboardShortcuts />
|
||||||
</Modal>
|
</Guide>
|
||||||
<MenuButton {...menu}>{props.children}</MenuButton>
|
<MenuButton {...menu}>{props.children}</MenuButton>
|
||||||
<ContextMenu {...menu} aria-label={t("Account")}>
|
<ContextMenu {...menu} aria-label={t("Account")}>
|
||||||
<MenuItem {...menu} as={Link} to={settings()}>
|
<MenuItem {...menu} as={Link} to={settings()}>
|
||||||
|
|
|
@ -126,6 +126,7 @@ function CollectionScene() {
|
||||||
label={`${t("Search in collection")}…`}
|
label={`${t("Search in collection")}…`}
|
||||||
labelHidden
|
labelHidden
|
||||||
collectionId={collectionId}
|
collectionId={collectionId}
|
||||||
|
maxWidth="30vw"
|
||||||
/>
|
/>
|
||||||
</Action>
|
</Action>
|
||||||
{can.update && (
|
{can.update && (
|
||||||
|
|
|
@ -1,52 +1,49 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { observable } from "mobx";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { KeyboardIcon } from "outline-icons";
|
import { KeyboardIcon } from "outline-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import breakpoint from "styled-components-breakpoint";
|
import breakpoint from "styled-components-breakpoint";
|
||||||
import KeyboardShortcuts from "scenes/KeyboardShortcuts";
|
import KeyboardShortcuts from "scenes/KeyboardShortcuts";
|
||||||
import Modal from "components/Modal";
|
import Guide from "components/Guide";
|
||||||
import NudeButton from "components/NudeButton";
|
import NudeButton from "components/NudeButton";
|
||||||
import Tooltip from "components/Tooltip";
|
import Tooltip from "components/Tooltip";
|
||||||
|
|
||||||
type Props = {};
|
function KeyboardShortcutsButton() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [keyboardShortcutsOpen, setKeyboardShortcutsOpen] = React.useState(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
@observer
|
const handleCloseKeyboardShortcuts = React.useCallback(() => {
|
||||||
class KeyboardShortcutsButton extends React.Component<Props> {
|
setKeyboardShortcutsOpen(false);
|
||||||
@observable keyboardShortcutsOpen: boolean = false;
|
}, []);
|
||||||
|
|
||||||
handleOpenKeyboardShortcuts = () => {
|
const handleOpenKeyboardShortcuts = React.useCallback(() => {
|
||||||
this.keyboardShortcutsOpen = true;
|
setKeyboardShortcutsOpen(true);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
handleCloseKeyboardShortcuts = () => {
|
return (
|
||||||
this.keyboardShortcutsOpen = false;
|
<>
|
||||||
};
|
<Guide
|
||||||
|
isOpen={keyboardShortcutsOpen}
|
||||||
render() {
|
onRequestClose={handleCloseKeyboardShortcuts}
|
||||||
return (
|
title={t("Keyboard shortcuts")}
|
||||||
<>
|
>
|
||||||
<Modal
|
<KeyboardShortcuts />
|
||||||
isOpen={this.keyboardShortcutsOpen}
|
</Guide>
|
||||||
onRequestClose={this.handleCloseKeyboardShortcuts}
|
<Tooltip
|
||||||
title="Keyboard shortcuts"
|
tooltip={t("Keyboard shortcuts")}
|
||||||
>
|
shortcut="?"
|
||||||
<KeyboardShortcuts />
|
placement="left"
|
||||||
</Modal>
|
delay={500}
|
||||||
<Tooltip
|
>
|
||||||
tooltip="Keyboard shortcuts"
|
<Button onClick={handleOpenKeyboardShortcuts}>
|
||||||
shortcut="?"
|
<KeyboardIcon />
|
||||||
placement="left"
|
</Button>
|
||||||
delay={500}
|
</Tooltip>
|
||||||
>
|
</>
|
||||||
<Button onClick={this.handleOpenKeyboardShortcuts}>
|
);
|
||||||
<KeyboardIcon />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = styled(NudeButton)`
|
const Button = styled(NudeButton)`
|
||||||
|
|
|
@ -86,6 +86,7 @@ class Drafts extends React.Component<Props> {
|
||||||
<InputSearch
|
<InputSearch
|
||||||
source="drafts"
|
source="drafts"
|
||||||
label={t("Search documents")}
|
label={t("Search documents")}
|
||||||
|
maxWidth="30vw"
|
||||||
labelHidden
|
labelHidden
|
||||||
/>
|
/>
|
||||||
</Action>
|
</Action>
|
||||||
|
|
|
@ -32,6 +32,7 @@ function Home() {
|
||||||
<InputSearch
|
<InputSearch
|
||||||
source="dashboard"
|
source="dashboard"
|
||||||
label={t("Search documents")}
|
label={t("Search documents")}
|
||||||
|
maxWidth="30vw"
|
||||||
labelHidden
|
labelHidden
|
||||||
/>
|
/>
|
||||||
</Action>
|
</Action>
|
||||||
|
|
|
@ -3,165 +3,395 @@ import * as React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
import HelpText from "components/HelpText";
|
import Input from "components/InputSearch";
|
||||||
import Key from "components/Key";
|
import Key from "components/Key";
|
||||||
import { metaDisplay } from "utils/keyboard";
|
import { metaDisplay } from "utils/keyboard";
|
||||||
|
|
||||||
function KeyboardShortcuts() {
|
function KeyboardShortcuts() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const categories = React.useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
title: t("Navigation"),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
shortcut: <Key>n</Key>,
|
||||||
|
label: t("New document"),
|
||||||
|
},
|
||||||
|
{ shortcut: <Key>e</Key>, label: t("Edit current document") },
|
||||||
|
{ shortcut: <Key>m</Key>, label: t("Move current document") },
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>/</Key> or <Key>t</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Jump to search"),
|
||||||
|
},
|
||||||
|
{ shortcut: <Key>d</Key>, label: t("Jump to home") },
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Ctrl</Key> + <Key>{metaDisplay}</Key> + <Key>h</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Table of contents"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>.</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Toggle navigation"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>f</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Focus search input"),
|
||||||
|
},
|
||||||
|
{ shortcut: <Key>?</Key>, label: t("Open this guide") },
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>Enter</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Save document and exit"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>⇧</Key> + <Key>p</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Publish document and exit"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>s</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Save document"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>Esc</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Cancel editing"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("Formatting"),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Ctrl</Key> + <Key>⇧</Key> + <Key>0</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Paragraph"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Ctrl</Key> + <Key>⇧</Key> + <Key>1</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Large header"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Ctrl</Key> + <Key>⇧</Key> + <Key>2</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Medium header"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Ctrl</Key> + <Key>⇧</Key> + <Key>3</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Small header"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Ctrl</Key> + <Key>⇧</Key> + <Key>\</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Code block"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>b</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Bold"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>i</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Italic"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>u</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Underline"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>d</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Strikethrough"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>k</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Link"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>z</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Undo"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>{metaDisplay}</Key> + <Key>⇧</Key> + <Key>z</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Redo"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("Lists"),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Ctrl</Key> + <Key>⇧</Key> + <Key>7</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Todo list"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Ctrl</Key> + <Key>⇧</Key> + <Key>8</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Bulleted list"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Ctrl</Key> + <Key>⇧</Key> + <Key>9</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Ordered list"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: <Key>Tab</Key>,
|
||||||
|
label: t("Indent list item"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>⇧</Key> + <Key>Tab</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Outdent list item"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Alt</Key> + <Key>↑</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Move list item up"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>Alt</Key> + <Key>↓</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Move list item down"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Markdown",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>#</Key> <Key>Space</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Large header"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>##</Key> <Key>Space</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Medium header"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>###</Key> <Key>Space</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Small header"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>1.</Key> <Key>Space</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Numbered list"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>-</Key> <Key>Space</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Bulleted list"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>[ ]</Key> <Key>Space</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Todo list"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: (
|
||||||
|
<>
|
||||||
|
<Key>></Key> <Key>Space</Key>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
label: t("Blockquote"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: <Key>---</Key>,
|
||||||
|
label: t("Horizontal divider"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: <Key>{"```"}</Key>,
|
||||||
|
label: t("Code block"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: <Key>{":::"}</Key>,
|
||||||
|
label: t("Info notice"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: "_italic_",
|
||||||
|
label: t("Italic"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: "**bold**",
|
||||||
|
label: t("Bold"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: "~~strikethrough~~",
|
||||||
|
label: t("Strikethrough"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: "`code`",
|
||||||
|
label: t("Inline code"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut: "==highlight==",
|
||||||
|
label: t("Highlight"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [searchTerm, setSearchTerm] = React.useState("");
|
||||||
|
|
||||||
|
const handleChange = React.useCallback((event) => {
|
||||||
|
setSearchTerm(event.target.value.toLowerCase());
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex column>
|
<Flex column>
|
||||||
<HelpText>
|
<Input type="search" onChange={handleChange} redirectDisabled />
|
||||||
{t(
|
{categories.map((category, x) => {
|
||||||
"Outline is designed to be fast and easy to use. All of your usual keyboard shortcuts work here, and there’s Markdown too."
|
const filtered = searchTerm
|
||||||
)}
|
? category.items.filter((item) =>
|
||||||
</HelpText>
|
item.label.toLowerCase().includes(searchTerm)
|
||||||
|
)
|
||||||
|
: category.items;
|
||||||
|
|
||||||
<h2>{t("Navigation")}</h2>
|
if (!filtered.length) {
|
||||||
<List>
|
return null;
|
||||||
<Keys>
|
}
|
||||||
<Key>n</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("New document in current collection")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>e</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Edit current document")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>m</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Move current document")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>/</Key> or <Key>t</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Jump to search")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>d</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Jump to dashboard")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{metaDisplay}</Key> + <Key>Ctrl</Key> + <Key>h</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Table of contents")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{metaDisplay}</Key> + <Key>.</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Toggle sidebar")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>?</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Open this guide")}</Label>
|
|
||||||
</List>
|
|
||||||
|
|
||||||
<h2>{t("Editor")}</h2>
|
return (
|
||||||
<List>
|
<React.Fragment key={x}>
|
||||||
<Keys>
|
<Header>{category.title}</Header>
|
||||||
<Key>{metaDisplay}</Key> + <Key>Enter</Key>
|
<List>
|
||||||
</Keys>
|
{filtered.map((item) => (
|
||||||
<Label>{t("Save and exit document edit mode")}</Label>
|
<React.Fragment key={item.label}>
|
||||||
<Keys>
|
<Keys>
|
||||||
<Key>{metaDisplay}</Key> + <Key>Shift</Key> + <Key>p</Key>
|
<span>{item.shortcut}</span>
|
||||||
</Keys>
|
</Keys>
|
||||||
<Label>{t("Publish and exit document edit mode")}</Label>
|
<Label>{item.label}</Label>
|
||||||
<Keys>
|
</React.Fragment>
|
||||||
<Key>{metaDisplay}</Key> + <Key>s</Key>
|
))}
|
||||||
</Keys>
|
</List>
|
||||||
<Label>{t("Save document and continue editing")}</Label>
|
</React.Fragment>
|
||||||
<Keys>
|
);
|
||||||
<Key>{metaDisplay}</Key> + <Key>Esc</Key>
|
})}
|
||||||
</Keys>
|
|
||||||
<Label>{t("Cancel editing")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{metaDisplay}</Key> + <Key>b</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Bold")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{metaDisplay}</Key> + <Key>i</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Italic")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{metaDisplay}</Key> + <Key>u</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Underline")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{metaDisplay}</Key> + <Key>d</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Strikethrough")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{metaDisplay}</Key> + <Key>k</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Link")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{metaDisplay}</Key> + <Key>z</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Undo")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{metaDisplay}</Key> + <Key>Shift</Key> + <Key>z</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Redo")}</Label>
|
|
||||||
</List>
|
|
||||||
|
|
||||||
<h2>{t("Markdown")}</h2>
|
|
||||||
<List>
|
|
||||||
<Keys>
|
|
||||||
<Key>#</Key> <Key>Space</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Large header")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>##</Key> <Key>Space</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Medium header")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>###</Key> <Key>Space</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Small header")}</Label>
|
|
||||||
|
|
||||||
<Keys>
|
|
||||||
<Key>1.</Key> <Key>Space</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Numbered list")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>-</Key> <Key>Space</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Bulleted list")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>[ ]</Key> <Key>Space</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Todo list")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>></Key> <Key>Space</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Blockquote")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>---</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Horizontal divider")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{"```"}</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Code block")}</Label>
|
|
||||||
<Keys>
|
|
||||||
<Key>{":::"}</Key>
|
|
||||||
</Keys>
|
|
||||||
<Label>{t("Info notice")}</Label>
|
|
||||||
|
|
||||||
<Keys>_italic_</Keys>
|
|
||||||
<Label>{t("Italic")}</Label>
|
|
||||||
<Keys>**bold**</Keys>
|
|
||||||
<Label>{t("Bold")}</Label>
|
|
||||||
<Keys>~~strikethrough~~</Keys>
|
|
||||||
<Label>{t("Strikethrough")}</Label>
|
|
||||||
<Keys>{"`code`"}</Keys>
|
|
||||||
<Label>{t("Inline code")}</Label>
|
|
||||||
<Keys>==highlight==</Keys>
|
|
||||||
<Label>{t("Highlight")}</Label>
|
|
||||||
</List>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Header = styled.h2`
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 2em;
|
||||||
|
`;
|
||||||
|
|
||||||
const List = styled.dl`
|
const List = styled.dl`
|
||||||
|
font-size: 14px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -169,19 +399,26 @@ const List = styled.dl`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Keys = styled.dt`
|
const Keys = styled.dt`
|
||||||
float: left;
|
float: right;
|
||||||
width: 25%;
|
width: 45%;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 12px;
|
||||||
|
color: ${(props) => props.theme.textSecondary};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Label = styled.dd`
|
const Label = styled.dd`
|
||||||
float: left;
|
float: left;
|
||||||
width: 75%;
|
width: 55%;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
color: ${(props) => props.theme.textSecondary};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default KeyboardShortcuts;
|
export default KeyboardShortcuts;
|
||||||
|
|
|
@ -35,6 +35,7 @@ function Starred(props: Props) {
|
||||||
<InputSearch
|
<InputSearch
|
||||||
source="starred"
|
source="starred"
|
||||||
label={t("Search documents")}
|
label={t("Search documents")}
|
||||||
|
maxWidth="30vw"
|
||||||
labelHidden
|
labelHidden
|
||||||
/>
|
/>
|
||||||
</Action>
|
</Action>
|
||||||
|
|
|
@ -301,28 +301,32 @@
|
||||||
"This group has no members.": "This group has no members.",
|
"This group has no members.": "This group has no members.",
|
||||||
"Recently viewed": "Recently viewed",
|
"Recently viewed": "Recently viewed",
|
||||||
"Created by me": "Created by me",
|
"Created by me": "Created by me",
|
||||||
"Outline is designed to be fast and easy to use. All of your usual keyboard shortcuts work here, and there’s Markdown too.": "Outline is designed to be fast and easy to use. All of your usual keyboard shortcuts work here, and there’s Markdown too.",
|
|
||||||
"Navigation": "Navigation",
|
"Navigation": "Navigation",
|
||||||
"New document in current collection": "New document in current collection",
|
|
||||||
"Edit current document": "Edit current document",
|
"Edit current document": "Edit current document",
|
||||||
"Move current document": "Move current document",
|
"Move current document": "Move current document",
|
||||||
"Jump to search": "Jump to search",
|
"Jump to search": "Jump to search",
|
||||||
"Jump to dashboard": "Jump to dashboard",
|
"Jump to home": "Jump to home",
|
||||||
"Table of contents": "Table of contents",
|
"Table of contents": "Table of contents",
|
||||||
"Toggle sidebar": "Toggle sidebar",
|
"Toggle navigation": "Toggle navigation",
|
||||||
|
"Focus search input": "Focus search input",
|
||||||
"Open this guide": "Open this guide",
|
"Open this guide": "Open this guide",
|
||||||
"Editor": "Editor",
|
"Save document and exit": "Save document and exit",
|
||||||
"Save and exit document edit mode": "Save and exit document edit mode",
|
"Publish document and exit": "Publish document and exit",
|
||||||
"Publish and exit document edit mode": "Publish and exit document edit mode",
|
"Save document": "Save document",
|
||||||
"Save document and continue editing": "Save document and continue editing",
|
|
||||||
"Cancel editing": "Cancel editing",
|
"Cancel editing": "Cancel editing",
|
||||||
"Underline": "Underline",
|
"Formatting": "Formatting",
|
||||||
"Undo": "Undo",
|
"Paragraph": "Paragraph",
|
||||||
"Redo": "Redo",
|
|
||||||
"Markdown": "Markdown",
|
|
||||||
"Large header": "Large header",
|
"Large header": "Large header",
|
||||||
"Medium header": "Medium header",
|
"Medium header": "Medium header",
|
||||||
"Small header": "Small header",
|
"Small header": "Small header",
|
||||||
|
"Underline": "Underline",
|
||||||
|
"Undo": "Undo",
|
||||||
|
"Redo": "Redo",
|
||||||
|
"Lists": "Lists",
|
||||||
|
"Indent list item": "Indent list item",
|
||||||
|
"Outdent list item": "Outdent list item",
|
||||||
|
"Move list item up": "Move list item up",
|
||||||
|
"Move list item down": "Move list item down",
|
||||||
"Numbered list": "Numbered list",
|
"Numbered list": "Numbered list",
|
||||||
"Blockquote": "Blockquote",
|
"Blockquote": "Blockquote",
|
||||||
"Horizontal divider": "Horizontal divider",
|
"Horizontal divider": "Horizontal divider",
|
||||||
|
|
Reference in New Issue