cozy-sharing 5.0.0 → 6.0.1

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 (117) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/SharedRecipients.js +1 -1
  3. package/dist/SharedStatus.js +1 -1
  4. package/dist/SharingBanner/hooks/useSharingInfos.js +1 -1
  5. package/dist/SharingDetailsModal.js +1 -1
  6. package/dist/SharingOwnerAvatar.js +1 -1
  7. package/dist/SharingProvider.js +21 -8
  8. package/dist/components/ConfirmTrustedRecipientsDialog.js +1 -1
  9. package/dist/components/ContactSuggestion.js +15 -27
  10. package/dist/components/EditLinkPermissionDialog.js +72 -0
  11. package/dist/components/EditLinkPermissionDialog.spec.js +48 -0
  12. package/dist/components/EditableSharingModal.js +15 -62
  13. package/dist/components/{AvatarPlusX.js → Recipient/AvatarPlusX.js} +1 -1
  14. package/dist/components/{Identity.js → Recipient/Identity.js} +17 -16
  15. package/dist/components/Recipient/LinkRecipient.js +72 -0
  16. package/dist/components/Recipient/LinkRecipientPermissions.js +159 -0
  17. package/dist/components/Recipient/OwnerIdentity.js +44 -0
  18. package/dist/components/Recipient/Recipient.js +72 -0
  19. package/dist/components/{Recipient.spec.js → Recipient/Recipient.spec.js} +4 -45
  20. package/dist/components/Recipient/RecipientAvatar.js +33 -0
  21. package/dist/components/Recipient/RecipientConfirm.js +63 -0
  22. package/dist/components/Recipient/RecipientPermissions.js +126 -0
  23. package/dist/components/Recipient/RecipientPlusX.js +45 -0
  24. package/dist/components/Recipient/RecipientStatus.js +64 -0
  25. package/dist/components/Recipient/RecipientWithoutStatus.js +45 -0
  26. package/dist/components/Recipient/RecipientsAvatars.js +100 -0
  27. package/dist/components/{RecipientsAvatars.spec.js → Recipient/RecipientsAvatars.spec.js} +43 -2
  28. package/dist/components/{recipient.styl → Recipient/recipient.styl} +6 -2
  29. package/dist/components/ShareAutosuggest.js +176 -252
  30. package/dist/components/ShareAutosuggest.spec.js +3 -2
  31. package/dist/components/ShareByEmail.js +142 -193
  32. package/dist/components/ShareByEmail.spec.js +42 -23
  33. package/dist/components/ShareByLink.js +251 -357
  34. package/dist/components/ShareDialogCozyToCozy.js +16 -4
  35. package/dist/components/ShareDialogCozyToCozy.spec.js +12 -1
  36. package/dist/components/ShareDialogOnlyByLink.js +23 -10
  37. package/dist/components/ShareDialogTwoStepsConfirmationContainer.js +7 -7
  38. package/dist/components/ShareModal.js +5 -1
  39. package/dist/components/ShareRecipientsInput.js +44 -128
  40. package/dist/components/WhoHasAccess.js +43 -5
  41. package/dist/components/WhoHasAccessLight.js +3 -19
  42. package/dist/components/autosuggest.styl +21 -36
  43. package/dist/helpers/documentType.js +6 -0
  44. package/dist/helpers/hooks.js +8 -1
  45. package/dist/helpers/link.js +5 -0
  46. package/dist/helpers/permissions.js +8 -0
  47. package/dist/helpers/recipients.js +24 -0
  48. package/dist/helpers/sharings.js +72 -53
  49. package/dist/helpers/synchronousJobQueue.js +67 -0
  50. package/dist/helpers/synchronousJobQueue.spec.js +97 -0
  51. package/dist/index.js +2 -1
  52. package/dist/queries/queries.js +72 -0
  53. package/dist/share.styl +0 -15
  54. package/dist/stylesheet.css +67 -92
  55. package/locales/en.json +29 -41
  56. package/locales/fr.json +26 -39
  57. package/package.json +10 -10
  58. package/src/SharedRecipients.jsx +1 -1
  59. package/src/SharedStatus.jsx +1 -1
  60. package/src/SharingBanner/hooks/useSharingInfos.jsx +1 -1
  61. package/src/SharingDetailsModal.jsx +1 -1
  62. package/src/SharingOwnerAvatar.jsx +1 -1
  63. package/src/SharingProvider.jsx +17 -4
  64. package/src/components/ConfirmTrustedRecipientsDialog.jsx +1 -1
  65. package/src/components/ContactSuggestion.jsx +25 -10
  66. package/src/components/EditLinkPermissionDialog.jsx +89 -0
  67. package/src/components/EditLinkPermissionDialog.spec.jsx +52 -0
  68. package/src/components/EditableSharingModal.jsx +58 -100
  69. package/src/components/{AvatarPlusX.jsx → Recipient/AvatarPlusX.jsx} +1 -1
  70. package/src/components/{Identity.jsx → Recipient/Identity.jsx} +0 -0
  71. package/src/components/Recipient/LinkRecipient.jsx +59 -0
  72. package/src/components/Recipient/LinkRecipientPermissions.jsx +157 -0
  73. package/src/components/Recipient/OwnerIdentity.jsx +20 -0
  74. package/src/components/Recipient/Recipient.jsx +79 -0
  75. package/src/components/{Recipient.spec.jsx → Recipient/Recipient.spec.jsx} +4 -42
  76. package/src/components/Recipient/RecipientAvatar.jsx +38 -0
  77. package/src/components/Recipient/RecipientConfirm.jsx +37 -0
  78. package/src/components/Recipient/RecipientPermissions.jsx +114 -0
  79. package/src/components/Recipient/RecipientPlusX.jsx +28 -0
  80. package/src/components/Recipient/RecipientStatus.jsx +48 -0
  81. package/src/components/Recipient/RecipientWithoutStatus.jsx +21 -0
  82. package/src/components/Recipient/RecipientsAvatars.jsx +108 -0
  83. package/src/components/{RecipientsAvatars.spec.jsx → Recipient/RecipientsAvatars.spec.jsx} +43 -2
  84. package/src/components/{recipient.styl → Recipient/recipient.styl} +6 -2
  85. package/src/components/ShareAutosuggest.jsx +136 -127
  86. package/src/components/ShareAutosuggest.spec.jsx +12 -8
  87. package/src/components/ShareByEmail.jsx +65 -90
  88. package/src/components/ShareByEmail.spec.jsx +39 -11
  89. package/src/components/ShareByLink.jsx +146 -238
  90. package/src/components/ShareDialogCozyToCozy.jsx +14 -2
  91. package/src/components/ShareDialogCozyToCozy.spec.jsx +8 -1
  92. package/src/components/ShareDialogOnlyByLink.jsx +30 -13
  93. package/src/components/ShareDialogTwoStepsConfirmationContainer.jsx +5 -5
  94. package/src/components/ShareModal.jsx +5 -1
  95. package/src/components/ShareRecipientsInput.jsx +30 -44
  96. package/src/components/WhoHasAccess.jsx +85 -27
  97. package/src/components/WhoHasAccessLight.jsx +2 -1
  98. package/src/components/__snapshots__/ContactSuggestion.spec.jsx.snap +915 -132
  99. package/src/components/__snapshots__/ShareRecipientsInput.spec.jsx.snap +86 -102
  100. package/src/components/autosuggest.styl +21 -36
  101. package/src/helpers/documentType.js +6 -0
  102. package/src/helpers/hooks.js +11 -1
  103. package/src/helpers/link.js +4 -0
  104. package/src/helpers/permissions.js +15 -0
  105. package/src/helpers/recipients.js +21 -0
  106. package/src/helpers/sharings.js +15 -12
  107. package/src/helpers/synchronousJobQueue.js +22 -0
  108. package/src/helpers/synchronousJobQueue.spec.js +75 -0
  109. package/src/index.jsx +1 -0
  110. package/src/queries/queries.js +75 -0
  111. package/src/share.styl +0 -15
  112. package/dist/SharingBanner/helpers/queries.js +0 -12
  113. package/dist/components/ContactsAndGroupsDataLoader.js +0 -50
  114. package/dist/components/Recipient.js +0 -450
  115. package/src/SharingBanner/helpers/queries.js +0 -14
  116. package/src/components/ContactsAndGroupsDataLoader.jsx +0 -30
  117. package/src/components/Recipient.jsx +0 -415
@@ -1,415 +0,0 @@
1
- import React, { useState, useCallback } from 'react'
2
- import PropTypes from 'prop-types'
3
- import cx from 'classnames'
4
-
5
- import { useClient } from 'cozy-client'
6
- import Spinner from 'cozy-ui/transpiled/react/Spinner'
7
- import { useI18n } from 'cozy-ui/transpiled/react/I18n'
8
- import Avatar from 'cozy-ui/transpiled/react/Avatar'
9
- import Button from 'cozy-ui/transpiled/react/Button'
10
- import CompositeRow from 'cozy-ui/transpiled/react/CompositeRow'
11
- import DropdownButton from 'cozy-ui/transpiled/react/DropdownButton'
12
- import ActionMenu, { ActionMenuItem } from 'cozy-ui/transpiled/react/ActionMenu'
13
- import Icon from 'cozy-ui/transpiled/react/Icon'
14
- import { Media, Img, Bd } from 'cozy-ui/transpiled/react/Media'
15
- import RenameIcon from 'cozy-ui/transpiled/react/Icons/Rename'
16
- import TrashIcon from 'cozy-ui/transpiled/react/Icons/Trash'
17
- import EyeIcon from 'cozy-ui/transpiled/react/Icons/Eye'
18
- import PaperplaneIcon from 'cozy-ui/transpiled/react/Icons/Paperplane'
19
- import ToTheCloudIcon from 'cozy-ui/transpiled/react/Icons/ToTheCloud'
20
-
21
- import AvatarPlusX from './AvatarPlusX'
22
- import styles from './recipient.styl'
23
- import modalStyles from '../share.styl'
24
- import { getDisplayName, getInitials } from '../models'
25
- import Identity from './Identity'
26
-
27
- import LinkIcon from 'cozy-ui/transpiled/react/Icons/Link'
28
-
29
- import Typography from 'cozy-ui/transpiled/react/Typography'
30
-
31
- export const MAX_DISPLAYED_RECIPIENTS = 3
32
- const DEFAULT_DISPLAY_NAME = 'Share.contacts.defaultDisplayName'
33
-
34
- /**
35
- * Exclude me from the list of recipients.
36
- * @typedef {object} Recipient
37
- * @param {array<Recipient>} recipients - List of recipients
38
- * @param {boolean} isOwner - Indicates if I'm the owner or not
39
- * @param {object} client - CozyClient instance
40
- * @returns {array<Recipient>} List of recipients without me if I'm the owner
41
- */
42
- export const excludeMeAsOwnerFromRecipients = ({
43
- recipients,
44
- isOwner,
45
- client
46
- }) => {
47
- return recipients.filter(recipient => {
48
- if (isOwner) {
49
- return recipient.status !== 'owner'
50
- }
51
- return recipient.instance !== client.options.uri
52
- })
53
- }
54
-
55
- export const RecipientsAvatars = ({
56
- recipients,
57
- link,
58
- size,
59
- className,
60
- onClick,
61
- isOwner,
62
- showMeAsOwner
63
- }) => {
64
- const client = useClient()
65
- const filteredRecipients = showMeAsOwner
66
- ? recipients.slice().reverse() // we slice first to clone the original array because reverser() mutates it
67
- : excludeMeAsOwnerFromRecipients({
68
- recipients,
69
- isOwner,
70
- client
71
- }).reverse()
72
- // we reverse the recipients array because we use `flex-direction: row-reverse` to display them correctly
73
-
74
- const isAvatarPlusX = filteredRecipients.length > MAX_DISPLAYED_RECIPIENTS
75
- const extraRecipients = filteredRecipients
76
- .slice(MAX_DISPLAYED_RECIPIENTS)
77
- .map(recipient => getDisplayName(recipient))
78
- const shownRecipients = filteredRecipients.slice(0, MAX_DISPLAYED_RECIPIENTS)
79
-
80
- return (
81
- <div
82
- className={cx(
83
- styles['recipients-avatars'],
84
- {
85
- [styles['--interactive']]: onClick
86
- },
87
- className
88
- )}
89
- onClick={onClick}
90
- >
91
- {link && (
92
- <span data-testid="recipientsAvatars-link">
93
- <Avatar
94
- className={styles['recipients-avatars--link']}
95
- icon={LinkIcon}
96
- size={size}
97
- />
98
- </span>
99
- )}
100
- {isAvatarPlusX && (
101
- <span data-testid="recipientsAvatars-plusX">
102
- <AvatarPlusX
103
- className={styles['recipients-avatars--plusX']}
104
- extraRecipients={extraRecipients}
105
- size={size}
106
- />
107
- </span>
108
- )}
109
- {shownRecipients.map((recipient, idx) => (
110
- <span
111
- data-testid={`recipientsAvatars-avatar${
112
- recipient.status === 'owner' ? '-owner' : ''
113
- }`}
114
- key={idx}
115
- >
116
- <RecipientAvatar
117
- recipient={recipient}
118
- size={size}
119
- className={cx(styles['recipients-avatars--avatar'])}
120
- />
121
- </span>
122
- ))}
123
- </div>
124
- )
125
- }
126
-
127
- export const RecipientAvatar = ({ recipient, ...rest }) => {
128
- const client = useClient()
129
-
130
- /**
131
- * avatarPath is always the same for a recipient, but image
132
- * can be different since the stack generate it on the fly.
133
- * It can be "gray" during the first load depending of the
134
- * status' sharing, but can become active (from the realtime)
135
- * so we need a way to "refresh" the image. Passing the
136
- * status in the url force the refresh of the image when the
137
- * status changes
138
- */
139
- const image =
140
- recipient.avatarPath && recipient.status
141
- ? `${client.options.uri}${recipient.avatarPath}?v=${recipient.status}`
142
- : null
143
-
144
- return (
145
- <Avatar
146
- image={image}
147
- text={getInitials(recipient)}
148
- textId={getDisplayName(recipient)}
149
- disabled={
150
- recipient.status === 'pending' || recipient.status === 'mail-not-send'
151
- }
152
- {...rest}
153
- />
154
- )
155
- }
156
-
157
- export const OwnerIdentity = ({ url, size, ...rest }) => (
158
- <div className={styles['avatar']}>
159
- <Avatar
160
- text={getInitials(rest)}
161
- size={size}
162
- textId={getDisplayName(rest)}
163
- />
164
- <Identity name={getDisplayName(rest)} details={url} />
165
- </div>
166
- )
167
-
168
- export const Permissions = ({
169
- isOwner,
170
- status,
171
- instance,
172
- type,
173
- document,
174
- documentType,
175
- className,
176
- onRevoke,
177
- onRevokeSelf,
178
- sharingId,
179
- index
180
- }) => {
181
- const { t } = useI18n()
182
- const client = useClient()
183
- const [revoking, setRevoking] = useState(false)
184
- const [isMenuDisplayed, setIsMenuDisplayed] = useState(false)
185
- const instanceMatchesClient =
186
- instance !== undefined && instance === client.options.uri
187
- const contactIsOwner = status === 'owner'
188
- const shouldShowMenu =
189
- !revoking &&
190
- !contactIsOwner &&
191
- ((instanceMatchesClient && !isOwner) || isOwner)
192
-
193
- const showMenu = () => setIsMenuDisplayed(true)
194
- const hideMenu = () => setIsMenuDisplayed(false)
195
-
196
- const onRevokeClick = useCallback(async () => {
197
- setRevoking(true)
198
- if (isOwner) {
199
- await onRevoke(document, sharingId, index)
200
- } else {
201
- await onRevokeSelf(document)
202
- }
203
-
204
- setRevoking(false)
205
- }, [isOwner, onRevoke, onRevokeSelf, document, sharingId, index])
206
-
207
- const buttonRef = React.createRef()
208
- const PermissionIcon = type === 'two-way' ? RenameIcon : EyeIcon
209
-
210
- return (
211
- <div className={className}>
212
- {revoking && <Spinner />}
213
- {!shouldShowMenu && !revoking && (
214
- <span>{t(`Share.status.${status}`)}</span>
215
- )}
216
- {shouldShowMenu && (
217
- <>
218
- <DropdownButton
219
- onClick={showMenu}
220
- ref={buttonRef}
221
- className={modalStyles['aligned-dropdown-button']}
222
- >
223
- <Typography variant="body1">{t(`Share.type.${type}`)}</Typography>
224
- </DropdownButton>
225
- {isMenuDisplayed && (
226
- <ActionMenu
227
- onClose={hideMenu}
228
- popperOptions={{
229
- placement: 'bottom-end',
230
- modifiers: [
231
- {
232
- name: 'preventOverflow'
233
- }
234
- ]
235
- }}
236
- anchorElRef={buttonRef}
237
- >
238
- <ActionMenuItem
239
- left={
240
- <Icon icon={PermissionIcon} color="var(--primaryTextColor)" />
241
- }
242
- >
243
- {t(`Share.type.${type}`)}
244
- </ActionMenuItem>
245
-
246
- <hr />
247
- <ActionMenuItem
248
- onClick={onRevokeClick}
249
- left={<Icon icon={TrashIcon} color="var(--errorColor)" />}
250
- >
251
- <Typography className="u-error" variant="body1">
252
- {isOwner
253
- ? t(`${documentType}.share.revoke.title`)
254
- : t(`${documentType}.share.revokeSelf.title`)}
255
- </Typography>
256
- <Typography variant="caption" color="textSecondary">
257
- {isOwner
258
- ? t(`${documentType}.share.revoke.desc`)
259
- : t(`${documentType}.share.revokeSelf.desc`)}
260
- </Typography>
261
- </ActionMenuItem>
262
- </ActionMenu>
263
- )}
264
- </>
265
- )}
266
- </div>
267
- )
268
- }
269
-
270
- const Status = ({ status, isMe, instance }) => {
271
- const { t } = useI18n()
272
-
273
- const isSendingEmail = !isMe && status === 'mail-not-sent'
274
- const isReady = isMe || status === 'ready'
275
- let text, icon
276
- if (isReady) {
277
- text = instance
278
- icon = ToTheCloudIcon
279
- } else if (isSendingEmail) {
280
- text = t('Share.status.mail-not-sent')
281
- icon = PaperplaneIcon
282
- } else {
283
- const supportedStatus = ['pending', 'seen']
284
- text = supportedStatus.includes(status)
285
- ? t(`Share.status.${status}`)
286
- : t('Share.status.pending')
287
-
288
- icon = status === 'seen' ? EyeIcon : PaperplaneIcon
289
- }
290
-
291
- return (
292
- <Media>
293
- <Img className={styles['recipient-status-icon']}>
294
- <Icon icon={icon} size={10} />
295
- </Img>
296
- <Bd>
297
- <Typography variant="caption" color="textSecondary">
298
- {text}
299
- </Typography>
300
- </Bd>
301
- </Media>
302
- )
303
- }
304
-
305
- /**
306
- * Displays the confirmation interface that allows to confirm or reject a recipient
307
- */
308
- const RecipientConfirm = ({ recipientConfirmationData, verifyRecipient }) => {
309
- const { t } = useI18n()
310
-
311
- const verify = () => {
312
- verifyRecipient(recipientConfirmationData)
313
- }
314
-
315
- return (
316
- <>
317
- <Button
318
- style={{ position: 'initial' }} // fix z-index bug on iOS when under a BottomDrawer due to relative position
319
- theme="text"
320
- className={modalStyles['aligned-dropdown-button']}
321
- onClick={verify}
322
- label={t(`Share.twoStepsConfirmation.verify`)}
323
- />
324
- </>
325
- )
326
- }
327
-
328
- RecipientConfirm.propTypes = {
329
- recipientConfirmationData: PropTypes.object.isRequired,
330
- verifyRecipient: PropTypes.func.isRequired
331
- }
332
-
333
- const Recipient = props => {
334
- const { t } = useI18n()
335
- const client = useClient()
336
-
337
- const {
338
- instance,
339
- isOwner,
340
- status,
341
- recipientConfirmationData,
342
- verifyRecipient,
343
- ...rest
344
- } = props
345
-
346
- const isMe =
347
- (isOwner && status === 'owner') || instance === client.options.uri
348
- const defaultDisplayName = t(DEFAULT_DISPLAY_NAME)
349
- const name = getDisplayName(rest, defaultDisplayName)
350
-
351
- const RightPart = recipientConfirmationData ? (
352
- <RecipientConfirm
353
- recipientConfirmationData={recipientConfirmationData}
354
- verifyRecipient={verifyRecipient}
355
- ></RecipientConfirm>
356
- ) : (
357
- <Permissions {...props} className="u-flex-shrink-0" />
358
- )
359
-
360
- return (
361
- <CompositeRow
362
- dense
363
- className={cx(styles['recipient'], 'u-ph-0')}
364
- primaryText={
365
- <Typography className="u-ellipsis" variant="body1">
366
- {isMe ? t('Share.recipients.you') : name}
367
- </Typography>
368
- }
369
- secondaryText={<Status status={status} isMe={isMe} instance={instance} />}
370
- image={<RecipientAvatar recipient={props} />}
371
- right={RightPart}
372
- />
373
- )
374
- }
375
-
376
- Recipient.propTypes = {
377
- instance: PropTypes.string,
378
- isOwner: PropTypes.bool,
379
- status: PropTypes.string,
380
- recipientConfirmationData: PropTypes.object,
381
- verifyRecipient: PropTypes.func
382
- }
383
-
384
- export default Recipient
385
-
386
- export const RecipientWithoutStatus = ({ instance, ...rest }) => {
387
- const name = getDisplayName(rest)
388
- return (
389
- <div className={styles['recipient']}>
390
- <Avatar text={getInitials(rest)} textId={name} />
391
- <div className={styles['recipient-ident-status']}>
392
- <Identity name={name} details={instance} />
393
- </div>
394
- </div>
395
- )
396
- }
397
-
398
- export const RecipientPlusX = ({ extraRecipients }, { t }) => (
399
- <div className={styles['recipient']}>
400
- <AvatarPlusX
401
- extraRecipients={extraRecipients.map(recipient =>
402
- getDisplayName(recipient)
403
- )}
404
- />
405
- <div className={styles['recipient-ident-status']}>
406
- <Identity
407
- name={t('Share.members.otherContacts', extraRecipients.length)}
408
- />
409
- </div>
410
- </div>
411
- )
412
-
413
- RecipientPlusX.contextTypes = {
414
- t: PropTypes.func.isRequired
415
- }