diff --git a/app/components/Sharing/OtherAccess.tsx b/app/components/Sharing/OtherAccess.tsx new file mode 100644 index 000000000..c5163f18f --- /dev/null +++ b/app/components/Sharing/OtherAccess.tsx @@ -0,0 +1,158 @@ +import { observer } from "mobx-react"; +import { MoreIcon, QuestionMarkIcon, UserIcon } from "outline-icons"; +import * as React from "react"; +import { useTranslation } from "react-i18next"; +import { useTheme } from "styled-components"; +import { CollectionPermission } from "@shared/types"; +import type Collection from "~/models/Collection"; +import type Document from "~/models/Document"; +import Flex from "~/components/Flex"; +import Text from "~/components/Text"; +import useCurrentUser from "~/hooks/useCurrentUser"; +import useRequest from "~/hooks/useRequest"; +import useStores from "~/hooks/useStores"; +import Avatar from "../Avatar"; +import { AvatarSize } from "../Avatar/Avatar"; +import CollectionIcon from "../Icons/CollectionIcon"; +import Squircle from "../Squircle"; +import Tooltip from "../Tooltip"; +import { StyledListItem } from "./MemberListItem"; + +export const OtherAccess = observer( + ({ + document, + children, + }: { + document: Document; + children: React.ReactNode; + }) => { + const { t } = useTranslation(); + const theme = useTheme(); + const collection = document.collection; + const usersInCollection = useUsersInCollection(collection); + const user = useCurrentUser(); + + return ( + <> + {collection ? ( + <> + {collection.permission ? ( + + + + } + title={t("All members")} + subtitle={t("Everyone in the workspace")} + actions={ + + {collection?.permission === CollectionPermission.ReadWrite + ? t("Can edit") + : t("Can view")} + + } + /> + ) : usersInCollection ? ( + + + + } + title={collection.name} + subtitle={t("Everyone in the collection")} + actions={{t("Can view")}} + /> + ) : ( + } + title={user.name} + subtitle={t("You have full access")} + actions={{t("Can edit")}} + /> + )} + {children} + + ) : document.isDraft ? ( + <> + } + title={document.createdBy.name} + actions={ + + {t("Can edit")} + + } + /> + {children} + + ) : ( + <> + {children} + + + + } + title={t("Other people")} + subtitle={t("Other workspace members may have access")} + actions={ + + } + /> + + )} + + ); + } +); + +const AccessTooltip = ({ + children, + tooltip, +}: { + children?: React.ReactNode; + tooltip?: string; +}) => { + const { t } = useTranslation(); + + return ( + + + {children} + + + + + + ); +}; + +function useUsersInCollection(collection?: Collection) { + const { users, memberships } = useStores(); + const { request } = useRequest(() => + memberships.fetchPage({ limit: 1, id: collection!.id }) + ); + + React.useEffect(() => { + if (collection && !collection.permission) { + void request(); + } + }, [collection]); + + return collection + ? collection.permission + ? true + : users.inCollection(collection.id).length > 1 + : false; +} diff --git a/app/components/Sharing/SharePopover.tsx b/app/components/Sharing/SharePopover.tsx index 6026cac51..c55441e4d 100644 --- a/app/components/Sharing/SharePopover.tsx +++ b/app/components/Sharing/SharePopover.tsx @@ -1,49 +1,33 @@ import { AnimatePresence, m } from "framer-motion"; import { observer } from "mobx-react"; -import { - BackIcon, - LinkIcon, - MoreIcon, - QuestionMarkIcon, - UserIcon, -} from "outline-icons"; +import { BackIcon, LinkIcon } from "outline-icons"; import { darken } from "polished"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; -import styled, { useTheme } from "styled-components"; +import styled from "styled-components"; import { s } from "@shared/styles"; -import { CollectionPermission } from "@shared/types"; -import Collection from "~/models/Collection"; import Document from "~/models/Document"; import Share from "~/models/Share"; import User from "~/models/User"; import CopyToClipboard from "~/components/CopyToClipboard"; import Flex from "~/components/Flex"; -import Text from "~/components/Text"; import useBoolean from "~/hooks/useBoolean"; import useCurrentTeam from "~/hooks/useCurrentTeam"; -import useCurrentUser from "~/hooks/useCurrentUser"; import useKeyDown from "~/hooks/useKeyDown"; import useMobile from "~/hooks/useMobile"; import usePolicy from "~/hooks/usePolicy"; -import useRequest from "~/hooks/useRequest"; import useStores from "~/hooks/useStores"; -import useThrottledCallback from "~/hooks/useThrottledCallback"; import { hover } from "~/styles"; import { documentPath, urlify } from "~/utils/routeHelpers"; -import Avatar from "../Avatar"; -import { AvatarSize } from "../Avatar/Avatar"; import ButtonSmall from "../ButtonSmall"; -import Empty from "../Empty"; -import CollectionIcon from "../Icons/CollectionIcon"; import Input, { NativeInput } from "../Input"; import NudeButton from "../NudeButton"; -import Squircle from "../Squircle"; import Tooltip from "../Tooltip"; import DocumentMembersList from "./DocumentMemberList"; -import { InviteIcon, StyledListItem } from "./MemberListItem"; +import { OtherAccess } from "./OtherAccess"; import PublicAccess from "./PublicAccess"; +import { UserSuggestions } from "./UserSuggestions"; type Props = { /** The document to share. */ @@ -81,25 +65,6 @@ const presence = { }, }; -function useUsersInCollection(collection?: Collection) { - const { users, memberships } = useStores(); - const { request } = useRequest(() => - memberships.fetchPage({ limit: 1, id: collection!.id }) - ); - - React.useEffect(() => { - if (collection && !collection.permission) { - void request(); - } - }, [collection]); - - return collection - ? collection.permission - ? true - : users.inCollection(collection.id).length > 1 - : false; -} - function SharePopover({ document, share, @@ -274,17 +239,21 @@ function SharePopover({ {picker && (
- +
)}
- + - + {team.sharing && can.share && !collectionSharingDisabled && ( <> @@ -302,184 +271,6 @@ function SharePopover({ ); } -const Picker = observer( - ({ - document, - query, - onInvite, - }: { - document: Document; - query: string; - onInvite: (user: User) => Promise; - }) => { - const { users } = useStores(); - const { t } = useTranslation(); - const user = useCurrentUser(); - - const fetchUsersByQuery = useThrottledCallback( - (query) => users.fetchPage({ query }), - 250 - ); - - const suggestions = React.useMemo( - () => - users - .notInDocument(document.id, query) - .filter((u) => u.id !== user.id && !user.isSuspended), - [users, users.orderedData, document.id, document.members, user.id, query] - ); - - React.useEffect(() => { - if (query) { - void fetchUsersByQuery(query); - } - }, [query, fetchUsersByQuery]); - - return suggestions.length ? ( - <> - {suggestions.map((suggestion) => ( - onInvite(suggestion)} - title={suggestion.name} - subtitle={suggestion.isViewer ? t("Viewer") : t("Editor")} - image={ - - } - actions={} - /> - ))} - - ) : ( - {t("No matches")} - ); - } -); - -const DocumentOtherAccessList = observer( - ({ - document, - children, - }: { - document: Document; - children: React.ReactNode; - }) => { - const { t } = useTranslation(); - const theme = useTheme(); - const collection = document.collection; - const usersInCollection = useUsersInCollection(collection); - const user = useCurrentUser(); - - return ( - <> - {collection ? ( - <> - {collection.permission ? ( - - - - } - title={t("All members")} - subtitle={t("Everyone in the workspace")} - actions={ - - {collection?.permission === CollectionPermission.ReadWrite - ? t("Can edit") - : t("Can view")} - - } - /> - ) : usersInCollection ? ( - - - - } - title={collection.name} - subtitle={t("Everyone in the collection")} - actions={{t("Can view")}} - /> - ) : ( - } - title={user.name} - subtitle={t("You have full access")} - actions={{t("Can edit")}} - /> - )} - {children} - - ) : document.isDraft ? ( - <> - } - title={document.createdBy.name} - actions={ - - {t("Can edit")} - - } - /> - {children} - - ) : ( - <> - {children} - - - - } - title={t("Other people")} - subtitle={t("Other workspace members may have access")} - actions={ - - } - /> - - )} - - ); - } -); - -const AccessTooltip = ({ - children, - tooltip, -}: { - children?: React.ReactNode; - tooltip?: string; -}) => { - const { t } = useTranslation(); - - return ( - - - {children} - - - - - - ); -}; - // TODO: Temp until Button/NudeButton styles are normalized const Wrapper = styled.div` ${NudeButton}:${hover}, diff --git a/app/components/Sharing/UserSuggestions.tsx b/app/components/Sharing/UserSuggestions.tsx new file mode 100644 index 000000000..710bc0755 --- /dev/null +++ b/app/components/Sharing/UserSuggestions.tsx @@ -0,0 +1,70 @@ +import { observer } from "mobx-react"; +import * as React from "react"; +import { useTranslation } from "react-i18next"; +import Document from "~/models/Document"; +import User from "~/models/User"; +import useCurrentUser from "~/hooks/useCurrentUser"; +import useStores from "~/hooks/useStores"; +import useThrottledCallback from "~/hooks/useThrottledCallback"; +import Avatar from "../Avatar"; +import { AvatarSize } from "../Avatar/Avatar"; +import Empty from "../Empty"; +import { InviteIcon, StyledListItem } from "./MemberListItem"; + +export const UserSuggestions = observer( + ({ + document, + query, + onInvite, + }: { + document: Document; + query: string; + onInvite: (user: User) => Promise; + }) => { + const { users } = useStores(); + const { t } = useTranslation(); + const user = useCurrentUser(); + + const fetchUsersByQuery = useThrottledCallback( + (query) => users.fetchPage({ query }), + 250 + ); + + const suggestions = React.useMemo( + () => + users + .notInDocument(document.id, query) + .filter((u) => u.id !== user.id && !u.isSuspended), + [users, users.orderedData, document.id, document.members, user.id, query] + ); + + React.useEffect(() => { + if (query) { + void fetchUsersByQuery(query); + } + }, [query, fetchUsersByQuery]); + + return suggestions.length ? ( + <> + {suggestions.map((suggestion) => ( + onInvite(suggestion)} + title={suggestion.name} + subtitle={suggestion.isViewer ? t("Viewer") : t("Editor")} + image={ + + } + actions={} + /> + ))} + + ) : ( + {t("No matches")} + ); + } +); diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index 621fafd22..99e93e1e2 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -257,6 +257,16 @@ "Suspended": "Suspended", "Invited": "Invited", "Leave": "Leave", + "All members": "All members", + "Everyone in the workspace": "Everyone in the workspace", + "Can view": "Can view", + "Everyone in the collection": "Everyone in the collection", + "You have full access": "You have full access", + "Created the document": "Created the document", + "Other people": "Other people", + "Other workspace members may have access": "Other workspace members may have access", + "This document may be shared with more workspace members through a parent document or collection you do not have access to": "This document may be shared with more workspace members through a parent document or collection you do not have access to", + "Access inherited from collection": "Access inherited from collection", "Only lowercase letters, digits and dashes allowed": "Only lowercase letters, digits and dashes allowed", "Sorry, this link has already been used": "Sorry, this link has already been used", "Public link copied to clipboard": "Public link copied to clipboard", @@ -270,16 +280,6 @@ "Done": "Done", "Invite by name": "Invite by name", "No matches": "No matches", - "All members": "All members", - "Everyone in the workspace": "Everyone in the workspace", - "Can view": "Can view", - "Everyone in the collection": "Everyone in the collection", - "You have full access": "You have full access", - "Created the document": "Created the document", - "Other people": "Other people", - "Other workspace members may have access": "Other workspace members may have access", - "This document may be shared with more workspace members through a parent document or collection you do not have access to": "This document may be shared with more workspace members through a parent document or collection you do not have access to", - "Access inherited from collection": "Access inherited from collection", "Logo": "Logo", "Move document": "Move document", "New doc": "New doc",