chore: Remove old collection permissions UI (#6995)
This commit is contained in:
@@ -11,7 +11,6 @@ import {
|
||||
import * as React from "react";
|
||||
import stores from "~/stores";
|
||||
import Collection from "~/models/Collection";
|
||||
import CollectionPermissions from "~/scenes/CollectionPermissions";
|
||||
import { CollectionEdit } from "~/components/Collection/CollectionEdit";
|
||||
import { CollectionNew } from "~/components/Collection/CollectionNew";
|
||||
import CollectionDeleteDialog from "~/components/CollectionDeleteDialog";
|
||||
@@ -21,7 +20,6 @@ import { getHeaderExpandedKey } from "~/components/Sidebar/components/Header";
|
||||
import { createAction } from "~/actions";
|
||||
import { CollectionSection } from "~/actions/sections";
|
||||
import { setPersistedState } from "~/hooks/usePersistedState";
|
||||
import { Feature, FeatureFlags } from "~/utils/FeatureFlags";
|
||||
import history from "~/utils/history";
|
||||
import { searchPath } from "~/utils/routeHelpers";
|
||||
|
||||
@@ -111,24 +109,16 @@ export const editCollectionPermissions = createAction({
|
||||
return;
|
||||
}
|
||||
|
||||
if (FeatureFlags.isEnabled(Feature.newCollectionSharing)) {
|
||||
stores.dialogs.openModal({
|
||||
title: t("Share this collection"),
|
||||
content: (
|
||||
<SharePopover
|
||||
collection={collection}
|
||||
onRequestClose={stores.dialogs.closeAllModals}
|
||||
visible
|
||||
/>
|
||||
),
|
||||
});
|
||||
} else {
|
||||
stores.dialogs.openModal({
|
||||
title: t("Collection permissions"),
|
||||
fullscreen: true,
|
||||
content: <CollectionPermissions collectionId={activeCollectionId} />,
|
||||
});
|
||||
}
|
||||
stores.dialogs.openModal({
|
||||
title: t("Share this collection"),
|
||||
content: (
|
||||
<SharePopover
|
||||
collection={collection}
|
||||
onRequestClose={stores.dialogs.closeAllModals}
|
||||
visible
|
||||
/>
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
import debounce from "lodash/debounce";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import Collection from "~/models/Collection";
|
||||
import Group from "~/models/Group";
|
||||
import GroupNew from "~/scenes/GroupNew";
|
||||
import Button from "~/components/Button";
|
||||
import ButtonLink from "~/components/ButtonLink";
|
||||
import Empty from "~/components/Empty";
|
||||
import Flex from "~/components/Flex";
|
||||
import GroupListItem from "~/components/GroupListItem";
|
||||
import Input from "~/components/Input";
|
||||
import Modal from "~/components/Modal";
|
||||
import PaginatedList from "~/components/PaginatedList";
|
||||
import Text from "~/components/Text";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useStores from "~/hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
collection: Collection;
|
||||
};
|
||||
|
||||
function AddGroupsToCollection(props: Props) {
|
||||
const { collection } = props;
|
||||
|
||||
const [newGroupModalOpen, handleNewGroupModalOpen, handleNewGroupModalClose] =
|
||||
useBoolean(false);
|
||||
const [query, setQuery] = React.useState("");
|
||||
const team = useCurrentTeam();
|
||||
const { collectionGroupMemberships, groups, policies } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const { fetchPage: fetchGroups } = groups;
|
||||
const can = policies.abilities(team.id);
|
||||
|
||||
const debouncedFetch = React.useMemo(
|
||||
() => debounce((query) => fetchGroups({ query }), 250),
|
||||
[fetchGroups]
|
||||
);
|
||||
|
||||
const handleFilter = React.useCallback(
|
||||
(ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const updatedQuery = ev.target.value;
|
||||
setQuery(updatedQuery);
|
||||
void debouncedFetch(updatedQuery);
|
||||
},
|
||||
[debouncedFetch]
|
||||
);
|
||||
|
||||
const handleAddGroup = async (group: Group) => {
|
||||
try {
|
||||
await collectionGroupMemberships.create({
|
||||
collectionId: collection.id,
|
||||
groupId: group.id,
|
||||
});
|
||||
toast.success(
|
||||
t("{{ groupName }} was added to the collection", {
|
||||
groupName: group.name,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not add user"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex column>
|
||||
{can.createGroup ? (
|
||||
<Text as="p" type="secondary">
|
||||
{t("Can’t find the group you’re looking for?")}{" "}
|
||||
<ButtonLink onClick={handleNewGroupModalOpen}>
|
||||
{t("Create a group")}
|
||||
</ButtonLink>
|
||||
.
|
||||
</Text>
|
||||
) : null}
|
||||
|
||||
<Input
|
||||
type="search"
|
||||
placeholder={`${t("Search by group name")}…`}
|
||||
value={query}
|
||||
onChange={handleFilter}
|
||||
label={t("Search groups")}
|
||||
labelHidden
|
||||
flex
|
||||
/>
|
||||
<PaginatedList
|
||||
empty={
|
||||
query ? (
|
||||
<Empty>{t("No groups matching your search")}</Empty>
|
||||
) : (
|
||||
<Empty>{t("No groups left to add")}</Empty>
|
||||
)
|
||||
}
|
||||
items={groups.notInCollection(collection.id, query)}
|
||||
fetch={query ? undefined : fetchGroups}
|
||||
renderItem={(item: Group) => (
|
||||
<GroupListItem
|
||||
key={item.id}
|
||||
group={item}
|
||||
showFacepile
|
||||
renderActions={() => (
|
||||
<ButtonWrap>
|
||||
<Button onClick={() => handleAddGroup(item)} neutral>
|
||||
{t("Add")}
|
||||
</Button>
|
||||
</ButtonWrap>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{can.createGroup ? (
|
||||
<Modal
|
||||
title={t("Create a group")}
|
||||
onRequestClose={handleNewGroupModalClose}
|
||||
isOpen={newGroupModalOpen}
|
||||
>
|
||||
<GroupNew onSubmit={handleNewGroupModalClose} />
|
||||
</Modal>
|
||||
) : null}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
const ButtonWrap = styled.div`
|
||||
margin-left: 6px;
|
||||
`;
|
||||
|
||||
export default observer(AddGroupsToCollection);
|
||||
@@ -1,130 +0,0 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { CollectionPermission, UserRole } from "@shared/types";
|
||||
import Collection from "~/models/Collection";
|
||||
import User from "~/models/User";
|
||||
import Invite from "~/scenes/Invite";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import { AvatarSize } from "~/components/Avatar/Avatar";
|
||||
import ButtonLink from "~/components/ButtonLink";
|
||||
import Empty from "~/components/Empty";
|
||||
import Flex from "~/components/Flex";
|
||||
import Input from "~/components/Input";
|
||||
import Modal from "~/components/Modal";
|
||||
import PaginatedList from "~/components/PaginatedList";
|
||||
import Text from "~/components/Text";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useThrottledCallback from "~/hooks/useThrottledCallback";
|
||||
import MemberListItem from "./components/MemberListItem";
|
||||
|
||||
type Props = {
|
||||
collection: Collection;
|
||||
};
|
||||
|
||||
function AddPeopleToCollection({ collection }: Props) {
|
||||
const { memberships, users } = useStores();
|
||||
const team = useCurrentTeam();
|
||||
const { t } = useTranslation();
|
||||
const can = usePolicy(team);
|
||||
|
||||
const [inviteModalOpen, setInviteModalOpen, setInviteModalClosed] =
|
||||
useBoolean();
|
||||
const [query, setQuery] = React.useState("");
|
||||
|
||||
const debouncedFetch = useThrottledCallback(
|
||||
(query) =>
|
||||
users.fetchPage({
|
||||
query,
|
||||
}),
|
||||
250
|
||||
);
|
||||
|
||||
const handleFilter = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQuery(ev.target.value);
|
||||
void debouncedFetch(ev.target.value);
|
||||
};
|
||||
|
||||
const handleAddUser = async (user: User) => {
|
||||
try {
|
||||
await memberships.create({
|
||||
permission:
|
||||
user.role === UserRole.Viewer || user.role === UserRole.Guest
|
||||
? CollectionPermission.Read
|
||||
: CollectionPermission.ReadWrite,
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
});
|
||||
toast.success(
|
||||
t("{{ userName }} was added to the collection", {
|
||||
userName: user.name,
|
||||
}),
|
||||
{
|
||||
icon: <Avatar model={user} size={AvatarSize.Toast} />,
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not add user"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex column>
|
||||
<Text as="p" type="secondary">
|
||||
{t("Need to add someone who’s not on the team yet?")}{" "}
|
||||
{can.inviteUser ? (
|
||||
<ButtonLink onClick={setInviteModalOpen}>
|
||||
{t("Invite people to {{ teamName }}", {
|
||||
teamName: team.name,
|
||||
})}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
t("Ask an admin to invite them first")
|
||||
)}
|
||||
.
|
||||
</Text>
|
||||
<Input
|
||||
type="search"
|
||||
placeholder={`${t("Search by name")}…`}
|
||||
value={query}
|
||||
onChange={handleFilter}
|
||||
label={t("Search people")}
|
||||
autoFocus
|
||||
labelHidden
|
||||
flex
|
||||
/>
|
||||
<PaginatedList
|
||||
empty={
|
||||
query ? (
|
||||
<Empty>{t("No people matching your search")}</Empty>
|
||||
) : (
|
||||
<Empty>{t("No people left to add")}</Empty>
|
||||
)
|
||||
}
|
||||
items={users.notInCollection(collection.id, query)}
|
||||
fetch={query ? undefined : users.fetchPage}
|
||||
renderItem={(item: User) => (
|
||||
<MemberListItem
|
||||
key={item.id}
|
||||
user={item}
|
||||
onAdd={() => handleAddUser(item)}
|
||||
canEdit
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Modal
|
||||
title={t("Invite people")}
|
||||
onRequestClose={setInviteModalClosed}
|
||||
isOpen={inviteModalOpen}
|
||||
>
|
||||
<Invite onSubmit={setInviteModalClosed} />
|
||||
</Modal>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(AddPeopleToCollection);
|
||||
@@ -1,59 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import CollectionGroupMembership from "~/models/CollectionGroupMembership";
|
||||
import Group from "~/models/Group";
|
||||
import GroupListItem from "~/components/GroupListItem";
|
||||
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
|
||||
import CollectionGroupMemberMenu from "~/menus/CollectionGroupMemberMenu";
|
||||
|
||||
type Props = {
|
||||
group: Group;
|
||||
collectionGroupMembership: CollectionGroupMembership | null | undefined;
|
||||
onUpdate: (permission: CollectionPermission) => void;
|
||||
onRemove: () => void;
|
||||
};
|
||||
|
||||
const CollectionGroupMemberListItem = ({
|
||||
group,
|
||||
collectionGroupMembership,
|
||||
onUpdate,
|
||||
onRemove,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<GroupListItem
|
||||
group={group}
|
||||
showAvatar
|
||||
renderActions={({ openMembersModal }) => (
|
||||
<>
|
||||
<InputMemberPermissionSelect
|
||||
value={collectionGroupMembership?.permission}
|
||||
onChange={onUpdate}
|
||||
permissions={[
|
||||
{
|
||||
label: t("View only"),
|
||||
value: CollectionPermission.Read,
|
||||
},
|
||||
{
|
||||
label: t("Can edit"),
|
||||
value: CollectionPermission.ReadWrite,
|
||||
},
|
||||
{
|
||||
label: t("Admin"),
|
||||
value: CollectionPermission.Admin,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<CollectionGroupMemberMenu
|
||||
onMembers={openMembersModal}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollectionGroupMemberListItem;
|
||||
@@ -1,92 +0,0 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import Membership from "~/models/Membership";
|
||||
import User from "~/models/User";
|
||||
import UserMembership from "~/models/UserMembership";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import Badge from "~/components/Badge";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
|
||||
import ListItem from "~/components/List/Item";
|
||||
import Time from "~/components/Time";
|
||||
import MemberMenu from "~/menus/MemberMenu";
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
membership?: Membership | UserMembership | undefined;
|
||||
canEdit: boolean;
|
||||
onAdd?: () => void;
|
||||
onRemove?: () => void;
|
||||
onUpdate?: (permission: CollectionPermission) => void;
|
||||
};
|
||||
|
||||
const MemberListItem = ({
|
||||
user,
|
||||
membership,
|
||||
onRemove,
|
||||
onUpdate,
|
||||
onAdd,
|
||||
canEdit,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
title={user.name}
|
||||
subtitle={
|
||||
<>
|
||||
{user.lastActiveAt ? (
|
||||
<Trans>
|
||||
Active <Time dateTime={user.lastActiveAt} /> ago
|
||||
</Trans>
|
||||
) : (
|
||||
t("Never signed in")
|
||||
)}
|
||||
{user.isInvited && <Badge>{t("Invited")}</Badge>}
|
||||
{user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>}
|
||||
</>
|
||||
}
|
||||
image={<Avatar model={user} size={32} />}
|
||||
actions={
|
||||
<Flex align="center" gap={8}>
|
||||
{onUpdate && (
|
||||
<InputMemberPermissionSelect
|
||||
permissions={[
|
||||
{
|
||||
label: t("View only"),
|
||||
value: CollectionPermission.Read,
|
||||
},
|
||||
{
|
||||
label: t("Can edit"),
|
||||
value: CollectionPermission.ReadWrite,
|
||||
},
|
||||
{
|
||||
label: t("Admin"),
|
||||
value: CollectionPermission.Admin,
|
||||
},
|
||||
]}
|
||||
value={membership?.permission}
|
||||
onChange={onUpdate}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
)}
|
||||
{canEdit && (
|
||||
<>
|
||||
{onRemove && <MemberMenu user={user} onRemove={onRemove} />}
|
||||
{onAdd && (
|
||||
<Button onClick={onAdd} neutral>
|
||||
{t("Add")}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(MemberListItem);
|
||||
@@ -1,49 +0,0 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { PlusIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import User from "~/models/User";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import Badge from "~/components/Badge";
|
||||
import Button from "~/components/Button";
|
||||
import ListItem from "~/components/List/Item";
|
||||
import Time from "~/components/Time";
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
canEdit: boolean;
|
||||
onAdd: () => void;
|
||||
};
|
||||
|
||||
const UserListItem = ({ user, onAdd, canEdit }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
title={user.name}
|
||||
image={<Avatar model={user} size={32} />}
|
||||
subtitle={
|
||||
<>
|
||||
{user.lastActiveAt ? (
|
||||
<Trans>
|
||||
Active <Time dateTime={user.lastActiveAt} /> ago
|
||||
</Trans>
|
||||
) : (
|
||||
t("Never signed in")
|
||||
)}
|
||||
{user.isInvited && <Badge>{t("Invited")}</Badge>}
|
||||
{user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>}
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
canEdit ? (
|
||||
<Button type="button" onClick={onAdd} icon={<PlusIcon />} neutral>
|
||||
{t("Add")}
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(UserListItem);
|
||||
@@ -1,334 +0,0 @@
|
||||
import invariant from "invariant";
|
||||
import { observer } from "mobx-react";
|
||||
import { PlusIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import Group from "~/models/Group";
|
||||
import User from "~/models/User";
|
||||
import Button from "~/components/Button";
|
||||
import Divider from "~/components/Divider";
|
||||
import Flex from "~/components/Flex";
|
||||
import InputSelectPermission from "~/components/InputSelectPermission";
|
||||
import Labeled from "~/components/Labeled";
|
||||
import Modal from "~/components/Modal";
|
||||
import PaginatedList from "~/components/PaginatedList";
|
||||
import Switch from "~/components/Switch";
|
||||
import Text from "~/components/Text";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import AddGroupsToCollection from "./AddGroupsToCollection";
|
||||
import AddPeopleToCollection from "./AddPeopleToCollection";
|
||||
import CollectionGroupMemberListItem from "./components/CollectionGroupMemberListItem";
|
||||
import MemberListItem from "./components/MemberListItem";
|
||||
|
||||
type Props = {
|
||||
collectionId: string;
|
||||
};
|
||||
|
||||
function CollectionPermissions({ collectionId }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const user = useCurrentUser();
|
||||
const {
|
||||
collections,
|
||||
memberships,
|
||||
collectionGroupMemberships,
|
||||
users,
|
||||
groups,
|
||||
auth,
|
||||
} = useStores();
|
||||
const collection = collections.get(collectionId);
|
||||
invariant(collection, "Collection not found");
|
||||
|
||||
const [addGroupModalOpen, handleAddGroupModalOpen, handleAddGroupModalClose] =
|
||||
useBoolean();
|
||||
|
||||
const [
|
||||
addMemberModalOpen,
|
||||
handleAddMemberModalOpen,
|
||||
handleAddMemberModalClose,
|
||||
] = useBoolean();
|
||||
|
||||
const handleRemoveUser = React.useCallback(
|
||||
async (user) => {
|
||||
try {
|
||||
await memberships.delete({
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
});
|
||||
toast.success(
|
||||
t(`{{ userName }} was removed from the collection`, {
|
||||
userName: user.name,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not remove user"));
|
||||
}
|
||||
},
|
||||
[memberships, collection, t]
|
||||
);
|
||||
|
||||
const handleUpdateUser = React.useCallback(
|
||||
async (user, permission) => {
|
||||
try {
|
||||
await memberships.create({
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
permission,
|
||||
});
|
||||
toast.success(
|
||||
t(`{{ userName }} permissions were updated`, {
|
||||
userName: user.name,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not update user"));
|
||||
}
|
||||
},
|
||||
[memberships, collection, t]
|
||||
);
|
||||
|
||||
const handleRemoveGroup = React.useCallback(
|
||||
async (group) => {
|
||||
try {
|
||||
await collectionGroupMemberships.delete({
|
||||
collectionId: collection.id,
|
||||
groupId: group.id,
|
||||
});
|
||||
toast.success(
|
||||
t(`The {{ groupName }} group was removed from the collection`, {
|
||||
groupName: group.name,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not remove group"));
|
||||
}
|
||||
},
|
||||
[collectionGroupMemberships, collection, t]
|
||||
);
|
||||
|
||||
const handleUpdateGroup = React.useCallback(
|
||||
async (group, permission) => {
|
||||
try {
|
||||
await collectionGroupMemberships.create({
|
||||
collectionId: collection.id,
|
||||
groupId: group.id,
|
||||
permission,
|
||||
});
|
||||
toast.success(
|
||||
t(`{{ groupName }} permissions were updated`, {
|
||||
groupName: group.name,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not update user"));
|
||||
}
|
||||
},
|
||||
[collectionGroupMemberships, collection, t]
|
||||
);
|
||||
|
||||
const handleChangePermission = React.useCallback(
|
||||
async (permission: CollectionPermission) => {
|
||||
try {
|
||||
await collection.save({
|
||||
permission,
|
||||
});
|
||||
toast.success(t("Default access permissions were updated"));
|
||||
} catch (err) {
|
||||
toast.error(t("Could not update permissions"));
|
||||
}
|
||||
},
|
||||
[collection, t]
|
||||
);
|
||||
|
||||
const fetchOptions = React.useMemo(
|
||||
() => ({
|
||||
id: collection.id,
|
||||
}),
|
||||
[collection.id]
|
||||
);
|
||||
|
||||
const handleSharingChange = React.useCallback(
|
||||
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
try {
|
||||
await collection.save({
|
||||
sharing: ev.target.checked,
|
||||
});
|
||||
toast.success(t("Public document sharing permissions were updated"));
|
||||
} catch (err) {
|
||||
toast.error(t("Could not update public document sharing"));
|
||||
}
|
||||
},
|
||||
[collection, t]
|
||||
);
|
||||
|
||||
const collectionName = collection.name;
|
||||
const collectionGroups = groups.inCollection(collection.id);
|
||||
const collectionUsers = users.inCollection(collection.id);
|
||||
const isEmpty = !collectionGroups.length && !collectionUsers.length;
|
||||
const sharing = collection.sharing;
|
||||
const teamSharingEnabled = !!auth.team && auth.team.sharing;
|
||||
|
||||
return (
|
||||
<Flex column>
|
||||
<InputSelectPermission
|
||||
onChange={handleChangePermission}
|
||||
value={collection.permission || ""}
|
||||
/>
|
||||
<PermissionExplainer size="small">
|
||||
{collection.isPrivate && (
|
||||
<Trans
|
||||
defaults="The <em>{{ collectionName }}</em> collection is private. Workspace members have no access to it by default."
|
||||
values={{
|
||||
collectionName,
|
||||
}}
|
||||
components={{
|
||||
em: <strong />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{collection.permission === CollectionPermission.ReadWrite && (
|
||||
<Trans
|
||||
defaults="Workspace members can view and edit documents in the <em>{{ collectionName }}</em> collection by default."
|
||||
values={{
|
||||
collectionName,
|
||||
}}
|
||||
components={{
|
||||
em: <strong />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{collection.permission === CollectionPermission.Read && (
|
||||
<Trans
|
||||
defaults="Workspace members can view documents in the <em>{{ collectionName }}</em> collection by
|
||||
default."
|
||||
values={{
|
||||
collectionName,
|
||||
}}
|
||||
components={{
|
||||
em: <strong />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</PermissionExplainer>
|
||||
<Switch
|
||||
id="sharing"
|
||||
label={t("Public document sharing")}
|
||||
onChange={handleSharingChange}
|
||||
checked={sharing && teamSharingEnabled}
|
||||
disabled={!teamSharingEnabled}
|
||||
note={
|
||||
teamSharingEnabled ? (
|
||||
<Trans>
|
||||
When enabled, documents can be shared publicly on the internet.
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
Public sharing is currently disabled in the workspace security
|
||||
settings.
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Labeled label={t("Additional access")}>
|
||||
<Actions gap={8}>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleAddGroupModalOpen}
|
||||
icon={<PlusIcon />}
|
||||
neutral
|
||||
>
|
||||
{t("Add groups")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleAddMemberModalOpen}
|
||||
icon={<PlusIcon />}
|
||||
neutral
|
||||
>
|
||||
{t("Add people")}
|
||||
</Button>
|
||||
</Actions>
|
||||
</Labeled>
|
||||
<Divider />
|
||||
{isEmpty && (
|
||||
<Empty>
|
||||
<Trans>Add additional access for individual members and groups</Trans>
|
||||
</Empty>
|
||||
)}
|
||||
<PaginatedList
|
||||
items={collectionGroups}
|
||||
fetch={collectionGroupMemberships.fetchPage}
|
||||
options={fetchOptions}
|
||||
renderItem={(group: Group) => (
|
||||
<CollectionGroupMemberListItem
|
||||
key={group.id}
|
||||
group={group}
|
||||
collectionGroupMembership={collectionGroupMemberships.find({
|
||||
collectionId: collection.id,
|
||||
groupId: group.id,
|
||||
})}
|
||||
onRemove={() => handleRemoveGroup(group)}
|
||||
onUpdate={(permission) => handleUpdateGroup(group, permission)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{collectionGroups.length ? <Divider /> : null}
|
||||
<PaginatedList
|
||||
key={`collection-users-${collection.permission || "none"}`}
|
||||
items={collectionUsers}
|
||||
fetch={memberships.fetchPage}
|
||||
options={fetchOptions}
|
||||
renderItem={(item: User) => (
|
||||
<MemberListItem
|
||||
key={item.id}
|
||||
user={item}
|
||||
membership={memberships.find({
|
||||
collectionId: collection.id,
|
||||
userId: item.id,
|
||||
})}
|
||||
canEdit={item.id !== user.id || user.isAdmin}
|
||||
onRemove={() => handleRemoveUser(item)}
|
||||
onUpdate={(permission) => handleUpdateUser(item, permission)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Modal
|
||||
title={t(`Add groups to {{ collectionName }}`, {
|
||||
collectionName: collection.name,
|
||||
})}
|
||||
onRequestClose={handleAddGroupModalClose}
|
||||
isOpen={addGroupModalOpen}
|
||||
>
|
||||
<AddGroupsToCollection collection={collection} />
|
||||
</Modal>
|
||||
<Modal
|
||||
title={t(`Add people to {{ collectionName }}`, {
|
||||
collectionName: collection.name,
|
||||
})}
|
||||
onRequestClose={handleAddMemberModalClose}
|
||||
isOpen={addMemberModalOpen}
|
||||
>
|
||||
<AddPeopleToCollection collection={collection} />
|
||||
</Modal>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
const Empty = styled(Text)`
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
const PermissionExplainer = styled(Text)`
|
||||
margin-top: -8px;
|
||||
margin-bottom: 24px;
|
||||
`;
|
||||
|
||||
const Actions = styled(Flex)`
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
export default observer(CollectionPermissions);
|
||||
@@ -518,43 +518,6 @@
|
||||
"Recently published": "Recently published",
|
||||
"Least recently updated": "Least recently updated",
|
||||
"A–Z": "A–Z",
|
||||
"{{ groupName }} was added to the collection": "{{ groupName }} was added to the collection",
|
||||
"Could not add user": "Could not add user",
|
||||
"Can’t find the group you’re looking for?": "Can’t find the group you’re looking for?",
|
||||
"Create a group": "Create a group",
|
||||
"Search by group name": "Search by group name",
|
||||
"Search groups": "Search groups",
|
||||
"No groups matching your search": "No groups matching your search",
|
||||
"No groups left to add": "No groups left to add",
|
||||
"Need to add someone who’s not on the team yet?": "Need to add someone who’s not on the team yet?",
|
||||
"Invite people to {{ teamName }}": "Invite people to {{ teamName }}",
|
||||
"Ask an admin to invite them first": "Ask an admin to invite them first",
|
||||
"Search by name": "Search by name",
|
||||
"Search people": "Search people",
|
||||
"No people matching your search": "No people matching your search",
|
||||
"No people left to add": "No people left to add",
|
||||
"Active <1></1> ago": "Active <1></1> ago",
|
||||
"Never signed in": "Never signed in",
|
||||
"{{ userName }} was removed from the collection": "{{ userName }} was removed from the collection",
|
||||
"{{ userName }} permissions were updated": "{{ userName }} permissions were updated",
|
||||
"The {{ groupName }} group was removed from the collection": "The {{ groupName }} group was removed from the collection",
|
||||
"Could not remove group": "Could not remove group",
|
||||
"{{ groupName }} permissions were updated": "{{ groupName }} permissions were updated",
|
||||
"Default access permissions were updated": "Default access permissions were updated",
|
||||
"Could not update permissions": "Could not update permissions",
|
||||
"Public document sharing permissions were updated": "Public document sharing permissions were updated",
|
||||
"Could not update public document sharing": "Could not update public document sharing",
|
||||
"The <em>{{ collectionName }}</em> collection is private. Workspace members have no access to it by default.": "The <em>{{ collectionName }}</em> collection is private. Workspace members have no access to it by default.",
|
||||
"Workspace members can view and edit documents in the <em>{{ collectionName }}</em> collection by default.": "Workspace members can view and edit documents in the <em>{{ collectionName }}</em> collection by default.",
|
||||
"Workspace members can view documents in the <em>{{ collectionName }}</em> collection by\n default.": "Workspace members can view documents in the <em>{{ collectionName }}</em> collection by\n default.",
|
||||
"When enabled, documents can be shared publicly on the internet.": "When enabled, documents can be shared publicly on the internet.",
|
||||
"Public sharing is currently disabled in the workspace security settings.": "Public sharing is currently disabled in the workspace security settings.",
|
||||
"Additional access": "Additional access",
|
||||
"Add groups": "Add groups",
|
||||
"Add people": "Add people",
|
||||
"Add additional access for individual members and groups": "Add additional access for individual members and groups",
|
||||
"Add groups to {{ collectionName }}": "Add groups to {{ collectionName }}",
|
||||
"Add people to {{ collectionName }}": "Add people to {{ collectionName }}",
|
||||
"Signing in": "Signing in",
|
||||
"You can safely close this window once the Outline desktop app has opened": "You can safely close this window once the Outline desktop app has opened",
|
||||
"Error creating comment": "Error creating comment",
|
||||
@@ -658,10 +621,19 @@
|
||||
"Are you sure about that? Deleting the <em>{{groupName}}</em> group will cause its members to lose access to collections and documents that it is associated with.": "Are you sure about that? Deleting the <em>{{groupName}}</em> group will cause its members to lose access to collections and documents that it is associated with.",
|
||||
"You can edit the name of this group at any time, however doing so too often might confuse your team mates.": "You can edit the name of this group at any time, however doing so too often might confuse your team mates.",
|
||||
"{{userName}} was added to the group": "{{userName}} was added to the group",
|
||||
"Could not add user": "Could not add user",
|
||||
"Add members below to give them access to the group. Need to add someone who’s not yet a member?": "Add members below to give them access to the group. Need to add someone who’s not yet a member?",
|
||||
"Invite them to {{teamName}}": "Invite them to {{teamName}}",
|
||||
"Ask an admin to invite them first": "Ask an admin to invite them first",
|
||||
"Search by name": "Search by name",
|
||||
"Search people": "Search people",
|
||||
"No people matching your search": "No people matching your search",
|
||||
"No people left to add": "No people left to add",
|
||||
"Active <1></1> ago": "Active <1></1> ago",
|
||||
"Never signed in": "Never signed in",
|
||||
"{{userName}} was removed from the group": "{{userName}} was removed from the group",
|
||||
"Add and remove members to the <em>{{groupName}}</em> group. Members of the group will have access to any collections this group has been added to.": "Add and remove members to the <em>{{groupName}}</em> group. Members of the group will have access to any collections this group has been added to.",
|
||||
"Add people": "Add people",
|
||||
"Listing members of the <em>{{groupName}}</em> group.": "Listing members of the <em>{{groupName}}</em> group.",
|
||||
"This group has no members.": "This group has no members.",
|
||||
"Add people to {{groupName}}": "Add people to {{groupName}}",
|
||||
@@ -876,6 +848,7 @@
|
||||
"Groups can be used to organize and manage the people on your team.": "Groups can be used to organize and manage the people on your team.",
|
||||
"No groups have been created yet": "No groups have been created yet",
|
||||
"All": "All",
|
||||
"Create a group": "Create a group",
|
||||
"Quickly transfer your existing documents, pages, and files from other tools and services into {{appName}}. You can also drag and drop any HTML, Markdown, and text documents directly into Collections in the app.": "Quickly transfer your existing documents, pages, and files from other tools and services into {{appName}}. You can also drag and drop any HTML, Markdown, and text documents directly into Collections in the app.",
|
||||
"Import a zip file of Markdown documents (exported from version 0.67.0 or earlier)": "Import a zip file of Markdown documents (exported from version 0.67.0 or earlier)",
|
||||
"Import data": "Import data",
|
||||
|
||||
Reference in New Issue
Block a user