diff --git a/app/components/Breadcrumb.js b/app/components/Breadcrumb.js
index c3f38f468..4e7c521c0 100644
--- a/app/components/Breadcrumb.js
+++ b/app/components/Breadcrumb.js
@@ -4,7 +4,6 @@ import {
ArchiveIcon,
EditIcon,
GoToIcon,
- PadlockIcon,
ShapesIcon,
TrashIcon,
} from "outline-icons";
@@ -103,11 +102,6 @@ const Breadcrumb = ({ document, children, onlyText }: Props) => {
if (onlyText === true) {
return (
<>
- {collection.private && (
- <>
- {" "}
- >
- )}
{collection.name}
{path.map((n) => (
@@ -154,11 +148,6 @@ export const Slash = styled(GoToIcon)`
fill: ${(props) => props.theme.divider};
`;
-const SmallPadlockIcon = styled(PadlockIcon)`
- display: inline-block;
- vertical-align: sub;
-`;
-
const SmallSlash = styled(GoToIcon)`
width: 12px;
height: 12px;
diff --git a/app/components/Divider.js b/app/components/Divider.js
new file mode 100644
index 000000000..9b4561c0f
--- /dev/null
+++ b/app/components/Divider.js
@@ -0,0 +1,11 @@
+// @flow
+import styled from "styled-components";
+
+const Divider = styled.hr`
+ border: 0;
+ border-bottom: 1px solid ${(props) => props.theme.divider};
+ margin: 0;
+ padding: 0;
+`;
+
+export default Divider;
diff --git a/app/components/GroupListItem.js b/app/components/GroupListItem.js
index 7687265e2..481556914 100644
--- a/app/components/GroupListItem.js
+++ b/app/components/GroupListItem.js
@@ -1,6 +1,7 @@
// @flow
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
+import { GroupIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import { MAX_AVATAR_DISPLAY } from "shared/constants";
@@ -17,7 +18,8 @@ type Props = {
group: Group,
groupMemberships: GroupMembershipsStore,
membership?: CollectionGroupMembership,
- showFacepile: boolean,
+ showFacepile?: boolean,
+ showAvatar?: boolean,
renderActions: ({ openMembersModal: () => void }) => React.Node,
};
@@ -48,6 +50,11 @@ class GroupListItem extends React.Component {
return (
<>
+
+
+ }
title={
{group.name}
}
@@ -84,6 +91,15 @@ class GroupListItem extends React.Component {
}
}
+const Image = styled(Flex)`
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ background: ${(props) => props.theme.secondaryBackground};
+ border-radius: 20px;
+`;
+
const Title = styled.span`
&:hover {
text-decoration: underline;
diff --git a/app/components/InputSelect.js b/app/components/InputSelect.js
index 2187b9682..83624b2f0 100644
--- a/app/components/InputSelect.js
+++ b/app/components/InputSelect.js
@@ -27,7 +27,7 @@ const Wrapper = styled.label`
max-width: ${(props) => (props.short ? "350px" : "100%")};
`;
-type Option = { label: string, value: string };
+export type Option = { label: string, value: string };
export type Props = {
value?: string,
diff --git a/app/components/InputSelectPermission.js b/app/components/InputSelectPermission.js
new file mode 100644
index 000000000..f462408bd
--- /dev/null
+++ b/app/components/InputSelectPermission.js
@@ -0,0 +1,22 @@
+// @flow
+import * as React from "react";
+import { useTranslation } from "react-i18next";
+import InputSelect, { type Props, type Option } from "./InputSelect";
+
+export default function InputSelectPermission(
+ props: $Rest }>
+) {
+ const { t } = useTranslation();
+
+ return (
+
+ );
+}
diff --git a/app/components/Labeled.js b/app/components/Labeled.js
index 41b57dec4..58194ef71 100644
--- a/app/components/Labeled.js
+++ b/app/components/Labeled.js
@@ -17,12 +17,10 @@ const Labeled = ({ label, children, ...props }: Props) => (
);
export const Label = styled(Flex)`
- margin-bottom: 8px;
- font-size: 13px;
font-weight: 500;
- text-transform: uppercase;
- color: ${(props) => props.theme.textTertiary};
- letter-spacing: 0.04em;
+ padding-bottom: 4px;
+ display: inline-block;
+ color: ${(props) => props.theme.text};
`;
export default observer(Labeled);
diff --git a/app/components/List/Item.js b/app/components/List/Item.js
index 60da80854..f38df86ae 100644
--- a/app/components/List/Item.js
+++ b/app/components/List/Item.js
@@ -27,7 +27,7 @@ const ListItem = ({ image, title, subtitle, actions }: Props) => {
const Wrapper = styled.li`
display: flex;
- padding: ${(props) => (props.compact ? "8px" : "12px")} 0;
+ padding: 8px 0;
margin: 0;
border-bottom: 1px solid ${(props) => props.theme.divider};
diff --git a/app/menus/CollectionMenu.js b/app/menus/CollectionMenu.js
index 4c3ad675c..5c0a1fbef 100644
--- a/app/menus/CollectionMenu.js
+++ b/app/menus/CollectionMenu.js
@@ -9,7 +9,7 @@ import Collection from "models/Collection";
import CollectionDelete from "scenes/CollectionDelete";
import CollectionEdit from "scenes/CollectionEdit";
import CollectionExport from "scenes/CollectionExport";
-import CollectionMembers from "scenes/CollectionMembers";
+import CollectionPermissions from "scenes/CollectionPermissions";
import ContextMenu from "components/ContextMenu";
import OverflowMenuButton from "components/ContextMenu/OverflowMenuButton";
import Template from "components/ContextMenu/Template";
@@ -42,9 +42,10 @@ function CollectionMenu({
const history = useHistory();
const file = React.useRef();
- const [showCollectionMembers, setShowCollectionMembers] = React.useState(
- false
- );
+ const [
+ showCollectionPermissions,
+ setShowCollectionPermissions,
+ ] = React.useState(false);
const [showCollectionEdit, setShowCollectionEdit] = React.useState(false);
const [showCollectionDelete, setShowCollectionDelete] = React.useState(false);
const [showCollectionExport, setShowCollectionExport] = React.useState(false);
@@ -155,9 +156,9 @@ function CollectionMenu({
onClick: () => setShowCollectionEdit(true),
},
{
- title: `${t("Members")}…`,
+ title: `${t("Permissions")}…`,
visible: can.update,
- onClick: () => setShowCollectionMembers(true),
+ onClick: () => setShowCollectionPermissions(true),
},
{
title: `${t("Export")}…`,
@@ -178,15 +179,11 @@ function CollectionMenu({
{renderModals && (
<>
setShowCollectionMembers(false)}
- isOpen={showCollectionMembers}
+ title={t("Collection permissions")}
+ onRequestClose={() => setShowCollectionPermissions(false)}
+ isOpen={showCollectionPermissions}
>
- setShowCollectionMembers(false)}
- onEdit={() => setShowCollectionEdit(true)}
- />
+
{
@observable collection: ?Collection;
@observable isFetching: boolean = true;
@observable permissionsModalOpen: boolean = false;
- @observable editModalOpen: boolean = false;
componentDidMount() {
const { id } = this.props.match.params;
@@ -113,14 +111,6 @@ class CollectionScene extends React.Component {
this.permissionsModalOpen = false;
};
- handleEditModalOpen = () => {
- this.editModalOpen = true;
- };
-
- handleEditModalClose = () => {
- this.editModalOpen = false;
- };
-
renderActions() {
const { match, policies, t } = this.props;
const can = policies.abilities(match.params.id || "");
@@ -221,32 +211,16 @@ class CollectionScene extends React.Component {
- {collection.private && (
-
- )}
+
-
-
-
-
+
) : (
@@ -254,10 +228,10 @@ class CollectionScene extends React.Component {
{" "}
{collection.name}{" "}
- {collection.private && (
+ {!collection.permission && (
diff --git a/app/scenes/CollectionEdit.js b/app/scenes/CollectionEdit.js
index a759821ad..0d4e3a2cb 100644
--- a/app/scenes/CollectionEdit.js
+++ b/app/scenes/CollectionEdit.js
@@ -28,7 +28,6 @@ class CollectionEdit extends React.Component {
@observable sharing: boolean = this.props.collection.sharing;
@observable icon: string = this.props.collection.icon;
@observable color: string = this.props.collection.color || "#4E5C6E";
- @observable private: boolean = this.props.collection.private;
@observable sort: { field: string, direction: "asc" | "desc" } = this.props
.collection.sort;
@observable isSaving: boolean;
@@ -43,7 +42,6 @@ class CollectionEdit extends React.Component {
name: this.name,
icon: this.icon,
color: this.color,
- private: this.private,
sharing: this.sharing,
sort: this.sort,
});
@@ -75,10 +73,6 @@ class CollectionEdit extends React.Component {
this.icon = icon;
};
- handlePrivateChange = (ev: SyntheticInputEvent<*>) => {
- this.private = ev.target.checked;
- };
-
handleSharingChange = (ev: SyntheticInputEvent<*>) => {
this.sharing = ev.target.checked;
};
@@ -122,17 +116,6 @@ class CollectionEdit extends React.Component {
value={`${this.sort.field}.${this.sort.direction}`}
onChange={this.handleSortChange}
/>
-
-
-
- A private collection will only be visible to invited team members.
-
-
void,
-};
-
-@observer
-class CollectionMembers extends React.Component {
- @observable addGroupModalOpen: boolean = false;
- @observable addMemberModalOpen: boolean = false;
-
- handleAddGroupModalOpen = () => {
- this.addGroupModalOpen = true;
- };
-
- handleAddGroupModalClose = () => {
- this.addGroupModalOpen = false;
- };
-
- handleAddMemberModalOpen = () => {
- this.addMemberModalOpen = true;
- };
-
- handleAddMemberModalClose = () => {
- this.addMemberModalOpen = false;
- };
-
- handleRemoveUser = (user) => {
- try {
- this.props.memberships.delete({
- collectionId: this.props.collection.id,
- userId: user.id,
- });
- this.props.ui.showToast(`${user.name} was removed from the collection`, {
- type: "success",
- });
- } catch (err) {
- this.props.ui.showToast("Could not remove user", { type: "error" });
- }
- };
-
- handleUpdateUser = (user, permission) => {
- try {
- this.props.memberships.create({
- collectionId: this.props.collection.id,
- userId: user.id,
- permission,
- });
- this.props.ui.showToast(`${user.name} permissions were updated`, {
- type: "success",
- });
- } catch (err) {
- this.props.ui.showToast("Could not update user", { type: "error" });
- }
- };
-
- handleRemoveGroup = (group) => {
- try {
- this.props.collectionGroupMemberships.delete({
- collectionId: this.props.collection.id,
- groupId: group.id,
- });
- this.props.ui.showToast(`${group.name} was removed from the collection`, {
- type: "success",
- });
- } catch (err) {
- this.props.ui.showToast("Could not remove group", { type: "error" });
- }
- };
-
- handleUpdateGroup = (group, permission) => {
- try {
- this.props.collectionGroupMemberships.create({
- collectionId: this.props.collection.id,
- groupId: group.id,
- permission,
- });
- this.props.ui.showToast(`${group.name} permissions were updated`, {
- type: "success",
- });
- } catch (err) {
- this.props.ui.showToast("Could not update user", { type: "error" });
- }
- };
-
- render() {
- const {
- collection,
- users,
- groups,
- memberships,
- collectionGroupMemberships,
- auth,
- } = this.props;
- const { user } = auth;
- if (!user) return null;
-
- const key = memberships.orderedData
- .map((m) => m.permission)
- .concat(collection.private)
- .join("-");
-
- return (
-
- {collection.private ? (
- <>
-
- Choose which groups and team members have access to view and edit
- documents in the private {collection.name}{" "}
- collection. You can make this collection visible to the entire
- team by{" "}
-
- changing the visibility
-
- .
-
-
- }
- neutral
- >
- Add groups
-
-
- >
- ) : (
-
- The {collection.name} collection is accessible by
- everyone on the team. If you want to limit who can view the
- collection,{" "}
- make it private
- .
-
- )}
-
- {collection.private && (
-
- Groups
- This collection has no groups.}
- renderItem={(group) => (
- this.handleRemoveGroup(group)}
- onUpdate={(permission) =>
- this.handleUpdateGroup(group, permission)
- }
- />
- )}
- />
-
-
-
-
- )}
- {collection.private ? (
- <>
-
- }
- neutral
- >
- Add individual members
-
-
-
- Individual Members
- >
- ) : (
- Members
- )}
- (
- this.handleRemoveUser(item)}
- onUpdate={(permission) => this.handleUpdateUser(item, permission)}
- />
- )}
- />
-
-
-
-
- );
- }
-}
-
-const GroupsWrap = styled.div`
- margin-bottom: 50px;
-`;
-
-export default inject(
- "auth",
- "users",
- "memberships",
- "collectionGroupMemberships",
- "groups",
- "ui"
-)(CollectionMembers);
diff --git a/app/scenes/CollectionMembers/index.js b/app/scenes/CollectionMembers/index.js
deleted file mode 100644
index e1607f75b..000000000
--- a/app/scenes/CollectionMembers/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// @flow
-import CollectionMembers from "./CollectionMembers";
-export default CollectionMembers;
diff --git a/app/scenes/CollectionNew.js b/app/scenes/CollectionNew.js
index 04516eaaf..ae058bd84 100644
--- a/app/scenes/CollectionNew.js
+++ b/app/scenes/CollectionNew.js
@@ -14,6 +14,7 @@ import Flex from "components/Flex";
import HelpText from "components/HelpText";
import IconPicker, { icons } from "components/IconPicker";
import Input from "components/Input";
+import InputSelectPermission from "components/InputSelectPermission";
import Switch from "components/Switch";
type Props = {
@@ -31,7 +32,7 @@ class CollectionNew extends React.Component {
@observable icon: string = "";
@observable color: string = "#4E5C6E";
@observable sharing: boolean = true;
- @observable private: boolean = false;
+ @observable permission: string = "read_write";
@observable isSaving: boolean;
hasOpenedIconPicker: boolean = false;
@@ -44,7 +45,7 @@ class CollectionNew extends React.Component {
sharing: this.sharing,
icon: this.icon,
color: this.color,
- private: this.private,
+ permission: this.permission,
},
this.props.collections
);
@@ -87,8 +88,8 @@ class CollectionNew extends React.Component {
this.hasOpenedIconPicker = true;
};
- handlePrivateChange = (ev: SyntheticInputEvent) => {
- this.private = ev.target.checked;
+ handlePermissionChange = (ev: SyntheticInputEvent) => {
+ this.permission = ev.target.value;
};
handleSharingChange = (ev: SyntheticInputEvent) => {
@@ -131,15 +132,16 @@ class CollectionNew extends React.Component {
icon={this.icon}
/>
-
- A private collection will only be visible to invited team members.
+ This is the default level of access given to team members, you can
+ give specific users or groups more access once the collection is
+ created.
{teamSharingEnabled && (
diff --git a/app/scenes/CollectionMembers/AddGroupsToCollection.js b/app/scenes/CollectionPermissions/AddGroupsToCollection.js
similarity index 100%
rename from app/scenes/CollectionMembers/AddGroupsToCollection.js
rename to app/scenes/CollectionPermissions/AddGroupsToCollection.js
diff --git a/app/scenes/CollectionMembers/AddPeopleToCollection.js b/app/scenes/CollectionPermissions/AddPeopleToCollection.js
similarity index 100%
rename from app/scenes/CollectionMembers/AddPeopleToCollection.js
rename to app/scenes/CollectionPermissions/AddPeopleToCollection.js
diff --git a/app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js b/app/scenes/CollectionPermissions/components/CollectionGroupMemberListItem.js
similarity index 72%
rename from app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js
rename to app/scenes/CollectionPermissions/components/CollectionGroupMemberListItem.js
index ce74910b5..a3aecc995 100644
--- a/app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js
+++ b/app/scenes/CollectionPermissions/components/CollectionGroupMemberListItem.js
@@ -8,14 +8,14 @@ import GroupListItem from "components/GroupListItem";
import InputSelect from "components/InputSelect";
import CollectionGroupMemberMenu from "menus/CollectionGroupMemberMenu";
-type Props = {
+type Props = {|
group: Group,
collectionGroupMembership: ?CollectionGroupMembership,
- onUpdate: (permission: string) => void,
- onRemove: () => void,
-};
+ onUpdate: (permission: string) => any,
+ onRemove: () => any,
+|};
-const MemberListItem = ({
+const CollectionGroupMemberListItem = ({
group,
collectionGroupMembership,
onUpdate,
@@ -25,8 +25,8 @@ const MemberListItem = ({
const PERMISSIONS = React.useMemo(
() => [
- { label: t("Read only"), value: "read" },
- { label: t("Read & Edit"), value: "read_write" },
+ { label: t("View only"), value: "read" },
+ { label: t("View and edit"), value: "read_write" },
],
[t]
);
@@ -36,6 +36,7 @@ const MemberListItem = ({
group={group}
onRemove={onRemove}
onUpdate={onUpdate}
+ showAvatar
renderActions={({ openMembersModal }) => (
<>