cozy-sharing 37.2.10 → 37.2.12

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.
package/AGENTS.md ADDED
@@ -0,0 +1,21 @@
1
+ # cozy-sharing — agent rules
2
+
3
+ ## Keep README.md in sync with the code
4
+
5
+ The implementation is the behavioral source of truth. `README.md` documents the implemented behavior and usage, and MUST be updated in the same commit/PR whenever the behavior or usage changes.
6
+
7
+ ### When to update the README
8
+
9
+ - **Feature flags** — a flag is added, removed, renamed, or its default changes → update the "Feature flags" table.
10
+ - **Feature behavior or conditions** — a permission rule, a restriction (e.g. `hasSharedParent` / `hasSharedChild`), a link lifecycle step, or a gating condition changes → update the matching "Feature conditions" table.
11
+ - **Architecture** — the `ShareModal` routing, a new modal/dialog, or the `SharingProvider` context shape changes → update "Architecture".
12
+
13
+ ### When NOT to update
14
+
15
+ - Pure refactors with no behavior change.
16
+ - Bug fixes that don't change documented behavior.
17
+ - Test-only or style-only changes.
18
+
19
+ ### Rule of thumb
20
+
21
+ If the code and the README disagree after your change, the change is incomplete — fix the README before committing.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [37.2.12](https://github.com/cozy/cozy-libs/compare/cozy-sharing@37.2.11...cozy-sharing@37.2.12) (2026-07-30)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **sharing:** Make shared-drive member modals consistent and correct ([9dcf67d](https://github.com/cozy/cozy-libs/commit/9dcf67d375600a1a02298dc3036880ae37d08aef))
11
+
12
+ ## [37.2.11](https://github.com/cozy/cozy-libs/compare/cozy-sharing@37.2.10...cozy-sharing@37.2.11) (2026-07-30)
13
+
14
+ **Note:** Version bump only for package cozy-sharing
15
+
6
16
  ## [37.2.10](https://github.com/cozy/cozy-libs/compare/cozy-sharing@37.2.9...cozy-sharing@37.2.10) (2026-07-30)
7
17
 
8
18
  **Note:** Version bump only for package cozy-sharing
package/README.md CHANGED
@@ -1,83 +1,231 @@
1
- # Test 'n play
1
+ # cozy-sharing
2
2
 
3
- Open the playgrounds in cozy-libs and run `yarn start`
3
+ React library that provides sharing UI and logic for Cozy / Twake Drive applications.
4
4
 
5
- # How to use the lib
5
+ It wraps the Cozy Stack sharing API and exposes ready-to-use components (modal, button, banners), hooks, and context for building sharing experiences.
6
6
 
7
- In your app, you have to :
7
+ ***
8
8
 
9
- - import the Provider: `import SharingProvider, { ShareButton, ShareModal } from 'cozy-sharing'`
10
- - import the stylesheet.css: `import 'cozy-sharing/dist/stylesheet.css'`
9
+ ## Installation
11
10
 
12
- ## Using the built-in components
11
+ ```bash
12
+ yarn add cozy-sharing
13
+ ```
14
+
15
+ In your app, you must:
16
+
17
+ - wrap the component tree with `SharingProvider`
18
+ - import the stylesheet: `import 'cozy-sharing/dist/stylesheet.css'`
19
+
20
+ ***
13
21
 
14
- Some of the exposed components are fully featured components, ready to render. They need a `SharingProvider` above them in the render tree and the imported stylesheet for their styles.
22
+ ## Usage
15
23
 
24
+ ### Provider
25
+
26
+ ```jsx
27
+ import SharingProvider from 'cozy-sharing'
28
+
29
+ <SharingProvider doctype="io.cozy.files" documentType="Files">
30
+ <App />
31
+ </SharingProvider>
16
32
  ```
33
+
34
+ **Props:**
35
+
36
+ | Prop | Required | Description |
37
+ |------|----------|-------------|
38
+ | `doctype` | yes | The cozy doctype (e.g. `io.cozy.files`) |
39
+ | `documentType` | no | Human-readable type for i18n keys (default `'Document'`) |
40
+ | `onShared` | no | Callback fired after a share is created |
41
+ | `isPublic` | no | Set to `true` when rendering a public page (disables fetching) |
42
+ | `previewPath` | no | Custom preview path sent to the share creation |
43
+
44
+ ### Built-in components
45
+
46
+ Surrounding application code (imports, state, props) is omitted for brevity.
47
+
48
+ ```jsx
17
49
  import { ShareModal } from 'cozy-sharing'
18
50
 
19
51
  const ToggleModal = () => {
20
- const [isModalDisplayed, setIsModalDisplayed] = useState(false)
21
-
52
+ const [isOpen, setIsOpen] = useState(false)
22
53
  return (
23
54
  <div>
24
- <Button onClick={() => setIsModalDisplayed(true)}>Open modal</Button>
25
- {isModalDisplayed && <ShareModal document={doc} />}
55
+ <Button onClick={() => setIsOpen(true)}>Share</Button>
56
+ {isOpen && <ShareModal document={doc} onClose={() => setIsOpen(false)} />}
26
57
  </div>
27
58
  )
28
59
  }
29
60
  ```
30
61
 
31
- Other components accept a render prop as children that receive some information from the sharing context.
62
+ ### Render-prop components
32
63
 
33
- ```
64
+ ```jsx
34
65
  import { SharedDocument } from 'cozy-sharing'
35
66
 
67
+ const MyComp = () => (
68
+ <SharedDocument docId="123">
69
+ {({ isShared, link }) => (isShared ? link : 'Not shared yet')}
70
+ </SharedDocument>
71
+ )
72
+ ```
73
+
74
+ ### Hooks
75
+
76
+ ```jsx
77
+ import { useSharingContext } from 'cozy-sharing'
78
+
36
79
  const MyComp = () => {
37
- return (
38
- <SharedDocument docId='123'>
39
- {({ isShared, link }) => (
40
- {isShared ? link : 'Not shared yet'}
41
- )}
42
- </SharedDocument>
43
- )
80
+ const { share } = useSharingContext()
81
+ return <Button onClick={() => share({ document, recipients, ... })}>Share</Button>
44
82
  }
45
83
  ```
46
84
 
47
- ## Usage with hooks
85
+ ***
48
86
 
49
- `cozy-sharing` can now be used with hooks as well:
87
+ ## How it works
50
88
 
51
- ```
52
- import { SharingContext } from 'cozy-sharing'
89
+ ### Permission model
53
90
 
54
- const MyComp = () => {
55
- const { share } = useContext(SharingContext)
91
+ The UI exposes two roles, but the stack has more granular verb control:
56
92
 
57
- return <Button onClick={() => share(document, recipients, sharingType, description)}>Share</Button>
58
- }
59
- ```
93
+ | Role | Verbs | Description |
94
+ |------|-------|-------------|
95
+ | **Viewer** | `GET` | Read-only access |
96
+ | **Editor** | `GET, POST, PUT, PATCH` | Read + write + share management |
97
+
98
+ The `write` permission allows editing content, uploading files, deleting files (to the owner's trash), and managing share members (add/remove members, change permissions, view/manage link).
99
+
100
+ ### Link sharing (public URL)
101
+
102
+ Creates a public URL accessible without a Twake account. One link per resource at any time.
103
+
104
+ **Options (set in `ShareRestrictionModal` or `ShareLinkAccessModal`):**
105
+
106
+ | Option | Default | Details |
107
+ |--------|---------|---------|
108
+ | Permission | `readOnly` | Can be changed to `write` |
109
+ | Expiration date | Off | Toggle; defaults to 30 days ahead when enabled; expires at end of that day |
110
+ | Password | Off | Minimum 4 characters; owner communicates it out-of-band |
111
+
112
+ **Lifecycle:**
113
+
114
+ - Deactivating a link revokes the current permissions. Reactivating generates a **new URL** — the old one stays invalid.
115
+ - Deleting the root resource revokes all associated shares (link and email).
116
+ - Link sharing coexists with email sharing on the same resource.
60
117
 
61
- # Share and send mail in development
118
+ ### Email sharing (cozy-to-cozy)
119
+
120
+ Invites specific users or groups with individualized permissions. Two technical modes:
121
+
122
+ - **Cozy-to-cozy sharing** (classic) — data is **replicated** to each recipient's Cozy.
123
+ - **Shared drives** (shared folders, team drives) — recipients access files at the **owner's instance** (no replication).
124
+
125
+ In both cases, content appears in the recipient's **Sharing tab**.
126
+
127
+ **By recipient status:**
128
+
129
+ | Recipient status | Behavior |
130
+ |-----------------|----------|
131
+ | Trusted contact (same org or previously accepted) | Content appears directly in the Sharing tab; notification sent |
132
+ | Has Twake account, not in contacts | Invitation email → login → Sharing tab |
133
+ | **No Twake account** | ❌ Not supported today |
134
+ | **Different SSO** | ❌ Not supported today |
135
+ | Not trusted (guest in org) | ❌ Only if added manually; not automated today |
136
+
137
+ ### Cohabitation
138
+
139
+ Link and email sharing can coexist on the same resource. The recipient's permission is the most permissive when both apply.
140
+
141
+ ***
142
+
143
+ ## Feature conditions
144
+
145
+ ### Link sharing
146
+
147
+ | Condition | Rule | Source |
148
+ |-----------|------|--------|
149
+ | Who can create/manage | Owner or Editor (`canReshare` = owner, or `open_sharing && !read_only`) | `canReshare()` in `state.js` |
150
+ | Document type restriction | Hidden when `documentType === 'Organizations'` | `ShareModal.jsx:33` |
151
+ | Albums | Link-only, read-only permissions only | `ShareModal.jsx:30`, `link.js:3` |
152
+ | Editing rights box | Hidden when `drive.federated-shared-folder.enabled` flag is true | `BoxEditingRights.jsx:54` |
153
+ | Expiration date toggle default | Controlled by `sharing.date-toggle.enabled` flag (default: false) | `ShareRestrictionModal.jsx:62` |
154
+ | Link deactivation | Revokes the permission document; regenerates a new URL on reactivation | `SharingProvider.jsx:635` |
155
+ | Password | 4+ characters; no limit on attempts | `ShareLinkSettings.jsx:10` |
156
+ | Guest access | Via `/public` URL with `sharecode`; no auth required | `helpers.js:106` |
157
+
158
+ ### Email sharing
159
+
160
+ | Condition | Rule | Source |
161
+ |-----------|------|--------|
162
+ | Who can share | Owner or Editor (`canReshare`) | `SharingProvider.jsx` |
163
+ | **Core constraint** | Disabled if `hasSharedParent` OR `hasSharedChild` — a subfolder of an email-shared folder cannot be shared by email independently | `ShareModal.jsx:32`, `state.js:508-523` |
164
+ | Recipients limit | Default 100; overridable via `sharing.recipients-limit` flag | `helpers/recipients.js:33` |
165
+ | Recipient display mode | `sharing.show-recipient-groups` flag: `true` = groups as units, `false` = spread group members | `ShareByEmail.jsx:31` |
166
+ | Read-only sharing | If the existing sharing is read-only, only the `readOnly` option is offered | `ShareByEmail.jsx:46-57` |
167
+ | Contacts shown | Only contacts with a defined `email` or `cozy` URL | `helpers/recipients.js:72-73` |
168
+ | Group sharing | All members get the same permission; dynamic membership (add/remove) propagates | `state.js:414-451` |
169
+
170
+ ### Shared drives & federated folders
171
+
172
+ | Condition | Rule | Source |
173
+ |-----------|------|--------|
174
+ | Org shared drive | `isOrgSharedDrive` = `sharing.drive === true && sharing.org_drive === true` | `state.js:324` |
175
+ | `canLeave` | `false` for org shared drives | `state.js:335` |
176
+ | `canReshare` | `!org_drive && !read_only` for drives; `open_sharing && !read_only` for folders | `state.js:342` |
177
+ | Federated folder modal | Enabled by `drive.federated-shared-folder.enabled` flag and `document.driveId` | `ShareModal.jsx:39` |
178
+ | Email sharing in federated folder | Disabled for files inside a federated shared folder (has `driveId` but is not the root) | `FederatedFolderModal.jsx:122-126` |
179
+ | Link sharing in federated folder | Uses `getFederatedShareLink` which resolves the owner's instance URL | `FederatedFolderModal.jsx:108-110` |
180
+ | Member view inside shared drive | Subfolder members see link management only (no member cross/perm). Root members with `canReshare` see full member management; read-only root members see link management only. | `FederatedFolderModal.jsx:118-133`, `SharingDetailsModal.jsx:38-39` |
181
+
182
+ ### Share management
183
+
184
+ | Condition | Rule | Source |
185
+ |-----------|------|--------|
186
+ | Editable modal | `isEditable = !byDocId[doc] \|\| isOwner(doc) \|\| canReshare(doc)` | `ShareModal.jsx:35-36` |
187
+ | Non-editable view | `SharingDetailsModal` — read-only members (no cross/perm menu), with link management (gear + copy link) for shared drives | `ShareModal.jsx:53-62` |
188
+ | Updating member type | `updateSharingMemberType` calls `setReadOnly` / `setReadWrite` on the stack | `SharingProvider.jsx:361-420` |
189
+ | Revoke self | Available to all recipients | `SharingProvider.jsx:349-353` |
190
+ | Revoke group | Available to owner/editor; removes all group members at once | `SharingProvider.jsx:336-347` |
191
+
192
+ ### Native mobile sharing
193
+
194
+ | Condition | Rule | Source |
195
+ |-----------|------|--------|
196
+ | Availability | Only in flagship app (`isFlagshipApp()`) when `shareFiles` intent is available | `NativeFileSharingProvider.jsx:26-27` |
197
+ | Restriction | Files only (not directories) | `shareNative.js:42` |
198
+
199
+ ***
200
+
201
+ ## Feature flags
202
+
203
+ | Flag | Default | Effect |
204
+ |------|---------|--------|
205
+ | `cozy.hide-sharing-cozy-to-cozy` | `false` | When `true`, hides cozy-to-cozy sharing entirely; only link sharing is available |
206
+ | `drive.federated-shared-folder.enabled` | `false` | Enables federated folder modal (shared drives across instances) |
207
+ | `sharing.date-toggle.enabled` | `false` | Default state of the expiration date toggle when creating a new link |
208
+ | `sharing.show-recipient-groups` | `false` | When `true`, groups are shown as distinct recipients; when `false`, group members are spread |
209
+ | `sharing.recipients-limit` | `100` | Maximum number of recipients per document |
210
+ | `signup-url` / `signup.url` | `https://sign-up.twake.app` | URL used for the sharing banner "create account" call-to-action |
211
+
212
+ ## Development
213
+
214
+ ### Share and send mail in development
62
215
 
63
216
  Cozy apps let users [share documents from cozy to cozy](https://github.com/cozy/cozy-stack/blob/master/docs/sharing.md#cozy-to-cozy-sharing).
64
217
 
65
- Meet Alice and Bob.
66
- Alice wants to share a folder with Bob.
67
- Alice clicks on the share button and fills in the email input with Bob's email address.
68
- Bob receives an email with a *« Accept the sharing »* button.
69
- Bob clicks on that button and is redirected to Alice's cozy to enter his own cozy url to link both cozys.
70
- Bob sees Alice's shared folder in his own cozy.
218
+ Meet Alice and Bob. Alice wants to share a folder with Bob. Alice clicks on the share button and fills in the email input with Bob's email address. Bob receives an email with a *"Accept the sharing"* button. Bob clicks on that button and is redirected to Alice's cozy to enter his own cozy url to link both cozys. Bob sees Alice's shared folder in his own cozy.
71
219
 
72
- 🤔 But how could we do this scenario on development environment?
220
+ But how could we do this scenario on development environment?
73
221
 
74
- ## With the docker image
222
+ #### With the docker image
75
223
 
76
224
  If you develop with the [cozy-app-dev docker image](https://github.com/cozy/cozy-stack/blob/master/docs/client-app-dev.md#with-docker), [MailHog](https://github.com/mailhog/MailHog) is running inside it to catch emails.
77
225
 
78
226
  If cozy-stack has to send an email, MailHog catches it and exposes it on its web interface on <http://cozy.tools:8025/>.
79
227
 
80
- ## With the binary cozy-stack
228
+ #### With the binary cozy-stack
81
229
 
82
230
  If you develop with the [cozy-stack CLI](https://github.com/cozy/cozy-stack/blob/master/docs/cli/cozy-stack.md), you have to run [MailHog](https://github.com/mailhog/MailHog) on your computer and tell `cozy-stack serve` where to find the mail server with some [options](https://github.com/cozy/cozy-stack/blob/master/docs/cli/cozy-stack_serve.md#options):
83
231
 
@@ -89,6 +237,20 @@ If you develop with the [cozy-stack CLI](https://github.com/cozy/cozy-stack/blob
89
237
 
90
238
  Then simply run `mailhog` and open <http://cozy.tools:8025/>.
91
239
 
92
- ## Retrieve sent emails
240
+ #### Retrieve sent emails
93
241
 
94
242
  With MailHog, **every email** sent by cozy-stack is caught. That means the email address *does not have to be a real one*, ie. `bob@cozy`, `bob@cozy.tools` are perfectly fine. It *could be a real one*, but the email will not reach the real recipient's inbox, say `contact@cozycloud.cc`.
243
+
244
+ ***
245
+
246
+ ## Architecture
247
+
248
+ The share modal's component tree is documented in [`docs/share-modal-architecture.md`](docs/share-modal-architecture.md).
249
+
250
+ Key decisions:
251
+
252
+ - `ShareModal` checks `isEditable` → `EditableSharingModal` (editable) or `SharingDetailsModal` (read-only).
253
+ - `EditableSharingModal` renders `ShareModal` (the dumb component), which decides `ShareDialogCozyToCozy` vs `ShareDialogOnlyByLink` based on flags and context.
254
+ - `ShareDialogTwoStepsConfirmationContainer` wraps the cozy-to-cozy dialog when recipients need confirmation (untrusted contacts).
255
+ - Federated mode: when `drive.federated-shared-folder.enabled` is true, `FederatedFolderModal` replaces the standard `EditableSharingModal`.
256
+ - `WhoHasAccess` accepts an independent `canManageLink` prop to control link management (gear + perm dropdown) separately from `canManageMembers` which controls member management. This allows shared-drive members to see the link gear without the member cross/perm menu.
@@ -41,7 +41,7 @@ import WhoHasAccess from '../WhoHasAccess';
41
41
  var log = minilog('FederatedFolderModal');
42
42
 
43
43
  var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
44
- var _sharedDriveSharing$a, _sharedDriveSharing$a2;
44
+ var _sharedDriveSharing$a, _sharedDriveSharing$a2, _sharedDriveSharing$a3;
45
45
 
46
46
  var onClose = _ref.onClose,
47
47
  onRevokeSuccess = _ref.onRevokeSuccess,
@@ -137,7 +137,8 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
137
137
  return id === (existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument._id) || id === (existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.id);
138
138
  }));
139
139
  var isInsideSharedDrive = Boolean((existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.driveId) && !isSharedDriveRoot);
140
- var isCurrentUserOwner = documentId ? isOwner(documentId) : false;
140
+ var isCurrentUserOwner = existingDocument !== null && existingDocument !== void 0 && existingDocument.driveId ? Boolean(sharedDriveSharing === null || sharedDriveSharing === void 0 || (_sharedDriveSharing$a3 = sharedDriveSharing.attributes) === null || _sharedDriveSharing$a3 === void 0 ? void 0 : _sharedDriveSharing$a3.owner) : documentId ? isOwner(documentId) : false;
141
+ var isMemberReadOnly = isInsideSharedDrive && !isCurrentUserOwner;
141
142
  var hasParentRestriction = isInsideSharedDrive || hasSharedParentByPath;
142
143
  var hasChildRestriction = Boolean(documentPath && hasSharedChild(documentPath));
143
144
  var canShareByEmail = !hasParentRestriction && !hasChildRestriction;
@@ -391,6 +392,8 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
391
392
  isOwner: isCurrentUserOwner,
392
393
  canManageSharing: canManageSharing,
393
394
  isSharedDrive: true,
395
+ canManageMembers: !isMemberReadOnly,
396
+ canManageLink: true,
394
397
  recipients: isSending ? frozenRecipients : existingRecipients,
395
398
  document: isSending ? frozenDoc : existingDocument,
396
399
  documentType: "Files",
@@ -21,7 +21,8 @@ var GroupRecipientPermissions = function GroupRecipientPermissions(_ref) {
21
21
  var name = _ref.name,
22
22
  color = _ref.color,
23
23
  isOwner = _ref.isOwner,
24
- isReadOnly = _ref.isReadOnly,
24
+ _ref$canManageMembers = _ref.canManageMembers,
25
+ canManageMembers = _ref$canManageMembers === void 0 ? true : _ref$canManageMembers,
25
26
  sharingId = _ref.sharingId,
26
27
  groupIndex = _ref.groupIndex,
27
28
  _ref$read_only = _ref.read_only,
@@ -49,7 +50,7 @@ var GroupRecipientPermissions = function GroupRecipientPermissions(_ref) {
49
50
  revoking = _useState4[0],
50
51
  setRevoking = _useState4[1];
51
52
 
52
- var shouldShowMenu = !isReadOnly && !revoking && (isOwner || isUserInsideMembers);
53
+ var shouldShowMenu = canManageMembers && !revoking && (isOwner || isUserInsideMembers);
53
54
 
54
55
  var toggleMenu = function toggleMenu() {
55
56
  return setMenuDisplayed(!isMenuDisplayed);
@@ -14,7 +14,8 @@ var MemberRecipientPermissions = function MemberRecipientPermissions(_ref) {
14
14
  var isOwner = _ref.isOwner,
15
15
  _ref$canManageSharing = _ref.canManageSharing,
16
16
  canManageSharing = _ref$canManageSharing === void 0 ? isOwner : _ref$canManageSharing,
17
- isReadOnly = _ref.isReadOnly,
17
+ _ref$canManageMembers = _ref.canManageMembers,
18
+ canManageMembers = _ref$canManageMembers === void 0 ? true : _ref$canManageMembers,
18
19
  status = _ref.status,
19
20
  instance = _ref.instance,
20
21
  type = _ref.type,
@@ -36,7 +37,7 @@ var MemberRecipientPermissions = function MemberRecipientPermissions(_ref) {
36
37
 
37
38
  var instanceMatchesClient = instance !== undefined && instance === client.options.uri;
38
39
  var contactIsOwner = status === 'owner';
39
- var shouldShowMenu = !isReadOnly && !revoking && !contactIsOwner && (instanceMatchesClient && !isOwner || canManageSharing);
40
+ var shouldShowMenu = canManageMembers && !revoking && !contactIsOwner && (instanceMatchesClient && !isOwner || canManageSharing);
40
41
  var handleRevocation = useCallback( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
41
42
  return _regeneratorRuntime.wrap(function _callee$(_context) {
42
43
  while (1) switch (_context.prev = _context.next) {
@@ -10,8 +10,8 @@ var RecipientList = function RecipientList(_ref) {
10
10
  recipientsToBeConfirmed = _ref.recipientsToBeConfirmed,
11
11
  isOwner = _ref.isOwner,
12
12
  canManageSharing = _ref.canManageSharing,
13
+ canManageMembers = _ref.canManageMembers,
13
14
  isSharedDrive = _ref.isSharedDrive,
14
- isReadOnly = _ref.isReadOnly,
15
15
  document = _ref.document,
16
16
  documentType = _ref.documentType,
17
17
  onRevoke = _ref.onRevoke,
@@ -28,7 +28,7 @@ var RecipientList = function RecipientList(_ref) {
28
28
  if (isGroupRecipient) {
29
29
  return /*#__PURE__*/React.createElement(GroupRecipient, _extends({}, recipient, {
30
30
  isOwner: isOwner,
31
- isReadOnly: isReadOnly,
31
+ canManageMembers: canManageMembers,
32
32
  key: recipient.index,
33
33
  document: document,
34
34
  documentType: documentType,
@@ -42,8 +42,8 @@ var RecipientList = function RecipientList(_ref) {
42
42
  key: recipient.index,
43
43
  isOwner: isOwner,
44
44
  canManageSharing: canManageSharing,
45
+ canManageMembers: canManageMembers,
45
46
  isSharedDrive: isSharedDrive,
46
- isReadOnly: isReadOnly,
47
47
  document: document,
48
48
  documentType: documentType,
49
49
  onRevoke: onRevoke,
@@ -30,8 +30,7 @@ export var ShareModal = withLocales(function (props) {
30
30
  canReshare = _useSharingContext.canReshare,
31
31
  documentType = _useSharingContext.documentType,
32
32
  getRecipients = _useSharingContext.getRecipients,
33
- revokeSelf = _useSharingContext.revokeSelf,
34
- allLoaded = _useSharingContext.allLoaded;
33
+ revokeSelf = _useSharingContext.revokeSelf;
35
34
 
36
35
  var handleRevokeSelf = /*#__PURE__*/function () {
37
36
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(document) {
@@ -61,7 +60,7 @@ export var ShareModal = withLocales(function (props) {
61
60
  if (isEditable) {
62
61
  var isFederatedMode = flag('drive.federated-shared-folder.enabled');
63
62
 
64
- if (isFederatedMode && allLoaded) {
63
+ if (isFederatedMode && document.driveId) {
65
64
  return /*#__PURE__*/React.createElement(FederatedFolderModal, _extends({
66
65
  document: document,
67
66
  onRevokeSuccess: onRevokeSuccess
@@ -1,7 +1,8 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
- import { Dialog } from 'cozy-ui/transpiled/react/CozyDialogs';
3
+ import { FixedDialog } from 'cozy-ui/transpiled/react/CozyDialogs';
4
4
  import { useI18n } from 'twake-i18n';
5
+ import { useSharingContext } from '../../hooks/useSharingContext';
5
6
  var styles = {
6
7
  "share-modal-content": "share__share-modal-content___1iqEq",
7
8
  "coz-form": "share__coz-form___1ICST",
@@ -20,6 +21,7 @@ var styles = {
20
21
  "share-byemail-onlybylink": "share__share-byemail-onlybylink___29W-t",
21
22
  "aligned-dropdown-button": "share__aligned-dropdown-button___3crgG"
22
23
  };
24
+ import ShareByLink from '../ShareByLink';
23
25
  import WhoHasAccess from '../WhoHasAccess';
24
26
  export var SharingDetailsModal = function SharingDetailsModal(_ref) {
25
27
  var recipients = _ref.recipients,
@@ -33,7 +35,13 @@ export var SharingDetailsModal = function SharingDetailsModal(_ref) {
33
35
  var _useI18n = useI18n(),
34
36
  t = _useI18n.t;
35
37
 
36
- return /*#__PURE__*/React.createElement(Dialog, {
38
+ var _useSharingContext = useSharingContext(),
39
+ getSharingLink = _useSharingContext.getSharingLink,
40
+ getFederatedShareLink = _useSharingContext.getFederatedShareLink;
41
+
42
+ var isSharedDrive = Boolean(document === null || document === void 0 ? void 0 : document.driveId);
43
+ var displayedLink = isSharedDrive ? getFederatedShareLink(document) : getSharingLink(document === null || document === void 0 ? void 0 : document._id);
44
+ return /*#__PURE__*/React.createElement(FixedDialog, {
37
45
  disableGutters: true,
38
46
  open: true,
39
47
  onClose: onClose,
@@ -42,14 +50,23 @@ export var SharingDetailsModal = function SharingDetailsModal(_ref) {
42
50
  content: /*#__PURE__*/React.createElement("div", {
43
51
  className: styles['share-modal-content']
44
52
  }, /*#__PURE__*/React.createElement(WhoHasAccess, {
45
- isReadOnly: true,
53
+ canManageMembers: false,
54
+ canManageLink: isSharedDrive,
55
+ isSharedDrive: isSharedDrive,
46
56
  recipients: recipients,
47
57
  document: document,
48
58
  documentType: documentType,
49
59
  onRevoke: onRevoke,
50
60
  onRevokeSelf: onRevokeSelf,
51
- link: "ok"
52
- }))
61
+ link: displayedLink
62
+ })),
63
+ actions: isSharedDrive && /*#__PURE__*/React.createElement(ShareByLink, {
64
+ link: displayedLink,
65
+ document: document,
66
+ documentType: "Files",
67
+ showGenerateLinkButton: true,
68
+ autoOpenShareRestriction: false
69
+ })
53
70
  });
54
71
  };
55
72
  SharingDetailsModal.propTypes = {
@@ -37,6 +37,10 @@ var WhoHasAccess = function WhoHasAccess(_ref2) {
37
37
  isSharedDrive = _ref2$isSharedDrive === void 0 ? false : _ref2$isSharedDrive,
38
38
  _ref2$isReadOnly = _ref2.isReadOnly,
39
39
  isReadOnly = _ref2$isReadOnly === void 0 ? false : _ref2$isReadOnly,
40
+ _ref2$canManageMember = _ref2.canManageMembers,
41
+ canManageMembers = _ref2$canManageMember === void 0 ? !isReadOnly : _ref2$canManageMember,
42
+ _ref2$canManageLink = _ref2.canManageLink,
43
+ canManageLink = _ref2$canManageLink === void 0 ? canManageMembers : _ref2$canManageLink,
40
44
  _ref2$showOwner = _ref2.showOwner,
41
45
  showOwner = _ref2$showOwner === void 0 ? true : _ref2$showOwner,
42
46
  recipients = _ref2.recipients,
@@ -63,7 +67,7 @@ var WhoHasAccess = function WhoHasAccess(_ref2) {
63
67
  }, /*#__PURE__*/React.createElement(RecipientWaitingForConfirmationAlert, {
64
68
  recipientsToBeConfirmed: recipientsToBeConfirmed
65
69
  }), /*#__PURE__*/React.createElement(List, null, link && /*#__PURE__*/React.createElement(LinkRecipient, {
66
- isReadOnly: isReadOnly,
70
+ isReadOnly: !canManageLink,
67
71
  document: document,
68
72
  documentType: documentType,
69
73
  onRevoke: onRevoke,
@@ -78,8 +82,8 @@ var WhoHasAccess = function WhoHasAccess(_ref2) {
78
82
  recipientsToBeConfirmed: recipientsToBeConfirmed,
79
83
  isOwner: isOwner,
80
84
  canManageSharing: canManageSharing,
85
+ canManageMembers: canManageMembers,
81
86
  isSharedDrive: isSharedDrive,
82
- isReadOnly: isReadOnly,
83
87
  document: document,
84
88
  documentType: documentType,
85
89
  onRevoke: onRevoke,
@@ -92,6 +96,8 @@ var WhoHasAccess = function WhoHasAccess(_ref2) {
92
96
  WhoHasAccess.propTypes = {
93
97
  isOwner: PropTypes.bool,
94
98
  canManageSharing: PropTypes.bool,
99
+ canManageMembers: PropTypes.bool,
100
+ canManageLink: PropTypes.bool,
95
101
  showOwner: PropTypes.bool,
96
102
  recipients: PropTypes.array.isRequired,
97
103
  recipientsToBeConfirmed: PropTypes.arrayOf(PropTypes.shape({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "37.2.10",
3
+ "version": "37.2.12",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -59,7 +59,7 @@
59
59
  "cozy-intent": "^2.31.1",
60
60
  "cozy-minilog": "^3.10.1",
61
61
  "cozy-ui": "^140.5.0",
62
- "cozy-ui-plus": "^12.3.4",
62
+ "cozy-ui-plus": "^12.3.6",
63
63
  "jest": "30.3.0",
64
64
  "jest-environment-jsdom": "30.3.0",
65
65
  "react": "16.12.0",
@@ -85,5 +85,5 @@
85
85
  "sideEffects": [
86
86
  "*.css"
87
87
  ],
88
- "gitHead": "05e50a31f37a323eba5d55d5c1e5c3b90090b636"
88
+ "gitHead": "8dbc03f6ef437764cfec18a0b2a6e1b44102f7c3"
89
89
  }
@@ -118,7 +118,12 @@ const FederatedFolderModalContent = ({
118
118
  const isInsideSharedDrive = Boolean(
119
119
  existingDocument?.driveId && !isSharedDriveRoot
120
120
  )
121
- const isCurrentUserOwner = documentId ? isOwner(documentId) : false
121
+ const isCurrentUserOwner = existingDocument?.driveId
122
+ ? Boolean(sharedDriveSharing?.attributes?.owner)
123
+ : documentId
124
+ ? isOwner(documentId)
125
+ : false
126
+ const isMemberReadOnly = isInsideSharedDrive && !isCurrentUserOwner
122
127
  const hasParentRestriction = isInsideSharedDrive || hasSharedParentByPath
123
128
  const hasChildRestriction = Boolean(
124
129
  documentPath && hasSharedChild(documentPath)
@@ -322,6 +327,8 @@ const FederatedFolderModalContent = ({
322
327
  isOwner={isCurrentUserOwner}
323
328
  canManageSharing={canManageSharing}
324
329
  isSharedDrive
330
+ canManageMembers={!isMemberReadOnly}
331
+ canManageLink={true}
325
332
  recipients={isSending ? frozenRecipients : existingRecipients}
326
333
  document={isSending ? frozenDoc : existingDocument}
327
334
  documentType="Files"
@@ -20,7 +20,7 @@ const GroupRecipientPermissions = ({
20
20
  name,
21
21
  color,
22
22
  isOwner,
23
- isReadOnly,
23
+ canManageMembers = true,
24
24
  sharingId,
25
25
  groupIndex,
26
26
  read_only = false,
@@ -36,7 +36,7 @@ const GroupRecipientPermissions = ({
36
36
  const [revoking, setRevoking] = useState(false)
37
37
 
38
38
  const shouldShowMenu =
39
- !isReadOnly && !revoking && (isOwner || isUserInsideMembers)
39
+ canManageMembers && !revoking && (isOwner || isUserInsideMembers)
40
40
 
41
41
  const toggleMenu = () => setMenuDisplayed(!isMenuDisplayed)
42
42
  const hideMenu = () => setMenuDisplayed(false)
@@ -12,7 +12,7 @@ import { PermissionTypeMenu } from './PermissionTypeMenu'
12
12
  const MemberRecipientPermissions = ({
13
13
  isOwner,
14
14
  canManageSharing = isOwner,
15
- isReadOnly,
15
+ canManageMembers = true,
16
16
  status,
17
17
  instance,
18
18
  type,
@@ -31,7 +31,7 @@ const MemberRecipientPermissions = ({
31
31
  instance !== undefined && instance === client.options.uri
32
32
  const contactIsOwner = status === 'owner'
33
33
  const shouldShowMenu =
34
- !isReadOnly &&
34
+ canManageMembers &&
35
35
  !revoking &&
36
36
  !contactIsOwner &&
37
37
  ((instanceMatchesClient && !isOwner) || canManageSharing)
@@ -10,8 +10,8 @@ const RecipientList = ({
10
10
  recipientsToBeConfirmed,
11
11
  isOwner,
12
12
  canManageSharing,
13
+ canManageMembers,
13
14
  isSharedDrive,
14
- isReadOnly,
15
15
  document,
16
16
  documentType,
17
17
  onRevoke,
@@ -35,7 +35,7 @@ const RecipientList = ({
35
35
  <GroupRecipient
36
36
  {...recipient}
37
37
  isOwner={isOwner}
38
- isReadOnly={isReadOnly}
38
+ canManageMembers={canManageMembers}
39
39
  key={recipient.index}
40
40
  document={document}
41
41
  documentType={documentType}
@@ -52,8 +52,8 @@ const RecipientList = ({
52
52
  key={recipient.index}
53
53
  isOwner={isOwner}
54
54
  canManageSharing={canManageSharing}
55
+ canManageMembers={canManageMembers}
55
56
  isSharedDrive={isSharedDrive}
56
- isReadOnly={isReadOnly}
57
57
  document={document}
58
58
  documentType={documentType}
59
59
  onRevoke={onRevoke}
@@ -23,8 +23,7 @@ export const ShareModal = withLocales(props => {
23
23
  canReshare,
24
24
  documentType,
25
25
  getRecipients,
26
- revokeSelf,
27
- allLoaded
26
+ revokeSelf
28
27
  } = useSharingContext()
29
28
 
30
29
  const handleRevokeSelf = async document => {
@@ -37,7 +36,7 @@ export const ShareModal = withLocales(props => {
37
36
 
38
37
  if (isEditable) {
39
38
  const isFederatedMode = flag('drive.federated-shared-folder.enabled')
40
- if (isFederatedMode && allLoaded) {
39
+ if (isFederatedMode && document.driveId) {
41
40
  return (
42
41
  <FederatedFolderModal
43
42
  document={document}
@@ -1,10 +1,12 @@
1
1
  import PropTypes from 'prop-types'
2
2
  import React from 'react'
3
3
 
4
- import { Dialog } from 'cozy-ui/transpiled/react/CozyDialogs'
4
+ import { FixedDialog } from 'cozy-ui/transpiled/react/CozyDialogs'
5
5
  import { useI18n } from 'twake-i18n'
6
6
 
7
+ import { useSharingContext } from '../../hooks/useSharingContext'
7
8
  import styles from '../../styles/share.styl'
9
+ import ShareByLink from '../ShareByLink'
8
10
  import WhoHasAccess from '../WhoHasAccess'
9
11
 
10
12
  export const SharingDetailsModal = ({
@@ -16,9 +18,15 @@ export const SharingDetailsModal = ({
16
18
  onClose
17
19
  }) => {
18
20
  const { t } = useI18n()
21
+ const { getSharingLink, getFederatedShareLink } = useSharingContext()
22
+
23
+ const isSharedDrive = Boolean(document?.driveId)
24
+ const displayedLink = isSharedDrive
25
+ ? getFederatedShareLink(document)
26
+ : getSharingLink(document?._id)
19
27
 
20
28
  return (
21
- <Dialog
29
+ <FixedDialog
22
30
  disableGutters
23
31
  open={true}
24
32
  onClose={onClose}
@@ -27,16 +35,29 @@ export const SharingDetailsModal = ({
27
35
  content={
28
36
  <div className={styles['share-modal-content']}>
29
37
  <WhoHasAccess
30
- isReadOnly
38
+ canManageMembers={false}
39
+ canManageLink={isSharedDrive}
40
+ isSharedDrive={isSharedDrive}
31
41
  recipients={recipients}
32
42
  document={document}
33
43
  documentType={documentType}
34
44
  onRevoke={onRevoke}
35
45
  onRevokeSelf={onRevokeSelf}
36
- link="ok"
46
+ link={displayedLink}
37
47
  />
38
48
  </div>
39
49
  }
50
+ actions={
51
+ isSharedDrive && (
52
+ <ShareByLink
53
+ link={displayedLink}
54
+ document={document}
55
+ documentType="Files"
56
+ showGenerateLinkButton={true}
57
+ autoOpenShareRestriction={false}
58
+ />
59
+ )
60
+ }
40
61
  />
41
62
  )
42
63
  }
@@ -32,6 +32,8 @@ const WhoHasAccess = ({
32
32
  canManageSharing = isOwner,
33
33
  isSharedDrive = false,
34
34
  isReadOnly = false,
35
+ canManageMembers = !isReadOnly,
36
+ canManageLink = canManageMembers,
35
37
  showOwner = true,
36
38
  recipients,
37
39
  recipientsToBeConfirmed = [],
@@ -58,7 +60,7 @@ const WhoHasAccess = ({
58
60
  <List>
59
61
  {link && (
60
62
  <LinkRecipient
61
- isReadOnly={isReadOnly}
63
+ isReadOnly={!canManageLink}
62
64
  document={document}
63
65
  documentType={documentType}
64
66
  onRevoke={onRevoke}
@@ -80,8 +82,8 @@ const WhoHasAccess = ({
80
82
  recipientsToBeConfirmed={recipientsToBeConfirmed}
81
83
  isOwner={isOwner}
82
84
  canManageSharing={canManageSharing}
85
+ canManageMembers={canManageMembers}
83
86
  isSharedDrive={isSharedDrive}
84
- isReadOnly={isReadOnly}
85
87
  document={document}
86
88
  documentType={documentType}
87
89
  onRevoke={onRevoke}
@@ -97,6 +99,8 @@ const WhoHasAccess = ({
97
99
  WhoHasAccess.propTypes = {
98
100
  isOwner: PropTypes.bool,
99
101
  canManageSharing: PropTypes.bool,
102
+ canManageMembers: PropTypes.bool,
103
+ canManageLink: PropTypes.bool,
100
104
  showOwner: PropTypes.bool,
101
105
  recipients: PropTypes.array.isRequired,
102
106
  recipientsToBeConfirmed: PropTypes.arrayOf(