cozy-sharing 28.4.4 → 28.6.0

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 (48) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/SharingProvider.js +296 -66
  3. package/dist/SharingProvider.spec.js +332 -2
  4. package/dist/components/FederatedFolder/DumbFederatedFolderModal.js +7 -4
  5. package/dist/components/FederatedFolder/FederatedFolderModal.js +79 -23
  6. package/dist/components/Recipient/LinkRecipient.js +6 -3
  7. package/dist/components/Recipient/LinkRecipient.stories.js +0 -1
  8. package/dist/components/Recipient/MemberRecipient.spec.js +13 -0
  9. package/dist/components/Recipient/MemberRecipientPermissions.js +80 -20
  10. package/dist/components/Recipient/MemberRecipientPermissions.spec.js +214 -0
  11. package/dist/components/Recipient/actions/setReadOnlySharedPermission.js +0 -1
  12. package/dist/components/Recipient/actions/setReadOnlySharedPermission.spec.js +72 -0
  13. package/dist/components/Recipient/actions/setReadWriteSharedPermission.js +0 -1
  14. package/dist/components/Recipient/actions/setReadWriteSharedPermission.spec.js +72 -0
  15. package/dist/components/ShareByLink.js +5 -1
  16. package/dist/components/ShareDialogCozyToCozy.spec.js +9 -0
  17. package/dist/components/ShareRestrictionModal/helpers.js +66 -29
  18. package/dist/components/SharedDrive/DumbSharedDriveModal.js +1 -1
  19. package/dist/components/SharedFolder/DumbBatchSharedFolderModal.js +17 -4
  20. package/dist/components/WhoHasAccess.js +0 -1
  21. package/dist/helpers/shortcodes.js +24 -0
  22. package/dist/state.js +3 -10
  23. package/locales/en.json +4 -1
  24. package/locales/fr.json +4 -1
  25. package/locales/ru.json +4 -1
  26. package/locales/vi.json +4 -1
  27. package/package.json +4 -4
  28. package/src/SharingProvider.jsx +128 -9
  29. package/src/SharingProvider.spec.jsx +224 -1
  30. package/src/components/FederatedFolder/DumbFederatedFolderModal.jsx +6 -3
  31. package/src/components/FederatedFolder/FederatedFolderModal.jsx +39 -6
  32. package/src/components/Recipient/LinkRecipient.jsx +10 -4
  33. package/src/components/Recipient/LinkRecipient.stories.jsx +0 -1
  34. package/src/components/Recipient/MemberRecipient.spec.jsx +11 -0
  35. package/src/components/Recipient/MemberRecipientPermissions.jsx +69 -16
  36. package/src/components/Recipient/MemberRecipientPermissions.spec.jsx +165 -0
  37. package/src/components/Recipient/actions/setReadOnlySharedPermission.js +1 -1
  38. package/src/components/Recipient/actions/setReadOnlySharedPermission.spec.jsx +95 -0
  39. package/src/components/Recipient/actions/setReadWriteSharedPermission.js +1 -1
  40. package/src/components/Recipient/actions/setReadWriteSharedPermission.spec.jsx +95 -0
  41. package/src/components/ShareByLink.jsx +3 -1
  42. package/src/components/ShareDialogCozyToCozy.spec.jsx +6 -0
  43. package/src/components/ShareRestrictionModal/helpers.js +42 -4
  44. package/src/components/SharedDrive/DumbSharedDriveModal.jsx +1 -1
  45. package/src/components/SharedFolder/DumbBatchSharedFolderModal.jsx +32 -16
  46. package/src/components/WhoHasAccess.jsx +0 -1
  47. package/src/helpers/shortcodes.js +28 -0
  48. package/src/state.js +3 -14
@@ -8,7 +8,7 @@ import Radios from 'cozy-ui/transpiled/react/Radios'
8
8
  const makeComponent = ({ label, type, onClick }) => {
9
9
  const Component = forwardRef((props, ref) => {
10
10
  return (
11
- <ActionsMenuItem {...props} ref={ref} button={false} onClick={onClick}>
11
+ <ActionsMenuItem {...props} ref={ref} onClick={onClick}>
12
12
  <ListItemIcon>
13
13
  <Radios checked={type === 'one-way'} />
14
14
  </ListItemIcon>
@@ -0,0 +1,95 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react'
2
+ import React from 'react'
3
+
4
+ import { setReadOnlySharedPermission } from './setReadOnlySharedPermission'
5
+ import AppLike from '../../../../test/AppLike'
6
+
7
+ const mockT = key => {
8
+ const translations = {
9
+ 'Share.type.one-way': 'Can view',
10
+ 'Share.type.two-way': 'Can change'
11
+ }
12
+ return translations[key] || key
13
+ }
14
+
15
+ describe('setReadOnlySharedPermission', () => {
16
+ let setType
17
+
18
+ beforeEach(() => {
19
+ setType = jest.fn()
20
+ })
21
+
22
+ it('should return correct action structure', () => {
23
+ const action = setReadOnlySharedPermission({
24
+ t: mockT,
25
+ type: 'one-way',
26
+ setType
27
+ })
28
+
29
+ expect(action.name).toBe('setReadOnlySharedPermission')
30
+ expect(action.label).toBe('Can view')
31
+ expect(action.icon).toBeNull()
32
+ })
33
+
34
+ it('should call setType with one-way when action is triggered', () => {
35
+ const action = setReadOnlySharedPermission({
36
+ t: mockT,
37
+ type: 'two-way',
38
+ setType
39
+ })
40
+
41
+ action.action()
42
+
43
+ expect(setType).toHaveBeenCalledWith('one-way')
44
+ })
45
+
46
+ it('should render radio as checked when type is one-way', () => {
47
+ const { Component } = setReadOnlySharedPermission({
48
+ t: mockT,
49
+ type: 'one-way',
50
+ setType
51
+ })
52
+
53
+ render(
54
+ <AppLike>
55
+ <Component />
56
+ </AppLike>
57
+ )
58
+
59
+ expect(screen.getByRole('radio')).toBeChecked()
60
+ })
61
+
62
+ it('should render radio as unchecked when type is two-way', () => {
63
+ const { Component } = setReadOnlySharedPermission({
64
+ t: mockT,
65
+ type: 'two-way',
66
+ setType
67
+ })
68
+
69
+ render(
70
+ <AppLike>
71
+ <Component />
72
+ </AppLike>
73
+ )
74
+
75
+ expect(screen.getByRole('radio')).not.toBeChecked()
76
+ })
77
+
78
+ it('should call setType with one-way when the component is clicked', () => {
79
+ const { Component } = setReadOnlySharedPermission({
80
+ t: mockT,
81
+ type: 'two-way',
82
+ setType
83
+ })
84
+
85
+ render(
86
+ <AppLike>
87
+ <Component />
88
+ </AppLike>
89
+ )
90
+
91
+ fireEvent.click(screen.getByText('Can view'))
92
+
93
+ expect(setType).toHaveBeenCalledWith('one-way')
94
+ })
95
+ })
@@ -8,7 +8,7 @@ import Radios from 'cozy-ui/transpiled/react/Radios'
8
8
  const makeComponent = ({ label, type, onClick }) => {
9
9
  const Component = forwardRef((props, ref) => {
10
10
  return (
11
- <ActionsMenuItem {...props} ref={ref} button={false} onClick={onClick}>
11
+ <ActionsMenuItem {...props} ref={ref} onClick={onClick}>
12
12
  <ListItemIcon>
13
13
  <Radios checked={type === 'two-way'} />
14
14
  </ListItemIcon>
@@ -0,0 +1,95 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react'
2
+ import React from 'react'
3
+
4
+ import { setReadWriteSharedPermission } from './setReadWriteSharedPermission'
5
+ import AppLike from '../../../../test/AppLike'
6
+
7
+ const mockT = key => {
8
+ const translations = {
9
+ 'Share.type.one-way': 'Can view',
10
+ 'Share.type.two-way': 'Can change'
11
+ }
12
+ return translations[key] || key
13
+ }
14
+
15
+ describe('setReadWriteSharedPermission', () => {
16
+ let setType
17
+
18
+ beforeEach(() => {
19
+ setType = jest.fn()
20
+ })
21
+
22
+ it('should return correct action structure', () => {
23
+ const action = setReadWriteSharedPermission({
24
+ t: mockT,
25
+ type: 'two-way',
26
+ setType
27
+ })
28
+
29
+ expect(action.name).toBe('setReadWriteSharedPermission')
30
+ expect(action.label).toBe('Can change')
31
+ expect(action.icon).toBeNull()
32
+ })
33
+
34
+ it('should call setType with two-way when action is triggered', () => {
35
+ const action = setReadWriteSharedPermission({
36
+ t: mockT,
37
+ type: 'one-way',
38
+ setType
39
+ })
40
+
41
+ action.action()
42
+
43
+ expect(setType).toHaveBeenCalledWith('two-way')
44
+ })
45
+
46
+ it('should render radio as checked when type is two-way', () => {
47
+ const { Component } = setReadWriteSharedPermission({
48
+ t: mockT,
49
+ type: 'two-way',
50
+ setType
51
+ })
52
+
53
+ render(
54
+ <AppLike>
55
+ <Component />
56
+ </AppLike>
57
+ )
58
+
59
+ expect(screen.getByRole('radio')).toBeChecked()
60
+ })
61
+
62
+ it('should render radio as unchecked when type is one-way', () => {
63
+ const { Component } = setReadWriteSharedPermission({
64
+ t: mockT,
65
+ type: 'one-way',
66
+ setType
67
+ })
68
+
69
+ render(
70
+ <AppLike>
71
+ <Component />
72
+ </AppLike>
73
+ )
74
+
75
+ expect(screen.getByRole('radio')).not.toBeChecked()
76
+ })
77
+
78
+ it('should call setType with two-way when the component is clicked', () => {
79
+ const { Component } = setReadWriteSharedPermission({
80
+ t: mockT,
81
+ type: 'one-way',
82
+ setType
83
+ })
84
+
85
+ render(
86
+ <AppLike>
87
+ <Component />
88
+ </AppLike>
89
+ )
90
+
91
+ fireEvent.click(screen.getByText('Can change'))
92
+
93
+ expect(setType).toHaveBeenCalledWith('two-way')
94
+ })
95
+ })
@@ -23,7 +23,7 @@ const ShareByLink = ({ link, document, documentType }) => {
23
23
  const { isMobile } = useBreakpoints()
24
24
  const { showAlert } = useAlert()
25
25
  const client = useClient()
26
- const { shareByLink } = useSharingContext()
26
+ const { shareByLink, getOwner, getSharingById } = useSharingContext()
27
27
  const [isGenerating, setIsGenerating] = useState(false)
28
28
 
29
29
  const canShare = typeof navigator?.share === 'function'
@@ -44,6 +44,8 @@ const ShareByLink = ({ link, document, documentType }) => {
44
44
  file: document,
45
45
  documentType,
46
46
  shareByLink,
47
+ getOwner,
48
+ getSharingById,
47
49
  t,
48
50
  showAlert,
49
51
  password: '',
@@ -14,6 +14,12 @@ jest.mock('cozy-client', () => ({
14
14
  useClient: () => ({ options: { uri: mockRecipientAlice.instance } })
15
15
  }))
16
16
 
17
+ jest.mock('../hooks/useSharingContext', () => ({
18
+ useSharingContext: () => ({
19
+ updateSharingMemberType: jest.fn()
20
+ })
21
+ }))
22
+
17
23
  describe('ShareDialogCozyToCozy', () => {
18
24
  const client = createMockClient({})
19
25
  client.options = {
@@ -2,6 +2,7 @@ import { generateWebLink } from 'cozy-client'
2
2
  import minilog from 'cozy-minilog'
3
3
 
4
4
  import { getAppSlugFromDocumentType } from '../../helpers/link'
5
+ import { getShortcode } from '../../helpers/shortcodes'
5
6
 
6
7
  const log = minilog('ShareRestrictionModal/helpers')
7
8
 
@@ -28,6 +29,8 @@ export const createSharingLink = async ({
28
29
  file,
29
30
  documentType,
30
31
  shareByLink,
32
+ getOwner,
33
+ getSharingById,
31
34
  t,
32
35
  ttl,
33
36
  editingRights = 'readOnly',
@@ -44,13 +47,48 @@ export const createSharingLink = async ({
44
47
  shareByLink,
45
48
  showAlert
46
49
  })
47
- if (!permsResult?.data?.attributes?.shortcodes?.code) {
50
+ const code = getShortcode(permsResult?.data)
51
+ if (!code) {
48
52
  return null
49
53
  }
50
- const { data: perms } = permsResult
54
+ return generateShareLinkFromFile({
55
+ client,
56
+ file,
57
+ sharecode: code,
58
+ getOwner,
59
+ getSharingById,
60
+ documentType
61
+ })
62
+ }
63
+
64
+ /**
65
+ * Generate a sharing link from a file and sharecode
66
+ * @param {object} options
67
+ * @param {object} options.client - CozyClient instance
68
+ * @param {object} options.file - File to share
69
+ * @param {string} options.sharecode - Share code
70
+ * @param {Function} options.getOwner - Function to get the owner of a sharing
71
+ * @param {Function} options.getSharingById - Function to get a sharing by ID
72
+ * @param {string} options.documentType - Type of the document
73
+ * @returns {string} - The generated web link
74
+ */
75
+ export const generateShareLinkFromFile = ({
76
+ client,
77
+ file,
78
+ sharecode,
79
+ getOwner,
80
+ getSharingById,
81
+ documentType
82
+ }) => {
83
+ let cozyUrl = client.getStackClient().uri
84
+ if (file.driveId) {
85
+ const sharing = getSharingById(file.driveId)
86
+ const owner = getOwner(sharing?.rules?.[0]?.values?.[0])
87
+ cozyUrl = owner?.instance || client.getStackClient().uri
88
+ }
51
89
  return generateWebLink({
52
- cozyUrl: client.getStackClient().uri,
53
- searchParams: [['sharecode', perms.attributes.shortcodes.code]],
90
+ cozyUrl,
91
+ searchParams: [['sharecode', sharecode]],
54
92
  pathname: '/public',
55
93
  slug: getAppSlugFromDocumentType({ documentType }),
56
94
  subDomainType: client.capabilities.flat_subdomains ? 'flat' : 'nested'
@@ -3,7 +3,7 @@ import React from 'react'
3
3
  import { useI18n } from 'twake-i18n'
4
4
 
5
5
  import withLocales from '../../hoc/withLocales'
6
- import DumbBatchSharedFolderModal from '../SharedFolder/DumbBatchSharedFolderModal'
6
+ import { DumbBatchSharedFolderModal } from '../SharedFolder/DumbBatchSharedFolderModal'
7
7
 
8
8
  export const DumbSharedDriveModal = withLocales(
9
9
  ({
@@ -37,7 +37,8 @@ export const DumbBatchSharedFolderModal = withLocales(
37
37
  createLabel,
38
38
  shareLabel,
39
39
  saveLabel,
40
- sharingDesc
40
+ sharingDesc,
41
+ showShareByEmail = true
41
42
  }) => {
42
43
  const hasRecipients = Boolean(recipients?.length)
43
44
 
@@ -52,6 +53,16 @@ export const DumbBatchSharedFolderModal = withLocales(
52
53
  )
53
54
  }
54
55
 
56
+ if (!showShareByEmail) {
57
+ return (
58
+ <ShareByLink
59
+ link={sharingLink}
60
+ document={document}
61
+ documentType="Files"
62
+ />
63
+ )
64
+ }
65
+
55
66
  if (!showNameField) {
56
67
  return (
57
68
  <>
@@ -111,19 +122,21 @@ export const DumbBatchSharedFolderModal = withLocales(
111
122
  <Typography variant="h6" className="u-mt-1-half u-mb-half">
112
123
  {addPeopleLabel}
113
124
  </Typography>
114
- <DumbShareByEmail
115
- createContact={createContact}
116
- currentRecipients={currentRecipients}
117
- document={document}
118
- documentType="Files"
119
- sharedDrive
120
- sharingDesc={sharingDesc}
121
- onShare={params => {
122
- onShare(params)
123
- }}
124
- submitLabel={addButtonLabel}
125
- showNotifications={false}
126
- />
125
+ {showShareByEmail && (
126
+ <DumbShareByEmail
127
+ createContact={createContact}
128
+ currentRecipients={currentRecipients}
129
+ document={document}
130
+ documentType="Files"
131
+ sharedDrive
132
+ sharingDesc={sharingDesc}
133
+ onShare={params => {
134
+ onShare(params)
135
+ }}
136
+ submitLabel={addButtonLabel}
137
+ showNotifications={false}
138
+ />
139
+ )}
127
140
  </div>
128
141
  <WhoHasAccess
129
142
  isOwner
@@ -168,7 +181,10 @@ DumbBatchSharedFolderModal.propTypes = {
168
181
  createLabel: PropTypes.string,
169
182
  shareLabel: PropTypes.string,
170
183
  saveLabel: PropTypes.string,
171
- sharingDesc: PropTypes.string.isRequired
184
+ sharingDesc: PropTypes.string,
185
+ showShareByEmail: PropTypes.bool
172
186
  }
173
187
 
174
- export default DumbBatchSharedFolderModal
188
+ DumbBatchSharedFolderModal.defaultProps = {
189
+ sharingDesc: ''
190
+ }
@@ -56,7 +56,6 @@ const WhoHasAccess = ({
56
56
  documentType={documentType}
57
57
  onRevoke={onRevoke}
58
58
  onRevokeSelf={onRevokeSelf}
59
- link={link}
60
59
  permissions={permissions}
61
60
  fadeIn={linkHasBeenJustCreated}
62
61
  />
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Get the shortcode from a permission object
3
+ * The shortcode can be in different places depending on how the sharing was created:
4
+ * - attributes.shortcodes.email
5
+ * - attributes.shortcodes.code
6
+ * - attributes.codes.email
7
+ * - attributes.codes.code
8
+ *
9
+ * We used to use `email` as `codes` attribute for a sharingByLink.
10
+ * But when we use cozy-client to create a Permission, by default
11
+ * the codes attribute is set to `code`. MesPapiers app is using this
12
+ * default behavior... So the sharing by link created by mes papiers
13
+ * didn't appear correctly in cozy-sharing.
14
+ * This is a bit ugly, we should have a better way to know if this is
15
+ * a sharing by link or not.
16
+ *
17
+ * @param {object} permission - The permission object
18
+ * @returns {string|null} The shortcode or null if not found
19
+ */
20
+ export const getShortcode = permission => {
21
+ return (
22
+ permission?.attributes?.shortcodes?.email ||
23
+ permission?.attributes?.shortcodes?.code ||
24
+ permission?.attributes?.codes?.email ||
25
+ permission?.attributes?.codes?.code ||
26
+ null
27
+ )
28
+ }
package/src/state.js CHANGED
@@ -1,7 +1,7 @@
1
- import get from 'lodash/get'
2
-
3
1
  import flag from 'cozy-flags'
4
2
 
3
+ import { getShortcode } from './helpers/shortcodes'
4
+
5
5
  const RECEIVE_SHARINGS = 'RECEIVE_SHARINGS'
6
6
  const ADD_SHARING = 'ADD_SHARING'
7
7
  const UPDATE_SHARING = 'UPDATE_SHARING'
@@ -423,18 +423,7 @@ export const getSharingLink = (state, docId, documentType) => {
423
423
  const perms = getDocumentPermissions(state, docId)
424
424
  if (perms.length === 0) return null
425
425
  const perm = perms[0]
426
- // We used to use `email` as `codes` attribute for a sharingByLink.
427
- // But when we use cozy-client to create a Permission, by default
428
- // the codes attribute is set to `code`. MesPapiers app is using this
429
- // default behavior... So the sharing by link created by mes papiers
430
- // didn't appear correctly in cozy-sharing.
431
- // This is a bit ugly, we should have a better way to know if this is
432
- // a sharing by link or not.
433
- const code =
434
- get(perm, 'attributes.shortcodes.email') ||
435
- get(perm, 'attributes.shortcodes.code') ||
436
- get(perm, 'attributes.codes.email') ||
437
- get(perm, 'attributes.codes.code')
426
+ const code = getShortcode(perm)
438
427
  if (code) {
439
428
  return buildSharingLink(state, documentType, code)
440
429
  } else {