fix: Move "public document sharing" to "Permissions" (#2496)

* Convert to functional component

* Move public sharing to permissions

* Add collections.permission_changed event

* Account for null

* Update server/events.js

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Add collections.permission_changed event

* Remove name

Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
Saumya Pandey
2021-08-30 11:43:42 +05:30
committed by GitHub
parent 08a8fea69a
commit 4929fbaccb
6 changed files with 161 additions and 127 deletions

View File

@ -1,10 +1,8 @@
// @flow // @flow
import { observable } from "mobx"; import { observer } from "mobx-react";
import { inject, observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import { withTranslation, Trans, type TFunction } from "react-i18next"; import { useState } from "react";
import AuthStore from "stores/AuthStore"; import { Trans, useTranslation } from "react-i18next";
import ToastsStore from "stores/ToastsStore";
import Collection from "models/Collection"; import Collection from "models/Collection";
import Button from "components/Button"; import Button from "components/Button";
import Flex from "components/Flex"; import Flex from "components/Flex";
@ -12,141 +10,104 @@ import HelpText from "components/HelpText";
import IconPicker from "components/IconPicker"; import IconPicker from "components/IconPicker";
import Input from "components/Input"; import Input from "components/Input";
import InputSelect from "components/InputSelect"; import InputSelect from "components/InputSelect";
import Switch from "components/Switch"; import useToasts from "hooks/useToasts";
type Props = { type Props = {
collection: Collection, collection: Collection,
toasts: ToastsStore,
auth: AuthStore,
onSubmit: () => void, onSubmit: () => void,
t: TFunction,
}; };
@observer const CollectionEdit = ({ collection, onSubmit }: Props) => {
class CollectionEdit extends React.Component<Props> { const [name, setName] = useState(collection.name);
@observable name: string = this.props.collection.name; const [icon, setIcon] = useState(collection.icon);
@observable sharing: boolean = this.props.collection.sharing; const [color, setColor] = useState(collection.color || "#4E5C6E");
@observable icon: string = this.props.collection.icon; const [sort, setSort] = useState<{
@observable color: string = this.props.collection.color || "#4E5C6E"; field: string,
@observable sort: { field: string, direction: "asc" | "desc" } = this.props direction: "asc" | "desc",
.collection.sort; }>(collection.sort);
@observable isSaving: boolean; const [isSaving, setIsSaving] = useState();
const { showToast } = useToasts();
const { t } = useTranslation();
handleSubmit = async (ev: SyntheticEvent<*>) => { const handleSubmit = React.useCallback(
ev.preventDefault(); async (ev: SyntheticEvent<*>) => {
this.isSaving = true; ev.preventDefault();
const { t } = this.props; setIsSaving(true);
try { try {
await this.props.collection.save({ await collection.save({
name: this.name, name,
icon: this.icon, icon,
color: this.color, color,
sharing: this.sharing, sort,
sort: this.sort, });
}); onSubmit();
this.props.onSubmit(); showToast(t("The collection was updated"), {
this.props.toasts.showToast(t("The collection was updated"), { type: "success",
type: "success", });
}); } catch (err) {
} catch (err) { showToast(err.message, { type: "error" });
this.props.toasts.showToast(err.message, { type: "error" }); } finally {
} finally { setIsSaving(false);
this.isSaving = false; }
} },
}; [collection, color, icon, name, onSubmit, showToast, sort, t]
);
handleSortChange = (ev: SyntheticInputEvent<HTMLSelectElement>) => { const handleSortChange = (ev: SyntheticInputEvent<HTMLSelectElement>) => {
const [field, direction] = ev.target.value.split("."); const [field, direction] = ev.target.value.split(".");
if (direction === "asc" || direction === "desc") { if (direction === "asc" || direction === "desc") {
this.sort = { field, direction }; setSort({ field, direction });
} }
}; };
handleNameChange = (ev: SyntheticInputEvent<*>) => { const handleNameChange = (ev: SyntheticInputEvent<*>) => {
this.name = ev.target.value; setName(ev.target.value.trim());
}; };
handleChange = (color: string, icon: string) => { const handleChange = (color: string, icon: string) => {
this.color = color; setColor(color);
this.icon = icon; setIcon(icon);
}; };
handleSharingChange = (ev: SyntheticInputEvent<*>) => { return (
this.sharing = ev.target.checked; <Flex column>
}; <form onSubmit={handleSubmit}>
<HelpText>
render() { <Trans>
const { auth, t } = this.props; You can edit the name and other details at any time, however doing
const teamSharingEnabled = !!auth.team && auth.team.sharing; so often might confuse your team mates.
</Trans>
return ( </HelpText>
<Flex column> <Flex>
<form onSubmit={this.handleSubmit}> <Input
<HelpText> type="text"
<Trans> label={t("Name")}
You can edit the name and other details at any time, however doing onChange={handleNameChange}
so often might confuse your team mates. value={name}
</Trans> required
</HelpText> autoFocus
<Flex> flex
<Input
type="text"
label={t("Name")}
onChange={this.handleNameChange}
value={this.name}
required
autoFocus
flex
/>
&nbsp;
<IconPicker
onChange={this.handleChange}
color={this.color}
icon={this.icon}
/>
</Flex>
<InputSelect
label={t("Sort in sidebar")}
options={[
{ label: t("Alphabetical"), value: "title.asc" },
{ label: t("Manual sort"), value: "index.asc" },
]}
value={`${this.sort.field}.${this.sort.direction}`}
onChange={this.handleSortChange}
/> />
<Switch &nbsp;
id="sharing" <IconPicker onChange={handleChange} color={color} icon={icon} />
label={t("Public document sharing")} </Flex>
onChange={this.handleSharingChange} <InputSelect
checked={this.sharing && teamSharingEnabled} label={t("Sort in sidebar")}
disabled={!teamSharingEnabled} options={[
/> { label: t("Alphabetical"), value: "title.asc" },
<HelpText> { label: t("Manual sort"), value: "index.asc" },
{teamSharingEnabled ? ( ]}
<Trans> value={`${sort.field}.${sort.direction}`}
When enabled, documents can be shared publicly on the internet. onChange={handleSortChange}
</Trans> />
) : ( <Button type="submit" disabled={isSaving || !collection.name}>
<Trans> {isSaving ? `${t("Saving")}` : t("Save")}
Public sharing is currently disabled in the team security </Button>
settings. </form>
</Trans> </Flex>
)} );
</HelpText> };
<Button
type="submit"
disabled={this.isSaving || !this.props.collection.name}
>
{this.isSaving ? `${t("Saving")}` : t("Save")}
</Button>
</form>
</Flex>
);
}
}
export default withTranslation()<CollectionEdit>( export default observer(CollectionEdit);
inject("toasts", "auth")(CollectionEdit)
);

View File

@ -13,6 +13,7 @@ import InputSelectPermission from "components/InputSelectPermission";
import Labeled from "components/Labeled"; import Labeled from "components/Labeled";
import Modal from "components/Modal"; import Modal from "components/Modal";
import PaginatedList from "components/PaginatedList"; import PaginatedList from "components/PaginatedList";
import Switch from "components/Switch";
import AddGroupsToCollection from "./AddGroupsToCollection"; import AddGroupsToCollection from "./AddGroupsToCollection";
import AddPeopleToCollection from "./AddPeopleToCollection"; import AddPeopleToCollection from "./AddPeopleToCollection";
import CollectionGroupMemberListItem from "./components/CollectionGroupMemberListItem"; import CollectionGroupMemberListItem from "./components/CollectionGroupMemberListItem";
@ -34,6 +35,7 @@ function CollectionPermissions({ collection }: Props) {
collectionGroupMemberships, collectionGroupMemberships,
users, users,
groups, groups,
auth,
} = useStores(); } = useStores();
const { showToast } = useToasts(); const { showToast } = useToasts();
const [ const [
@ -153,10 +155,28 @@ function CollectionPermissions({ collection }: Props) {
collection.id, collection.id,
]); ]);
const handleSharingChange = React.useCallback(
async (ev: SyntheticInputEvent<*>) => {
try {
await collection.save({ sharing: ev.target.checked });
showToast(t("Public document sharing permissions were updated"), {
type: "success",
});
} catch (err) {
showToast(t("Could not update public document sharing"), {
type: "error",
});
}
},
[collection, showToast, t]
);
const collectionName = collection.name; const collectionName = collection.name;
const collectionGroups = groups.inCollection(collection.id); const collectionGroups = groups.inCollection(collection.id);
const collectionUsers = users.inCollection(collection.id); const collectionUsers = users.inCollection(collection.id);
const isEmpty = !collectionGroups.length && !collectionUsers.length; const isEmpty = !collectionGroups.length && !collectionUsers.length;
const sharing = collection.sharing;
const teamSharingEnabled = !!auth.team && auth.team.sharing;
return ( return (
<Flex column> <Flex column>
@ -189,6 +209,24 @@ function CollectionPermissions({ collection }: Props) {
/> />
)} )}
</PermissionExplainer> </PermissionExplainer>
<Switch
id="sharing"
label={t("Public document sharing")}
onChange={handleSharingChange}
checked={sharing && teamSharingEnabled}
disabled={!teamSharingEnabled}
/>
<HelpText>
{teamSharingEnabled ? (
<Trans>
When enabled, documents can be shared publicly on the internet.
</Trans>
) : (
<Trans>
Public sharing is currently disabled in the team security settings.
</Trans>
)}
</HelpText>
<Labeled label={t("Additional access")}> <Labeled label={t("Additional access")}>
<Actions> <Actions>
<Button <Button

View File

@ -58,6 +58,7 @@ Event.ACTIVITY_EVENTS = [
"collections.create", "collections.create",
"collections.delete", "collections.delete",
"collections.move", "collections.move",
"collections.permission_changed",
"documents.publish", "documents.publish",
"documents.archive", "documents.archive",
"documents.unarchive", "documents.unarchive",
@ -77,6 +78,7 @@ Event.AUDIT_EVENTS = [
"authenticationProviders.update", "authenticationProviders.update",
"collections.create", "collections.create",
"collections.update", "collections.update",
"collections.permission_changed",
"collections.move", "collections.move",
"collections.add_user", "collections.add_user",
"collections.remove_user", "collections.remove_user",

View File

@ -559,7 +559,8 @@ router.post("collections.update", auth(), async (ctx) => {
}); });
} }
const permissionChanged = permission !== collection.permission; let privacyChanged = false;
let sharingChanged = false;
if (name !== undefined) { if (name !== undefined) {
collection.name = name; collection.name = name;
@ -574,9 +575,17 @@ router.post("collections.update", auth(), async (ctx) => {
collection.color = color; collection.color = color;
} }
if (permission !== undefined) { if (permission !== undefined) {
// frontend sends empty string
ctx.assertIn(
permission,
["read_write", "read", "", null],
"Invalid permission"
);
privacyChanged = permission !== collection.permission;
collection.permission = permission ? permission : null; collection.permission = permission ? permission : null;
} }
if (sharing !== undefined) { if (sharing !== undefined) {
sharingChanged = sharing !== collection.sharing;
collection.sharing = sharing; collection.sharing = sharing;
} }
if (sort !== undefined) { if (sort !== undefined) {
@ -594,9 +603,20 @@ router.post("collections.update", auth(), async (ctx) => {
ip: ctx.request.ip, ip: ctx.request.ip,
}); });
if (privacyChanged || sharingChanged) {
await Event.create({
name: "collections.permission_changed",
collectionId: collection.id,
teamId: collection.teamId,
actorId: user.id,
data: { privacyChanged, sharingChanged },
ip: ctx.request.ip,
});
}
// must reload to update collection membership for correct policy calculation // must reload to update collection membership for correct policy calculation
// if the privacy level has changed. Otherwise skip this query for speed. // if the privacy level has changed. Otherwise skip this query for speed.
if (permissionChanged) { if (privacyChanged || sharingChanged) {
await collection.reload(); await collection.reload();
} }

View File

@ -174,6 +174,17 @@ export type CollectionEvent =
actorId: string, actorId: string,
data: { index: string }, data: { index: string },
ip: string, ip: string,
}
| {
name: "collections.permission_changed",
collectionId: string,
teamId: string,
actorId: string,
data: {
privacyChanged: boolean,
sharingChanged: boolean,
},
ip: string,
}; };
export type GroupEvent = export type GroupEvent =

View File

@ -278,9 +278,6 @@
"You can edit the name and other details at any time, however doing so often might confuse your team mates.": "You can edit the name and other details at any time, however doing so often might confuse your team mates.", "You can edit the name and other details at any time, however doing so often might confuse your team mates.": "You can edit the name and other details at any time, however doing so often might confuse your team mates.",
"Name": "Name", "Name": "Name",
"Alphabetical": "Alphabetical", "Alphabetical": "Alphabetical",
"Public document sharing": "Public document sharing",
"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 team security settings.": "Public sharing is currently disabled in the team security settings.",
"Saving": "Saving", "Saving": "Saving",
"Save": "Save", "Save": "Save",
"Export started, you will receive an email when its complete.": "Export started, you will receive an email when its complete.", "Export started, you will receive an email when its complete.": "Export started, you will receive an email when its complete.",
@ -289,6 +286,8 @@
"Export Collection": "Export Collection", "Export Collection": "Export Collection",
"Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.", "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.",
"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.": "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.", "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.": "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.",
"Public document sharing": "Public document sharing",
"When enabled, documents can be shared publicly on the internet.": "When enabled, documents can be shared publicly on the internet.",
"Creating": "Creating", "Creating": "Creating",
"Create": "Create", "Create": "Create",
"{{ groupName }} was added to the collection": "{{ groupName }} was added to the collection", "{{ groupName }} was added to the collection": "{{ groupName }} was added to the collection",
@ -319,9 +318,12 @@
"{{ groupName }} permissions were updated": "{{ groupName }} permissions were updated", "{{ groupName }} permissions were updated": "{{ groupName }} permissions were updated",
"Default access permissions were updated": "Default access permissions were updated", "Default access permissions were updated": "Default access permissions were updated",
"Could not update permissions": "Could not update permissions", "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. Team members have no access to it by default.": "The <em>{{ collectionName }}</em> collection is private. Team members have no access to it by default.", "The <em>{{ collectionName }}</em> collection is private. Team members have no access to it by default.": "The <em>{{ collectionName }}</em> collection is private. Team members have no access to it by default.",
"Team members can view documents in the <em>{{ collectionName }}</em> collection by default.": "Team members can view documents in the <em>{{ collectionName }}</em> collection by default.", "Team members can view documents in the <em>{{ collectionName }}</em> collection by default.": "Team members can view documents in the <em>{{ collectionName }}</em> collection by default.",
"Team members can view and edit documents in the <em>{{ collectionName }}</em> collection by\n default.": "Team members can view and edit documents in the <em>{{ collectionName }}</em> collection by\n default.", "Team members can view and edit documents in the <em>{{ collectionName }}</em> collection by\n default.": "Team members can view and edit documents in the <em>{{ collectionName }}</em> collection by\n default.",
"Public sharing is currently disabled in the team security settings.": "Public sharing is currently disabled in the team security settings.",
"Additional access": "Additional access", "Additional access": "Additional access",
"Add groups": "Add groups", "Add groups": "Add groups",
"Add people": "Add people", "Add people": "Add people",