cozy-sharing 36.2.1 → 36.4.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.
- package/CHANGELOG.md +15 -0
- package/dist/DoctypeContact.js +11 -7
- package/dist/components/Avatar/MemberAvatar.js +24 -12
- package/dist/components/Avatar/MemberAvatar.spec.js +47 -6
- package/dist/components/ContactSuggestion.js +20 -22
- package/dist/components/FederatedFolder/FederatedFolderModal.spec.js +4 -1
- package/dist/components/Recipient/MemberRecipient.js +6 -3
- package/dist/components/Recipient/MemberRecipientStatus.js +4 -1
- package/dist/components/Recipient/MemberRecipientStatus.spec.js +22 -8
- package/dist/components/Recipient/OwnerRecipient.js +8 -3
- package/dist/components/ShareDialogCozyToCozy.spec.js +4 -1
- package/dist/components/ShareDialogOnlyByLink.spec.js +4 -1
- package/dist/components/WhoHasAccess.js +8 -1
- package/dist/index.js +1 -0
- package/package.json +2 -2
- package/src/DoctypeContact.js +23 -6
- package/src/components/Avatar/MemberAvatar.jsx +27 -16
- package/src/components/Avatar/MemberAvatar.spec.jsx +36 -0
- package/src/components/ContactSuggestion.jsx +23 -26
- package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +2 -1
- package/src/components/Recipient/MemberRecipient.jsx +4 -1
- package/src/components/Recipient/MemberRecipientStatus.jsx +9 -1
- package/src/components/Recipient/MemberRecipientStatus.spec.jsx +13 -0
- package/src/components/Recipient/OwnerRecipient.jsx +9 -3
- package/src/components/ShareDialogCozyToCozy.spec.jsx +2 -1
- package/src/components/ShareDialogOnlyByLink.spec.jsx +2 -1
- package/src/components/WhoHasAccess.jsx +9 -1
- package/src/index.jsx +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
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
|
+
# [36.4.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@36.3.0...cozy-sharing@36.4.0) (2026-07-07)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- **cozy-sharing:** Expose MemberAvatar in main API ([2af294e](https://github.com/cozy/cozy-libs/commit/2af294e565eb3c727a9d0505e6e5c204f3d8e05c))
|
|
11
|
+
- **cozy-sharing:** MemberAvatar can get avatar image with cozy.url path ([2f694bd](https://github.com/cozy/cozy-libs/commit/2f694bde7c8bd2e30ae9b5c6a51d31e229fec2ef))
|
|
12
|
+
- **cozy-sharing:** Now getInitials return 2 letters if possible ([175ad40](https://github.com/cozy/cozy-libs/commit/175ad40116f6face3bdfdff1642a8d26cdc01596))
|
|
13
|
+
- **cozy-sharing:** Use MemberAvatar in ContactSuggestion ([b03da9f](https://github.com/cozy/cozy-libs/commit/b03da9f1d3f3e23b5b90154c313fd5adbab7aa8a))
|
|
14
|
+
|
|
15
|
+
# [36.3.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@36.2.1...cozy-sharing@36.3.0) (2026-07-01)
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- **cozy-sharing:** Hide email for org instance owner ([5d15a29](https://github.com/cozy/cozy-libs/commit/5d15a297a485e98ff6c0e3afe7ab466d9bf4fe99))
|
|
20
|
+
|
|
6
21
|
## [36.2.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@36.2.0...cozy-sharing@36.2.1) (2026-06-30)
|
|
7
22
|
|
|
8
23
|
**Note:** Version bump only for package cozy-sharing
|
package/dist/DoctypeContact.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getInitials as clientGetInitials, getDisplayName as clientGetDisplayName } from 'cozy-client/dist/models/contact';
|
|
2
2
|
import { Contact as DoctypeContact } from 'cozy-doctypes';
|
|
3
|
-
var ContactModel = models.contact;
|
|
4
3
|
|
|
5
4
|
var isContact = function isContact(candidate) {
|
|
6
5
|
return candidate._type === 'io.cozy.contacts';
|
|
@@ -10,21 +9,26 @@ export var getInitials = function getInitials(contactOrRecipient) {
|
|
|
10
9
|
var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
|
11
10
|
|
|
12
11
|
if (isContact(contactOrRecipient)) {
|
|
13
|
-
return
|
|
12
|
+
return clientGetInitials(contactOrRecipient);
|
|
14
13
|
} else {
|
|
15
14
|
// @todo Extract to RecipientModel ?
|
|
16
|
-
var s = contactOrRecipient.public_name || contactOrRecipient.name || contactOrRecipient.email;
|
|
17
|
-
|
|
15
|
+
var s = contactOrRecipient.public_name || contactOrRecipient.displayName || contactOrRecipient.name || contactOrRecipient.email;
|
|
16
|
+
if (!s) return defaultValue;
|
|
17
|
+
var parts = s.split(' ').filter(Boolean);
|
|
18
|
+
if (parts.length === 0) return defaultValue;
|
|
19
|
+
var firstLetter = parts[0][0];
|
|
20
|
+
var lastLetter = parts.length > 1 ? parts.at(-1)[0] : '';
|
|
21
|
+
return (firstLetter + lastLetter).toUpperCase();
|
|
18
22
|
}
|
|
19
23
|
};
|
|
20
24
|
export var getDisplayName = function getDisplayName(contact) {
|
|
21
25
|
var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
|
22
26
|
|
|
23
27
|
if (isContact(contact)) {
|
|
24
|
-
return
|
|
28
|
+
return clientGetDisplayName(contact);
|
|
25
29
|
} else {
|
|
26
30
|
// @todo Extract to RecipientModel ?
|
|
27
|
-
return contact.public_name || contact.name || contact.email || defaultValue;
|
|
31
|
+
return contact.public_name || contact.displayName || contact.name || contact.email || defaultValue;
|
|
28
32
|
}
|
|
29
33
|
};
|
|
30
34
|
export default DoctypeContact;
|
|
@@ -9,9 +9,32 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
|
|
|
9
9
|
|
|
10
10
|
import React, { useState } from 'react';
|
|
11
11
|
import { useClient } from 'cozy-client';
|
|
12
|
+
import { getPrimaryCozy } from 'cozy-client/dist/models/contact';
|
|
12
13
|
import Avatar from 'cozy-ui/transpiled/react/Avatar';
|
|
13
14
|
import logger from '../../logger';
|
|
14
15
|
import { getInitials } from '../../models';
|
|
16
|
+
/**
|
|
17
|
+
* makeAvatarUrl derives the avatar image URL for the recipient.
|
|
18
|
+
* - If the recipient has an avatarPath (sharing context), it is
|
|
19
|
+
* used directly with a base URI prefix (unless it is already
|
|
20
|
+
* a full URL for a remote Cozy).
|
|
21
|
+
* - If the recipient has no avatarPath but has a cozy URL
|
|
22
|
+
* (contact context), it points to that Cozy's public avatar.
|
|
23
|
+
* - The status is used as a cache-buster in the URL so the
|
|
24
|
+
* browser refreshes the image when the sharing status changes.
|
|
25
|
+
* For non-sharing recipients, 'contact' is the stable default.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
var makeAvatarUrl = function makeAvatarUrl(recipient, instanceUri) {
|
|
29
|
+
var avatarPath = recipient.avatarPath;
|
|
30
|
+
var cozyUrl = !avatarPath ? getPrimaryCozy(recipient) : null;
|
|
31
|
+
if (!avatarPath && !cozyUrl) return null;
|
|
32
|
+
var finalPath = avatarPath || "".concat(cozyUrl.replace(/\/$/, ''), "/public/avatar?fallback=initials");
|
|
33
|
+
var status = recipient.status || 'contact';
|
|
34
|
+
var isFullPath = finalPath.startsWith('http');
|
|
35
|
+
var base = isFullPath ? '' : instanceUri;
|
|
36
|
+
return "".concat(base).concat(finalPath).concat(finalPath.includes('?') ? '&' : '?', "v=").concat(status);
|
|
37
|
+
};
|
|
15
38
|
|
|
16
39
|
var MemberAvatar = function MemberAvatar(_ref) {
|
|
17
40
|
var recipient = _ref.recipient,
|
|
@@ -37,19 +60,8 @@ var MemberAvatar = function MemberAvatar(_ref) {
|
|
|
37
60
|
}, rest));
|
|
38
61
|
return null;
|
|
39
62
|
}
|
|
40
|
-
/**
|
|
41
|
-
* avatarPath is always the same for a recipient, but image
|
|
42
|
-
* can be different since the stack generate it on the fly
|
|
43
|
-
* because they can be customized by the user.
|
|
44
|
-
* It can be "gray" during the first load depending of the
|
|
45
|
-
* status' sharing, but can become active (from the realtime)
|
|
46
|
-
* so we need a way to "refresh" the image. Passing the
|
|
47
|
-
* status in the url force the refresh of the image when the
|
|
48
|
-
* status changes
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
63
|
|
|
52
|
-
var image = recipient
|
|
64
|
+
var image = makeAvatarUrl(recipient, client.options.uri); // The avatar can become unreachable (eg. the related sharing is revoked
|
|
53
65
|
// while its members are still rendered), in which case the image request
|
|
54
66
|
// fails. Without a fallback the browser shows a broken-image icon, so we
|
|
55
67
|
// render the initials instead.
|
|
@@ -48,25 +48,66 @@ describe('MemberAvatar', function () {
|
|
|
48
48
|
|
|
49
49
|
expect(container.querySelector('img').getAttribute('src')).toBe('http://cozy.example.com/public/avatar?fallback=initials&v=owner');
|
|
50
50
|
});
|
|
51
|
-
it('renders
|
|
51
|
+
it('renders a full URL avatarPath without prefixing the local instance URI', function () {
|
|
52
52
|
var _setup4 = setup({
|
|
53
|
+
public_name: 'Jon Snow',
|
|
54
|
+
status: 'contact',
|
|
55
|
+
avatarPath: 'https://jonsnow.mycozy.cloud/public/avatar?fallback=initials'
|
|
56
|
+
}),
|
|
57
|
+
container = _setup4.container;
|
|
58
|
+
|
|
59
|
+
expect(container.querySelector('img').getAttribute('src')).toBe('https://jonsnow.mycozy.cloud/public/avatar?fallback=initials&v=contact');
|
|
60
|
+
});
|
|
61
|
+
it('derives avatarPath from cozy URL when avatarPath is not set', function () {
|
|
62
|
+
var _setup5 = setup({
|
|
63
|
+
_type: 'io.cozy.contacts',
|
|
64
|
+
name: {
|
|
65
|
+
givenName: 'Jon',
|
|
66
|
+
familyName: 'Snow'
|
|
67
|
+
},
|
|
68
|
+
cozy: [{
|
|
69
|
+
url: 'https://jonsnow.mycozy.cloud'
|
|
70
|
+
}]
|
|
71
|
+
}),
|
|
72
|
+
container = _setup5.container;
|
|
73
|
+
|
|
74
|
+
var img = container.querySelector('img');
|
|
75
|
+
expect(img).toBeTruthy();
|
|
76
|
+
expect(img.getAttribute('src')).toBe('https://jonsnow.mycozy.cloud/public/avatar?fallback=initials&v=contact');
|
|
77
|
+
});
|
|
78
|
+
it('renders initials when there is no avatarPath and no cozy URL', function () {
|
|
79
|
+
var _setup6 = setup({
|
|
80
|
+
_type: 'io.cozy.contacts',
|
|
81
|
+
name: {
|
|
82
|
+
givenName: 'Jon',
|
|
83
|
+
familyName: 'Snow'
|
|
84
|
+
}
|
|
85
|
+
}),
|
|
86
|
+
container = _setup6.container,
|
|
87
|
+
getByText = _setup6.getByText;
|
|
88
|
+
|
|
89
|
+
expect(container.querySelector('img')).toBeNull();
|
|
90
|
+
expect(getByText('JS')).toBeTruthy();
|
|
91
|
+
});
|
|
92
|
+
it('renders the initials when there is no avatarPath', function () {
|
|
93
|
+
var _setup7 = setup({
|
|
53
94
|
public_name: 'Bob',
|
|
54
95
|
status: 'owner'
|
|
55
96
|
}),
|
|
56
|
-
container =
|
|
57
|
-
getByText =
|
|
97
|
+
container = _setup7.container,
|
|
98
|
+
getByText = _setup7.getByText;
|
|
58
99
|
|
|
59
100
|
expect(container.querySelector('img')).toBeNull();
|
|
60
101
|
expect(getByText('B')).toBeTruthy();
|
|
61
102
|
});
|
|
62
103
|
it('falls back to the initials when the avatar image fails to load', function () {
|
|
63
|
-
var
|
|
104
|
+
var _setup8 = setup({
|
|
64
105
|
public_name: 'Bob',
|
|
65
106
|
status: 'owner',
|
|
66
107
|
avatarPath: '/sharings/123/recipients/0/avatar'
|
|
67
108
|
}),
|
|
68
|
-
container =
|
|
69
|
-
getByText =
|
|
109
|
+
container = _setup8.container,
|
|
110
|
+
getByText = _setup8.getByText;
|
|
70
111
|
|
|
71
112
|
var img = container.querySelector('img');
|
|
72
113
|
expect(img).toBeTruthy();
|
|
@@ -1,44 +1,42 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import Avatar from 'cozy-ui/transpiled/react/Avatar';
|
|
3
|
+
import { getPrimaryCozy } from 'cozy-client/dist/models/contact';
|
|
5
4
|
import ListItem from 'cozy-ui/transpiled/react/ListItem';
|
|
6
5
|
import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon';
|
|
7
6
|
import ListItemText from 'cozy-ui/transpiled/react/ListItemText';
|
|
8
7
|
import { useI18n } from 'twake-i18n';
|
|
9
|
-
import { Contact, Group, getDisplayName
|
|
8
|
+
import { Contact, Group, getDisplayName } from '../models';
|
|
10
9
|
import { GroupAvatar } from './Avatar/GroupAvatar';
|
|
11
|
-
|
|
10
|
+
import { MemberAvatar } from './Avatar/MemberAvatar';
|
|
12
11
|
export var ContactSuggestion = function ContactSuggestion(_ref) {
|
|
13
12
|
var contactOrGroup = _ref.contactOrGroup;
|
|
14
13
|
|
|
15
14
|
var _useI18n = useI18n(),
|
|
16
15
|
t = _useI18n.t;
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
avatarText = getInitials(contactOrGroup);
|
|
30
|
-
details = ContactModel.getPrimaryCozy(contactOrGroup);
|
|
17
|
+
if (contactOrGroup._type === Group.doctype) {
|
|
18
|
+
return /*#__PURE__*/React.createElement(ListItem, {
|
|
19
|
+
button: true
|
|
20
|
+
}, /*#__PURE__*/React.createElement(ListItemIcon, null, /*#__PURE__*/React.createElement(GroupAvatar, {
|
|
21
|
+
size: "m"
|
|
22
|
+
})), /*#__PURE__*/React.createElement(ListItemText, {
|
|
23
|
+
primary: contactOrGroup.name,
|
|
24
|
+
secondary: t('Share.members.count', {
|
|
25
|
+
smart_count: contactOrGroup.members.length.toString()
|
|
26
|
+
})
|
|
27
|
+
}));
|
|
31
28
|
}
|
|
32
29
|
|
|
30
|
+
var name = getDisplayName(contactOrGroup);
|
|
31
|
+
var cozyUrl = getPrimaryCozy(contactOrGroup);
|
|
33
32
|
return /*#__PURE__*/React.createElement(ListItem, {
|
|
34
33
|
button: true
|
|
35
|
-
}, /*#__PURE__*/React.createElement(ListItemIcon, null,
|
|
36
|
-
|
|
37
|
-
}) : /*#__PURE__*/React.createElement(Avatar, {
|
|
34
|
+
}, /*#__PURE__*/React.createElement(ListItemIcon, null, /*#__PURE__*/React.createElement(MemberAvatar, {
|
|
35
|
+
recipient: contactOrGroup,
|
|
38
36
|
size: "m"
|
|
39
|
-
}
|
|
37
|
+
})), /*#__PURE__*/React.createElement(ListItemText, {
|
|
40
38
|
primary: name,
|
|
41
|
-
secondary:
|
|
39
|
+
secondary: cozyUrl || '-'
|
|
42
40
|
}));
|
|
43
41
|
};
|
|
44
42
|
var newUnknownContactProptypes = PropTypes.shape({
|
|
@@ -47,7 +47,10 @@ jest.mock('../../hooks/useSharingContext', function () {
|
|
|
47
47
|
getSharedParentPath: jest.fn().mockReturnValue(null),
|
|
48
48
|
getSharingById: mockGetSharingById,
|
|
49
49
|
hasSharedChild: mockHasSharedChild,
|
|
50
|
-
hasSharedParent: mockHasSharedParent
|
|
50
|
+
hasSharedParent: mockHasSharedParent,
|
|
51
|
+
isOrgSharedDrive: jest.fn(function () {
|
|
52
|
+
return false;
|
|
53
|
+
})
|
|
51
54
|
};
|
|
52
55
|
}
|
|
53
56
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
|
|
2
|
-
var _excluded = ["instance", "isOwner", "status", "recipientConfirmationData", "verifyRecipient", "fadeIn"];
|
|
2
|
+
var _excluded = ["instance", "isOwner", "status", "recipientConfirmationData", "verifyRecipient", "fadeIn", "isOrgSharedDrive"];
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { useClient } from 'cozy-client';
|
|
@@ -32,6 +32,7 @@ var MemberRecipient = function MemberRecipient(props) {
|
|
|
32
32
|
recipientConfirmationData = props.recipientConfirmationData,
|
|
33
33
|
verifyRecipient = props.verifyRecipient,
|
|
34
34
|
fadeIn = props.fadeIn,
|
|
35
|
+
isOrgSharedDrive = props.isOrgSharedDrive,
|
|
35
36
|
rest = _objectWithoutProperties(props, _excluded);
|
|
36
37
|
|
|
37
38
|
var isMe = isOwner && status === 'owner' || instance === client.options.uri;
|
|
@@ -59,7 +60,8 @@ var MemberRecipient = function MemberRecipient(props) {
|
|
|
59
60
|
isMe: isMe,
|
|
60
61
|
instance: instance,
|
|
61
62
|
email: rest.email,
|
|
62
|
-
name: rest.name || rest.public_name
|
|
63
|
+
name: rest.name || rest.public_name,
|
|
64
|
+
isOrgSharedDrive: isOrgSharedDrive
|
|
63
65
|
})
|
|
64
66
|
}), RightPart));
|
|
65
67
|
};
|
|
@@ -69,6 +71,7 @@ MemberRecipient.propTypes = {
|
|
|
69
71
|
isOwner: PropTypes.bool,
|
|
70
72
|
status: PropTypes.string,
|
|
71
73
|
recipientConfirmationData: PropTypes.object,
|
|
72
|
-
verifyRecipient: PropTypes.func
|
|
74
|
+
verifyRecipient: PropTypes.func,
|
|
75
|
+
isOrgSharedDrive: PropTypes.bool
|
|
73
76
|
};
|
|
74
77
|
export default MemberRecipient;
|
|
@@ -10,6 +10,7 @@ var MemberRecipientStatus = function MemberRecipientStatus(_ref) {
|
|
|
10
10
|
instance = _ref.instance,
|
|
11
11
|
email = _ref.email,
|
|
12
12
|
name = _ref.name,
|
|
13
|
+
isOrgSharedDrive = _ref.isOrgSharedDrive,
|
|
13
14
|
typographyProps = _ref.typographyProps;
|
|
14
15
|
|
|
15
16
|
var _useI18n = useI18n(),
|
|
@@ -24,7 +25,8 @@ var MemberRecipientStatus = function MemberRecipientStatus(_ref) {
|
|
|
24
25
|
// (getDisplayName falls back to email when the recipient has no name).
|
|
25
26
|
// Keep the instance visible when there is no email at all, so the user
|
|
26
27
|
// can still tell the recipient lives on a different Cozy instance.
|
|
27
|
-
|
|
28
|
+
// For org shared drive owners, hide the email and instance entirely.
|
|
29
|
+
text = status === 'owner' && isOrgSharedDrive ? '' : isMe || name || !email ? email || instance : '';
|
|
28
30
|
} else if (isSendingEmail) {
|
|
29
31
|
text = t('Share.status.mail-not-sent');
|
|
30
32
|
} else {
|
|
@@ -44,6 +46,7 @@ MemberRecipientStatus.propTypes = {
|
|
|
44
46
|
instance: PropTypes.string,
|
|
45
47
|
email: PropTypes.string,
|
|
46
48
|
name: PropTypes.string,
|
|
49
|
+
isOrgSharedDrive: PropTypes.bool,
|
|
47
50
|
typographyProps: PropTypes.object
|
|
48
51
|
};
|
|
49
52
|
export default MemberRecipientStatus;
|
|
@@ -63,39 +63,53 @@ describe('MemberRecipientStatus component', function () {
|
|
|
63
63
|
|
|
64
64
|
expect(getByText('owner@example.com')).toBeTruthy();
|
|
65
65
|
});
|
|
66
|
-
it('should
|
|
66
|
+
it('should hide email and instance when status is owner and isOrgSharedDrive is true', function () {
|
|
67
67
|
var _setup6 = setup({
|
|
68
|
+
status: 'owner',
|
|
69
|
+
isMe: false,
|
|
70
|
+
email: 'org@example.com',
|
|
71
|
+
instance: 'foo.mycozy.cloud',
|
|
72
|
+
name: 'Org Owner',
|
|
73
|
+
isOrgSharedDrive: true
|
|
74
|
+
}),
|
|
75
|
+
queryByText = _setup6.queryByText;
|
|
76
|
+
|
|
77
|
+
expect(queryByText('org@example.com')).toBe(null);
|
|
78
|
+
expect(queryByText('foo.mycozy.cloud')).toBe(null);
|
|
79
|
+
});
|
|
80
|
+
it('should show "Sending invitation mail..." when status is mail-not-sent and not isMe', function () {
|
|
81
|
+
var _setup7 = setup({
|
|
68
82
|
status: 'mail-not-sent',
|
|
69
83
|
isMe: false
|
|
70
84
|
}),
|
|
71
|
-
getByText =
|
|
85
|
+
getByText = _setup7.getByText;
|
|
72
86
|
|
|
73
87
|
expect(getByText('Sending invitation mail...')).toBeTruthy();
|
|
74
88
|
});
|
|
75
89
|
it('should show "Invitation sent" when status is pending', function () {
|
|
76
|
-
var
|
|
90
|
+
var _setup8 = setup({
|
|
77
91
|
status: 'pending',
|
|
78
92
|
isMe: false
|
|
79
93
|
}),
|
|
80
|
-
getByText =
|
|
94
|
+
getByText = _setup8.getByText;
|
|
81
95
|
|
|
82
96
|
expect(getByText('Invitation sent')).toBeTruthy();
|
|
83
97
|
});
|
|
84
98
|
it('should show "Invitation seen" when status is seen', function () {
|
|
85
|
-
var
|
|
99
|
+
var _setup9 = setup({
|
|
86
100
|
status: 'seen',
|
|
87
101
|
isMe: false
|
|
88
102
|
}),
|
|
89
|
-
getByText =
|
|
103
|
+
getByText = _setup9.getByText;
|
|
90
104
|
|
|
91
105
|
expect(getByText('Invitation seen')).toBeTruthy();
|
|
92
106
|
});
|
|
93
107
|
it('should fallback to "Invitation sent" for unknown status', function () {
|
|
94
|
-
var
|
|
108
|
+
var _setup10 = setup({
|
|
95
109
|
status: 'unknown-status',
|
|
96
110
|
isMe: false
|
|
97
111
|
}),
|
|
98
|
-
getByText =
|
|
112
|
+
getByText = _setup10.getByText;
|
|
99
113
|
|
|
100
114
|
expect(getByText('Invitation sent')).toBeTruthy();
|
|
101
115
|
});
|
|
@@ -1,22 +1,27 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
1
2
|
import PropTypes from 'prop-types';
|
|
2
3
|
import React from 'react';
|
|
3
4
|
import MemberRecipient from './MemberRecipient';
|
|
4
5
|
import OwnerRecipientDefault from './OwnerRecipientDefault';
|
|
5
6
|
|
|
6
7
|
var OwnerRecipient = function OwnerRecipient(_ref) {
|
|
7
|
-
var recipients = _ref.recipients
|
|
8
|
+
var recipients = _ref.recipients,
|
|
9
|
+
isOrgSharedDrive = _ref.isOrgSharedDrive;
|
|
8
10
|
var ownerRecipient = recipients.find(function (recipient) {
|
|
9
11
|
return recipient.status === 'owner';
|
|
10
12
|
});
|
|
11
13
|
|
|
12
14
|
if (ownerRecipient) {
|
|
13
|
-
return /*#__PURE__*/React.createElement(MemberRecipient, ownerRecipient
|
|
15
|
+
return /*#__PURE__*/React.createElement(MemberRecipient, _extends({}, ownerRecipient, {
|
|
16
|
+
isOrgSharedDrive: isOrgSharedDrive
|
|
17
|
+
}));
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
return /*#__PURE__*/React.createElement(OwnerRecipientDefault, null);
|
|
17
21
|
};
|
|
18
22
|
|
|
19
23
|
OwnerRecipient.propTypes = {
|
|
20
|
-
recipients: PropTypes.array.isRequired
|
|
24
|
+
recipients: PropTypes.array.isRequired,
|
|
25
|
+
isOrgSharedDrive: PropTypes.bool
|
|
21
26
|
};
|
|
22
27
|
export default OwnerRecipient;
|
|
@@ -36,7 +36,10 @@ jest.mock('../hooks/useSharingContext', function () {
|
|
|
36
36
|
getSharingLink: jest.fn(function () {
|
|
37
37
|
return null;
|
|
38
38
|
}),
|
|
39
|
-
updateSharingMemberType: jest.fn()
|
|
39
|
+
updateSharingMemberType: jest.fn(),
|
|
40
|
+
isOrgSharedDrive: jest.fn(function () {
|
|
41
|
+
return false;
|
|
42
|
+
})
|
|
40
43
|
};
|
|
41
44
|
}
|
|
42
45
|
};
|
|
@@ -20,7 +20,10 @@ jest.mock('../hooks/useSharingContext', function () {
|
|
|
20
20
|
return mockSharingLink;
|
|
21
21
|
},
|
|
22
22
|
revokeSharingLink: jest.fn(),
|
|
23
|
-
updateDocumentPermissions: jest.fn()
|
|
23
|
+
updateDocumentPermissions: jest.fn(),
|
|
24
|
+
isOrgSharedDrive: jest.fn(function () {
|
|
25
|
+
return false;
|
|
26
|
+
})
|
|
24
27
|
};
|
|
25
28
|
}
|
|
26
29
|
};
|
|
@@ -6,6 +6,7 @@ import LinkRecipient from './Recipient/LinkRecipient';
|
|
|
6
6
|
import OwnerRecipient from './Recipient/OwnerRecipient';
|
|
7
7
|
import { RecipientList } from './Recipient/RecipientList';
|
|
8
8
|
import { usePrevious } from '../helpers/hooks';
|
|
9
|
+
import { useSharingContext } from '../hooks/useSharingContext';
|
|
9
10
|
/**
|
|
10
11
|
* Displays a warning if some contacts are waiting for confirmation of their sharing
|
|
11
12
|
*
|
|
@@ -52,6 +53,11 @@ var WhoHasAccess = function WhoHasAccess(_ref2) {
|
|
|
52
53
|
permissions = _ref2.permissions;
|
|
53
54
|
var previousLink = usePrevious(link);
|
|
54
55
|
var linkHasBeenJustCreated = link && previousLink === null;
|
|
56
|
+
|
|
57
|
+
var _useSharingContext = useSharingContext(),
|
|
58
|
+
isOrgSharedDrive = _useSharingContext.isOrgSharedDrive;
|
|
59
|
+
|
|
60
|
+
var isOrgDrive = isOrgSharedDrive(document === null || document === void 0 ? void 0 : document._id);
|
|
55
61
|
return /*#__PURE__*/React.createElement("div", {
|
|
56
62
|
className: className
|
|
57
63
|
}, /*#__PURE__*/React.createElement(RecipientWaitingForConfirmationAlert, {
|
|
@@ -65,7 +71,8 @@ var WhoHasAccess = function WhoHasAccess(_ref2) {
|
|
|
65
71
|
permissions: permissions,
|
|
66
72
|
fadeIn: linkHasBeenJustCreated
|
|
67
73
|
}), showOwner && /*#__PURE__*/React.createElement(OwnerRecipient, {
|
|
68
|
-
recipients: recipients
|
|
74
|
+
recipients: recipients,
|
|
75
|
+
isOrgSharedDrive: isOrgDrive
|
|
69
76
|
}), /*#__PURE__*/React.createElement(RecipientList, {
|
|
70
77
|
recipients: recipients,
|
|
71
78
|
recipientsToBeConfirmed: recipientsToBeConfirmed,
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export default SharingProvider;
|
|
|
5
5
|
export { SharingContext, withLocales };
|
|
6
6
|
export { useSharingContext } from './hooks/useSharingContext';
|
|
7
7
|
export { useFetchDocumentPath } from './hooks/useFetchDocumentPath';
|
|
8
|
+
export { MemberAvatar } from './components/Avatar/MemberAvatar';
|
|
8
9
|
export { SharedDocument } from './SharedDocument';
|
|
9
10
|
export { SharedStatus } from './SharedStatus';
|
|
10
11
|
export { SharedBadge } from './SharedBadge';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-sharing",
|
|
3
|
-
"version": "36.
|
|
3
|
+
"version": "36.4.0",
|
|
4
4
|
"description": "Provides sharing login for React applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "Cozy",
|
|
@@ -85,5 +85,5 @@
|
|
|
85
85
|
"sideEffects": [
|
|
86
86
|
"*.css"
|
|
87
87
|
],
|
|
88
|
-
"gitHead": "
|
|
88
|
+
"gitHead": "db82eddb8f69384c5b5a8a8257572657330b35f2"
|
|
89
89
|
}
|
package/src/DoctypeContact.js
CHANGED
|
@@ -1,29 +1,46 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getInitials as clientGetInitials,
|
|
3
|
+
getDisplayName as clientGetDisplayName
|
|
4
|
+
} from 'cozy-client/dist/models/contact'
|
|
2
5
|
import { Contact as DoctypeContact } from 'cozy-doctypes'
|
|
3
|
-
const ContactModel = models.contact
|
|
4
6
|
|
|
5
7
|
const isContact = candidate => {
|
|
6
8
|
return candidate._type === 'io.cozy.contacts'
|
|
7
9
|
}
|
|
8
10
|
export const getInitials = (contactOrRecipient, defaultValue = '') => {
|
|
9
11
|
if (isContact(contactOrRecipient)) {
|
|
10
|
-
return
|
|
12
|
+
return clientGetInitials(contactOrRecipient)
|
|
11
13
|
} else {
|
|
12
14
|
// @todo Extract to RecipientModel ?
|
|
13
15
|
const s =
|
|
14
16
|
contactOrRecipient.public_name ||
|
|
17
|
+
contactOrRecipient.displayName ||
|
|
15
18
|
contactOrRecipient.name ||
|
|
16
19
|
contactOrRecipient.email
|
|
17
|
-
|
|
20
|
+
|
|
21
|
+
if (!s) return defaultValue
|
|
22
|
+
|
|
23
|
+
const parts = s.split(' ').filter(Boolean)
|
|
24
|
+
if (parts.length === 0) return defaultValue
|
|
25
|
+
const firstLetter = parts[0][0]
|
|
26
|
+
const lastLetter = parts.length > 1 ? parts.at(-1)[0] : ''
|
|
27
|
+
|
|
28
|
+
return (firstLetter + lastLetter).toUpperCase()
|
|
18
29
|
}
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
export const getDisplayName = (contact, defaultValue = '') => {
|
|
22
33
|
if (isContact(contact)) {
|
|
23
|
-
return
|
|
34
|
+
return clientGetDisplayName(contact)
|
|
24
35
|
} else {
|
|
25
36
|
// @todo Extract to RecipientModel ?
|
|
26
|
-
return
|
|
37
|
+
return (
|
|
38
|
+
contact.public_name ||
|
|
39
|
+
contact.displayName ||
|
|
40
|
+
contact.name ||
|
|
41
|
+
contact.email ||
|
|
42
|
+
defaultValue
|
|
43
|
+
)
|
|
27
44
|
}
|
|
28
45
|
}
|
|
29
46
|
|
|
@@ -1,11 +1,37 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { useClient } from 'cozy-client'
|
|
4
|
+
import { getPrimaryCozy } from 'cozy-client/dist/models/contact'
|
|
4
5
|
import Avatar from 'cozy-ui/transpiled/react/Avatar'
|
|
5
6
|
|
|
6
7
|
import logger from '../../logger'
|
|
7
8
|
import { getInitials } from '../../models'
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* makeAvatarUrl derives the avatar image URL for the recipient.
|
|
12
|
+
* - If the recipient has an avatarPath (sharing context), it is
|
|
13
|
+
* used directly with a base URI prefix (unless it is already
|
|
14
|
+
* a full URL for a remote Cozy).
|
|
15
|
+
* - If the recipient has no avatarPath but has a cozy URL
|
|
16
|
+
* (contact context), it points to that Cozy's public avatar.
|
|
17
|
+
* - The status is used as a cache-buster in the URL so the
|
|
18
|
+
* browser refreshes the image when the sharing status changes.
|
|
19
|
+
* For non-sharing recipients, 'contact' is the stable default.
|
|
20
|
+
*/
|
|
21
|
+
const makeAvatarUrl = (recipient, instanceUri) => {
|
|
22
|
+
const avatarPath = recipient.avatarPath
|
|
23
|
+
const cozyUrl = !avatarPath ? getPrimaryCozy(recipient) : null
|
|
24
|
+
if (!avatarPath && !cozyUrl) return null
|
|
25
|
+
|
|
26
|
+
const finalPath =
|
|
27
|
+
avatarPath ||
|
|
28
|
+
`${cozyUrl.replace(/\/$/, '')}/public/avatar?fallback=initials`
|
|
29
|
+
const status = recipient.status || 'contact'
|
|
30
|
+
const isFullPath = finalPath.startsWith('http')
|
|
31
|
+
const base = isFullPath ? '' : instanceUri
|
|
32
|
+
return `${base}${finalPath}${finalPath.includes('?') ? '&' : '?'}v=${status}`
|
|
33
|
+
}
|
|
34
|
+
|
|
9
35
|
const MemberAvatar = ({ recipient, ...rest }) => {
|
|
10
36
|
const client = useClient()
|
|
11
37
|
// Remember which image url failed to load so we can fall back to the
|
|
@@ -23,22 +49,7 @@ const MemberAvatar = ({ recipient, ...rest }) => {
|
|
|
23
49
|
return null
|
|
24
50
|
}
|
|
25
51
|
|
|
26
|
-
|
|
27
|
-
* avatarPath is always the same for a recipient, but image
|
|
28
|
-
* can be different since the stack generate it on the fly
|
|
29
|
-
* because they can be customized by the user.
|
|
30
|
-
* It can be "gray" during the first load depending of the
|
|
31
|
-
* status' sharing, but can become active (from the realtime)
|
|
32
|
-
* so we need a way to "refresh" the image. Passing the
|
|
33
|
-
* status in the url force the refresh of the image when the
|
|
34
|
-
* status changes
|
|
35
|
-
*/
|
|
36
|
-
const image =
|
|
37
|
-
recipient.avatarPath && recipient.status
|
|
38
|
-
? `${client.options.uri}${recipient.avatarPath}${
|
|
39
|
-
recipient.avatarPath.includes('?') ? '&' : '?'
|
|
40
|
-
}v=${recipient.status}`
|
|
41
|
-
: null
|
|
52
|
+
const image = makeAvatarUrl(recipient, client.options.uri)
|
|
42
53
|
|
|
43
54
|
// The avatar can become unreachable (eg. the related sharing is revoked
|
|
44
55
|
// while its members are still rendered), in which case the image request
|
|
@@ -52,6 +52,42 @@ describe('MemberAvatar', () => {
|
|
|
52
52
|
)
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
+
it('renders a full URL avatarPath without prefixing the local instance URI', () => {
|
|
56
|
+
const { container } = setup({
|
|
57
|
+
public_name: 'Jon Snow',
|
|
58
|
+
status: 'contact',
|
|
59
|
+
avatarPath: 'https://jonsnow.mycozy.cloud/public/avatar?fallback=initials'
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
expect(container.querySelector('img').getAttribute('src')).toBe(
|
|
63
|
+
'https://jonsnow.mycozy.cloud/public/avatar?fallback=initials&v=contact'
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('derives avatarPath from cozy URL when avatarPath is not set', () => {
|
|
68
|
+
const { container } = setup({
|
|
69
|
+
_type: 'io.cozy.contacts',
|
|
70
|
+
name: { givenName: 'Jon', familyName: 'Snow' },
|
|
71
|
+
cozy: [{ url: 'https://jonsnow.mycozy.cloud' }]
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const img = container.querySelector('img')
|
|
75
|
+
expect(img).toBeTruthy()
|
|
76
|
+
expect(img.getAttribute('src')).toBe(
|
|
77
|
+
'https://jonsnow.mycozy.cloud/public/avatar?fallback=initials&v=contact'
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('renders initials when there is no avatarPath and no cozy URL', () => {
|
|
82
|
+
const { container, getByText } = setup({
|
|
83
|
+
_type: 'io.cozy.contacts',
|
|
84
|
+
name: { givenName: 'Jon', familyName: 'Snow' }
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
expect(container.querySelector('img')).toBeNull()
|
|
88
|
+
expect(getByText('JS')).toBeTruthy()
|
|
89
|
+
})
|
|
90
|
+
|
|
55
91
|
it('renders the initials when there is no avatarPath', () => {
|
|
56
92
|
const { container, getByText } = setup({
|
|
57
93
|
public_name: 'Bob',
|
|
@@ -1,47 +1,44 @@
|
|
|
1
1
|
import PropTypes from 'prop-types'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import Avatar from 'cozy-ui/transpiled/react/Avatar'
|
|
4
|
+
import { getPrimaryCozy } from 'cozy-client/dist/models/contact'
|
|
6
5
|
import ListItem from 'cozy-ui/transpiled/react/ListItem'
|
|
7
6
|
import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon'
|
|
8
7
|
import ListItemText from 'cozy-ui/transpiled/react/ListItemText'
|
|
9
8
|
import { useI18n } from 'twake-i18n'
|
|
10
9
|
|
|
11
|
-
import { Contact, Group, getDisplayName
|
|
10
|
+
import { Contact, Group, getDisplayName } from '../models'
|
|
12
11
|
import { GroupAvatar } from './Avatar/GroupAvatar'
|
|
13
|
-
|
|
14
|
-
const ContactModel = models.contact
|
|
12
|
+
import { MemberAvatar } from './Avatar/MemberAvatar'
|
|
15
13
|
|
|
16
14
|
export const ContactSuggestion = ({ contactOrGroup }) => {
|
|
17
15
|
const { t } = useI18n()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
|
|
17
|
+
if (contactOrGroup._type === Group.doctype) {
|
|
18
|
+
return (
|
|
19
|
+
<ListItem button>
|
|
20
|
+
<ListItemIcon>
|
|
21
|
+
<GroupAvatar size="m" />
|
|
22
|
+
</ListItemIcon>
|
|
23
|
+
<ListItemText
|
|
24
|
+
primary={contactOrGroup.name}
|
|
25
|
+
secondary={t('Share.members.count', {
|
|
26
|
+
smart_count: contactOrGroup.members.length.toString()
|
|
27
|
+
})}
|
|
28
|
+
/>
|
|
29
|
+
</ListItem>
|
|
30
|
+
)
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
const name = getDisplayName(contactOrGroup)
|
|
34
|
+
const cozyUrl = getPrimaryCozy(contactOrGroup)
|
|
35
|
+
|
|
32
36
|
return (
|
|
33
37
|
<ListItem button>
|
|
34
38
|
<ListItemIcon>
|
|
35
|
-
{
|
|
36
|
-
<GroupAvatar size="m" />
|
|
37
|
-
) : (
|
|
38
|
-
<Avatar size="m">{avatarText}</Avatar>
|
|
39
|
-
)}
|
|
39
|
+
<MemberAvatar recipient={contactOrGroup} size="m" />
|
|
40
40
|
</ListItemIcon>
|
|
41
|
-
<ListItemText
|
|
42
|
-
primary={name}
|
|
43
|
-
secondary={details && details !== '' ? details : '-'}
|
|
44
|
-
/>
|
|
41
|
+
<ListItemText primary={name} secondary={cozyUrl || '-'} />
|
|
45
42
|
</ListItem>
|
|
46
43
|
)
|
|
47
44
|
}
|
|
@@ -40,7 +40,8 @@ jest.mock('../../hooks/useSharingContext', () => ({
|
|
|
40
40
|
getSharedParentPath: jest.fn().mockReturnValue(null),
|
|
41
41
|
getSharingById: mockGetSharingById,
|
|
42
42
|
hasSharedChild: mockHasSharedChild,
|
|
43
|
-
hasSharedParent: mockHasSharedParent
|
|
43
|
+
hasSharedParent: mockHasSharedParent,
|
|
44
|
+
isOrgSharedDrive: jest.fn(() => false)
|
|
44
45
|
})
|
|
45
46
|
}))
|
|
46
47
|
|
|
@@ -32,6 +32,7 @@ const MemberRecipient = props => {
|
|
|
32
32
|
recipientConfirmationData,
|
|
33
33
|
verifyRecipient,
|
|
34
34
|
fadeIn,
|
|
35
|
+
isOrgSharedDrive,
|
|
35
36
|
...rest
|
|
36
37
|
} = props
|
|
37
38
|
|
|
@@ -68,6 +69,7 @@ const MemberRecipient = props => {
|
|
|
68
69
|
instance={instance}
|
|
69
70
|
email={rest.email}
|
|
70
71
|
name={rest.name || rest.public_name}
|
|
72
|
+
isOrgSharedDrive={isOrgSharedDrive}
|
|
71
73
|
/>
|
|
72
74
|
}
|
|
73
75
|
/>
|
|
@@ -82,7 +84,8 @@ MemberRecipient.propTypes = {
|
|
|
82
84
|
isOwner: PropTypes.bool,
|
|
83
85
|
status: PropTypes.string,
|
|
84
86
|
recipientConfirmationData: PropTypes.object,
|
|
85
|
-
verifyRecipient: PropTypes.func
|
|
87
|
+
verifyRecipient: PropTypes.func,
|
|
88
|
+
isOrgSharedDrive: PropTypes.bool
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
export default MemberRecipient
|
|
@@ -10,6 +10,7 @@ const MemberRecipientStatus = ({
|
|
|
10
10
|
instance,
|
|
11
11
|
email,
|
|
12
12
|
name,
|
|
13
|
+
isOrgSharedDrive,
|
|
13
14
|
typographyProps
|
|
14
15
|
}) => {
|
|
15
16
|
const { t } = useI18n()
|
|
@@ -22,7 +23,13 @@ const MemberRecipientStatus = ({
|
|
|
22
23
|
// (getDisplayName falls back to email when the recipient has no name).
|
|
23
24
|
// Keep the instance visible when there is no email at all, so the user
|
|
24
25
|
// can still tell the recipient lives on a different Cozy instance.
|
|
25
|
-
|
|
26
|
+
// For org shared drive owners, hide the email and instance entirely.
|
|
27
|
+
text =
|
|
28
|
+
status === 'owner' && isOrgSharedDrive
|
|
29
|
+
? ''
|
|
30
|
+
: isMe || name || !email
|
|
31
|
+
? email || instance
|
|
32
|
+
: ''
|
|
26
33
|
} else if (isSendingEmail) {
|
|
27
34
|
text = t('Share.status.mail-not-sent')
|
|
28
35
|
} else {
|
|
@@ -45,6 +52,7 @@ MemberRecipientStatus.propTypes = {
|
|
|
45
52
|
instance: PropTypes.string,
|
|
46
53
|
email: PropTypes.string,
|
|
47
54
|
name: PropTypes.string,
|
|
55
|
+
isOrgSharedDrive: PropTypes.bool,
|
|
48
56
|
typographyProps: PropTypes.object
|
|
49
57
|
}
|
|
50
58
|
|
|
@@ -63,6 +63,19 @@ describe('MemberRecipientStatus component', () => {
|
|
|
63
63
|
expect(getByText('owner@example.com')).toBeTruthy()
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
it('should hide email and instance when status is owner and isOrgSharedDrive is true', () => {
|
|
67
|
+
const { queryByText } = setup({
|
|
68
|
+
status: 'owner',
|
|
69
|
+
isMe: false,
|
|
70
|
+
email: 'org@example.com',
|
|
71
|
+
instance: 'foo.mycozy.cloud',
|
|
72
|
+
name: 'Org Owner',
|
|
73
|
+
isOrgSharedDrive: true
|
|
74
|
+
})
|
|
75
|
+
expect(queryByText('org@example.com')).toBe(null)
|
|
76
|
+
expect(queryByText('foo.mycozy.cloud')).toBe(null)
|
|
77
|
+
})
|
|
78
|
+
|
|
66
79
|
it('should show "Sending invitation mail..." when status is mail-not-sent and not isMe', () => {
|
|
67
80
|
const { getByText } = setup({
|
|
68
81
|
status: 'mail-not-sent',
|
|
@@ -4,20 +4,26 @@ import React from 'react'
|
|
|
4
4
|
import MemberRecipient from './MemberRecipient'
|
|
5
5
|
import OwnerRecipientDefault from './OwnerRecipientDefault'
|
|
6
6
|
|
|
7
|
-
const OwnerRecipient = ({ recipients }) => {
|
|
7
|
+
const OwnerRecipient = ({ recipients, isOrgSharedDrive }) => {
|
|
8
8
|
const ownerRecipient = recipients.find(
|
|
9
9
|
recipient => recipient.status === 'owner'
|
|
10
10
|
)
|
|
11
11
|
|
|
12
12
|
if (ownerRecipient) {
|
|
13
|
-
return
|
|
13
|
+
return (
|
|
14
|
+
<MemberRecipient
|
|
15
|
+
{...ownerRecipient}
|
|
16
|
+
isOrgSharedDrive={isOrgSharedDrive}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
return <OwnerRecipientDefault />
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
OwnerRecipient.propTypes = {
|
|
20
|
-
recipients: PropTypes.array.isRequired
|
|
25
|
+
recipients: PropTypes.array.isRequired,
|
|
26
|
+
isOrgSharedDrive: PropTypes.bool
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
export default OwnerRecipient
|
|
@@ -17,7 +17,8 @@ jest.mock('cozy-client', () => ({
|
|
|
17
17
|
jest.mock('../hooks/useSharingContext', () => ({
|
|
18
18
|
useSharingContext: () => ({
|
|
19
19
|
getSharingLink: jest.fn(() => null),
|
|
20
|
-
updateSharingMemberType: jest.fn()
|
|
20
|
+
updateSharingMemberType: jest.fn(),
|
|
21
|
+
isOrgSharedDrive: jest.fn(() => false)
|
|
21
22
|
})
|
|
22
23
|
}))
|
|
23
24
|
|
|
@@ -14,7 +14,8 @@ jest.mock('../hooks/useSharingContext', () => ({
|
|
|
14
14
|
getDocumentPermissions: () => [],
|
|
15
15
|
getSharingLink: () => mockSharingLink,
|
|
16
16
|
revokeSharingLink: jest.fn(),
|
|
17
|
-
updateDocumentPermissions: jest.fn()
|
|
17
|
+
updateDocumentPermissions: jest.fn(),
|
|
18
|
+
isOrgSharedDrive: jest.fn(() => false)
|
|
18
19
|
})
|
|
19
20
|
}))
|
|
20
21
|
|
|
@@ -8,6 +8,7 @@ import LinkRecipient from './Recipient/LinkRecipient'
|
|
|
8
8
|
import OwnerRecipient from './Recipient/OwnerRecipient'
|
|
9
9
|
import { RecipientList } from './Recipient/RecipientList'
|
|
10
10
|
import { usePrevious } from '../helpers/hooks'
|
|
11
|
+
import { useSharingContext } from '../hooks/useSharingContext'
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Displays a warning if some contacts are waiting for confirmation of their sharing
|
|
@@ -46,6 +47,8 @@ const WhoHasAccess = ({
|
|
|
46
47
|
}) => {
|
|
47
48
|
const previousLink = usePrevious(link)
|
|
48
49
|
const linkHasBeenJustCreated = link && previousLink === null
|
|
50
|
+
const { isOrgSharedDrive } = useSharingContext()
|
|
51
|
+
const isOrgDrive = isOrgSharedDrive(document?._id)
|
|
49
52
|
|
|
50
53
|
return (
|
|
51
54
|
<div className={className}>
|
|
@@ -65,7 +68,12 @@ const WhoHasAccess = ({
|
|
|
65
68
|
/>
|
|
66
69
|
)}
|
|
67
70
|
|
|
68
|
-
{showOwner &&
|
|
71
|
+
{showOwner && (
|
|
72
|
+
<OwnerRecipient
|
|
73
|
+
recipients={recipients}
|
|
74
|
+
isOrgSharedDrive={isOrgDrive}
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
69
77
|
|
|
70
78
|
<RecipientList
|
|
71
79
|
recipients={recipients}
|
package/src/index.jsx
CHANGED
|
@@ -9,6 +9,7 @@ export { SharingContext, withLocales }
|
|
|
9
9
|
export { useSharingContext } from './hooks/useSharingContext'
|
|
10
10
|
export { useFetchDocumentPath } from './hooks/useFetchDocumentPath'
|
|
11
11
|
|
|
12
|
+
export { MemberAvatar } from './components/Avatar/MemberAvatar'
|
|
12
13
|
export { SharedDocument } from './SharedDocument'
|
|
13
14
|
export { SharedStatus } from './SharedStatus'
|
|
14
15
|
export { SharedBadge } from './SharedBadge'
|