feat: Nested document sharing (#2075)
* migration * frontend routing, api permissioning * feat: apiVersion=2 * feat: re-writing document links to point to share * poc nested documents on share links * fix: nested shareId permissions * ui and language tweaks, comments * breadcrumbs * Add icons to reference list items * refactor: Breadcrumb component * tweaks * Add shared parent note
This commit is contained in:
@@ -22,11 +22,10 @@ import DocumentComponent from "./Document";
|
||||
import HideSidebar from "./HideSidebar";
|
||||
import Loading from "./Loading";
|
||||
import SocketPresence from "./SocketPresence";
|
||||
import { type LocationWithState } from "types";
|
||||
import { type LocationWithState, type NavigationNode } from "types";
|
||||
import { NotFoundError, OfflineError } from "utils/errors";
|
||||
import { matchDocumentEdit, updateDocumentUrl } from "utils/routeHelpers";
|
||||
import { isInternalUrl } from "utils/urls";
|
||||
|
||||
type Props = {|
|
||||
match: Match,
|
||||
location: LocationWithState,
|
||||
@@ -41,6 +40,7 @@ type Props = {|
|
||||
|
||||
@observer
|
||||
class DataLoader extends React.Component<Props> {
|
||||
@observable sharedTree: ?NavigationNode;
|
||||
@observable document: ?Document;
|
||||
@observable revision: ?Revision;
|
||||
@observable error: ?Error;
|
||||
@@ -89,7 +89,7 @@ class DataLoader extends React.Component<Props> {
|
||||
// search for exact internal document
|
||||
const slug = parseDocumentSlug(term);
|
||||
try {
|
||||
const document = await this.props.documents.fetch(slug);
|
||||
const { document } = await this.props.documents.fetch(slug);
|
||||
const time = distanceInWordsToNow(document.updatedAt, {
|
||||
addSuffix: true,
|
||||
});
|
||||
@@ -159,10 +159,13 @@ class DataLoader extends React.Component<Props> {
|
||||
}
|
||||
|
||||
try {
|
||||
this.document = await this.props.documents.fetch(documentSlug, {
|
||||
const response = await this.props.documents.fetch(documentSlug, {
|
||||
shareId,
|
||||
});
|
||||
|
||||
this.document = response.document;
|
||||
this.sharedTree = response.sharedTree;
|
||||
|
||||
if (revisionId && revisionId !== "latest") {
|
||||
await this.loadRevision();
|
||||
} else {
|
||||
@@ -249,6 +252,7 @@ class DataLoader extends React.Component<Props> {
|
||||
readOnly={!this.isEditing || !abilities.update || document.isArchived}
|
||||
onSearchLink={this.onSearchLink}
|
||||
onCreateLink={this.onCreateLink}
|
||||
sharedTree={this.sharedTree}
|
||||
/>
|
||||
</SocketPresence>
|
||||
);
|
||||
|
||||
@@ -29,8 +29,9 @@ import Editor from "./Editor";
|
||||
import Header from "./Header";
|
||||
import KeyboardShortcutsButton from "./KeyboardShortcutsButton";
|
||||
import MarkAsViewed from "./MarkAsViewed";
|
||||
import PublicReferences from "./PublicReferences";
|
||||
import References from "./References";
|
||||
import { type LocationWithState, type Theme } from "types";
|
||||
import { type LocationWithState, type NavigationNode, type Theme } from "types";
|
||||
import { isCustomDomain } from "utils/domains";
|
||||
import { emojiToUrl } from "utils/emoji";
|
||||
import { meta } from "utils/keyboard";
|
||||
@@ -57,6 +58,7 @@ type Props = {
|
||||
match: Match,
|
||||
history: RouterHistory,
|
||||
location: LocationWithState,
|
||||
sharedTree: ?NavigationNode,
|
||||
abilities: Object,
|
||||
document: Document,
|
||||
revision: Revision,
|
||||
@@ -311,7 +313,8 @@ class DocumentScene extends React.Component<Props> {
|
||||
match,
|
||||
} = this.props;
|
||||
const team = auth.team;
|
||||
const isShare = !!match.params.shareId;
|
||||
const { shareId } = match.params;
|
||||
const isShare = !!shareId;
|
||||
|
||||
const value = revision ? revision.text : document.text;
|
||||
const injectTemplate = document.injectTemplate;
|
||||
@@ -367,7 +370,7 @@ class DocumentScene extends React.Component<Props> {
|
||||
)}
|
||||
<Header
|
||||
document={document}
|
||||
isShare={isShare}
|
||||
shareId={shareId}
|
||||
isRevision={!!revision}
|
||||
isDraft={document.isDraft}
|
||||
isEditing={!readOnly}
|
||||
@@ -377,6 +380,7 @@ class DocumentScene extends React.Component<Props> {
|
||||
document.isSaving || this.isPublishing || this.isEmpty
|
||||
}
|
||||
savingIsDisabled={document.isSaving || this.isEmpty}
|
||||
sharedTree={this.props.sharedTree}
|
||||
goBack={this.goBack}
|
||||
onSave={this.onSave}
|
||||
/>
|
||||
@@ -420,7 +424,7 @@ class DocumentScene extends React.Component<Props> {
|
||||
<Editor
|
||||
id={document.id}
|
||||
innerRef={this.editor}
|
||||
isShare={isShare}
|
||||
shareId={shareId}
|
||||
isDraft={document.isDraft}
|
||||
template={document.isTemplate}
|
||||
key={[injectTemplate, disableEmbeds].join("-")}
|
||||
@@ -442,6 +446,15 @@ class DocumentScene extends React.Component<Props> {
|
||||
readOnlyWriteCheckboxes={readOnly && abilities.update}
|
||||
ui={this.props.ui}
|
||||
>
|
||||
{shareId && (
|
||||
<ReferencesWrapper isOnlyTitle={document.isOnlyTitle}>
|
||||
<PublicReferences
|
||||
shareId={shareId}
|
||||
documentId={document.id}
|
||||
sharedTree={this.props.sharedTree}
|
||||
/>
|
||||
</ReferencesWrapper>
|
||||
)}
|
||||
{!isShare && !revision && (
|
||||
<>
|
||||
<MarkAsViewed document={document} />
|
||||
|
||||
@@ -24,7 +24,7 @@ type Props = {|
|
||||
title: string,
|
||||
document: Document,
|
||||
isDraft: boolean,
|
||||
isShare: boolean,
|
||||
shareId: ?string,
|
||||
onSave: ({ done?: boolean, autosave?: boolean, publish?: boolean }) => any,
|
||||
innerRef: { current: any },
|
||||
children: React.Node,
|
||||
@@ -97,7 +97,7 @@ class DocumentEditor extends React.Component<Props> {
|
||||
title,
|
||||
onChangeTitle,
|
||||
isDraft,
|
||||
isShare,
|
||||
shareId,
|
||||
readOnly,
|
||||
innerRef,
|
||||
children,
|
||||
@@ -118,7 +118,7 @@ class DocumentEditor extends React.Component<Props> {
|
||||
$isStarred={document.isStarred}
|
||||
>
|
||||
<span>{normalizedTitle}</span>{" "}
|
||||
{!isShare && <StarButton document={document} size={32} />}
|
||||
{!shareId && <StarButton document={document} size={32} />}
|
||||
</Title>
|
||||
) : (
|
||||
<Title
|
||||
@@ -144,11 +144,12 @@ class DocumentEditor extends React.Component<Props> {
|
||||
onHoverLink={this.handleLinkActive}
|
||||
scrollTo={window.location.hash}
|
||||
readOnly={readOnly}
|
||||
shareId={shareId}
|
||||
grow
|
||||
{...rest}
|
||||
/>
|
||||
{!readOnly && <ClickablePadding onClick={this.focusAtEnd} grow />}
|
||||
{this.activeLinkEvent && !isShare && readOnly && (
|
||||
{this.activeLinkEvent && !shareId && readOnly && (
|
||||
<HoverPreview
|
||||
node={this.activeLinkEvent.target}
|
||||
event={this.activeLinkEvent}
|
||||
|
||||
@@ -13,23 +13,26 @@ import styled from "styled-components";
|
||||
import Document from "models/Document";
|
||||
import { Action, Separator } from "components/Actions";
|
||||
import Badge from "components/Badge";
|
||||
import Breadcrumb, { Slash } from "components/Breadcrumb";
|
||||
import Button from "components/Button";
|
||||
import Collaborators from "components/Collaborators";
|
||||
import DocumentBreadcrumb from "components/DocumentBreadcrumb";
|
||||
import Header from "components/Header";
|
||||
import Tooltip from "components/Tooltip";
|
||||
import PublicBreadcrumb from "./PublicBreadcrumb";
|
||||
import ShareButton from "./ShareButton";
|
||||
import useMobile from "hooks/useMobile";
|
||||
import useStores from "hooks/useStores";
|
||||
import DocumentMenu from "menus/DocumentMenu";
|
||||
import NewChildDocumentMenu from "menus/NewChildDocumentMenu";
|
||||
import TemplatesMenu from "menus/TemplatesMenu";
|
||||
import { type NavigationNode } from "types";
|
||||
import { metaDisplay } from "utils/keyboard";
|
||||
import { newDocumentUrl, editDocumentUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
isShare: boolean,
|
||||
sharedTree: ?NavigationNode,
|
||||
shareId: ?string,
|
||||
isDraft: boolean,
|
||||
isEditing: boolean,
|
||||
isRevision: boolean,
|
||||
@@ -47,7 +50,7 @@ type Props = {|
|
||||
|
||||
function DocumentHeader({
|
||||
document,
|
||||
isShare,
|
||||
shareId,
|
||||
isEditing,
|
||||
isDraft,
|
||||
isPublishing,
|
||||
@@ -55,6 +58,7 @@ function DocumentHeader({
|
||||
isSaving,
|
||||
savingIsDisabled,
|
||||
publishingIsDisabled,
|
||||
sharedTree,
|
||||
onSave,
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -115,11 +119,19 @@ function DocumentHeader({
|
||||
</Action>
|
||||
);
|
||||
|
||||
if (isShare) {
|
||||
if (shareId) {
|
||||
return (
|
||||
<Header
|
||||
title={document.title}
|
||||
breadcrumb={toc}
|
||||
breadcrumb={
|
||||
<PublicBreadcrumb
|
||||
documentId={document.id}
|
||||
shareId={shareId}
|
||||
sharedTree={sharedTree}
|
||||
>
|
||||
{toc}
|
||||
</PublicBreadcrumb>
|
||||
}
|
||||
actions={canEdit ? editAction : <div />}
|
||||
/>
|
||||
);
|
||||
@@ -129,14 +141,9 @@ function DocumentHeader({
|
||||
<>
|
||||
<Header
|
||||
breadcrumb={
|
||||
<Breadcrumb document={document}>
|
||||
{!isEditing && (
|
||||
<>
|
||||
<Slash />
|
||||
{toc}
|
||||
</>
|
||||
)}
|
||||
</Breadcrumb>
|
||||
<DocumentBreadcrumb document={document}>
|
||||
{!isEditing && toc}
|
||||
</DocumentBreadcrumb>
|
||||
}
|
||||
title={
|
||||
<>
|
||||
|
||||
54
app/scenes/Document/components/PublicBreadcrumb.js
Normal file
54
app/scenes/Document/components/PublicBreadcrumb.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import Breadcrumb from "components/Breadcrumb";
|
||||
import type { NavigationNode } from "types";
|
||||
|
||||
type Props = {|
|
||||
documentId: string,
|
||||
shareId: string,
|
||||
sharedTree: ?NavigationNode,
|
||||
children?: React.Node,
|
||||
|};
|
||||
|
||||
function pathToDocument(sharedTree, documentId) {
|
||||
let path = [];
|
||||
const traveler = (nodes, previousPath) => {
|
||||
nodes.forEach((childNode) => {
|
||||
const newPath = [...previousPath, childNode];
|
||||
if (childNode.id === documentId) {
|
||||
path = newPath;
|
||||
return;
|
||||
}
|
||||
return traveler(childNode.children, newPath);
|
||||
});
|
||||
};
|
||||
|
||||
if (sharedTree) {
|
||||
traveler([sharedTree], []);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
const PublicBreadcrumb = ({
|
||||
documentId,
|
||||
shareId,
|
||||
sharedTree,
|
||||
children,
|
||||
}: Props) => {
|
||||
const items = React.useMemo(
|
||||
() =>
|
||||
pathToDocument(sharedTree, documentId)
|
||||
.slice(0, -1)
|
||||
.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
to: `/share/${shareId}${item.url}`,
|
||||
};
|
||||
}),
|
||||
[sharedTree, shareId, documentId]
|
||||
);
|
||||
|
||||
return <Breadcrumb items={items} children={children} />;
|
||||
};
|
||||
|
||||
export default PublicBreadcrumb;
|
||||
57
app/scenes/Document/components/PublicReferences.js
Normal file
57
app/scenes/Document/components/PublicReferences.js
Normal file
@@ -0,0 +1,57 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Subheading from "components/Subheading";
|
||||
import ReferenceListItem from "./ReferenceListItem";
|
||||
import { type NavigationNode } from "types";
|
||||
|
||||
type Props = {|
|
||||
shareId: string,
|
||||
documentId: string,
|
||||
sharedTree: NavigationNode,
|
||||
|};
|
||||
|
||||
function PublicReferences(props: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { shareId, documentId, sharedTree } = props;
|
||||
|
||||
// The sharedTree is the entire document tree starting at the shared document
|
||||
// we must filter down the tree to only the part with the document we're
|
||||
// currently viewing
|
||||
const children = React.useMemo(() => {
|
||||
let result;
|
||||
|
||||
function findChildren(node) {
|
||||
if (!node) return;
|
||||
if (node.id === documentId) {
|
||||
result = node.children;
|
||||
} else {
|
||||
node.children.forEach((node) => {
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
findChildren(node);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return findChildren(sharedTree) || [];
|
||||
}, [documentId, sharedTree]);
|
||||
|
||||
if (!children.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subheading>{t("Nested documents")}</Subheading>
|
||||
{children.map((node) => (
|
||||
<ReferenceListItem key={node.id} document={node} shareId={shareId} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(PublicReferences);
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { DocumentIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
@@ -8,6 +9,7 @@ import DocumentMeta from "components/DocumentMeta";
|
||||
import type { NavigationNode } from "types";
|
||||
|
||||
type Props = {|
|
||||
shareId?: string,
|
||||
document: Document | NavigationNode,
|
||||
anchor?: string,
|
||||
showCollection?: boolean,
|
||||
@@ -31,6 +33,8 @@ const DocumentLink = styled(Link)`
|
||||
`;
|
||||
|
||||
const Title = styled.h3`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -43,27 +47,52 @@ const Title = styled.h3`
|
||||
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
`;
|
||||
|
||||
@observer
|
||||
class ReferenceListItem extends React.Component<Props> {
|
||||
render() {
|
||||
const { document, showCollection, anchor, ...rest } = this.props;
|
||||
const StyledDocumentIcon = styled(DocumentIcon)`
|
||||
margin-left: -4px;
|
||||
color: ${(props) => props.theme.textSecondary};
|
||||
`;
|
||||
|
||||
return (
|
||||
<DocumentLink
|
||||
to={{
|
||||
pathname: document.url,
|
||||
hash: anchor ? `d-${anchor}` : undefined,
|
||||
state: { title: document.title },
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<Title>{document.title}</Title>
|
||||
{document.updatedBy && (
|
||||
<DocumentMeta document={document} showCollection={showCollection} />
|
||||
)}
|
||||
</DocumentLink>
|
||||
);
|
||||
}
|
||||
const Emoji = styled.span`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: -4px;
|
||||
font-size: 16px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
`;
|
||||
|
||||
function ReferenceListItem({
|
||||
document,
|
||||
showCollection,
|
||||
anchor,
|
||||
shareId,
|
||||
...rest
|
||||
}: Props) {
|
||||
return (
|
||||
<DocumentLink
|
||||
to={{
|
||||
pathname: shareId ? `/share/${shareId}${document.url}` : document.url,
|
||||
hash: anchor ? `d-${anchor}` : undefined,
|
||||
state: { title: document.title },
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<Title>
|
||||
{document.emoji ? (
|
||||
<Emoji>{document.emoji}</Emoji>
|
||||
) : (
|
||||
<StyledDocumentIcon color="currentColor" />
|
||||
)}{" "}
|
||||
{document.emoji
|
||||
? document.title.replace(new RegExp(`^${document.emoji}`), "")
|
||||
: document.title}
|
||||
</Title>
|
||||
{document.updatedBy && (
|
||||
<DocumentMeta document={document} showCollection={showCollection} />
|
||||
)}
|
||||
</DocumentLink>
|
||||
);
|
||||
}
|
||||
|
||||
export default ReferenceListItem;
|
||||
export default observer(ReferenceListItem);
|
||||
|
||||
@@ -19,7 +19,9 @@ function ShareButton({ document }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { shares } = useStores();
|
||||
const share = shares.getByDocumentId(document.id);
|
||||
const isPubliclyShared = share && share.published;
|
||||
const sharedParent = shares.getByDocumentParents(document.id);
|
||||
const isPubliclyShared =
|
||||
(share && share.published) || (sharedParent && sharedParent.published);
|
||||
const popover = usePopoverState({
|
||||
gutter: 0,
|
||||
placement: "bottom-end",
|
||||
@@ -57,6 +59,7 @@ function ShareButton({ document }: Props) {
|
||||
<SharePopover
|
||||
document={document}
|
||||
share={share}
|
||||
sharedParent={sharedParent}
|
||||
onSubmit={popover.hide}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
@@ -4,7 +4,7 @@ import invariant from "invariant";
|
||||
import { observer } from "mobx-react";
|
||||
import { GlobeIcon, PadlockIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import Document from "models/Document";
|
||||
import Share from "models/Share";
|
||||
@@ -13,23 +13,25 @@ import CopyToClipboard from "components/CopyToClipboard";
|
||||
import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import Input from "components/Input";
|
||||
import Notice from "components/Notice";
|
||||
import Switch from "components/Switch";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
share: Share,
|
||||
sharedParent: ?Share,
|
||||
onSubmit: () => void,
|
||||
|};
|
||||
|
||||
function DocumentShare({ document, share, onSubmit }: Props) {
|
||||
function SharePopover({ document, share, sharedParent, onSubmit }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { policies, shares, ui } = useStores();
|
||||
const [isCopied, setIsCopied] = React.useState(false);
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
const timeout = React.useRef<?TimeoutID>();
|
||||
const can = policies.abilities(share ? share.id : "");
|
||||
const canPublish = can.update && !document.isTemplate;
|
||||
const isPubliclyShared = (share && share.published) || sharedParent;
|
||||
|
||||
React.useEffect(() => {
|
||||
document.share();
|
||||
@@ -41,14 +43,26 @@ function DocumentShare({ document, share, onSubmit }: Props) {
|
||||
const share = shares.getByDocumentId(document.id);
|
||||
invariant(share, "Share must exist");
|
||||
|
||||
setIsSaving(true);
|
||||
|
||||
try {
|
||||
await share.save({ published: event.currentTarget.checked });
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
},
|
||||
[document.id, shares, ui]
|
||||
);
|
||||
|
||||
const handleChildDocumentsChange = React.useCallback(
|
||||
async (event) => {
|
||||
const share = shares.getByDocumentId(document.id);
|
||||
invariant(share, "Share must exist");
|
||||
|
||||
try {
|
||||
await share.save({
|
||||
includeChildDocuments: event.currentTarget.checked,
|
||||
});
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
}
|
||||
},
|
||||
[document.id, shares, ui]
|
||||
@@ -68,7 +82,7 @@ function DocumentShare({ document, share, onSubmit }: Props) {
|
||||
return (
|
||||
<>
|
||||
<Heading>
|
||||
{share && share.published ? (
|
||||
{isPubliclyShared ? (
|
||||
<GlobeIcon size={28} color="currentColor" />
|
||||
) : (
|
||||
<PadlockIcon size={28} color="currentColor" />
|
||||
@@ -76,20 +90,30 @@ function DocumentShare({ document, share, onSubmit }: Props) {
|
||||
{t("Share this document")}
|
||||
</Heading>
|
||||
|
||||
{sharedParent && (
|
||||
<Notice>
|
||||
<Trans
|
||||
defaults="This document is shared because the parent <em>{{ documentTitle }}</em> is publicly shared"
|
||||
values={{ documentTitle: sharedParent.documentTitle }}
|
||||
components={{ em: <strong /> }}
|
||||
/>
|
||||
</Notice>
|
||||
)}
|
||||
|
||||
{canPublish && (
|
||||
<PrivacySwitch>
|
||||
<SwitchWrapper>
|
||||
<Switch
|
||||
id="published"
|
||||
label={t("Publish to internet")}
|
||||
onChange={handlePublishedChange}
|
||||
checked={share ? share.published : false}
|
||||
disabled={!share || isSaving}
|
||||
disabled={!share}
|
||||
/>
|
||||
<Privacy>
|
||||
<PrivacyText>
|
||||
<SwitchLabel>
|
||||
<SwitchText>
|
||||
{share.published
|
||||
? t("Anyone with the link can view this document")
|
||||
: t("Only team members with access can view")}
|
||||
: t("Only team members with permission can view")}
|
||||
{share.lastAccessedAt && (
|
||||
<>
|
||||
.{" "}
|
||||
@@ -100,9 +124,27 @@ function DocumentShare({ document, share, onSubmit }: Props) {
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</PrivacyText>
|
||||
</Privacy>
|
||||
</PrivacySwitch>
|
||||
</SwitchText>
|
||||
</SwitchLabel>
|
||||
</SwitchWrapper>
|
||||
)}
|
||||
{share && share.published && (
|
||||
<SwitchWrapper>
|
||||
<Switch
|
||||
id="includeChildDocuments"
|
||||
label={t("Share nested documents")}
|
||||
onChange={handleChildDocumentsChange}
|
||||
checked={share ? share.includeChildDocuments : false}
|
||||
disabled={!share}
|
||||
/>
|
||||
<SwitchLabel>
|
||||
<SwitchText>
|
||||
{share.includeChildDocuments
|
||||
? t("Nested documents are publicly available")
|
||||
: t("Nested documents are not shared")}
|
||||
</SwitchText>
|
||||
</SwitchLabel>
|
||||
</SwitchWrapper>
|
||||
)}
|
||||
<Flex>
|
||||
<InputLink
|
||||
@@ -130,7 +172,7 @@ const Heading = styled.h2`
|
||||
margin-left: -4px;
|
||||
`;
|
||||
|
||||
const PrivacySwitch = styled.div`
|
||||
const SwitchWrapper = styled.div`
|
||||
margin: 20px 0;
|
||||
`;
|
||||
|
||||
@@ -139,7 +181,7 @@ const InputLink = styled(Input)`
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const Privacy = styled(Flex)`
|
||||
const SwitchLabel = styled(Flex)`
|
||||
flex-align: center;
|
||||
|
||||
svg {
|
||||
@@ -147,9 +189,9 @@ const Privacy = styled(Flex)`
|
||||
}
|
||||
`;
|
||||
|
||||
const PrivacyText = styled(HelpText)`
|
||||
const SwitchText = styled(HelpText)`
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
`;
|
||||
|
||||
export default observer(DocumentShare);
|
||||
export default observer(SharePopover);
|
||||
|
||||
Reference in New Issue
Block a user