cozy-sharing 40.1.5 → 40.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/components/FederatedFolder/FederatedFolderModal.js +10 -12
  3. package/dist/components/Recipient/DowngradePermissionConfirmDialog.js +3 -12
  4. package/dist/components/Recipient/GroupRecipient.js +1 -4
  5. package/dist/components/Recipient/GroupRecipient.stories.js +3 -1
  6. package/dist/components/Recipient/GroupRecipientDetailWithAccess.js +1 -4
  7. package/dist/components/Recipient/GroupRecipientPermissions.js +5 -8
  8. package/dist/components/Recipient/GroupRecipientPermissions.stories.js +2 -0
  9. package/dist/components/Recipient/LinkRecipient.js +2 -6
  10. package/dist/components/Recipient/LinkRecipient.stories.js +4 -0
  11. package/dist/components/Recipient/LinkRecipientPermissions.js +13 -12
  12. package/dist/components/Recipient/LinkRecipientPermissions.stories.js +5 -1
  13. package/dist/components/Recipient/MemberRecipient.js +7 -6
  14. package/dist/components/Recipient/MemberRecipient.spec.js +7 -12
  15. package/dist/components/Recipient/MemberRecipientPermissions.js +28 -13
  16. package/dist/components/Recipient/MemberRecipientPermissions.spec.js +5 -14
  17. package/dist/components/Recipient/MemberRecipientPermissions.stories.js +2 -1
  18. package/dist/components/Recipient/OwnerRecipient.js +4 -17
  19. package/dist/components/Recipient/OwnerRecipient.stories.js +3 -1
  20. package/dist/components/Recipient/OwnerRecipientDefaultLite.js +1 -1
  21. package/dist/components/Recipient/PermissionTypeMenu.js +32 -155
  22. package/dist/components/Recipient/PermissionTypeMenuWrapper.js +37 -0
  23. package/dist/components/Recipient/{PermissionTypeMenu.spec.js → PermissionTypeMenuWrapper.spec.js} +2 -2
  24. package/dist/components/Recipient/RecipientList.js +10 -0
  25. package/dist/components/Recipient/RecipientList.stories.js +3 -0
  26. package/dist/components/ShareDialogCozyToCozy.js +20 -19
  27. package/dist/components/ShareDialogCozyToCozy.spec.js +3 -0
  28. package/dist/components/ShareDialogOnlyByLink.js +3 -5
  29. package/dist/components/ShareDialogOnlyByLink.spec.js +0 -1
  30. package/dist/components/ShareModal/SharingDetailsModal.js +2 -2
  31. package/dist/components/SharedDrive/EditSharedDriveModal.js +11 -10
  32. package/dist/components/SharedDrive/useSharedDrive.js +8 -11
  33. package/dist/components/SharingContent.js +3 -5
  34. package/dist/components/WhoHasAccess.js +27 -11
  35. package/dist/components/WhoHasAccessWrapper.js +122 -0
  36. package/dist/helpers/recipients.js +46 -0
  37. package/dist/helpers/recipients.spec.js +89 -0
  38. package/dist/hooks/useMemberPermissionType.js +191 -0
  39. package/dist/hooks/useResolveRecipients.js +53 -0
  40. package/dist/hooks/useResolveRecipients.spec.js +140 -0
  41. package/dist/index.js +1 -1
  42. package/package.json +3 -3
  43. package/src/components/FederatedFolder/FederatedFolderModal.jsx +6 -12
  44. package/src/components/Recipient/DowngradePermissionConfirmDialog.jsx +3 -10
  45. package/src/components/Recipient/GroupRecipient.jsx +1 -5
  46. package/src/components/Recipient/GroupRecipient.stories.jsx +3 -1
  47. package/src/components/Recipient/GroupRecipientDetailWithAccess.jsx +1 -4
  48. package/src/components/Recipient/GroupRecipientPermissions.jsx +5 -5
  49. package/src/components/Recipient/GroupRecipientPermissions.stories.jsx +2 -0
  50. package/src/components/Recipient/LinkRecipient.jsx +2 -4
  51. package/src/components/Recipient/LinkRecipient.stories.jsx +4 -0
  52. package/src/components/Recipient/LinkRecipientPermissions.jsx +16 -12
  53. package/src/components/Recipient/LinkRecipientPermissions.stories.jsx +5 -1
  54. package/src/components/Recipient/MemberRecipient.jsx +4 -4
  55. package/src/components/Recipient/MemberRecipient.spec.jsx +7 -10
  56. package/src/components/Recipient/MemberRecipientPermissions.jsx +29 -13
  57. package/src/components/Recipient/MemberRecipientPermissions.spec.jsx +5 -11
  58. package/src/components/Recipient/MemberRecipientPermissions.stories.jsx +2 -1
  59. package/src/components/Recipient/OwnerRecipient.jsx +4 -17
  60. package/src/components/Recipient/OwnerRecipient.stories.jsx +1 -1
  61. package/src/components/Recipient/OwnerRecipientDefaultLite.jsx +1 -1
  62. package/src/components/Recipient/PermissionTypeMenu.jsx +21 -81
  63. package/src/components/Recipient/PermissionTypeMenuWrapper.jsx +32 -0
  64. package/src/components/Recipient/{PermissionTypeMenu.spec.jsx → PermissionTypeMenuWrapper.spec.jsx} +2 -2
  65. package/src/components/Recipient/RecipientList.jsx +10 -0
  66. package/src/components/Recipient/RecipientList.stories.jsx +3 -0
  67. package/src/components/ShareDialogCozyToCozy.jsx +10 -11
  68. package/src/components/ShareDialogCozyToCozy.spec.jsx +1 -0
  69. package/src/components/ShareDialogOnlyByLink.jsx +2 -4
  70. package/src/components/ShareDialogOnlyByLink.spec.jsx +0 -1
  71. package/src/components/ShareModal/SharingDetailsModal.jsx +2 -2
  72. package/src/components/SharedDrive/EditSharedDriveModal.jsx +6 -11
  73. package/src/components/SharedDrive/useSharedDrive.js +4 -11
  74. package/src/components/SharingContent.jsx +2 -4
  75. package/src/components/WhoHasAccess.jsx +26 -12
  76. package/src/components/WhoHasAccessWrapper.jsx +117 -0
  77. package/src/helpers/recipients.js +44 -0
  78. package/src/helpers/recipients.spec.js +82 -0
  79. package/src/hooks/useMemberPermissionType.js +110 -0
  80. package/src/hooks/useResolveRecipients.js +38 -0
  81. package/src/hooks/useResolveRecipients.spec.js +70 -0
  82. package/src/index.jsx +1 -1
  83. package/dist/components/Recipient/OwnerRecipientDefault.js +0 -27
  84. package/dist/components/Recipient/OwnerRecipientDefault.stories.js +0 -10
  85. package/src/components/Recipient/OwnerRecipientDefault.jsx +0 -37
  86. package/src/components/Recipient/OwnerRecipientDefault.stories.jsx +0 -13
@@ -0,0 +1,110 @@
1
+ import { useCallback } from 'react'
2
+
3
+ import { useClient } from 'cozy-client'
4
+ import flag from 'cozy-flags'
5
+
6
+ import { useFetchDocumentPath } from './useFetchDocumentPath'
7
+ import { useResolveRecipients } from './useResolveRecipients'
8
+ import { useSharingContext } from './useSharingContext'
9
+ import { getSharingDocIds } from '../state'
10
+
11
+ /**
12
+ * Gives what is needed to change the permission type of the members
13
+ * of a document's sharing
14
+ *
15
+ * @param {object} document - Shared document
16
+ * @param {object} [options]
17
+ * @param {boolean} [options.shouldFetchPath] - Whether to fetch the document
18
+ * path, needed to know the shared parent
19
+ * @returns {{ isInsideSharedParent: boolean, sharedParentName: string|null, setMemberType: Function }}
20
+ * `setMemberType({ sharingId, memberIndex, type, recipient }, newType, { withAncestors })`
21
+ * also downgrades the member on the read-write ancestor sharings when `withAncestors` is set
22
+ */
23
+ export const useMemberPermissionType = (
24
+ document,
25
+ { shouldFetchPath = true } = {}
26
+ ) => {
27
+ const client = useClient()
28
+ const {
29
+ hasSharedParent,
30
+ getSharedParentPath,
31
+ updateSharingMemberType,
32
+ getSharingById,
33
+ share
34
+ } = useSharingContext()
35
+ const resolveRecipients = useResolveRecipients()
36
+
37
+ const documentPath = useFetchDocumentPath(
38
+ client,
39
+ shouldFetchPath ? document : null
40
+ )
41
+ const isInsideSharedParent =
42
+ Boolean(documentPath) && Boolean(hasSharedParent?.(documentPath))
43
+ const sharedParentPath = documentPath
44
+ ? getSharedParentPath?.(documentPath)
45
+ : null
46
+
47
+ const setMemberType = useCallback(
48
+ async (
49
+ { sharingId, memberIndex, type, recipient },
50
+ newType,
51
+ { withAncestors = false } = {}
52
+ ) => {
53
+ if (withAncestors) {
54
+ // Ancestors first: only the target's own update refetches its
55
+ // effective recipients, which must already see them downgraded.
56
+ const ancestors = (recipient?.sources || []).filter(
57
+ s => s.kind === 'ancestor' && !s.read_only
58
+ )
59
+ for (const ancestor of ancestors) {
60
+ await updateSharingMemberType(
61
+ ancestor.sharing_id,
62
+ ancestor.member_index,
63
+ newType
64
+ )
65
+ }
66
+ }
67
+
68
+ const documentId = document?._id || document?.id
69
+ if (
70
+ flag('drive.federated-shared-folder.enabled') &&
71
+ type === 'one-way' &&
72
+ newType === 'two-way' &&
73
+ documentId
74
+ ) {
75
+ const sourceSharing = getSharingById(sharingId)
76
+ if (
77
+ sourceSharing &&
78
+ !getSharingDocIds(sourceSharing).includes(documentId)
79
+ ) {
80
+ const { recipients } = await resolveRecipients([recipient])
81
+ if (!recipients[0]) throw new Error('Recipient contact not found')
82
+ await share({
83
+ document: { ...document, id: documentId },
84
+ description: document.name,
85
+ recipients,
86
+ readOnlyRecipients: [],
87
+ sharedDrive: true,
88
+ openSharing: false
89
+ })
90
+ return
91
+ }
92
+ }
93
+
94
+ await updateSharingMemberType(sharingId, memberIndex, newType, document)
95
+ },
96
+ [
97
+ document,
98
+ getSharingById,
99
+ resolveRecipients,
100
+ share,
101
+ updateSharingMemberType
102
+ ]
103
+ )
104
+
105
+ return {
106
+ isInsideSharedParent,
107
+ sharedParentName: sharedParentPath?.split('/').pop() ?? null,
108
+ setMemberType
109
+ }
110
+ }
@@ -0,0 +1,38 @@
1
+ import { useCallback } from 'react'
2
+
3
+ import { useClient } from 'cozy-client'
4
+
5
+ import { getOrCreateFromArray } from '../helpers/contacts'
6
+ import { DOCTYPE_CONTACTS } from '../models'
7
+
8
+ /**
9
+ * Turns the recipients picked in a sharing form into contacts, creating the
10
+ * missing ones, and splits them by the selected sharing right.
11
+ *
12
+ * @returns {Function} `resolveRecipients(pendingRecipients, selectedOption, createContact?)`
13
+ * resolving to `{ recipients, readOnlyRecipients }`, ready to be passed to `share`
14
+ */
15
+ export const useResolveRecipients = () => {
16
+ const client = useClient()
17
+
18
+ return useCallback(
19
+ async (
20
+ pendingRecipients,
21
+ selectedOption = 'readWrite',
22
+ createContact = contact => client.create(DOCTYPE_CONTACTS, contact)
23
+ ) => {
24
+ const contacts = await getOrCreateFromArray(
25
+ client,
26
+ pendingRecipients,
27
+ createContact
28
+ )
29
+ const isReadOnly = selectedOption === 'readOnly'
30
+
31
+ return {
32
+ recipients: isReadOnly ? [] : contacts,
33
+ readOnlyRecipients: isReadOnly ? contacts : []
34
+ }
35
+ },
36
+ [client]
37
+ )
38
+ }
@@ -0,0 +1,70 @@
1
+ import { renderHook } from '@testing-library/react-hooks'
2
+
3
+ import { useResolveRecipients } from './useResolveRecipients'
4
+ import { getOrCreateFromArray } from '../helpers/contacts'
5
+
6
+ const mockClient = { create: jest.fn() }
7
+
8
+ jest.mock('cozy-client', () => ({
9
+ useClient: () => mockClient
10
+ }))
11
+
12
+ jest.mock('../helpers/contacts', () => ({
13
+ getOrCreateFromArray: jest.fn()
14
+ }))
15
+
16
+ const contacts = [{ _id: 'alice' }, { _id: 'bob' }]
17
+
18
+ describe('useResolveRecipients', () => {
19
+ beforeEach(() => {
20
+ jest.clearAllMocks()
21
+ getOrCreateFromArray.mockResolvedValue(contacts)
22
+ })
23
+
24
+ it('gives read-write rights by default', async () => {
25
+ const { result } = renderHook(() => useResolveRecipients())
26
+
27
+ const resolved = await result.current([{ email: 'alice@cozy.cc' }])
28
+
29
+ expect(resolved).toEqual({ recipients: contacts, readOnlyRecipients: [] })
30
+ })
31
+
32
+ it('gives read-only rights when the read-only option is selected', async () => {
33
+ const { result } = renderHook(() => useResolveRecipients())
34
+
35
+ const resolved = await result.current([], 'readOnly')
36
+
37
+ expect(resolved).toEqual({ recipients: [], readOnlyRecipients: contacts })
38
+ })
39
+
40
+ it('creates missing contacts with the client by default', async () => {
41
+ const { result } = renderHook(() => useResolveRecipients())
42
+ const pendingRecipients = [{ email: 'alice@cozy.cc' }]
43
+
44
+ await result.current(pendingRecipients)
45
+ const createContact = getOrCreateFromArray.mock.calls[0][2]
46
+ createContact({ email: [{ address: 'alice@cozy.cc' }] })
47
+
48
+ expect(getOrCreateFromArray).toHaveBeenCalledWith(
49
+ mockClient,
50
+ pendingRecipients,
51
+ expect.any(Function)
52
+ )
53
+ expect(mockClient.create).toHaveBeenCalledWith('io.cozy.contacts', {
54
+ email: [{ address: 'alice@cozy.cc' }]
55
+ })
56
+ })
57
+
58
+ it('creates missing contacts with the given function', async () => {
59
+ const { result } = renderHook(() => useResolveRecipients())
60
+ const createContact = jest.fn()
61
+
62
+ await result.current([], 'readWrite', createContact)
63
+
64
+ expect(getOrCreateFromArray).toHaveBeenCalledWith(
65
+ mockClient,
66
+ [],
67
+ createContact
68
+ )
69
+ })
70
+ })
package/src/index.jsx CHANGED
@@ -36,7 +36,7 @@ export { ShareButtonWithRecipients } from './ShareButtonWithRecipients'
36
36
  export { DOCUMENT_TYPE } from './helpers/documentType'
37
37
  export { getRecipientsFromSharing } from './state'
38
38
  export { default as OpenSharingLinkButton } from './OpenSharingLinkButton'
39
- export { PermissionTypeMenu } from './components/Recipient/PermissionTypeMenu'
39
+ export { default as PermissionTypeMenu } from './components/Recipient/PermissionTypeMenuWrapper'
40
40
  export { default as OpenSharingLinkFabButton } from './OpenSharingLinkFabButton'
41
41
  export {
42
42
  NativeFileSharingProvider,
@@ -1,27 +0,0 @@
1
- import React from 'react';
2
- import { useClient, useQuery, hasQueryBeenLoaded } from 'cozy-client';
3
- import MemberRecipient from './MemberRecipient';
4
- import { buildInstanceSettingsQuery } from '../../queries/queries'; // When the owner is not part of a sharing's members (eg. the folder only has
5
- // a link), there is no per-sharing avatar to reference. The instance's public
6
- // avatar still holds the owner's picture. fallback=initials makes the stack
7
- // serve a generated initials avatar (instead of the app icon) when no picture
8
- // has been uploaded, matching how avatars are rendered everywhere else.
9
-
10
- var OWNER_PUBLIC_AVATAR_PATH = '/public/avatar?fallback=initials';
11
-
12
- var OwnerRecipientDefault = function OwnerRecipientDefault() {
13
- var _instanceSettingsResu;
14
-
15
- var client = useClient();
16
- var instanceSettingsQuery = buildInstanceSettingsQuery();
17
- var instanceSettingsResult = useQuery(instanceSettingsQuery.definition, instanceSettingsQuery.options);
18
- return hasQueryBeenLoaded(instanceSettingsResult) && /*#__PURE__*/React.createElement(MemberRecipient, {
19
- isOwner: true,
20
- status: "owner",
21
- instance: client.options.uri,
22
- avatarPath: OWNER_PUBLIC_AVATAR_PATH,
23
- public_name: instanceSettingsResult === null || instanceSettingsResult === void 0 || (_instanceSettingsResu = instanceSettingsResult.data) === null || _instanceSettingsResu === void 0 || (_instanceSettingsResu = _instanceSettingsResu.attributes) === null || _instanceSettingsResu === void 0 ? void 0 : _instanceSettingsResu.public_name
24
- });
25
- };
26
-
27
- export default OwnerRecipientDefault;
@@ -1,10 +0,0 @@
1
- import OwnerRecipientDefault from './OwnerRecipientDefault';
2
- var meta = {
3
- component: OwnerRecipientDefault,
4
- args: {}
5
- };
6
- export default meta;
7
- export var Default = {
8
- name: 'Default',
9
- args: {}
10
- };
@@ -1,37 +0,0 @@
1
- import React from 'react'
2
-
3
- import { useClient, useQuery, hasQueryBeenLoaded } from 'cozy-client'
4
-
5
- import MemberRecipient from './MemberRecipient'
6
- import { buildInstanceSettingsQuery } from '../../queries/queries'
7
-
8
- // When the owner is not part of a sharing's members (eg. the folder only has
9
- // a link), there is no per-sharing avatar to reference. The instance's public
10
- // avatar still holds the owner's picture. fallback=initials makes the stack
11
- // serve a generated initials avatar (instead of the app icon) when no picture
12
- // has been uploaded, matching how avatars are rendered everywhere else.
13
- const OWNER_PUBLIC_AVATAR_PATH = '/public/avatar?fallback=initials'
14
-
15
- const OwnerRecipientDefault = () => {
16
- const client = useClient()
17
-
18
- const instanceSettingsQuery = buildInstanceSettingsQuery()
19
- const instanceSettingsResult = useQuery(
20
- instanceSettingsQuery.definition,
21
- instanceSettingsQuery.options
22
- )
23
-
24
- return (
25
- hasQueryBeenLoaded(instanceSettingsResult) && (
26
- <MemberRecipient
27
- isOwner={true}
28
- status="owner"
29
- instance={client.options.uri}
30
- avatarPath={OWNER_PUBLIC_AVATAR_PATH}
31
- public_name={instanceSettingsResult?.data?.attributes?.public_name}
32
- />
33
- )
34
- )
35
- }
36
-
37
- export default OwnerRecipientDefault
@@ -1,13 +0,0 @@
1
- import OwnerRecipientDefault from './OwnerRecipientDefault'
2
-
3
- const meta = {
4
- component: OwnerRecipientDefault,
5
- args: {}
6
- }
7
-
8
- export default meta
9
-
10
- export const Default = {
11
- name: 'Default',
12
- args: {}
13
- }