cozy-sharing 30.1.1 → 30.2.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 (28) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/components/FederatedFolder/DumbFederatedFolderModal.js +9 -3
  3. package/dist/components/FederatedFolder/FederatedFolderModal.js +9 -3
  4. package/dist/components/ShareByLink.js +115 -54
  5. package/dist/components/ShareDialogCozyToCozy.js +18 -3
  6. package/dist/components/ShareDialogOnlyByLink.js +6 -2
  7. package/dist/components/ShareModal/ShareModal.js +6 -5
  8. package/dist/components/ShareModal.js +13 -3
  9. package/dist/components/ShareModal.spec.js +4 -4
  10. package/dist/components/ShareRestrictionModal/ShareRestrictionModal.js +51 -64
  11. package/dist/components/ShareRestrictionModal/helpers.js +61 -12
  12. package/dist/components/SharedDrive/DumbSharedDriveModal.js +9 -3
  13. package/dist/components/SharedDrive/SharedDriveEditModal.js +14 -3
  14. package/dist/components/SharedFolder/DumbBatchSharedFolderModal.js +12 -4
  15. package/package.json +2 -2
  16. package/src/components/FederatedFolder/DumbFederatedFolderModal.jsx +8 -2
  17. package/src/components/FederatedFolder/FederatedFolderModal.jsx +11 -2
  18. package/src/components/ShareByLink.jsx +66 -25
  19. package/src/components/ShareDialogCozyToCozy.jsx +17 -2
  20. package/src/components/ShareDialogOnlyByLink.jsx +5 -1
  21. package/src/components/ShareModal/ShareModal.jsx +4 -2
  22. package/src/components/ShareModal.jsx +10 -0
  23. package/src/components/ShareModal.spec.js +7 -3
  24. package/src/components/ShareRestrictionModal/ShareRestrictionModal.jsx +42 -38
  25. package/src/components/ShareRestrictionModal/helpers.js +23 -4
  26. package/src/components/SharedDrive/DumbSharedDriveModal.jsx +8 -2
  27. package/src/components/SharedDrive/SharedDriveEditModal.jsx +20 -2
  28. package/src/components/SharedFolder/DumbBatchSharedFolderModal.jsx +10 -2
@@ -36,7 +36,7 @@ export const ShareModal = withLocales(props => {
36
36
 
37
37
  const isFederatedMode = flag('drive.federated-shared-folder.enabled')
38
38
  if (isFederatedMode && allLoaded) {
39
- return <FederatedFolderModal document={document} onClose={rest.onClose} />
39
+ return <FederatedFolderModal document={document} {...rest} />
40
40
  }
41
41
 
42
42
  const isEditable =
@@ -61,6 +61,8 @@ export const ShareModal = withLocales(props => {
61
61
  })
62
62
 
63
63
  ShareModal.propTypes = {
64
+ autoOpenShareRestriction: PropTypes.bool,
64
65
  document: PropTypes.object.isRequired,
65
- onRevokeSuccess: PropTypes.func
66
+ onRevokeSuccess: PropTypes.func,
67
+ showGenerateLinkButton: PropTypes.bool
66
68
  }
@@ -8,6 +8,7 @@ import ShareDialogOnlyByLink from './ShareDialogOnlyByLink'
8
8
  import { SharedDriveEditModal } from './SharedDrive/SharedDriveEditModal'
9
9
 
10
10
  export const ShareModal = ({
11
+ autoOpenShareRestriction,
11
12
  createContact,
12
13
  document,
13
14
  documentType = 'Document',
@@ -24,6 +25,7 @@ export const ShareModal = ({
24
25
  recipients,
25
26
  sharing,
26
27
  sharingDesc,
28
+ showGenerateLinkButton,
27
29
  twoStepsConfirmationMethods
28
30
  }) => {
29
31
  if (isSharedDrive && !flag('drive.federated-shared-folder.enabled')) {
@@ -35,6 +37,8 @@ export const ShareModal = ({
35
37
  onShare={onShare}
36
38
  onRevoke={onRevoke}
37
39
  onClose={onClose}
40
+ autoOpenShareRestriction={autoOpenShareRestriction}
41
+ showGenerateLinkButton={showGenerateLinkButton}
38
42
  />
39
43
  )
40
44
  }
@@ -58,6 +62,8 @@ export const ShareModal = ({
58
62
  link={link}
59
63
  onClose={onClose}
60
64
  permissions={permissions}
65
+ showGenerateLinkButton={showGenerateLinkButton}
66
+ autoOpenShareRestriction={autoOpenShareRestriction}
61
67
  />
62
68
  ) : (
63
69
  <ShareDialogCozyToCozy
@@ -79,6 +85,8 @@ export const ShareModal = ({
79
85
  showShareByLink={showShareByLink}
80
86
  showShareOnlyByLink={showShareOnlyByLink}
81
87
  showWhoHasAccess={showWhoHasAccess}
88
+ showGenerateLinkButton={showGenerateLinkButton}
89
+ autoOpenShareRestriction={autoOpenShareRestriction}
82
90
  twoStepsConfirmationMethods={twoStepsConfirmationMethods}
83
91
  />
84
92
  )
@@ -87,6 +95,7 @@ export const ShareModal = ({
87
95
  export default ShareModal
88
96
 
89
97
  ShareModal.propTypes = {
98
+ autoOpenShareRestriction: PropTypes.bool,
90
99
  createContact: PropTypes.func.isRequired,
91
100
  document: PropTypes.object.isRequired,
92
101
  documentType: PropTypes.string,
@@ -100,6 +109,7 @@ ShareModal.propTypes = {
100
109
  permissions: PropTypes.array.isRequired,
101
110
  recipients: PropTypes.array.isRequired,
102
111
  sharingDesc: PropTypes.string,
112
+ showGenerateLinkButton: PropTypes.bool,
103
113
  twoStepsConfirmationMethods: PropTypes.shape({
104
114
  getRecipientsToBeConfirmed: PropTypes.func,
105
115
  confirmRecipient: PropTypes.func,
@@ -6,8 +6,12 @@ import flag from 'cozy-flags'
6
6
  import { ShareModal } from './ShareModal'
7
7
  import AppLike from '../../test/AppLike'
8
8
 
9
- jest.mock('./ShareDialogCozyToCozy', () => () => <>ShareDialogCozyToCozy</>)
10
- jest.mock('./ShareDialogOnlyByLink', () => () => <>ShareDialogOnlyByLink</>)
9
+ jest.mock('./ShareDialogCozyToCozy', () => () => (
10
+ <div>ShareDialogCozyToCozy</div>
11
+ ))
12
+ jest.mock('./ShareDialogOnlyByLink', () => () => (
13
+ <div>ShareDialogOnlyByLink</div>
14
+ ))
11
15
 
12
16
  jest.mock('cozy-flags')
13
17
 
@@ -43,7 +47,7 @@ describe('ShareModal component', () => {
43
47
  })
44
48
 
45
49
  it('should render ShareDialogOnlyByLink if flag cozy.hide-sharing-cozy-to-cozy is true', () => {
46
- flag.mockImplementation(() => true)
50
+ flag.mockImplementation(name => name === 'cozy.hide-sharing-cozy-to-cozy')
47
51
 
48
52
  const root = setup({ documentType: 'Document' })
49
53
 
@@ -77,51 +77,55 @@ export const ShareRestrictionModal = ({ file, onClose }) => {
77
77
 
78
78
  const handleClick = async () => {
79
79
  setLoading(true)
80
- // If the file is not shared, we create a new sharing link
81
- if (!hasSharingLink) {
82
- const ttl = makeTTL(dateToggle && selectedDate)
83
- const { data: perms } = await createPermissions({
84
- file,
85
- t,
86
- ttl,
87
- password,
88
- editingRights,
89
- documentType,
90
- shareByLink,
91
- showAlert
92
- })
93
- const url = generateWebLink({
94
- cozyUrl: client.getStackClient().uri,
95
- searchParams: [['sharecode', perms.attributes.shortcodes.code]],
96
- pathname: '/public',
97
- slug: getAppSlugFromDocumentType({ documentType }),
98
- subDomainType: client.capabilities.flat_subdomains ? 'flat' : 'nested'
99
- })
100
- await copyToClipboard(url, { t, showAlert })
101
- onClose()
102
- } else {
103
- const [{ data: perms }] = await updatePermissions({
104
- file,
105
- t,
106
- dateToggle,
107
- selectedDate,
108
- passwordToggle,
109
- password,
110
- editingRights,
111
- documentType,
112
- updateDocumentPermissions,
113
- showAlert
114
- })
115
- const url = generateWebLink({
80
+ // The clipboard write must be initiated synchronously within the click
81
+ // handler for Safari to honor the user gesture; we pass a pending blob
82
+ // whose content resolves after the permission round-trip.
83
+ const permsPromise = hasSharingLink
84
+ ? updatePermissions({
85
+ file,
86
+ t,
87
+ dateToggle,
88
+ selectedDate,
89
+ passwordToggle,
90
+ password,
91
+ editingRights,
92
+ documentType,
93
+ updateDocumentPermissions,
94
+ showAlert
95
+ }).then(([{ data: perms }]) => perms)
96
+ : createPermissions({
97
+ file,
98
+ t,
99
+ ttl: makeTTL(dateToggle && selectedDate),
100
+ password,
101
+ editingRights,
102
+ documentType,
103
+ shareByLink,
104
+ showAlert
105
+ }).then(({ data: perms }) => perms)
106
+
107
+ const urlPromise = permsPromise.then(perms =>
108
+ generateWebLink({
116
109
  cozyUrl: client.getStackClient().uri,
117
110
  searchParams: [['sharecode', perms.attributes.shortcodes.code]],
118
111
  pathname: '/public',
119
112
  slug: getAppSlugFromDocumentType({ documentType }),
120
113
  subDomainType: client.capabilities.flat_subdomains ? 'flat' : 'nested'
121
114
  })
122
- await copyToClipboard(url, { t, showAlert })
123
- onClose()
115
+ )
116
+
117
+ await copyToClipboard(urlPromise, { t, showAlert })
118
+ try {
119
+ // Observe the permission result so we can keep the modal open (and
120
+ // preserve the user's date / password / editing-rights inputs) if
121
+ // the permission round-trip failed. copyToClipboard swallows its
122
+ // own errors so the await above always resolves.
123
+ await permsPromise
124
+ } catch {
125
+ setLoading(false)
126
+ return
124
127
  }
128
+ onClose()
125
129
  }
126
130
 
127
131
  const handleRevokeLink = async () => {
@@ -96,17 +96,36 @@ export const generateShareLinkFromFile = ({
96
96
  }
97
97
 
98
98
  /**
99
- * Copy a string to the clipboard
99
+ * Copy a value to the clipboard.
100
100
  *
101
- * @param {string} value - The string saved to the clipboard
101
+ * Accepts a resolved string or a `Promise<string>`. The promise form is
102
+ * required when the value depends on a prior async step (e.g. creating a
103
+ * sharing link) from within a click handler: Safari drops the user-gesture
104
+ * context across any `await`, so `navigator.clipboard.writeText` called after
105
+ * an awaited call throws `NotAllowedError`. Passing a pending blob to
106
+ * `ClipboardItem` lets the clipboard API be invoked synchronously while the
107
+ * content is resolved later.
108
+ *
109
+ * @param {string | Promise<string>} value - Value to copy
102
110
  * @param {object} [options]
103
- * @param {number} options.t - Translation function
111
+ * @param {Function} options.t - Translation function
104
112
  * @param {Function} options.showAlert - Function to display an alert
105
113
  */
106
114
  export const copyToClipboard = async (value, { t, showAlert } = {}) => {
107
115
  if (!value) return false
108
116
  try {
109
- await navigator.clipboard.writeText(value)
117
+ if (typeof value === 'string') {
118
+ await navigator.clipboard.writeText(value)
119
+ } else if (typeof ClipboardItem !== 'undefined') {
120
+ const blobPromise = Promise.resolve(value).then(
121
+ text => new Blob([text], { type: 'text/plain' })
122
+ )
123
+ await navigator.clipboard.write([
124
+ new ClipboardItem({ 'text/plain': blobPromise })
125
+ ])
126
+ } else {
127
+ await navigator.clipboard.writeText(await value)
128
+ }
110
129
  showAlert({
111
130
  message: t('copyReminderContent.success'),
112
131
  severity: 'success',
@@ -23,7 +23,9 @@ export const DumbSharedDriveModal = withLocales(
23
23
  onShare,
24
24
  onRename,
25
25
  showNameField = true,
26
- sharingLink
26
+ sharingLink,
27
+ autoOpenShareRestriction,
28
+ showGenerateLinkButton
27
29
  }) => {
28
30
  const { t } = useI18n()
29
31
  return (
@@ -51,6 +53,8 @@ export const DumbSharedDriveModal = withLocales(
51
53
  createLabel={t('SharedDrive.sharedDriveModal.create')}
52
54
  shareLabel={t('FederatedFolder.share')}
53
55
  saveLabel={t('SharedDrive.sharedDriveModal.save')}
56
+ autoOpenShareRestriction={autoOpenShareRestriction}
57
+ showGenerateLinkButton={showGenerateLinkButton}
54
58
  />
55
59
  )
56
60
  }
@@ -72,7 +76,9 @@ DumbSharedDriveModal.propTypes = {
72
76
  onShare: PropTypes.func.isRequired,
73
77
  onRename: PropTypes.func,
74
78
  showNameField: PropTypes.bool,
75
- sharingLink: PropTypes.string
79
+ sharingLink: PropTypes.string,
80
+ autoOpenShareRestriction: PropTypes.bool,
81
+ showGenerateLinkButton: PropTypes.bool
76
82
  }
77
83
 
78
84
  export default DumbSharedDriveModal
@@ -11,7 +11,16 @@ import { useSharingContext } from '../../hooks/useSharingContext'
11
11
  import { Contact } from '../../models'
12
12
 
13
13
  export const SharedDriveEditModal = withLocales(
14
- ({ document, sharing, recipients, onShare, onRevoke, onClose }) => {
14
+ ({
15
+ document,
16
+ sharing,
17
+ recipients,
18
+ onShare,
19
+ onRevoke,
20
+ onClose,
21
+ autoOpenShareRestriction,
22
+ showGenerateLinkButton
23
+ }) => {
15
24
  const client = useClient()
16
25
  const { t } = useI18n()
17
26
  const { showAlert } = useAlert()
@@ -51,11 +60,20 @@ export const SharedDriveEditModal = withLocales(
51
60
  sharedDriveName={name}
52
61
  handleSharedDriveNameChange={handleNameChange}
53
62
  onRename={onRename}
63
+ autoOpenShareRestriction={autoOpenShareRestriction}
64
+ showGenerateLinkButton={showGenerateLinkButton}
54
65
  />
55
66
  )
56
67
  }
57
68
  )
58
69
 
59
70
  SharedDriveEditModal.propTypes = {
60
- onClose: PropTypes.func.isRequired
71
+ document: PropTypes.object,
72
+ sharing: PropTypes.object,
73
+ recipients: PropTypes.array,
74
+ onShare: PropTypes.func.isRequired,
75
+ onRevoke: PropTypes.func.isRequired,
76
+ onClose: PropTypes.func.isRequired,
77
+ autoOpenShareRestriction: PropTypes.bool,
78
+ showGenerateLinkButton: PropTypes.bool
61
79
  }
@@ -38,7 +38,9 @@ export const DumbBatchSharedFolderModal = withLocales(
38
38
  shareLabel,
39
39
  saveLabel,
40
40
  sharingDesc,
41
- showShareByEmail = true
41
+ showShareByEmail = true,
42
+ autoOpenShareRestriction,
43
+ showGenerateLinkButton
42
44
  }) => {
43
45
  const hasRecipients = Boolean(recipients?.length)
44
46
 
@@ -59,6 +61,8 @@ export const DumbBatchSharedFolderModal = withLocales(
59
61
  link={sharingLink}
60
62
  document={document}
61
63
  documentType="Files"
64
+ showGenerateLinkButton={showGenerateLinkButton}
65
+ autoOpenShareRestriction={autoOpenShareRestriction}
62
66
  />
63
67
  )
64
68
  }
@@ -70,6 +74,8 @@ export const DumbBatchSharedFolderModal = withLocales(
70
74
  link={sharingLink}
71
75
  document={document}
72
76
  documentType="Files"
77
+ showGenerateLinkButton={showGenerateLinkButton}
78
+ autoOpenShareRestriction={autoOpenShareRestriction}
73
79
  />
74
80
  <Button
75
81
  variant="primary"
@@ -182,7 +188,9 @@ DumbBatchSharedFolderModal.propTypes = {
182
188
  shareLabel: PropTypes.string,
183
189
  saveLabel: PropTypes.string,
184
190
  sharingDesc: PropTypes.string,
185
- showShareByEmail: PropTypes.bool
191
+ showShareByEmail: PropTypes.bool,
192
+ autoOpenShareRestriction: PropTypes.bool,
193
+ showGenerateLinkButton: PropTypes.bool
186
194
  }
187
195
 
188
196
  DumbBatchSharedFolderModal.defaultProps = {