feat: Optional full-width toggle for document display (#2869)

* Migration, model, presenter

* Working implementation

* fix: Account for table of contents

* Checkbox -> Toggle

* Checkbox -> Toggle
This commit is contained in:
Tom Moor
2021-12-19 13:58:16 -08:00
committed by GitHub
parent 73bc7d9f2a
commit 66d5a567c2
18 changed files with 239 additions and 146 deletions

View File

@@ -1,62 +0,0 @@
import * as React from "react";
import { VisuallyHidden } from "reakit/VisuallyHidden";
import styled from "styled-components";
import HelpText from "~/components/HelpText";
export type Props = {
checked?: boolean;
label?: React.ReactNode;
labelHidden?: boolean;
className?: string;
name?: string;
disabled?: boolean;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => unknown;
note?: React.ReactNode;
small?: boolean;
};
const LabelText = styled.span<{ small?: boolean }>`
font-weight: 500;
margin-left: ${(props) => (props.small ? "6px" : "10px")};
${(props) => (props.small ? `color: ${props.theme.textSecondary}` : "")};
`;
const Wrapper = styled.div<{ small?: boolean }>`
padding-bottom: 8px;
${(props) => (props.small ? "font-size: 14px" : "")};
width: 100%;
`;
const Label = styled.label`
display: flex;
align-items: center;
user-select: none;
`;
export default function Checkbox({
label,
labelHidden,
note,
className,
small,
...rest
}: Props) {
const wrappedLabel = <LabelText small={small}>{label}</LabelText>;
return (
<>
<Wrapper small={small} className={className}>
<Label>
<input type="checkbox" {...rest} />
{label &&
(labelHidden ? (
<VisuallyHidden>{wrappedLabel}</VisuallyHidden>
) : (
wrappedLabel
))}
</Label>
{note && <HelpText small>{note}</HelpText>}
</Wrapper>
</>
);
}

View File

@@ -7,7 +7,7 @@ import Arrow from "~/components/Arrow";
type Props = { type Props = {
direction: "left" | "right"; direction: "left" | "right";
style?: React.CSSProperties; style?: React.CSSProperties;
onClick?: () => any; onClick?: React.MouseEventHandler<HTMLButtonElement>;
}; };
const Toggle = React.forwardRef<HTMLButtonElement, Props>( const Toggle = React.forwardRef<HTMLButtonElement, Props>(

104
app/components/Toggle.tsx Normal file
View File

@@ -0,0 +1,104 @@
import * as React from "react";
import { VisuallyHidden } from "reakit/VisuallyHidden";
import styled from "styled-components";
import HelpText from "~/components/HelpText";
export type Props = {
checked?: boolean;
label?: React.ReactNode;
labelHidden?: boolean;
className?: string;
name?: string;
disabled?: boolean;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => unknown;
note?: React.ReactNode;
};
const LabelText = styled.span`
font-weight: 500;
margin-left: 10px;
`;
const Wrapper = styled.div`
padding-bottom: 8px;
width: 100%;
`;
const Label = styled.label`
display: flex;
align-items: center;
user-select: none;
`;
const SlideToggle = styled.label`
cursor: pointer;
text-indent: -9999px;
width: 26px;
height: 14px;
background: ${(props) => props.theme.slate};
display: block;
border-radius: 10px;
position: relative;
&:after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 10px;
height: 10px;
background: ${(props) => props.theme.white};
border-radius: 5px;
transition: width 100ms ease-in-out;
}
&:active:after {
width: 12px;
}
`;
const HiddenInput = styled.input`
height: 0;
width: 0;
visibility: hidden;
&:checked + ${SlideToggle} {
background: ${(props) => props.theme.primary};
}
&:checked + ${SlideToggle}:after {
left: calc(100% - 2px);
transform: translateX(-100%);
}
`;
let inputId = 0;
export default function Toggle({
label,
labelHidden,
note,
className,
...rest
}: Props) {
const wrappedLabel = <LabelText>{label}</LabelText>;
const [id] = React.useState(`checkbox-input-${inputId++}`);
return (
<>
<Wrapper className={className}>
<Label>
<HiddenInput type="checkbox" id={id} {...rest} />
<SlideToggle htmlFor={id} />
{label &&
(labelHidden ? (
<VisuallyHidden>{wrappedLabel}</VisuallyHidden>
) : (
wrappedLabel
))}
</Label>
{note && <HelpText small>{note}</HelpText>}
</Wrapper>
</>
);
}

View File

@@ -25,6 +25,7 @@ import { useHistory } from "react-router-dom";
import { useMenuState, MenuButton } from "reakit/Menu"; import { useMenuState, MenuButton } from "reakit/Menu";
import { VisuallyHidden } from "reakit/VisuallyHidden"; import { VisuallyHidden } from "reakit/VisuallyHidden";
import styled from "styled-components"; import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import Document from "~/models/Document"; import Document from "~/models/Document";
import DocumentDelete from "~/scenes/DocumentDelete"; import DocumentDelete from "~/scenes/DocumentDelete";
import DocumentMove from "~/scenes/DocumentMove"; import DocumentMove from "~/scenes/DocumentMove";
@@ -33,9 +34,11 @@ import DocumentTemplatize from "~/scenes/DocumentTemplatize";
import CollectionIcon from "~/components/CollectionIcon"; import CollectionIcon from "~/components/CollectionIcon";
import ContextMenu from "~/components/ContextMenu"; import ContextMenu from "~/components/ContextMenu";
import OverflowMenuButton from "~/components/ContextMenu/OverflowMenuButton"; import OverflowMenuButton from "~/components/ContextMenu/OverflowMenuButton";
import Separator from "~/components/ContextMenu/Separator";
import Template from "~/components/ContextMenu/Template"; import Template from "~/components/ContextMenu/Template";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import Modal from "~/components/Modal"; import Modal from "~/components/Modal";
import Toggle from "~/components/Toggle";
import useCurrentTeam from "~/hooks/useCurrentTeam"; import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts"; import useToasts from "~/hooks/useToasts";
@@ -52,7 +55,8 @@ type Props = {
document: Document; document: Document;
className?: string; className?: string;
isRevision?: boolean; isRevision?: boolean;
showPrint?: boolean; /** Pass true if the document is currently being displayed */
showDisplayOptions?: boolean;
modal?: boolean; modal?: boolean;
showToggleEmbeds?: boolean; showToggleEmbeds?: boolean;
showPin?: boolean; showPin?: boolean;
@@ -67,7 +71,7 @@ function DocumentMenu({
className, className,
modal = true, modal = true,
showToggleEmbeds, showToggleEmbeds,
showPrint, showDisplayOptions,
showPin, showPin,
label, label,
onOpen, onOpen,
@@ -448,11 +452,26 @@ function DocumentMenu({
type: "button", type: "button",
title: t("Print"), title: t("Print"),
onClick: handlePrint, onClick: handlePrint,
visible: !!showPrint, visible: !!showDisplayOptions,
icon: <PrintIcon />, icon: <PrintIcon />,
}, },
]} ]}
/> />
{showDisplayOptions && (
<>
<Separator />
<Style>
<ToggleMenuItem
label={t("Full width")}
checked={document.fullWidth}
onChange={(ev) => {
document.fullWidth = ev.target.checked;
document.save();
}}
/>
</Style>
</>
)}
</ContextMenu> </ContextMenu>
{renderModals && ( {renderModals && (
<> <>
@@ -516,6 +535,21 @@ function DocumentMenu({
); );
} }
const ToggleMenuItem = styled(Toggle)`
span {
font-weight: normal;
}
`;
const Style = styled.div`
padding: 12px;
${breakpoint("tablet")`
padding: 4px 12px;
font-size: 14px;
`};
`;
const CollectionName = styled.div` const CollectionName = styled.div`
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;

View File

@@ -50,6 +50,10 @@ export default class Document extends BaseModel {
@observable @observable
template: boolean; template: boolean;
@Field
@observable
fullWidth: boolean;
@Field @Field
@observable @observable
templateId: string | undefined; templateId: string | undefined;
@@ -311,6 +315,7 @@ export default class Document extends BaseModel {
{ {
id: this.id, id: this.id,
title: options.title || this.title, title: options.title || this.title,
fullWidth: this.fullWidth,
}, },
{ {
lastRevision: options.lastRevision, lastRevision: options.lastRevision,
@@ -327,7 +332,7 @@ export default class Document extends BaseModel {
}; };
@action @action
save = async (options: SaveOptions | undefined) => { save = async (options?: SaveOptions | undefined) => {
if (this.isSaving) return this; if (this.isSaving) return this;
const isCreating = !this.id; const isCreating = !this.id;
this.isSaving = true; this.isSaving = true;
@@ -349,24 +354,21 @@ export default class Document extends BaseModel {
); );
} }
if (options?.lastRevision) {
return await this.store.update( return await this.store.update(
{ {
id: this.id, id: this.id,
title: this.title, title: this.title,
text: this.text, text: this.text,
fullWidth: this.fullWidth,
templateId: this.templateId, templateId: this.templateId,
}, },
{ {
lastRevision: options?.lastRevision, lastRevision: options?.lastRevision || this.revision,
publish: options?.publish, publish: options?.publish,
done: options?.done, done: options?.done,
autosave: options?.autosave, autosave: options?.autosave,
} }
); );
}
throw new Error("Attempting to update without a lastRevision");
} finally { } finally {
this.isSaving = false; this.isSaving = false;
} }

View File

@@ -7,6 +7,7 @@ import useWindowScrollPosition from "~/hooks/useWindowScrollPosition";
const HEADING_OFFSET = 20; const HEADING_OFFSET = 20;
type Props = { type Props = {
isFullWidth: boolean;
headings: { headings: {
title: string; title: string;
level: number; level: number;
@@ -14,7 +15,7 @@ type Props = {
}[]; }[];
}; };
export default function Contents({ headings }: Props) { export default function Contents({ headings, isFullWidth }: Props) {
const [activeSlug, setActiveSlug] = React.useState<string>(); const [activeSlug, setActiveSlug] = React.useState<string>();
const position = useWindowScrollPosition({ const position = useWindowScrollPosition({
throttle: 100, throttle: 100,
@@ -49,8 +50,8 @@ export default function Contents({ headings }: Props) {
const headingAdjustment = minHeading - 1; const headingAdjustment = minHeading - 1;
return ( return (
<div> <Wrapper isFullWidth={isFullWidth}>
<Wrapper> <Sticky>
<Heading>Contents</Heading> <Heading>Contents</Heading>
{headings.length ? ( {headings.length ? (
<List> <List>
@@ -67,30 +68,38 @@ export default function Contents({ headings }: Props) {
) : ( ) : (
<Empty>Headings you add to the document will appear here</Empty> <Empty>Headings you add to the document will appear here</Empty>
)} )}
</Sticky>
</Wrapper> </Wrapper>
</div>
); );
} }
const Wrapper = styled.div` const Wrapper = styled.div<{ isFullWidth: boolean }>`
width: 256px;
display: none; display: none;
${breakpoint("tablet")`
display: block;
`};
${(props) =>
!props.isFullWidth &&
breakpoint("desktopLarge")`
transform: translateX(-256px);
width: 0;
`}
`;
const Sticky = styled.div`
position: sticky; position: sticky;
top: 80px; top: 80px;
max-height: calc(100vh - 80px); max-height: calc(100vh - 80px);
box-shadow: 1px 0 0 ${(props) => props.theme.divider}; box-shadow: 1px 0 0 ${(props) => props.theme.divider};
margin-top: 40px; margin-top: 40px;
margin-right: 3.2em; margin-right: 32px;
width: 224px;
min-height: 40px; min-height: 40px;
overflow-y: auto; overflow-y: auto;
${breakpoint("desktopLarge")`
margin-left: -16em;
`};
${breakpoint("tablet")`
display: block;
`};
`; `;
const Heading = styled.h3` const Heading = styled.h3`

View File

@@ -493,6 +493,7 @@ class DocumentScene extends React.Component<Props> {
archived={document.isArchived} archived={document.isArchived}
showContents={showContents} showContents={showContents}
isEditing={!readOnly} isEditing={!readOnly}
isFullWidth={document.fullWidth}
column column
auto auto
> >
@@ -544,7 +545,12 @@ class DocumentScene extends React.Component<Props> {
)} )}
<React.Suspense fallback={<PlaceholderDocument />}> <React.Suspense fallback={<PlaceholderDocument />}>
<Flex auto={!readOnly}> <Flex auto={!readOnly}>
{showContents && <Contents headings={headings} />} {showContents && (
<Contents
headings={headings}
isFullWidth={document.fullWidth}
/>
)}
<Editor <Editor
id={document.id} id={document.id}
key={disableEmbeds ? "disabled" : "enabled"} key={disableEmbeds ? "disabled" : "enabled"}
@@ -628,6 +634,7 @@ const ReferencesWrapper = styled.div<{ isOnlyTitle?: boolean }>`
type MaxWidthProps = { type MaxWidthProps = {
isEditing?: boolean; isEditing?: boolean;
isFullWidth?: boolean;
archived?: boolean; archived?: boolean;
showContents?: boolean; showContents?: boolean;
}; };
@@ -636,22 +643,23 @@ const MaxWidth = styled(Flex)<MaxWidthProps>`
${(props) => ${(props) =>
props.archived && `* { color: ${props.theme.textSecondary} !important; } `}; props.archived && `* { color: ${props.theme.textSecondary} !important; } `};
// Adds space to the gutter to make room for heading annotations on mobile // Adds space to the gutter to make room for heading annotations
padding: 0 32px; padding: 0 32px;
transition: padding 100ms; transition: padding 100ms;
max-width: 100vw; max-width: 100vw;
width: 100%; width: 100%;
${breakpoint("tablet")` ${breakpoint("tablet")`
padding: 0 24px;
margin: 4px auto 12px; margin: 4px auto 12px;
max-width: calc(48px + ${(props: MaxWidthProps) => max-width: ${(props: MaxWidthProps) =>
props.showContents ? "64em" : "46em"}); props.isFullWidth
? "100vw"
: `calc(64px + 46em + ${props.showContents ? "256px" : "0px"});`}
`}; `};
${breakpoint("desktopLarge")` ${breakpoint("desktopLarge")`
max-width: calc(48px + 52em); max-width: ${(props: MaxWidthProps) =>
props.isFullWidth ? "100vw" : `calc(64px + 52em);`}
`}; `};
`; `;

View File

@@ -306,7 +306,7 @@ function DocumentHeader({
/> />
)} )}
showToggleEmbeds={canToggleEmbeds} showToggleEmbeds={canToggleEmbeds}
showPrint showDisplayOptions
/> />
</Action> </Action>
</> </>

View File

@@ -4,10 +4,10 @@ import { BeakerIcon } from "outline-icons";
import { useState } from "react"; import { useState } from "react";
import * as React from "react"; import * as React from "react";
import { useTranslation, Trans } from "react-i18next"; import { useTranslation, Trans } from "react-i18next";
import Checkbox from "~/components/Checkbox";
import Heading from "~/components/Heading"; import Heading from "~/components/Heading";
import HelpText from "~/components/HelpText"; import HelpText from "~/components/HelpText";
import Scene from "~/components/Scene"; import Scene from "~/components/Scene";
import Toggle from "~/components/Toggle";
import useCurrentTeam from "~/hooks/useCurrentTeam"; import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts"; import useToasts from "~/hooks/useToasts";
@@ -52,7 +52,7 @@ function Features() {
the experience for all team members. the experience for all team members.
</Trans> </Trans>
</HelpText> </HelpText>
<Checkbox <Toggle
label={t("Collaborative editing")} label={t("Collaborative editing")}
name="collaborativeEditing" name="collaborativeEditing"
checked={data.collaborativeEditing} checked={data.collaborativeEditing}

View File

@@ -4,11 +4,11 @@ import { PadlockIcon } from "outline-icons";
import { useState } from "react"; import { useState } from "react";
import * as React from "react"; import * as React from "react";
import { useTranslation, Trans } from "react-i18next"; import { useTranslation, Trans } from "react-i18next";
import Checkbox from "~/components/Checkbox";
import Heading from "~/components/Heading"; import Heading from "~/components/Heading";
import HelpText from "~/components/HelpText"; import HelpText from "~/components/HelpText";
import InputSelect from "~/components/InputSelect"; import InputSelect from "~/components/InputSelect";
import Scene from "~/components/Scene"; import Scene from "~/components/Scene";
import Toggle from "~/components/Toggle";
import env from "~/env"; import env from "~/env";
import useCurrentTeam from "~/hooks/useCurrentTeam"; import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
@@ -69,7 +69,7 @@ function Security() {
</Trans> </Trans>
</HelpText> </HelpText>
<Checkbox <Toggle
label={t("Allow email authentication")} label={t("Allow email authentication")}
name="guestSignin" name="guestSignin"
checked={data.guestSignin} checked={data.guestSignin}
@@ -81,7 +81,7 @@ function Security() {
} }
disabled={!env.EMAIL_ENABLED} disabled={!env.EMAIL_ENABLED}
/> />
<Checkbox <Toggle
label={t("Public document sharing")} label={t("Public document sharing")}
name="sharing" name="sharing"
checked={data.sharing} checked={data.sharing}
@@ -90,7 +90,7 @@ function Security() {
"When enabled, documents can be shared publicly on the internet by any team member" "When enabled, documents can be shared publicly on the internet by any team member"
)} )}
/> />
<Checkbox <Toggle
label={t("Rich service embeds")} label={t("Rich service embeds")}
name="documentEmbeds" name="documentEmbeds"
checked={data.documentEmbeds} checked={data.documentEmbeds}

View File

@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import NotificationSetting from "~/models/NotificationSetting"; import NotificationSetting from "~/models/NotificationSetting";
import Checkbox from "~/components/Checkbox"; import Toggle from "~/components/Toggle";
type Props = { type Props = {
setting?: NotificationSetting; setting?: NotificationSetting;
@@ -20,7 +20,7 @@ const NotificationListItem = ({
description, description,
}: Props) => { }: Props) => {
return ( return (
<Checkbox <Toggle
label={title} label={title}
name={event} name={event}
checked={!!setting} checked={!!setting}

View File

@@ -8,12 +8,12 @@ import Collection from "~/models/Collection";
import Integration from "~/models/Integration"; import Integration from "~/models/Integration";
import Button from "~/components/Button"; import Button from "~/components/Button";
import ButtonLink from "~/components/ButtonLink"; import ButtonLink from "~/components/ButtonLink";
import Checkbox from "~/components/Checkbox";
import CollectionIcon from "~/components/CollectionIcon"; import CollectionIcon from "~/components/CollectionIcon";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import HelpText from "~/components/HelpText"; import HelpText from "~/components/HelpText";
import ListItem from "~/components/List/Item"; import ListItem from "~/components/List/Item";
import Popover from "~/components/Popover"; import Popover from "~/components/Popover";
import Toggle from "~/components/Toggle";
import useToasts from "~/hooks/useToasts"; import useToasts from "~/hooks/useToasts";
type Props = { type Props = {
@@ -82,13 +82,13 @@ function SlackListItem({ integration, collection }: Props) {
<Events> <Events>
<h3>{t("Notifications")}</h3> <h3>{t("Notifications")}</h3>
<HelpText>{t("These events should be posted to Slack")}</HelpText> <HelpText>{t("These events should be posted to Slack")}</HelpText>
<Checkbox <Toggle
label={t("Document published")} label={t("Document published")}
name="documents.publish" name="documents.publish"
checked={integration.events.includes("documents.publish")} checked={integration.events.includes("documents.publish")}
onChange={handleChange} onChange={handleChange}
/> />
<Checkbox <Toggle
label={t("Document updated")} label={t("Document updated")}
name="documents.update" name="documents.update"
checked={integration.events.includes("documents.update")} checked={integration.events.includes("documents.update")}

View File

@@ -643,6 +643,7 @@ export default class DocumentsStore extends BaseStore<Document> {
id: string; id: string;
title: string; title: string;
text?: string; text?: string;
fullWidth?: boolean;
templateId?: string; templateId?: string;
}, },
options?: { options?: {

View File

@@ -0,0 +1,14 @@
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn("documents", "fullWidth", {
type: Sequelize.BOOLEAN,
defaultValue: false,
allowNull: false,
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn("documents", "fullWidth");
},
};

View File

@@ -81,6 +81,7 @@ const Document = sequelize.define(
previousTitles: DataTypes.ARRAY(DataTypes.STRING), previousTitles: DataTypes.ARRAY(DataTypes.STRING),
version: DataTypes.SMALLINT, version: DataTypes.SMALLINT,
template: DataTypes.BOOLEAN, template: DataTypes.BOOLEAN,
fullWidth: DataTypes.BOOLEAN,
editorVersion: DataTypes.STRING, editorVersion: DataTypes.STRING,
text: DataTypes.TEXT, text: DataTypes.TEXT,
state: DataTypes.BLOB, state: DataTypes.BLOB,

View File

@@ -1,4 +1,4 @@
import { Attachment, Document } from "@server/models"; import { Attachment } from "@server/models";
import parseAttachmentIds from "@server/utils/parseAttachmentIds"; import parseAttachmentIds from "@server/utils/parseAttachmentIds";
import { getSignedUrl } from "@server/utils/s3"; import { getSignedUrl } from "@server/utils/s3";
import presentUser from "./user"; import presentUser from "./user";
@@ -25,80 +25,58 @@ async function replaceImageAttachments(text: string) {
} }
export default async function present( export default async function present(
document: Document, document: any,
options: Options | null | undefined options: Options | null | undefined
) { ) {
options = { options = {
isPublic: false, isPublic: false,
...options, ...options,
}; };
// @ts-expect-error ts-migrate(2339) FIXME: Property 'migrateVersion' does not exist on type '... Remove this comment to see the full error message
await document.migrateVersion(); await document.migrateVersion();
const text = options.isPublic const text = options.isPublic
? // @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Document'. ? await replaceImageAttachments(document.text)
await replaceImageAttachments(document.text) : document.text;
: // @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Document'.
document.text;
const data = { const data = {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Document'.
id: document.id, id: document.id,
// @ts-expect-error ts-migrate(2551) FIXME: Property 'url' does not exist on type 'Document'. ... Remove this comment to see the full error message
url: document.url, url: document.url,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'urlId' does not exist on type 'Document'... Remove this comment to see the full error message
urlId: document.urlId, urlId: document.urlId,
title: document.title, title: document.title,
text, text,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'emoji' does not exist on type 'Document'... Remove this comment to see the full error message
emoji: document.emoji, emoji: document.emoji,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'tasks' does not exist on type 'Document'... Remove this comment to see the full error message
tasks: document.tasks, tasks: document.tasks,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'createdAt' does not exist on type 'Docum... Remove this comment to see the full error message
createdAt: document.createdAt, createdAt: document.createdAt,
createdBy: undefined, createdBy: undefined,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'updatedAt' does not exist on type 'Docum... Remove this comment to see the full error message
updatedAt: document.updatedAt, updatedAt: document.updatedAt,
updatedBy: undefined, updatedBy: undefined,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'publishedAt' does not exist on type 'Doc... Remove this comment to see the full error message
publishedAt: document.publishedAt, publishedAt: document.publishedAt,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'archivedAt' does not exist on type 'Docu... Remove this comment to see the full error message
archivedAt: document.archivedAt, archivedAt: document.archivedAt,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'deletedAt' does not exist on type 'Docum... Remove this comment to see the full error message
deletedAt: document.deletedAt, deletedAt: document.deletedAt,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'teamId' does not exist on type 'Document... Remove this comment to see the full error message
teamId: document.teamId, teamId: document.teamId,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'template' does not exist on type 'Docume... Remove this comment to see the full error message
template: document.template, template: document.template,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'templateId' does not exist on type 'Docu... Remove this comment to see the full error message
templateId: document.templateId, templateId: document.templateId,
collaboratorIds: [], collaboratorIds: [],
// @ts-expect-error ts-migrate(2339) FIXME: Property 'starred' does not exist on type 'Documen... Remove this comment to see the full error message
starred: document.starred ? !!document.starred.length : undefined, starred: document.starred ? !!document.starred.length : undefined,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'revisionCount' does not exist on type 'D... Remove this comment to see the full error message
revision: document.revisionCount, revision: document.revisionCount,
fullWidth: document.fullWidth,
pinned: undefined, pinned: undefined,
collectionId: undefined, collectionId: undefined,
parentDocumentId: undefined, parentDocumentId: undefined,
lastViewedAt: undefined, lastViewedAt: undefined,
}; };
// @ts-expect-error ts-migrate(2339) FIXME: Property 'views' does not exist on type 'Document'... Remove this comment to see the full error message
if (!!document.views && document.views.length > 0) { if (!!document.views && document.views.length > 0) {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'views' does not exist on type 'Document'... Remove this comment to see the full error message
data.lastViewedAt = document.views[0].updatedAt; data.lastViewedAt = document.views[0].updatedAt;
} }
if (!options.isPublic) { if (!options.isPublic) {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'undefine... Remove this comment to see the full error message // @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'undefine... Remove this comment to see the full error message
data.pinned = !!document.pinnedById; data.pinned = !!document.pinnedById;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'collectionId' does not exist on type 'Do... Remove this comment to see the full error message
data.collectionId = document.collectionId; data.collectionId = document.collectionId;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'parentDocumentId' does not exist on type... Remove this comment to see the full error message
data.parentDocumentId = document.parentDocumentId; data.parentDocumentId = document.parentDocumentId;
// @ts-expect-error ts-migrate(2322) FIXME: Type 'UserPresentation | null | undefined' is not ... Remove this comment to see the full error message // @ts-expect-error ts-migrate(2322) FIXME: Type 'UserPresentation | null | undefined' is not ... Remove this comment to see the full error message
data.createdBy = presentUser(document.createdBy); data.createdBy = presentUser(document.createdBy);
// @ts-expect-error ts-migrate(2322) FIXME: Type 'UserPresentation | null | undefined' is not ... Remove this comment to see the full error message // @ts-expect-error ts-migrate(2322) FIXME: Type 'UserPresentation | null | undefined' is not ... Remove this comment to see the full error message
data.updatedBy = presentUser(document.updatedBy); data.updatedBy = presentUser(document.updatedBy);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'collaboratorIds' does not exist on type ... Remove this comment to see the full error message
data.collaboratorIds = document.collaboratorIds; data.collaboratorIds = document.collaboratorIds;
} }

View File

@@ -1106,6 +1106,7 @@ router.post("documents.update", auth(), async (ctx) => {
id, id,
title, title,
text, text,
fullWidth,
publish, publish,
autosave, autosave,
done, done,
@@ -1128,10 +1129,12 @@ router.post("documents.update", auth(), async (ctx) => {
} }
const previousTitle = document.title; const previousTitle = document.title;
// Update document // Update document
if (title) document.title = title; if (title) document.title = title;
if (editorVersion) document.editorVersion = editorVersion; if (editorVersion) document.editorVersion = editorVersion;
if (templateId) document.templateId = templateId; if (templateId) document.templateId = templateId;
if (fullWidth !== undefined) document.fullWidth = fullWidth;
if (!user.team?.collaborativeEditing) { if (!user.team?.collaborativeEditing) {
if (append) { if (append) {

View File

@@ -241,6 +241,7 @@
"Move": "Move", "Move": "Move",
"Enable embeds": "Enable embeds", "Enable embeds": "Enable embeds",
"Disable embeds": "Disable embeds", "Disable embeds": "Disable embeds",
"Full width": "Full width",
"Move {{ documentName }}": "Move {{ documentName }}", "Move {{ documentName }}": "Move {{ documentName }}",
"Permanently delete {{ documentName }}": "Permanently delete {{ documentName }}", "Permanently delete {{ documentName }}": "Permanently delete {{ documentName }}",
"Export options": "Export options", "Export options": "Export options",