mirror of
https://github.com/outline/outline.git
synced 2025-12-21 10:39:41 -06:00
feat: allow user to set TOC display preference (#6943)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@@ -3,15 +3,14 @@ import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { s } from "@shared/styles";
|
||||
import { EditorStyleHelper } from "@shared/editor/styles/EditorStyleHelper";
|
||||
import { depths, s } from "@shared/styles";
|
||||
import Text from "~/components/Text";
|
||||
import useWindowScrollPosition from "~/hooks/useWindowScrollPosition";
|
||||
|
||||
const HEADING_OFFSET = 20;
|
||||
|
||||
type Props = {
|
||||
/** Whether the document is rendering full width or not. */
|
||||
isFullWidth: boolean;
|
||||
/** The headings to render in the contents. */
|
||||
headings: {
|
||||
title: string;
|
||||
@@ -20,9 +19,9 @@ type Props = {
|
||||
}[];
|
||||
};
|
||||
|
||||
export default function Contents({ headings, isFullWidth }: Props) {
|
||||
export default function Contents({ headings }: Props) {
|
||||
const [activeSlug, setActiveSlug] = React.useState<string>();
|
||||
const position = useWindowScrollPosition({
|
||||
const scrollPosition = useWindowScrollPosition({
|
||||
throttle: 100,
|
||||
});
|
||||
|
||||
@@ -43,7 +42,7 @@ export default function Contents({ headings, isFullWidth }: Props) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [position, headings]);
|
||||
}, [scrollPosition, headings]);
|
||||
|
||||
// calculate the minimum heading level and adjust all the headings to make
|
||||
// that the top-most. This prevents the contents from being weirdly indented
|
||||
@@ -56,70 +55,53 @@ export default function Contents({ headings, isFullWidth }: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Wrapper isFullWidth={isFullWidth}>
|
||||
<Sticky>
|
||||
<Heading>{t("Contents")}</Heading>
|
||||
{headings.length ? (
|
||||
<List>
|
||||
{headings
|
||||
.filter((heading) => heading.level < 4)
|
||||
.map((heading) => (
|
||||
<ListItem
|
||||
key={heading.id}
|
||||
level={heading.level - headingAdjustment}
|
||||
active={activeSlug === heading.id}
|
||||
>
|
||||
<Link href={`#${heading.id}`}>{heading.title}</Link>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<Empty>
|
||||
{t("Headings you add to the document will appear here")}
|
||||
</Empty>
|
||||
)}
|
||||
</Sticky>
|
||||
</Wrapper>
|
||||
<StickyWrapper>
|
||||
<Heading>{t("Contents")}</Heading>
|
||||
{headings.length ? (
|
||||
<List>
|
||||
{headings
|
||||
.filter((heading) => heading.level < 4)
|
||||
.map((heading) => (
|
||||
<ListItem
|
||||
key={heading.id}
|
||||
level={heading.level - headingAdjustment}
|
||||
active={activeSlug === heading.id}
|
||||
>
|
||||
<Link href={`#${heading.id}`}>{heading.title}</Link>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<Empty>{t("Headings you add to the document will appear here")}</Empty>
|
||||
)}
|
||||
</StickyWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const Wrapper = styled.div<{ isFullWidth: boolean }>`
|
||||
width: 256px;
|
||||
const StickyWrapper = styled.div`
|
||||
display: none;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
display: block;
|
||||
`};
|
||||
|
||||
${(props) =>
|
||||
!props.isFullWidth &&
|
||||
breakpoint("desktopLarge")`
|
||||
transform: translateX(-256px);
|
||||
width: 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
const Sticky = styled.div`
|
||||
position: sticky;
|
||||
top: 80px;
|
||||
max-height: calc(100vh - 80px);
|
||||
top: 90px;
|
||||
max-height: calc(100vh - 90px);
|
||||
width: ${EditorStyleHelper.tocWidth}px;
|
||||
|
||||
padding: 0 16px;
|
||||
overflow-y: auto;
|
||||
border-radius: 8px;
|
||||
|
||||
background: ${s("background")};
|
||||
transition: ${s("backgroundTransition")};
|
||||
|
||||
margin-top: calc(50px + 6vh);
|
||||
margin-right: 52px;
|
||||
min-width: 204px;
|
||||
width: 228px;
|
||||
min-height: 40px;
|
||||
overflow-y: auto;
|
||||
padding: 0 16px;
|
||||
border-radius: 8px;
|
||||
|
||||
@supports (backdrop-filter: blur(20px)) {
|
||||
backdrop-filter: blur(20px);
|
||||
background: ${(props) => transparentize(0.2, props.theme.background)};
|
||||
}
|
||||
|
||||
${breakpoint("tablet")`
|
||||
display: block;
|
||||
z-index: ${depths.toc};
|
||||
`};
|
||||
`;
|
||||
|
||||
const Heading = styled.h3`
|
||||
@@ -131,15 +113,12 @@ const Heading = styled.h3`
|
||||
`;
|
||||
|
||||
const Empty = styled(Text)`
|
||||
margin: 1em 0 4em;
|
||||
padding-right: 2em;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
const ListItem = styled.li<{ level: number; active?: boolean }>`
|
||||
margin-left: ${(props) => (props.level - 1) * 10}px;
|
||||
margin-bottom: 8px;
|
||||
padding-right: 2em;
|
||||
line-height: 1.3;
|
||||
word-break: break-word;
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@ import {
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { EditorStyleHelper } from "@shared/editor/styles/EditorStyleHelper";
|
||||
import { s } from "@shared/styles";
|
||||
import { NavigationNode } from "@shared/types";
|
||||
import { NavigationNode, TOCPosition, TeamPreference } from "@shared/types";
|
||||
import { ProsemirrorHelper, Heading } from "@shared/utils/ProsemirrorHelper";
|
||||
import { parseDomain } from "@shared/utils/domains";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
@@ -403,6 +404,9 @@ class DocumentScene extends React.Component<Props> {
|
||||
const hasHeadings = this.headings.length > 0;
|
||||
const showContents =
|
||||
ui.tocVisible && ((readOnly && hasHeadings) || !readOnly);
|
||||
const tocPosition =
|
||||
(team?.getPreference(TeamPreference.TocPosition) as TOCPosition) ||
|
||||
TOCPosition.Left;
|
||||
const multiplayerEditor =
|
||||
!document.isArchived && !document.isDeleted && !revision && !isShare;
|
||||
|
||||
@@ -449,7 +453,7 @@ class DocumentScene extends React.Component<Props> {
|
||||
favicon={document.emoji ? emojiToUrl(document.emoji) : undefined}
|
||||
/>
|
||||
{(this.isUploading || this.isSaving) && <LoadingIndicator />}
|
||||
<Container justify="center" column auto>
|
||||
<Container column>
|
||||
{!readOnly && (
|
||||
<Prompt
|
||||
when={this.isUploading && !this.isEditorDirty}
|
||||
@@ -476,27 +480,39 @@ class DocumentScene extends React.Component<Props> {
|
||||
onSave={this.onSave}
|
||||
headings={this.headings}
|
||||
/>
|
||||
<MeasuredContainer
|
||||
as={MaxWidth}
|
||||
name="document"
|
||||
archived={document.isArchived}
|
||||
showContents={showContents}
|
||||
isEditing={!readOnly}
|
||||
isFullWidth={document.fullWidth}
|
||||
column
|
||||
auto
|
||||
>
|
||||
<Flex justify="center">
|
||||
<Notices document={document} readOnly={readOnly} />
|
||||
</Flex>
|
||||
<MeasuredContainer
|
||||
as={Main}
|
||||
name="document"
|
||||
fullWidth={document.fullWidth}
|
||||
tocPosition={tocPosition}
|
||||
>
|
||||
<React.Suspense fallback={<PlaceholderDocument />}>
|
||||
<Flex auto={!readOnly} reverse>
|
||||
{revision ? (
|
||||
{revision ? (
|
||||
<RevisionContainer docFullWidth={document.fullWidth}>
|
||||
<RevisionViewer
|
||||
document={document}
|
||||
revision={revision}
|
||||
id={revision.id}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
</RevisionContainer>
|
||||
) : (
|
||||
<>
|
||||
{showContents && (
|
||||
<ContentsContainer
|
||||
docFullWidth={document.fullWidth}
|
||||
position={tocPosition}
|
||||
>
|
||||
<Contents headings={this.headings} />
|
||||
</ContentsContainer>
|
||||
)}
|
||||
<EditorContainer
|
||||
docFullWidth={document.fullWidth}
|
||||
showContents={showContents}
|
||||
tocPosition={tocPosition}
|
||||
>
|
||||
<Editor
|
||||
id={document.id}
|
||||
key={embedsDisabled ? "disabled" : "enabled"}
|
||||
@@ -543,16 +559,9 @@ class DocumentScene extends React.Component<Props> {
|
||||
</>
|
||||
)}
|
||||
</Editor>
|
||||
|
||||
{showContents && (
|
||||
<Contents
|
||||
headings={this.headings}
|
||||
isFullWidth={document.fullWidth}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</EditorContainer>
|
||||
</>
|
||||
)}
|
||||
</React.Suspense>
|
||||
</MeasuredContainer>
|
||||
{isShare &&
|
||||
@@ -573,6 +582,95 @@ class DocumentScene extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
type MainProps = {
|
||||
fullWidth: boolean;
|
||||
tocPosition: TOCPosition;
|
||||
};
|
||||
|
||||
const Main = styled.div<MainProps>`
|
||||
margin-top: 4px;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
display: grid;
|
||||
grid-template-columns: ${({ fullWidth, tocPosition }: MainProps) =>
|
||||
fullWidth
|
||||
? tocPosition === TOCPosition.Left
|
||||
? `${EditorStyleHelper.tocWidth}px minmax(0, 1fr)`
|
||||
: `minmax(0, 1fr) ${EditorStyleHelper.tocWidth}px`
|
||||
: `1fr minmax(0, ${`calc(46em + 76px)`}) 1fr`};
|
||||
`};
|
||||
|
||||
${breakpoint("desktopLarge")`
|
||||
grid-template-columns: ${({ fullWidth, tocPosition }: MainProps) =>
|
||||
fullWidth
|
||||
? tocPosition === TOCPosition.Left
|
||||
? `${EditorStyleHelper.tocWidth}px minmax(0, 1fr)`
|
||||
: `minmax(0, 1fr) ${EditorStyleHelper.tocWidth}px`
|
||||
: `1fr minmax(0, ${`calc(52em + 76px)`}) 1fr`};
|
||||
`};
|
||||
`;
|
||||
|
||||
type ContentsContainerProps = {
|
||||
docFullWidth: boolean;
|
||||
position: TOCPosition;
|
||||
};
|
||||
|
||||
const ContentsContainer = styled.div<ContentsContainerProps>`
|
||||
margin-top: calc(44px + 6vh);
|
||||
|
||||
${breakpoint("tablet")`
|
||||
grid-row: 1;
|
||||
grid-column: ${({ docFullWidth, position }: ContentsContainerProps) =>
|
||||
position === TOCPosition.Left ? 1 : docFullWidth ? 2 : 3};
|
||||
justify-self: ${({ position }: ContentsContainerProps) =>
|
||||
position === TOCPosition.Left ? "end" : "start"};
|
||||
`};
|
||||
`;
|
||||
|
||||
type EditorContainerProps = {
|
||||
docFullWidth: boolean;
|
||||
showContents: boolean;
|
||||
tocPosition: TOCPosition;
|
||||
};
|
||||
|
||||
const EditorContainer = styled.div<EditorContainerProps>`
|
||||
// Adds space to the gutter to make room for icon & heading annotations
|
||||
padding: 0 44px;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
grid-row: 1;
|
||||
|
||||
// Decides the editor column position & span
|
||||
grid-column: ${({
|
||||
docFullWidth,
|
||||
showContents,
|
||||
tocPosition,
|
||||
}: EditorContainerProps) =>
|
||||
docFullWidth
|
||||
? showContents
|
||||
? tocPosition === TOCPosition.Left
|
||||
? 2
|
||||
: 1
|
||||
: "1 / -1"
|
||||
: 2};
|
||||
`};
|
||||
`;
|
||||
|
||||
type RevisionContainerProps = {
|
||||
docFullWidth: boolean;
|
||||
};
|
||||
|
||||
const RevisionContainer = styled.div<RevisionContainerProps>`
|
||||
// Adds space to the gutter to make room for icon
|
||||
padding: 0 44px;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
grid-row: 1;
|
||||
grid-column: ${({ docFullWidth }: RevisionContainerProps) =>
|
||||
docFullWidth ? "1 / -1" : 2};
|
||||
`}
|
||||
`;
|
||||
|
||||
const Footer = styled.div`
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
@@ -595,34 +693,4 @@ const ReferencesWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
type MaxWidthProps = {
|
||||
isEditing?: boolean;
|
||||
isFullWidth?: boolean;
|
||||
archived?: boolean;
|
||||
showContents?: boolean;
|
||||
};
|
||||
|
||||
const MaxWidth = styled(Flex)<MaxWidthProps>`
|
||||
// Adds space to the gutter to make room for heading annotations
|
||||
padding: 0 32px;
|
||||
transition: padding 100ms;
|
||||
max-width: 100vw;
|
||||
width: 100%;
|
||||
|
||||
padding-bottom: 16px;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
margin: 4px auto 12px;
|
||||
max-width: ${(props: MaxWidthProps) =>
|
||||
props.isFullWidth
|
||||
? "100vw"
|
||||
: `calc(64px + 46em + ${props.showContents ? "256px" : "0px"});`}
|
||||
`};
|
||||
|
||||
${breakpoint("desktopLarge")`
|
||||
max-width: ${(props: MaxWidthProps) =>
|
||||
props.isFullWidth ? "100vw" : `calc(64px + 52em);`}
|
||||
`};
|
||||
`;
|
||||
|
||||
export default withTranslation()(withStores(withRouter(DocumentScene)));
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Selection } from "prosemirror-state";
|
||||
import { __parseFromClipboard } from "prosemirror-view";
|
||||
import * as React from "react";
|
||||
import { mergeRefs } from "react-merge-refs";
|
||||
import styled, { css } from "styled-components";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import isMarkdown from "@shared/editor/lib/isMarkdown";
|
||||
import normalizePastedMarkdown from "@shared/editor/lib/markdown/normalize";
|
||||
@@ -33,8 +33,6 @@ type Props = {
|
||||
title: string;
|
||||
/** Emoji to display */
|
||||
emoji?: string | null;
|
||||
/** Position of the emoji relative to text */
|
||||
emojiPosition: "side" | "top";
|
||||
/** Placeholder to display when the document has no title */
|
||||
placeholder?: string;
|
||||
/** Should the title be editable, policies will also be considered separately */
|
||||
@@ -59,7 +57,6 @@ const DocumentTitle = React.forwardRef(function _DocumentTitle(
|
||||
documentId,
|
||||
title,
|
||||
emoji,
|
||||
emojiPosition,
|
||||
readOnly,
|
||||
onChangeTitle,
|
||||
onChangeEmoji,
|
||||
@@ -247,12 +244,7 @@ const DocumentTitle = React.forwardRef(function _DocumentTitle(
|
||||
ref={mergeRefs([ref, externalRef])}
|
||||
>
|
||||
{can.update && !readOnly ? (
|
||||
<EmojiWrapper
|
||||
align="center"
|
||||
justify="center"
|
||||
$position={emojiPosition}
|
||||
dir={dir}
|
||||
>
|
||||
<EmojiWrapper align="center" justify="center" dir={dir}>
|
||||
<React.Suspense fallback={emojiIcon}>
|
||||
<StyledEmojiPicker
|
||||
value={emoji}
|
||||
@@ -265,12 +257,7 @@ const DocumentTitle = React.forwardRef(function _DocumentTitle(
|
||||
</React.Suspense>
|
||||
</EmojiWrapper>
|
||||
) : emoji ? (
|
||||
<EmojiWrapper
|
||||
align="center"
|
||||
justify="center"
|
||||
$position={emojiPosition}
|
||||
dir={dir}
|
||||
>
|
||||
<EmojiWrapper align="center" justify="center" dir={dir}>
|
||||
{emojiIcon}
|
||||
</EmojiWrapper>
|
||||
) : null}
|
||||
@@ -282,25 +269,17 @@ const StyledEmojiPicker = styled(EmojiPicker)`
|
||||
${extraArea(8)}
|
||||
`;
|
||||
|
||||
const EmojiWrapper = styled(Flex)<{ $position: "top" | "side"; dir?: string }>`
|
||||
const EmojiWrapper = styled(Flex)<{ dir?: string }>`
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
|
||||
// Always move above TOC
|
||||
z-index: 1;
|
||||
|
||||
${(props) =>
|
||||
props.$position === "top"
|
||||
? css`
|
||||
position: relative;
|
||||
top: -8px;
|
||||
`
|
||||
: css`
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
${(props: { dir?: string }) =>
|
||||
props.dir === "rtl" ? "right: -40px" : "left: -40px"};
|
||||
`}
|
||||
${(props: { dir?: string }) =>
|
||||
props.dir === "rtl" ? "right: -40px" : "left: -40px"};
|
||||
`;
|
||||
|
||||
type TitleProps = {
|
||||
|
||||
@@ -187,7 +187,6 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
|
||||
: document.title
|
||||
}
|
||||
emoji={document.emoji}
|
||||
emojiPosition={document.fullWidth ? "top" : "side"}
|
||||
onChangeTitle={onChangeTitle}
|
||||
onChangeEmoji={onChangeEmoji}
|
||||
onGoToNextInput={handleGoToNextInput}
|
||||
|
||||
@@ -31,7 +31,6 @@ function RevisionViewer(props: Props) {
|
||||
documentId={revision.documentId}
|
||||
title={revision.title}
|
||||
emoji={revision.emoji}
|
||||
emojiPosition={document.fullWidth ? "top" : "side"}
|
||||
readOnly
|
||||
/>
|
||||
<DocumentMeta
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useTranslation, Trans } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { ThemeProvider, useTheme } from "styled-components";
|
||||
import { buildDarkTheme, buildLightTheme } from "@shared/styles/theme";
|
||||
import { CustomTheme, TeamPreference } from "@shared/types";
|
||||
import { CustomTheme, TOCPosition, TeamPreference } from "@shared/types";
|
||||
import { getBaseDomain } from "@shared/utils/domains";
|
||||
import Button from "~/components/Button";
|
||||
import ButtonLink from "~/components/ButtonLink";
|
||||
@@ -16,6 +16,7 @@ import DefaultCollectionInputSelect from "~/components/DefaultCollectionInputSel
|
||||
import Heading from "~/components/Heading";
|
||||
import Input from "~/components/Input";
|
||||
import InputColor from "~/components/InputColor";
|
||||
import InputSelect from "~/components/InputSelect";
|
||||
import Scene from "~/components/Scene";
|
||||
import Switch from "~/components/Switch";
|
||||
import Text from "~/components/Text";
|
||||
@@ -58,6 +59,10 @@ function Details() {
|
||||
isHexColor
|
||||
);
|
||||
|
||||
const [tocPosition, setTocPosition] = useState(
|
||||
team.getPreference(TeamPreference.TocPosition) as TOCPosition
|
||||
);
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
async (event?: React.SyntheticEvent) => {
|
||||
if (event) {
|
||||
@@ -73,6 +78,7 @@ function Details() {
|
||||
...team.preferences,
|
||||
publicBranding,
|
||||
customTheme,
|
||||
tocPosition,
|
||||
},
|
||||
});
|
||||
toast.success(t("Settings saved"));
|
||||
@@ -174,7 +180,6 @@ function Details() {
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
border={false}
|
||||
label={t("Theme")}
|
||||
name="accent"
|
||||
description={
|
||||
@@ -212,7 +217,6 @@ function Details() {
|
||||
</SettingRow>
|
||||
{team.avatarUrl && (
|
||||
<SettingRow
|
||||
border={false}
|
||||
name={TeamPreference.PublicBranding}
|
||||
label={t("Public branding")}
|
||||
description={t(
|
||||
@@ -229,6 +233,30 @@ function Details() {
|
||||
/>
|
||||
</SettingRow>
|
||||
)}
|
||||
<SettingRow
|
||||
border={false}
|
||||
label={t("Table of contents position")}
|
||||
name="tocPosition"
|
||||
description={t(
|
||||
"The side to display the table of contents in relation to the main content."
|
||||
)}
|
||||
>
|
||||
<InputSelect
|
||||
ariaLabel={t("Table of contents position")}
|
||||
options={[
|
||||
{
|
||||
label: t("Left"),
|
||||
value: TOCPosition.Left,
|
||||
},
|
||||
{
|
||||
label: t("Right"),
|
||||
value: TOCPosition.Right,
|
||||
},
|
||||
]}
|
||||
value={tocPosition}
|
||||
onChange={(p: TOCPosition) => setTocPosition(p)}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<Heading as="h2">{t("Behavior")}</Heading>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { UserRole } from "@shared/types";
|
||||
import { TOCPosition, UserRole } from "@shared/types";
|
||||
import { BaseSchema } from "@server/routes/api/schema";
|
||||
|
||||
export const TeamsUpdateSchema = BaseSchema.extend({
|
||||
@@ -50,6 +50,8 @@ export const TeamsUpdateSchema = BaseSchema.extend({
|
||||
accentText: z.string().min(4).max(7).regex(/^#/).optional(),
|
||||
})
|
||||
.optional(),
|
||||
/** Side to display the document's table of contents in relation to the main content. */
|
||||
tocPosition: z.nativeEnum(TOCPosition).optional(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
TOCPosition,
|
||||
TeamPreference,
|
||||
TeamPreferences,
|
||||
UserPreference,
|
||||
@@ -22,6 +23,7 @@ export const TeamPreferenceDefaults: TeamPreferences = {
|
||||
[TeamPreference.PublicBranding]: false,
|
||||
[TeamPreference.Commenting]: true,
|
||||
[TeamPreference.CustomTheme]: undefined,
|
||||
[TeamPreference.TocPosition]: TOCPosition.Left,
|
||||
};
|
||||
|
||||
export const UserPreferenceDefaults: UserPreferences = {
|
||||
|
||||
@@ -36,4 +36,7 @@ export class EditorStyleHelper {
|
||||
|
||||
/** Minimum padding around editor */
|
||||
static readonly padding = 32;
|
||||
|
||||
/** Table of contents width */
|
||||
static readonly tocWidth = 256;
|
||||
}
|
||||
|
||||
@@ -823,6 +823,10 @@
|
||||
"Accent text color": "Accent text color",
|
||||
"Public branding": "Public branding",
|
||||
"Show your team’s logo on public pages like login and shared documents.": "Show your team’s logo on public pages like login and shared documents.",
|
||||
"Table of contents position": "Table of contents position",
|
||||
"The side to display the table of contents in relation to the main content.": "The side to display the table of contents in relation to the main content.",
|
||||
"Left": "Left",
|
||||
"Right": "Right",
|
||||
"Behavior": "Behavior",
|
||||
"Subdomain": "Subdomain",
|
||||
"Your workspace will be accessible at": "Your workspace will be accessible at",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const depths = {
|
||||
toc: 100,
|
||||
header: 800,
|
||||
sidebar: 900,
|
||||
editorToolbar: 925,
|
||||
|
||||
@@ -184,6 +184,11 @@ export type PublicTeam = {
|
||||
customTheme: Partial<CustomTheme>;
|
||||
};
|
||||
|
||||
export enum TOCPosition {
|
||||
Left = "left",
|
||||
Right = "right",
|
||||
}
|
||||
|
||||
export enum TeamPreference {
|
||||
/** Whether documents have a separate edit mode instead of always editing. */
|
||||
SeamlessEdit = "seamlessEdit",
|
||||
@@ -199,6 +204,8 @@ export enum TeamPreference {
|
||||
Commenting = "commenting",
|
||||
/** The custom theme for the team. */
|
||||
CustomTheme = "customTheme",
|
||||
/** Side to display the document's table of contents in relation to the main content. */
|
||||
TocPosition = "tocPosition",
|
||||
}
|
||||
|
||||
export type TeamPreferences = {
|
||||
@@ -209,6 +216,7 @@ export type TeamPreferences = {
|
||||
[TeamPreference.MembersCanCreateApiKey]?: boolean;
|
||||
[TeamPreference.Commenting]?: boolean;
|
||||
[TeamPreference.CustomTheme]?: Partial<CustomTheme>;
|
||||
[TeamPreference.TocPosition]?: TOCPosition;
|
||||
};
|
||||
|
||||
export enum NavigationNodeType {
|
||||
|
||||
Reference in New Issue
Block a user