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:
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import Arrow from "~/components/Arrow";
|
||||
type Props = {
|
||||
direction: "left" | "right";
|
||||
style?: React.CSSProperties;
|
||||
onClick?: () => any;
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
};
|
||||
|
||||
const Toggle = React.forwardRef<HTMLButtonElement, Props>(
|
||||
|
||||
104
app/components/Toggle.tsx
Normal file
104
app/components/Toggle.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import { useHistory } from "react-router-dom";
|
||||
import { useMenuState, MenuButton } from "reakit/Menu";
|
||||
import { VisuallyHidden } from "reakit/VisuallyHidden";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import Document from "~/models/Document";
|
||||
import DocumentDelete from "~/scenes/DocumentDelete";
|
||||
import DocumentMove from "~/scenes/DocumentMove";
|
||||
@@ -33,9 +34,11 @@ import DocumentTemplatize from "~/scenes/DocumentTemplatize";
|
||||
import CollectionIcon from "~/components/CollectionIcon";
|
||||
import ContextMenu from "~/components/ContextMenu";
|
||||
import OverflowMenuButton from "~/components/ContextMenu/OverflowMenuButton";
|
||||
import Separator from "~/components/ContextMenu/Separator";
|
||||
import Template from "~/components/ContextMenu/Template";
|
||||
import Flex from "~/components/Flex";
|
||||
import Modal from "~/components/Modal";
|
||||
import Toggle from "~/components/Toggle";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
@@ -52,7 +55,8 @@ type Props = {
|
||||
document: Document;
|
||||
className?: string;
|
||||
isRevision?: boolean;
|
||||
showPrint?: boolean;
|
||||
/** Pass true if the document is currently being displayed */
|
||||
showDisplayOptions?: boolean;
|
||||
modal?: boolean;
|
||||
showToggleEmbeds?: boolean;
|
||||
showPin?: boolean;
|
||||
@@ -67,7 +71,7 @@ function DocumentMenu({
|
||||
className,
|
||||
modal = true,
|
||||
showToggleEmbeds,
|
||||
showPrint,
|
||||
showDisplayOptions,
|
||||
showPin,
|
||||
label,
|
||||
onOpen,
|
||||
@@ -448,11 +452,26 @@ function DocumentMenu({
|
||||
type: "button",
|
||||
title: t("Print"),
|
||||
onClick: handlePrint,
|
||||
visible: !!showPrint,
|
||||
visible: !!showDisplayOptions,
|
||||
icon: <PrintIcon />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{showDisplayOptions && (
|
||||
<>
|
||||
<Separator />
|
||||
<Style>
|
||||
<ToggleMenuItem
|
||||
label={t("Full width")}
|
||||
checked={document.fullWidth}
|
||||
onChange={(ev) => {
|
||||
document.fullWidth = ev.target.checked;
|
||||
document.save();
|
||||
}}
|
||||
/>
|
||||
</Style>
|
||||
</>
|
||||
)}
|
||||
</ContextMenu>
|
||||
{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`
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -50,6 +50,10 @@ export default class Document extends BaseModel {
|
||||
@observable
|
||||
template: boolean;
|
||||
|
||||
@Field
|
||||
@observable
|
||||
fullWidth: boolean;
|
||||
|
||||
@Field
|
||||
@observable
|
||||
templateId: string | undefined;
|
||||
@@ -311,6 +315,7 @@ export default class Document extends BaseModel {
|
||||
{
|
||||
id: this.id,
|
||||
title: options.title || this.title,
|
||||
fullWidth: this.fullWidth,
|
||||
},
|
||||
{
|
||||
lastRevision: options.lastRevision,
|
||||
@@ -327,7 +332,7 @@ export default class Document extends BaseModel {
|
||||
};
|
||||
|
||||
@action
|
||||
save = async (options: SaveOptions | undefined) => {
|
||||
save = async (options?: SaveOptions | undefined) => {
|
||||
if (this.isSaving) return this;
|
||||
const isCreating = !this.id;
|
||||
this.isSaving = true;
|
||||
@@ -349,24 +354,21 @@ export default class Document extends BaseModel {
|
||||
);
|
||||
}
|
||||
|
||||
if (options?.lastRevision) {
|
||||
return await this.store.update(
|
||||
{
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
text: this.text,
|
||||
templateId: this.templateId,
|
||||
},
|
||||
{
|
||||
lastRevision: options?.lastRevision,
|
||||
publish: options?.publish,
|
||||
done: options?.done,
|
||||
autosave: options?.autosave,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error("Attempting to update without a lastRevision");
|
||||
return await this.store.update(
|
||||
{
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
text: this.text,
|
||||
fullWidth: this.fullWidth,
|
||||
templateId: this.templateId,
|
||||
},
|
||||
{
|
||||
lastRevision: options?.lastRevision || this.revision,
|
||||
publish: options?.publish,
|
||||
done: options?.done,
|
||||
autosave: options?.autosave,
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import useWindowScrollPosition from "~/hooks/useWindowScrollPosition";
|
||||
const HEADING_OFFSET = 20;
|
||||
|
||||
type Props = {
|
||||
isFullWidth: boolean;
|
||||
headings: {
|
||||
title: string;
|
||||
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 position = useWindowScrollPosition({
|
||||
throttle: 100,
|
||||
@@ -49,8 +50,8 @@ export default function Contents({ headings }: Props) {
|
||||
const headingAdjustment = minHeading - 1;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Wrapper>
|
||||
<Wrapper isFullWidth={isFullWidth}>
|
||||
<Sticky>
|
||||
<Heading>Contents</Heading>
|
||||
{headings.length ? (
|
||||
<List>
|
||||
@@ -67,30 +68,38 @@ export default function Contents({ headings }: Props) {
|
||||
) : (
|
||||
<Empty>Headings you add to the document will appear here</Empty>
|
||||
)}
|
||||
</Wrapper>
|
||||
</div>
|
||||
</Sticky>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
const Wrapper = styled.div<{ isFullWidth: boolean }>`
|
||||
width: 256px;
|
||||
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);
|
||||
|
||||
box-shadow: 1px 0 0 ${(props) => props.theme.divider};
|
||||
margin-top: 40px;
|
||||
margin-right: 3.2em;
|
||||
margin-right: 32px;
|
||||
width: 224px;
|
||||
min-height: 40px;
|
||||
overflow-y: auto;
|
||||
|
||||
${breakpoint("desktopLarge")`
|
||||
margin-left: -16em;
|
||||
`};
|
||||
|
||||
${breakpoint("tablet")`
|
||||
display: block;
|
||||
`};
|
||||
`;
|
||||
|
||||
const Heading = styled.h3`
|
||||
|
||||
@@ -493,6 +493,7 @@ class DocumentScene extends React.Component<Props> {
|
||||
archived={document.isArchived}
|
||||
showContents={showContents}
|
||||
isEditing={!readOnly}
|
||||
isFullWidth={document.fullWidth}
|
||||
column
|
||||
auto
|
||||
>
|
||||
@@ -544,7 +545,12 @@ class DocumentScene extends React.Component<Props> {
|
||||
)}
|
||||
<React.Suspense fallback={<PlaceholderDocument />}>
|
||||
<Flex auto={!readOnly}>
|
||||
{showContents && <Contents headings={headings} />}
|
||||
{showContents && (
|
||||
<Contents
|
||||
headings={headings}
|
||||
isFullWidth={document.fullWidth}
|
||||
/>
|
||||
)}
|
||||
<Editor
|
||||
id={document.id}
|
||||
key={disableEmbeds ? "disabled" : "enabled"}
|
||||
@@ -628,6 +634,7 @@ const ReferencesWrapper = styled.div<{ isOnlyTitle?: boolean }>`
|
||||
|
||||
type MaxWidthProps = {
|
||||
isEditing?: boolean;
|
||||
isFullWidth?: boolean;
|
||||
archived?: boolean;
|
||||
showContents?: boolean;
|
||||
};
|
||||
@@ -636,22 +643,23 @@ const MaxWidth = styled(Flex)<MaxWidthProps>`
|
||||
${(props) =>
|
||||
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;
|
||||
transition: padding 100ms;
|
||||
|
||||
max-width: 100vw;
|
||||
width: 100%;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
padding: 0 24px;
|
||||
margin: 4px auto 12px;
|
||||
max-width: calc(48px + ${(props: MaxWidthProps) =>
|
||||
props.showContents ? "64em" : "46em"});
|
||||
max-width: ${(props: MaxWidthProps) =>
|
||||
props.isFullWidth
|
||||
? "100vw"
|
||||
: `calc(64px + 46em + ${props.showContents ? "256px" : "0px"});`}
|
||||
`};
|
||||
|
||||
${breakpoint("desktopLarge")`
|
||||
max-width: calc(48px + 52em);
|
||||
max-width: ${(props: MaxWidthProps) =>
|
||||
props.isFullWidth ? "100vw" : `calc(64px + 52em);`}
|
||||
`};
|
||||
`;
|
||||
|
||||
|
||||
@@ -306,7 +306,7 @@ function DocumentHeader({
|
||||
/>
|
||||
)}
|
||||
showToggleEmbeds={canToggleEmbeds}
|
||||
showPrint
|
||||
showDisplayOptions
|
||||
/>
|
||||
</Action>
|
||||
</>
|
||||
|
||||
@@ -4,10 +4,10 @@ import { BeakerIcon } from "outline-icons";
|
||||
import { useState } from "react";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import Checkbox from "~/components/Checkbox";
|
||||
import Heading from "~/components/Heading";
|
||||
import HelpText from "~/components/HelpText";
|
||||
import Scene from "~/components/Scene";
|
||||
import Toggle from "~/components/Toggle";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
@@ -52,7 +52,7 @@ function Features() {
|
||||
the experience for all team members.
|
||||
</Trans>
|
||||
</HelpText>
|
||||
<Checkbox
|
||||
<Toggle
|
||||
label={t("Collaborative editing")}
|
||||
name="collaborativeEditing"
|
||||
checked={data.collaborativeEditing}
|
||||
|
||||
@@ -4,11 +4,11 @@ import { PadlockIcon } from "outline-icons";
|
||||
import { useState } from "react";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import Checkbox from "~/components/Checkbox";
|
||||
import Heading from "~/components/Heading";
|
||||
import HelpText from "~/components/HelpText";
|
||||
import InputSelect from "~/components/InputSelect";
|
||||
import Scene from "~/components/Scene";
|
||||
import Toggle from "~/components/Toggle";
|
||||
import env from "~/env";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useStores from "~/hooks/useStores";
|
||||
@@ -69,7 +69,7 @@ function Security() {
|
||||
</Trans>
|
||||
</HelpText>
|
||||
|
||||
<Checkbox
|
||||
<Toggle
|
||||
label={t("Allow email authentication")}
|
||||
name="guestSignin"
|
||||
checked={data.guestSignin}
|
||||
@@ -81,7 +81,7 @@ function Security() {
|
||||
}
|
||||
disabled={!env.EMAIL_ENABLED}
|
||||
/>
|
||||
<Checkbox
|
||||
<Toggle
|
||||
label={t("Public document sharing")}
|
||||
name="sharing"
|
||||
checked={data.sharing}
|
||||
@@ -90,7 +90,7 @@ function Security() {
|
||||
"When enabled, documents can be shared publicly on the internet by any team member"
|
||||
)}
|
||||
/>
|
||||
<Checkbox
|
||||
<Toggle
|
||||
label={t("Rich service embeds")}
|
||||
name="documentEmbeds"
|
||||
checked={data.documentEmbeds}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import NotificationSetting from "~/models/NotificationSetting";
|
||||
import Checkbox from "~/components/Checkbox";
|
||||
import Toggle from "~/components/Toggle";
|
||||
|
||||
type Props = {
|
||||
setting?: NotificationSetting;
|
||||
@@ -20,7 +20,7 @@ const NotificationListItem = ({
|
||||
description,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Checkbox
|
||||
<Toggle
|
||||
label={title}
|
||||
name={event}
|
||||
checked={!!setting}
|
||||
|
||||
@@ -8,12 +8,12 @@ import Collection from "~/models/Collection";
|
||||
import Integration from "~/models/Integration";
|
||||
import Button from "~/components/Button";
|
||||
import ButtonLink from "~/components/ButtonLink";
|
||||
import Checkbox from "~/components/Checkbox";
|
||||
import CollectionIcon from "~/components/CollectionIcon";
|
||||
import Flex from "~/components/Flex";
|
||||
import HelpText from "~/components/HelpText";
|
||||
import ListItem from "~/components/List/Item";
|
||||
import Popover from "~/components/Popover";
|
||||
import Toggle from "~/components/Toggle";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
@@ -82,13 +82,13 @@ function SlackListItem({ integration, collection }: Props) {
|
||||
<Events>
|
||||
<h3>{t("Notifications")}</h3>
|
||||
<HelpText>{t("These events should be posted to Slack")}</HelpText>
|
||||
<Checkbox
|
||||
<Toggle
|
||||
label={t("Document published")}
|
||||
name="documents.publish"
|
||||
checked={integration.events.includes("documents.publish")}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Checkbox
|
||||
<Toggle
|
||||
label={t("Document updated")}
|
||||
name="documents.update"
|
||||
checked={integration.events.includes("documents.update")}
|
||||
|
||||
@@ -643,6 +643,7 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
id: string;
|
||||
title: string;
|
||||
text?: string;
|
||||
fullWidth?: boolean;
|
||||
templateId?: string;
|
||||
},
|
||||
options?: {
|
||||
|
||||
14
server/migrations/20211218193004-documents-full-width.js
Normal file
14
server/migrations/20211218193004-documents-full-width.js
Normal 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");
|
||||
},
|
||||
};
|
||||
@@ -81,6 +81,7 @@ const Document = sequelize.define(
|
||||
previousTitles: DataTypes.ARRAY(DataTypes.STRING),
|
||||
version: DataTypes.SMALLINT,
|
||||
template: DataTypes.BOOLEAN,
|
||||
fullWidth: DataTypes.BOOLEAN,
|
||||
editorVersion: DataTypes.STRING,
|
||||
text: DataTypes.TEXT,
|
||||
state: DataTypes.BLOB,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Attachment, Document } from "@server/models";
|
||||
import { Attachment } from "@server/models";
|
||||
import parseAttachmentIds from "@server/utils/parseAttachmentIds";
|
||||
import { getSignedUrl } from "@server/utils/s3";
|
||||
import presentUser from "./user";
|
||||
@@ -25,80 +25,58 @@ async function replaceImageAttachments(text: string) {
|
||||
}
|
||||
|
||||
export default async function present(
|
||||
document: Document,
|
||||
document: any,
|
||||
options: Options | null | undefined
|
||||
) {
|
||||
options = {
|
||||
isPublic: false,
|
||||
...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();
|
||||
const text = options.isPublic
|
||||
? // @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Document'.
|
||||
await replaceImageAttachments(document.text)
|
||||
: // @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Document'.
|
||||
document.text;
|
||||
? await replaceImageAttachments(document.text)
|
||||
: document.text;
|
||||
const data = {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Document'.
|
||||
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,
|
||||
// @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,
|
||||
title: document.title,
|
||||
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,
|
||||
// @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,
|
||||
// @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,
|
||||
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,
|
||||
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,
|
||||
// @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,
|
||||
// @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,
|
||||
// @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,
|
||||
// @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,
|
||||
// @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,
|
||||
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,
|
||||
// @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,
|
||||
fullWidth: document.fullWidth,
|
||||
pinned: undefined,
|
||||
collectionId: undefined,
|
||||
parentDocumentId: 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) {
|
||||
// @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;
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
// @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;
|
||||
// @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);
|
||||
// @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);
|
||||
// @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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1106,6 +1106,7 @@ router.post("documents.update", auth(), async (ctx) => {
|
||||
id,
|
||||
title,
|
||||
text,
|
||||
fullWidth,
|
||||
publish,
|
||||
autosave,
|
||||
done,
|
||||
@@ -1128,10 +1129,12 @@ router.post("documents.update", auth(), async (ctx) => {
|
||||
}
|
||||
|
||||
const previousTitle = document.title;
|
||||
|
||||
// Update document
|
||||
if (title) document.title = title;
|
||||
if (editorVersion) document.editorVersion = editorVersion;
|
||||
if (templateId) document.templateId = templateId;
|
||||
if (fullWidth !== undefined) document.fullWidth = fullWidth;
|
||||
|
||||
if (!user.team?.collaborativeEditing) {
|
||||
if (append) {
|
||||
|
||||
@@ -241,6 +241,7 @@
|
||||
"Move": "Move",
|
||||
"Enable embeds": "Enable embeds",
|
||||
"Disable embeds": "Disable embeds",
|
||||
"Full width": "Full width",
|
||||
"Move {{ documentName }}": "Move {{ documentName }}",
|
||||
"Permanently delete {{ documentName }}": "Permanently delete {{ documentName }}",
|
||||
"Export options": "Export options",
|
||||
|
||||
Reference in New Issue
Block a user