fix: Add access to document TOC on mobile (#2279)
* Add TOC button for mobile * Undo NewDocumentMenu changes * Place the toc button in the correct position. * Pass menu props to menuitem * Update app/menus/TableOfContentsMenu.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Update app/menus/TableOfContentsMenu.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Use the existing prop type * Write menu inside actions prop * Prevent blank webpage behaviour for toc * Use href instead of level to determine target * Update app/scenes/Document/components/Header.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Add heading to menu items * Use existing Heading component Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@ -15,6 +15,7 @@ type Props = {|
|
||||
target?: "_blank",
|
||||
as?: string | React.ComponentType<*>,
|
||||
hide?: () => void,
|
||||
level?: number,
|
||||
|};
|
||||
|
||||
const MenuItem = ({
|
||||
@ -86,6 +87,7 @@ const Spacer = styled.svg`
|
||||
export const MenuAnchor = styled.a`
|
||||
display: flex;
|
||||
margin: 0;
|
||||
margin-left: ${(props) => props.level * 10}px;
|
||||
border: 0;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
MenuItem as BaseMenuItem,
|
||||
} from "reakit/Menu";
|
||||
import styled from "styled-components";
|
||||
import Header from "./Header";
|
||||
import MenuItem, { MenuAnchor } from "./MenuItem";
|
||||
import Separator from "./Separator";
|
||||
import ContextMenu from ".";
|
||||
@ -34,6 +35,7 @@ type TMenuItem =
|
||||
visible?: boolean,
|
||||
selected?: boolean,
|
||||
disabled?: boolean,
|
||||
level?: number,
|
||||
|}
|
||||
| {|
|
||||
title: React.Node,
|
||||
@ -128,7 +130,8 @@ function Template({ items, ...menu }: Props): React.Node {
|
||||
key={index}
|
||||
disabled={item.disabled}
|
||||
selected={item.selected}
|
||||
target="_blank"
|
||||
level={item.level}
|
||||
target={item.href.startsWith("#") ? undefined : "_blank"}
|
||||
{...menu}
|
||||
>
|
||||
{item.title}
|
||||
@ -167,6 +170,10 @@ function Template({ items, ...menu }: Props): React.Node {
|
||||
return <Separator key={index} />;
|
||||
}
|
||||
|
||||
if (item.type === "heading") {
|
||||
return <Header>{item.title}</Header>;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
66
app/menus/TableOfContentsMenu.js
Normal file
66
app/menus/TableOfContentsMenu.js
Normal file
@ -0,0 +1,66 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { TableOfContentsIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MenuButton, useMenuState } from "reakit/Menu";
|
||||
import Button from "components/Button";
|
||||
import ContextMenu from "components/ContextMenu";
|
||||
import Template from "components/ContextMenu/Template";
|
||||
|
||||
type Props = {|
|
||||
headings: { title: string, level: number, id: string }[],
|
||||
|};
|
||||
|
||||
function TableOfContentsMenu({ headings }: Props) {
|
||||
const menu = useMenuState({
|
||||
modal: true,
|
||||
unstable_preventOverflow: true,
|
||||
unstable_fixed: true,
|
||||
unstable_flip: true,
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const minHeading = headings.reduce(
|
||||
(memo, heading) => (heading.level < memo ? heading.level : memo),
|
||||
Infinity
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuButton {...menu}>
|
||||
{(props) => (
|
||||
<Button
|
||||
{...props}
|
||||
icon={<TableOfContentsIcon />}
|
||||
iconColor="currentColor"
|
||||
borderOnHover
|
||||
neutral
|
||||
/>
|
||||
)}
|
||||
</MenuButton>
|
||||
<ContextMenu {...menu} aria-label={t("Table of contents")}>
|
||||
<Template
|
||||
{...menu}
|
||||
items={[
|
||||
{
|
||||
type: "heading",
|
||||
visible: true,
|
||||
title: t("Contents"),
|
||||
},
|
||||
...headings.map((heading) => {
|
||||
return {
|
||||
href: `#${heading.id}`,
|
||||
title: t(heading.title),
|
||||
level: heading.level - minHeading,
|
||||
};
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
</ContextMenu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(TableOfContentsMenu);
|
@ -374,6 +374,7 @@ class DocumentScene extends React.Component<Props> {
|
||||
sharedTree={this.props.sharedTree}
|
||||
goBack={this.goBack}
|
||||
onSave={this.onSave}
|
||||
headings={headings}
|
||||
/>
|
||||
<MaxWidth
|
||||
archived={document.isArchived}
|
||||
|
@ -24,6 +24,7 @@ import useMobile from "hooks/useMobile";
|
||||
import useStores from "hooks/useStores";
|
||||
import DocumentMenu from "menus/DocumentMenu";
|
||||
import NewChildDocumentMenu from "menus/NewChildDocumentMenu";
|
||||
import TableOfContentsMenu from "menus/TableOfContentsMenu";
|
||||
import TemplatesMenu from "menus/TemplatesMenu";
|
||||
import { type NavigationNode } from "types";
|
||||
import { metaDisplay } from "utils/keyboard";
|
||||
@ -46,6 +47,7 @@ type Props = {|
|
||||
publish?: boolean,
|
||||
autosave?: boolean,
|
||||
}) => void,
|
||||
headings: { title: string, level: number, id: string }[],
|
||||
|};
|
||||
|
||||
function DocumentHeader({
|
||||
@ -60,6 +62,7 @@ function DocumentHeader({
|
||||
publishingIsDisabled,
|
||||
sharedTree,
|
||||
onSave,
|
||||
headings,
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { auth, ui, policies } = useStores();
|
||||
@ -153,6 +156,11 @@ function DocumentHeader({
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
{isMobile && (
|
||||
<TocWrapper>
|
||||
<TableOfContentsMenu headings={headings} />
|
||||
</TocWrapper>
|
||||
)}
|
||||
{!isPublishing && isSaving && <Status>{t("Saving")}…</Status>}
|
||||
<Collaborators
|
||||
document={document}
|
||||
@ -274,4 +282,9 @@ const Status = styled(Action)`
|
||||
color: ${(props) => props.theme.slate};
|
||||
`;
|
||||
|
||||
const TocWrapper = styled(Action)`
|
||||
position: absolute;
|
||||
left: 42px;
|
||||
`;
|
||||
|
||||
export default observer(DocumentHeader);
|
||||
|
@ -206,6 +206,7 @@
|
||||
"Share options": "Share options",
|
||||
"Go to document": "Go to document",
|
||||
"Revoke link": "Revoke link",
|
||||
"Table of contents": "Table of contents",
|
||||
"By {{ author }}": "By {{ author }}",
|
||||
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
|
||||
"Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
|
||||
@ -337,7 +338,6 @@
|
||||
"Move current document": "Move current document",
|
||||
"Jump to search": "Jump to search",
|
||||
"Jump to home": "Jump to home",
|
||||
"Table of contents": "Table of contents",
|
||||
"Toggle navigation": "Toggle navigation",
|
||||
"Focus search input": "Focus search input",
|
||||
"Open this guide": "Open this guide",
|
||||
|
Reference in New Issue
Block a user