cozy-sharing 40.1.7 → 40.1.8
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 +4 -0
- package/dist/components/FederatedFolder/FederatedFolderModal.spec.js +2 -2
- package/dist/components/{ShareRecipientsInput.js → ShareAutosuggestWrapper.js} +12 -54
- package/dist/components/{ShareRecipientsInput.spec.js → ShareAutosuggestWrapper.spec.js} +3 -3
- package/dist/components/ShareByEmail.js +2 -2
- package/dist/components/SharingBanner/components/PublicBanner.js +8 -6
- package/dist/components/SharingBanner/components/PublicBanner.spec.js +1 -9
- package/dist/components/SharingBanner/components/SharingBanner.js +3 -0
- package/dist/components/SharingBanner/index.js +3 -0
- package/dist/helpers/contacts.js +79 -1
- package/dist/helpers/contacts.spec.js +122 -1
- package/docs/share-modal-architecture.md +2 -2
- package/package.json +3 -3
- package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +1 -0
- package/src/components/{ShareRecipientsInput.jsx → ShareAutosuggestWrapper.jsx} +14 -65
- package/src/components/{ShareRecipientsInput.spec.jsx → ShareAutosuggestWrapper.spec.jsx} +3 -3
- package/src/components/ShareByEmail.jsx +2 -2
- package/src/components/SharingBanner/components/PublicBanner.jsx +6 -6
- package/src/components/SharingBanner/components/PublicBanner.spec.jsx +1 -8
- package/src/components/SharingBanner/components/SharingBanner.jsx +7 -1
- package/src/components/SharingBanner/index.jsx +4 -0
- package/src/helpers/contacts.js +83 -0
- package/src/helpers/contacts.spec.js +78 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
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
|
+
## [40.1.8](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.7...cozy-sharing@40.1.8) (2026-10-27)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package cozy-sharing
|
|
9
|
+
|
|
6
10
|
## [40.1.7](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.6...cozy-sharing@40.1.7) (2026-10-27)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package cozy-sharing
|
|
@@ -80,9 +80,9 @@ jest.mock('../../hoc/withLocales', function () {
|
|
|
80
80
|
};
|
|
81
81
|
});
|
|
82
82
|
jest.mock('../../helpers/contacts', function () {
|
|
83
|
-
return {
|
|
83
|
+
return _objectSpread(_objectSpread({}, jest.requireActual('../../helpers/contacts')), {}, {
|
|
84
84
|
getOrCreateFromArray: jest.fn()
|
|
85
|
-
};
|
|
85
|
+
});
|
|
86
86
|
});
|
|
87
87
|
jest.mock('../../hooks/usePendingRecipients', function () {
|
|
88
88
|
return {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
2
|
|
|
4
3
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
@@ -8,23 +7,17 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
|
|
|
8
7
|
import PropTypes from 'prop-types';
|
|
9
8
|
import React from 'react';
|
|
10
9
|
import { useQueryAll, isQueryLoading } from 'cozy-client';
|
|
11
|
-
import { models } from 'cozy-client';
|
|
12
10
|
import flag from 'cozy-flags';
|
|
13
11
|
import ShareAutosuggest from './ShareAutosuggest';
|
|
14
|
-
import {
|
|
12
|
+
import { computeContactsAndGroups } from '../helpers/contacts';
|
|
15
13
|
import { buildReachableContactsQuery, buildContactGroupsQuery, buildUnreachableContactsWithGroupsQuery } from '../queries/queries';
|
|
16
|
-
var ContactModel = models.contact;
|
|
17
14
|
/**
|
|
18
|
-
*
|
|
15
|
+
* ShareAutosuggestWrapper is responsible for fetching contacts and groups.
|
|
19
16
|
* We retrieve all the contacts that are reachable and the groups they belong to.
|
|
20
17
|
* In order to display the total number of members in each group, we also retrieve the contacts that are not reachable.
|
|
21
18
|
*/
|
|
22
19
|
|
|
23
|
-
var
|
|
24
|
-
return contact.me === true;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
|
|
20
|
+
var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
|
|
28
21
|
var currentRecipients = _ref.currentRecipients,
|
|
29
22
|
recipients = _ref.recipients,
|
|
30
23
|
placeholder = _ref.placeholder,
|
|
@@ -43,48 +36,13 @@ var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
|
|
|
43
36
|
var contactGroupsQuery = buildContactGroupsQuery();
|
|
44
37
|
var contactGroupsResult = useQueryAll(contactGroupsQuery.definition, contactGroupsQuery.options);
|
|
45
38
|
var isLoading = isQueryLoading(reachableContactsResult) || reachableContactsResult.hasMore || isQueryLoading(contactGroupsResult) || contactGroupsResult.hasMore || flag('sharing.show-recipient-groups') && (isQueryLoading(unreachableContactsWithGroupsResult) || unreachableContactsWithGroupsResult.hasMore);
|
|
46
|
-
var
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return r._id === contact._id;
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
var unreachableContactsWithGroups = (unreachableContactsWithGroupsResult.data || []).filter(function (contact) {
|
|
56
|
-
return !isCurrentUser(contact);
|
|
57
|
-
});
|
|
58
|
-
var currentRecipientGroups = currentRecipients.map(function (_ref2) {
|
|
59
|
-
var id = _ref2.id;
|
|
60
|
-
return id;
|
|
61
|
-
}).filter(Boolean);
|
|
62
|
-
var contactGroups = (contactGroupsResult.data || []).filter(function (_ref3) {
|
|
63
|
-
var _id = _ref3._id;
|
|
64
|
-
return !flag('sharing.show-recipient-groups') || !currentRecipientGroups.includes(_id);
|
|
65
|
-
}).map(function (contactGroup) {
|
|
66
|
-
var reachableMembers = getContactsFromGroupId(reachableContacts, contactGroup.id).map(function (contact) {
|
|
67
|
-
return _objectSpread(_objectSpread({}, contact), {}, {
|
|
68
|
-
isReachable: true
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
if (flag('sharing.show-recipient-groups') !== true) {
|
|
73
|
-
return _objectSpread(_objectSpread({}, contactGroup), {}, {
|
|
74
|
-
members: reachableMembers
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
var unreachableMembers = getContactsFromGroupId(unreachableContactsWithGroups, contactGroup.id).map(function (contact) {
|
|
79
|
-
return _objectSpread(_objectSpread({}, contact), {}, {
|
|
80
|
-
isReachable: false
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
return _objectSpread(_objectSpread({}, contactGroup), {}, {
|
|
84
|
-
members: [].concat(_toConsumableArray(reachableMembers), _toConsumableArray(unreachableMembers))
|
|
85
|
-
});
|
|
39
|
+
var contactsAndGroups = computeContactsAndGroups({
|
|
40
|
+
reachableContacts: reachableContactsResult.data || [],
|
|
41
|
+
unreachableContactsWithGroups: unreachableContactsWithGroupsResult.data || [],
|
|
42
|
+
contactGroups: contactGroupsResult.data || [],
|
|
43
|
+
currentRecipients: currentRecipients,
|
|
44
|
+
pendingRecipients: recipients
|
|
86
45
|
});
|
|
87
|
-
var contactsAndGroups = [].concat(_toConsumableArray(reachableContacts), _toConsumableArray(contactGroups));
|
|
88
46
|
return /*#__PURE__*/React.createElement(ShareAutosuggest, {
|
|
89
47
|
loading: isLoading,
|
|
90
48
|
disabled: disabled,
|
|
@@ -99,7 +57,7 @@ var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
|
|
|
99
57
|
});
|
|
100
58
|
};
|
|
101
59
|
|
|
102
|
-
|
|
60
|
+
ShareAutosuggestWrapper.propTypes = {
|
|
103
61
|
currentRecipients: PropTypes.array,
|
|
104
62
|
recipients: PropTypes.array,
|
|
105
63
|
placeholder: PropTypes.string,
|
|
@@ -110,8 +68,8 @@ ShareRecipientsInput.propTypes = {
|
|
|
110
68
|
autoFocus: PropTypes.bool,
|
|
111
69
|
endAdornment: PropTypes.node
|
|
112
70
|
};
|
|
113
|
-
|
|
71
|
+
ShareAutosuggestWrapper.defaultProps = {
|
|
114
72
|
currentRecipients: [],
|
|
115
73
|
recipients: []
|
|
116
74
|
};
|
|
117
|
-
export default
|
|
75
|
+
export default ShareAutosuggestWrapper;
|
|
@@ -4,7 +4,7 @@ import { render, screen } from '@testing-library/react';
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { createMockClient } from 'cozy-client';
|
|
6
6
|
import flag from 'cozy-flags';
|
|
7
|
-
import
|
|
7
|
+
import ShareAutosuggestWrapper from './ShareAutosuggestWrapper';
|
|
8
8
|
import AppLike from './SharingBanner/test/AppLike';
|
|
9
9
|
jest.mock('cozy-flags', function () {
|
|
10
10
|
return {
|
|
@@ -33,7 +33,7 @@ jest.mock('./ShareAutosuggest', function () {
|
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
});
|
|
36
|
-
describe('
|
|
36
|
+
describe('ShareAutosuggestWrapper component', function () {
|
|
37
37
|
var setup = function setup(_ref2) {
|
|
38
38
|
var contacts = _ref2.contacts,
|
|
39
39
|
contactGroups = _ref2.contactGroups,
|
|
@@ -61,7 +61,7 @@ describe('ShareRecipientsInput component', function () {
|
|
|
61
61
|
});
|
|
62
62
|
render( /*#__PURE__*/React.createElement(AppLike, {
|
|
63
63
|
client: mockClient
|
|
64
|
-
}, /*#__PURE__*/React.createElement(
|
|
64
|
+
}, /*#__PURE__*/React.createElement(ShareAutosuggestWrapper, {
|
|
65
65
|
onPick: function onPick() {},
|
|
66
66
|
onRemove: function onRemove() {},
|
|
67
67
|
placeholder: "Enter recipients here",
|
|
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import flag from 'cozy-flags';
|
|
5
5
|
import { useI18n } from 'twake-i18n';
|
|
6
|
-
import
|
|
6
|
+
import ShareAutosuggestWrapper from './ShareAutosuggestWrapper';
|
|
7
7
|
import ShareTypeSelect from './Sharetypeselect';
|
|
8
8
|
import { mergeRecipients, spreadGroupAndMergeRecipients } from '../helpers/recipients';
|
|
9
9
|
import { isReadOnlySharing } from '../state';
|
|
@@ -69,7 +69,7 @@ export var ShareByEmail = function ShareByEmail(_ref) {
|
|
|
69
69
|
className: styles['coz-form-group']
|
|
70
70
|
}, /*#__PURE__*/React.createElement("div", {
|
|
71
71
|
className: styles['coz-form']
|
|
72
|
-
}, /*#__PURE__*/React.createElement(
|
|
72
|
+
}, /*#__PURE__*/React.createElement(ShareAutosuggestWrapper, {
|
|
73
73
|
placeholder: pendingRecipients.length === 0 ? t("".concat(documentType, ".share.shareByEmail.emailPlaceholder")) : '',
|
|
74
74
|
onPick: onRecipientPick,
|
|
75
75
|
onRemove: onRecipientRemove,
|
|
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import snarkdown from 'snarkdown';
|
|
5
5
|
import { Icon, SyncCozy, ToTheCloud } from '@linagora/twake-icons';
|
|
6
|
-
import { useClient } from 'cozy-client';
|
|
7
6
|
import flag from 'cozy-flags';
|
|
8
7
|
import Alert from 'cozy-ui/transpiled/react/Alert';
|
|
9
8
|
import Avatar from 'cozy-ui/transpiled/react/Avatar';
|
|
@@ -20,14 +19,13 @@ var getPublicNameFromSharing = function getPublicNameFromSharing(sharing) {
|
|
|
20
19
|
};
|
|
21
20
|
|
|
22
21
|
var PublicBannerCozyToCozyContent = function PublicBannerCozyToCozyContent(_ref) {
|
|
23
|
-
var sharing = _ref.sharing
|
|
22
|
+
var sharing = _ref.sharing,
|
|
23
|
+
avatarURL = _ref.avatarURL;
|
|
24
24
|
|
|
25
25
|
var _useI18n = useI18n(),
|
|
26
26
|
t = _useI18n.t;
|
|
27
27
|
|
|
28
|
-
var client = useClient();
|
|
29
28
|
var name = getPublicNameFromSharing(sharing);
|
|
30
|
-
var avatarURL = "".concat(client.options.uri, "/public/avatar?fallback=initials");
|
|
31
29
|
var withAvatar = snarkdown(t('Share.banner.shared_from', {
|
|
32
30
|
name: name,
|
|
33
31
|
image: avatarURL
|
|
@@ -48,7 +46,8 @@ var PublicBannerCozyToCozyContent = function PublicBannerCozyToCozyContent(_ref)
|
|
|
48
46
|
};
|
|
49
47
|
|
|
50
48
|
PublicBannerCozyToCozyContent.propTypes = {
|
|
51
|
-
sharing: PropTypes.object.isRequired
|
|
49
|
+
sharing: PropTypes.object.isRequired,
|
|
50
|
+
avatarURL: PropTypes.string.isRequired
|
|
52
51
|
};
|
|
53
52
|
|
|
54
53
|
var openExternalLink = function openExternalLink(url) {
|
|
@@ -57,6 +56,7 @@ var openExternalLink = function openExternalLink(url) {
|
|
|
57
56
|
|
|
58
57
|
var SharingBannerCozyToCozy = function SharingBannerCozyToCozy(_ref2) {
|
|
59
58
|
var sharing = _ref2.sharing,
|
|
59
|
+
avatarURL = _ref2.avatarURL,
|
|
60
60
|
isSharingShortcutCreated = _ref2.isSharingShortcutCreated,
|
|
61
61
|
addSharingLink = _ref2.addSharingLink,
|
|
62
62
|
onClose = _ref2.onClose;
|
|
@@ -101,7 +101,8 @@ var SharingBannerCozyToCozy = function SharingBannerCozyToCozy(_ref2) {
|
|
|
101
101
|
onClick: onClose
|
|
102
102
|
}))
|
|
103
103
|
}, /*#__PURE__*/React.createElement(PublicBannerCozyToCozyContent, {
|
|
104
|
-
sharing: sharing
|
|
104
|
+
sharing: sharing,
|
|
105
|
+
avatarURL: avatarURL
|
|
105
106
|
}));
|
|
106
107
|
};
|
|
107
108
|
|
|
@@ -168,6 +169,7 @@ SharingBannerByLink.propTypes = {
|
|
|
168
169
|
};
|
|
169
170
|
SharingBannerCozyToCozy.propTypes = {
|
|
170
171
|
sharing: PropTypes.object.isRequired,
|
|
172
|
+
avatarURL: PropTypes.string.isRequired,
|
|
171
173
|
isSharingShortcutCreated: PropTypes.bool.isRequired,
|
|
172
174
|
addSharingLink: PropTypes.string.isRequired,
|
|
173
175
|
onClose: PropTypes.func.isRequired
|
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
import { render } from '@testing-library/react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { useClient } from 'cozy-client';
|
|
4
3
|
import { BreakpointsProvider } from 'cozy-ui/transpiled/react/providers/Breakpoints';
|
|
5
4
|
import { useI18n } from 'twake-i18n';
|
|
6
5
|
import { SharingBannerCozyToCozy } from './PublicBanner';
|
|
7
6
|
jest.mock('twake-i18n');
|
|
8
|
-
jest.mock('cozy-client');
|
|
9
7
|
describe('PublicBanner', function () {
|
|
10
|
-
beforeEach(function () {
|
|
11
|
-
useClient.mockReturnValue({
|
|
12
|
-
options: {
|
|
13
|
-
uri: 'http://cozy.localhost:8080'
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
8
|
it('should not add dangerous html', function () {
|
|
18
9
|
// Given
|
|
19
10
|
useI18n.mockReturnValue({
|
|
@@ -36,6 +27,7 @@ describe('PublicBanner', function () {
|
|
|
36
27
|
|
|
37
28
|
var _render = render( /*#__PURE__*/React.createElement(BreakpointsProvider, null, /*#__PURE__*/React.createElement(SharingBannerCozyToCozy, {
|
|
38
29
|
sharing: sharing,
|
|
30
|
+
avatarURL: "http://cozy.localhost:8080/public/avatar?fallback=initials",
|
|
39
31
|
isSharingShortcutCreated: true,
|
|
40
32
|
addSharingLink: "discoveryLink",
|
|
41
33
|
onClose: function onClose() {}
|
|
@@ -4,6 +4,7 @@ import React, { useState, useCallback } from 'react';
|
|
|
4
4
|
import { SharingBannerByLink, SharingBannerCozyToCozy } from './PublicBanner';
|
|
5
5
|
export var SharingBanner = function SharingBanner(_ref) {
|
|
6
6
|
var sharingInfos = _ref.sharingInfos,
|
|
7
|
+
avatarURL = _ref.avatarURL,
|
|
7
8
|
hideCreateCozyAction = _ref.hideCreateCozyAction;
|
|
8
9
|
|
|
9
10
|
var _useState = useState(true),
|
|
@@ -25,12 +26,14 @@ export var SharingBanner = function SharingBanner(_ref) {
|
|
|
25
26
|
}) : /*#__PURE__*/React.createElement(SharingBannerCozyToCozy, {
|
|
26
27
|
isSharingShortcutCreated: isSharingShortcutCreated,
|
|
27
28
|
sharing: sharing,
|
|
29
|
+
avatarURL: avatarURL,
|
|
28
30
|
addSharingLink: addSharingLink,
|
|
29
31
|
onClose: onClose
|
|
30
32
|
}));
|
|
31
33
|
};
|
|
32
34
|
SharingBanner.propTypes = {
|
|
33
35
|
sharingInfos: PropTypes.object,
|
|
36
|
+
avatarURL: PropTypes.string,
|
|
34
37
|
hideCreateCozyAction: PropTypes.bool
|
|
35
38
|
};
|
|
36
39
|
export default SharingBanner;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { useClient } from 'cozy-client';
|
|
3
4
|
import { SharingBanner } from './components/SharingBanner';
|
|
4
5
|
import { useSharingInfos } from './hooks/useSharingInfos';
|
|
5
6
|
import withLocales from '../../hoc/withLocales';
|
|
@@ -7,9 +8,11 @@ import withLocales from '../../hoc/withLocales';
|
|
|
7
8
|
var Plugin = function Plugin(_ref) {
|
|
8
9
|
var previewPath = _ref.previewPath,
|
|
9
10
|
hideCreateCozyAction = _ref.hideCreateCozyAction;
|
|
11
|
+
var client = useClient();
|
|
10
12
|
var sharingInfos = useSharingInfos(previewPath);
|
|
11
13
|
return /*#__PURE__*/React.createElement(SharingBanner, {
|
|
12
14
|
sharingInfos: sharingInfos,
|
|
15
|
+
avatarURL: "".concat(client.options.uri, "/public/avatar?fallback=initials"),
|
|
13
16
|
hideCreateCozyAction: hideCreateCozyAction
|
|
14
17
|
});
|
|
15
18
|
};
|
package/dist/helpers/contacts.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
1
3
|
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
4
|
+
|
|
5
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
6
|
+
|
|
7
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
8
|
+
|
|
2
9
|
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
3
|
-
import {
|
|
10
|
+
import { models } from 'cozy-client';
|
|
11
|
+
import flag from 'cozy-flags';
|
|
12
|
+
import { validateEmail } from '../helpers/email';
|
|
13
|
+
var ContactModel = models.contact; // Return the contact object if present
|
|
4
14
|
// Else check if a contact exist with this email address, if yes return it
|
|
5
15
|
// if not create it
|
|
6
16
|
|
|
@@ -114,4 +124,72 @@ export var getContactsFromGroupId = function getContactsFromGroupId() {
|
|
|
114
124
|
|
|
115
125
|
export var isContactToBeCreated = function isContactToBeCreated(contact) {
|
|
116
126
|
return !contact._id;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
var isCurrentUser = function isCurrentUser(contact) {
|
|
130
|
+
return contact.me === true;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Computes the contacts and groups to suggest when adding recipients to a
|
|
134
|
+
* sharing. The unreachable contacts only count in the members of the groups,
|
|
135
|
+
* when flag sharing.show-recipient-groups is enabled.
|
|
136
|
+
*
|
|
137
|
+
* @param {object} params
|
|
138
|
+
* @param {object[]} params.reachableContacts - Contacts that can be shared with
|
|
139
|
+
* @param {object[]} params.unreachableContactsWithGroups - Contacts that cannot be shared with but belong to a group
|
|
140
|
+
* @param {object[]} params.contactGroups - Groups of contacts
|
|
141
|
+
* @param {object[]} params.currentRecipients - Recipients already in the sharing
|
|
142
|
+
* @param {object[]} params.pendingRecipients - Recipients picked but not yet shared with
|
|
143
|
+
* @returns {object[]} The contacts, then the groups with their members
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
export var computeContactsAndGroups = function computeContactsAndGroups(_ref3) {
|
|
148
|
+
var reachableContacts = _ref3.reachableContacts,
|
|
149
|
+
unreachableContactsWithGroups = _ref3.unreachableContactsWithGroups,
|
|
150
|
+
contactGroups = _ref3.contactGroups,
|
|
151
|
+
currentRecipients = _ref3.currentRecipients,
|
|
152
|
+
pendingRecipients = _ref3.pendingRecipients;
|
|
153
|
+
var suggestedContacts = reachableContacts.filter(function (contact) {
|
|
154
|
+
return !isCurrentUser(contact) && // We do not want contact already in the sharing
|
|
155
|
+
!currentRecipients.find(function (r) {
|
|
156
|
+
return r.email === ContactModel.getPrimaryEmail(contact);
|
|
157
|
+
}) && // We do not want contact currently in pending contacts (in chips)
|
|
158
|
+
!pendingRecipients.find(function (r) {
|
|
159
|
+
return r._id === contact._id;
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
var unreachableContacts = unreachableContactsWithGroups.filter(function (contact) {
|
|
163
|
+
return !isCurrentUser(contact);
|
|
164
|
+
});
|
|
165
|
+
var currentRecipientGroups = currentRecipients.map(function (_ref4) {
|
|
166
|
+
var id = _ref4.id;
|
|
167
|
+
return id;
|
|
168
|
+
}).filter(Boolean);
|
|
169
|
+
var suggestedGroups = contactGroups.filter(function (_ref5) {
|
|
170
|
+
var _id = _ref5._id;
|
|
171
|
+
return !flag('sharing.show-recipient-groups') || !currentRecipientGroups.includes(_id);
|
|
172
|
+
}).map(function (contactGroup) {
|
|
173
|
+
var reachableMembers = getContactsFromGroupId(suggestedContacts, contactGroup.id).map(function (contact) {
|
|
174
|
+
return _objectSpread(_objectSpread({}, contact), {}, {
|
|
175
|
+
isReachable: true
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (flag('sharing.show-recipient-groups') !== true) {
|
|
180
|
+
return _objectSpread(_objectSpread({}, contactGroup), {}, {
|
|
181
|
+
members: reachableMembers
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
var unreachableMembers = getContactsFromGroupId(unreachableContacts, contactGroup.id).map(function (contact) {
|
|
186
|
+
return _objectSpread(_objectSpread({}, contact), {}, {
|
|
187
|
+
isReachable: false
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
return _objectSpread(_objectSpread({}, contactGroup), {}, {
|
|
191
|
+
members: [].concat(_toConsumableArray(reachableMembers), _toConsumableArray(unreachableMembers))
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
return [].concat(_toConsumableArray(suggestedContacts), _toConsumableArray(suggestedGroups));
|
|
117
195
|
};
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
1
3
|
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
4
|
+
|
|
5
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
6
|
+
|
|
7
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
8
|
+
|
|
2
9
|
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
3
|
-
import
|
|
10
|
+
import flag from 'cozy-flags';
|
|
11
|
+
import { computeContactsAndGroups, getOrCreateFromArray } from './contacts';
|
|
12
|
+
jest.mock('cozy-flags', function () {
|
|
13
|
+
return {
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: jest.fn(function () {
|
|
16
|
+
return null;
|
|
17
|
+
})
|
|
18
|
+
};
|
|
19
|
+
});
|
|
4
20
|
var findMock = jest.fn().mockReturnValueOnce({
|
|
5
21
|
data: [{
|
|
6
22
|
id: 2,
|
|
@@ -63,4 +79,109 @@ describe('getOrCreateFromArray', function () {
|
|
|
63
79
|
}
|
|
64
80
|
}, _callee);
|
|
65
81
|
})));
|
|
82
|
+
});
|
|
83
|
+
describe('computeContactsAndGroups', function () {
|
|
84
|
+
var group = {
|
|
85
|
+
id: 'friends',
|
|
86
|
+
_id: 'friends',
|
|
87
|
+
name: 'Friends'
|
|
88
|
+
};
|
|
89
|
+
var inGroup = {
|
|
90
|
+
relationships: {
|
|
91
|
+
groups: {
|
|
92
|
+
data: [{
|
|
93
|
+
_id: 'friends'
|
|
94
|
+
}]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
var alice = _objectSpread({
|
|
100
|
+
_id: 'alice',
|
|
101
|
+
email: [{
|
|
102
|
+
address: 'alice@cozy.cc',
|
|
103
|
+
primary: true
|
|
104
|
+
}]
|
|
105
|
+
}, inGroup);
|
|
106
|
+
|
|
107
|
+
var bob = _objectSpread({
|
|
108
|
+
_id: 'bob',
|
|
109
|
+
email: [{
|
|
110
|
+
address: 'bob@cozy.cc'
|
|
111
|
+
}]
|
|
112
|
+
}, inGroup);
|
|
113
|
+
|
|
114
|
+
var me = _objectSpread({
|
|
115
|
+
_id: 'me',
|
|
116
|
+
me: true
|
|
117
|
+
}, inGroup);
|
|
118
|
+
|
|
119
|
+
var carol = _objectSpread({
|
|
120
|
+
_id: 'carol'
|
|
121
|
+
}, inGroup);
|
|
122
|
+
|
|
123
|
+
var setup = function setup() {
|
|
124
|
+
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
125
|
+
_ref2$currentRecipien = _ref2.currentRecipients,
|
|
126
|
+
currentRecipients = _ref2$currentRecipien === void 0 ? [] : _ref2$currentRecipien,
|
|
127
|
+
_ref2$pendingRecipien = _ref2.pendingRecipients,
|
|
128
|
+
pendingRecipients = _ref2$pendingRecipien === void 0 ? [] : _ref2$pendingRecipien,
|
|
129
|
+
_ref2$showRecipientGr = _ref2.showRecipientGroups,
|
|
130
|
+
showRecipientGroups = _ref2$showRecipientGr === void 0 ? null : _ref2$showRecipientGr;
|
|
131
|
+
|
|
132
|
+
flag.mockReturnValue(showRecipientGroups);
|
|
133
|
+
return computeContactsAndGroups({
|
|
134
|
+
reachableContacts: [alice, bob, me],
|
|
135
|
+
unreachableContactsWithGroups: [carol, me],
|
|
136
|
+
contactGroups: [group],
|
|
137
|
+
currentRecipients: currentRecipients,
|
|
138
|
+
pendingRecipients: pendingRecipients
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
it('suggests the contacts, then the groups with their reachable members', function () {
|
|
143
|
+
var _setup = setup(),
|
|
144
|
+
_setup2 = _slicedToArray(_setup, 3),
|
|
145
|
+
first = _setup2[0],
|
|
146
|
+
second = _setup2[1],
|
|
147
|
+
friends = _setup2[2];
|
|
148
|
+
|
|
149
|
+
expect([first._id, second._id, friends._id]).toEqual(['alice', 'bob', 'friends']);
|
|
150
|
+
expect(friends.members.map(function (m) {
|
|
151
|
+
return [m._id, m.isReachable];
|
|
152
|
+
})).toEqual([['alice', true], ['bob', true]]);
|
|
153
|
+
});
|
|
154
|
+
it('does not suggest the recipients already shared with or picked', function () {
|
|
155
|
+
var contactsAndGroups = setup({
|
|
156
|
+
currentRecipients: [{
|
|
157
|
+
email: 'alice@cozy.cc'
|
|
158
|
+
}],
|
|
159
|
+
pendingRecipients: [bob]
|
|
160
|
+
});
|
|
161
|
+
expect(contactsAndGroups.map(function (c) {
|
|
162
|
+
return c._id;
|
|
163
|
+
})).toEqual(['friends']);
|
|
164
|
+
});
|
|
165
|
+
it('adds the unreachable members when flag sharing.show-recipient-groups is enabled', function () {
|
|
166
|
+
var _setup3 = setup({
|
|
167
|
+
showRecipientGroups: true
|
|
168
|
+
}),
|
|
169
|
+
_setup4 = _slicedToArray(_setup3, 3),
|
|
170
|
+
friends = _setup4[2];
|
|
171
|
+
|
|
172
|
+
expect(friends.members.map(function (m) {
|
|
173
|
+
return [m._id, m.isReachable];
|
|
174
|
+
})).toEqual([['alice', true], ['bob', true], ['carol', false]]);
|
|
175
|
+
});
|
|
176
|
+
it('does not suggest the groups already in the sharing when flag sharing.show-recipient-groups is enabled', function () {
|
|
177
|
+
var contactsAndGroups = setup({
|
|
178
|
+
currentRecipients: [{
|
|
179
|
+
id: 'friends'
|
|
180
|
+
}],
|
|
181
|
+
showRecipientGroups: true
|
|
182
|
+
});
|
|
183
|
+
expect(contactsAndGroups.map(function (c) {
|
|
184
|
+
return c._id;
|
|
185
|
+
})).toEqual(['alice', 'bob']);
|
|
186
|
+
});
|
|
66
187
|
});
|
|
@@ -20,8 +20,8 @@ flowchart TD
|
|
|
20
20
|
SharingContent --> ShareByEmail
|
|
21
21
|
ConfirmTrustedRecipientsDialog --> DumbConfirmTrustedRecipientsDialog
|
|
22
22
|
DumbConfirmTrustedRecipientsDialog --> ShareDialogTwoStepsConfirmationContainer
|
|
23
|
-
ShareByEmail -->
|
|
24
|
-
|
|
23
|
+
ShareByEmail --> ShareAutosuggestWrapper
|
|
24
|
+
ShareAutosuggestWrapper --> ShareAutosuggest
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
### Specific case of `ShareDialogTwoStepsConfirmationContainer`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-sharing",
|
|
3
|
-
"version": "40.1.
|
|
3
|
+
"version": "40.1.8",
|
|
4
4
|
"description": "Provides sharing login for React applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "Cozy",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"cozy-intent": "^2.31.1",
|
|
59
59
|
"cozy-minilog": "^3.10.1",
|
|
60
60
|
"cozy-ui": "^140.5.0",
|
|
61
|
-
"cozy-ui-plus": "^14.1.
|
|
61
|
+
"cozy-ui-plus": "^14.1.3",
|
|
62
62
|
"jest": "30.3.0",
|
|
63
63
|
"jest-environment-jsdom": "30.3.0",
|
|
64
64
|
"react": "16.12.0",
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"sideEffects": [
|
|
85
85
|
"*.css"
|
|
86
86
|
],
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "3d97a76d7d6b7a07f27fa4d25b17b26474e1fdc0"
|
|
88
88
|
}
|
|
@@ -61,6 +61,7 @@ jest.mock('cozy-ui/transpiled/react/providers/Alert', () => ({
|
|
|
61
61
|
jest.mock('../../hoc/withLocales', () => Component => Component)
|
|
62
62
|
|
|
63
63
|
jest.mock('../../helpers/contacts', () => ({
|
|
64
|
+
...jest.requireActual('../../helpers/contacts'),
|
|
64
65
|
getOrCreateFromArray: jest.fn()
|
|
65
66
|
}))
|
|
66
67
|
|
|
@@ -2,27 +2,22 @@ import PropTypes from 'prop-types'
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
|
|
4
4
|
import { useQueryAll, isQueryLoading } from 'cozy-client'
|
|
5
|
-
import { models } from 'cozy-client'
|
|
6
5
|
import flag from 'cozy-flags'
|
|
7
6
|
|
|
8
7
|
import ShareAutosuggest from './ShareAutosuggest'
|
|
9
|
-
import {
|
|
8
|
+
import { computeContactsAndGroups } from '../helpers/contacts'
|
|
10
9
|
import {
|
|
11
10
|
buildReachableContactsQuery,
|
|
12
11
|
buildContactGroupsQuery,
|
|
13
12
|
buildUnreachableContactsWithGroupsQuery
|
|
14
13
|
} from '../queries/queries'
|
|
15
14
|
|
|
16
|
-
const ContactModel = models.contact
|
|
17
|
-
|
|
18
15
|
/**
|
|
19
|
-
*
|
|
16
|
+
* ShareAutosuggestWrapper is responsible for fetching contacts and groups.
|
|
20
17
|
* We retrieve all the contacts that are reachable and the groups they belong to.
|
|
21
18
|
* In order to display the total number of members in each group, we also retrieve the contacts that are not reachable.
|
|
22
19
|
*/
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const ShareRecipientsInput = ({
|
|
20
|
+
const ShareAutosuggestWrapper = ({
|
|
26
21
|
currentRecipients,
|
|
27
22
|
recipients,
|
|
28
23
|
placeholder,
|
|
@@ -64,60 +59,14 @@ const ShareRecipientsInput = ({
|
|
|
64
59
|
(isQueryLoading(unreachableContactsWithGroupsResult) ||
|
|
65
60
|
unreachableContactsWithGroupsResult.hasMore))
|
|
66
61
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
!recipients.find(r => r._id === contact._id)
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
const unreachableContactsWithGroups = (
|
|
79
|
-
unreachableContactsWithGroupsResult.data || []
|
|
80
|
-
).filter(contact => !isCurrentUser(contact))
|
|
81
|
-
const currentRecipientGroups = currentRecipients
|
|
82
|
-
.map(({ id }) => id)
|
|
83
|
-
.filter(Boolean)
|
|
84
|
-
const contactGroups = (contactGroupsResult.data || [])
|
|
85
|
-
.filter(
|
|
86
|
-
({ _id }) =>
|
|
87
|
-
!flag('sharing.show-recipient-groups') ||
|
|
88
|
-
!currentRecipientGroups.includes(_id)
|
|
89
|
-
)
|
|
90
|
-
.map(contactGroup => {
|
|
91
|
-
const reachableMembers = getContactsFromGroupId(
|
|
92
|
-
reachableContacts,
|
|
93
|
-
contactGroup.id
|
|
94
|
-
).map(contact => ({
|
|
95
|
-
...contact,
|
|
96
|
-
isReachable: true
|
|
97
|
-
}))
|
|
98
|
-
|
|
99
|
-
if (flag('sharing.show-recipient-groups') !== true) {
|
|
100
|
-
return {
|
|
101
|
-
...contactGroup,
|
|
102
|
-
members: reachableMembers
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const unreachableMembers = getContactsFromGroupId(
|
|
107
|
-
unreachableContactsWithGroups,
|
|
108
|
-
contactGroup.id
|
|
109
|
-
).map(contact => ({
|
|
110
|
-
...contact,
|
|
111
|
-
isReachable: false
|
|
112
|
-
}))
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
...contactGroup,
|
|
116
|
-
members: [...reachableMembers, ...unreachableMembers]
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
const contactsAndGroups = [...reachableContacts, ...contactGroups]
|
|
62
|
+
const contactsAndGroups = computeContactsAndGroups({
|
|
63
|
+
reachableContacts: reachableContactsResult.data || [],
|
|
64
|
+
unreachableContactsWithGroups:
|
|
65
|
+
unreachableContactsWithGroupsResult.data || [],
|
|
66
|
+
contactGroups: contactGroupsResult.data || [],
|
|
67
|
+
currentRecipients,
|
|
68
|
+
pendingRecipients: recipients
|
|
69
|
+
})
|
|
121
70
|
|
|
122
71
|
return (
|
|
123
72
|
<ShareAutosuggest
|
|
@@ -135,7 +84,7 @@ const ShareRecipientsInput = ({
|
|
|
135
84
|
)
|
|
136
85
|
}
|
|
137
86
|
|
|
138
|
-
|
|
87
|
+
ShareAutosuggestWrapper.propTypes = {
|
|
139
88
|
currentRecipients: PropTypes.array,
|
|
140
89
|
recipients: PropTypes.array,
|
|
141
90
|
placeholder: PropTypes.string,
|
|
@@ -147,9 +96,9 @@ ShareRecipientsInput.propTypes = {
|
|
|
147
96
|
endAdornment: PropTypes.node
|
|
148
97
|
}
|
|
149
98
|
|
|
150
|
-
|
|
99
|
+
ShareAutosuggestWrapper.defaultProps = {
|
|
151
100
|
currentRecipients: [],
|
|
152
101
|
recipients: []
|
|
153
102
|
}
|
|
154
103
|
|
|
155
|
-
export default
|
|
104
|
+
export default ShareAutosuggestWrapper
|
|
@@ -4,7 +4,7 @@ import React from 'react'
|
|
|
4
4
|
import { createMockClient } from 'cozy-client'
|
|
5
5
|
import flag from 'cozy-flags'
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import ShareAutosuggestWrapper from './ShareAutosuggestWrapper'
|
|
8
8
|
import AppLike from './SharingBanner/test/AppLike'
|
|
9
9
|
|
|
10
10
|
jest.mock('cozy-flags', () => ({
|
|
@@ -37,7 +37,7 @@ jest.mock('./ShareAutosuggest', () => ({
|
|
|
37
37
|
}
|
|
38
38
|
}))
|
|
39
39
|
|
|
40
|
-
describe('
|
|
40
|
+
describe('ShareAutosuggestWrapper component', () => {
|
|
41
41
|
const setup = (
|
|
42
42
|
{ contacts, contactGroups, unreachableContact },
|
|
43
43
|
{ currentRecipients, recipients } = {}
|
|
@@ -60,7 +60,7 @@ describe('ShareRecipientsInput component', () => {
|
|
|
60
60
|
})
|
|
61
61
|
render(
|
|
62
62
|
<AppLike client={mockClient}>
|
|
63
|
-
<
|
|
63
|
+
<ShareAutosuggestWrapper
|
|
64
64
|
onPick={() => {}}
|
|
65
65
|
onRemove={() => {}}
|
|
66
66
|
placeholder="Enter recipients here"
|
|
@@ -4,7 +4,7 @@ import React from 'react'
|
|
|
4
4
|
import flag from 'cozy-flags'
|
|
5
5
|
import { useI18n } from 'twake-i18n'
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import ShareAutosuggestWrapper from './ShareAutosuggestWrapper'
|
|
8
8
|
import ShareTypeSelect from './Sharetypeselect'
|
|
9
9
|
import {
|
|
10
10
|
mergeRecipients,
|
|
@@ -60,7 +60,7 @@ export const ShareByEmail = ({
|
|
|
60
60
|
return (
|
|
61
61
|
<div className={styles['coz-form-group']}>
|
|
62
62
|
<div className={styles['coz-form']}>
|
|
63
|
-
<
|
|
63
|
+
<ShareAutosuggestWrapper
|
|
64
64
|
placeholder={
|
|
65
65
|
pendingRecipients.length === 0
|
|
66
66
|
? t(`${documentType}.share.shareByEmail.emailPlaceholder`)
|
|
@@ -3,7 +3,6 @@ import React from 'react'
|
|
|
3
3
|
import snarkdown from 'snarkdown'
|
|
4
4
|
|
|
5
5
|
import { Icon, SyncCozy, ToTheCloud } from '@linagora/twake-icons'
|
|
6
|
-
import { useClient } from 'cozy-client'
|
|
7
6
|
import flag from 'cozy-flags'
|
|
8
7
|
import Alert from 'cozy-ui/transpiled/react/Alert'
|
|
9
8
|
import Avatar from 'cozy-ui/transpiled/react/Avatar'
|
|
@@ -17,11 +16,9 @@ import styles from '../../../styles/publicBanner.styl'
|
|
|
17
16
|
const getPublicNameFromSharing = sharing =>
|
|
18
17
|
sharing.attributes.members[0].public_name
|
|
19
18
|
|
|
20
|
-
const PublicBannerCozyToCozyContent = ({ sharing }) => {
|
|
19
|
+
const PublicBannerCozyToCozyContent = ({ sharing, avatarURL }) => {
|
|
21
20
|
const { t } = useI18n()
|
|
22
|
-
const client = useClient()
|
|
23
21
|
const name = getPublicNameFromSharing(sharing)
|
|
24
|
-
const avatarURL = `${client.options.uri}/public/avatar?fallback=initials`
|
|
25
22
|
|
|
26
23
|
const withAvatar = snarkdown(
|
|
27
24
|
t('Share.banner.shared_from', {
|
|
@@ -43,13 +40,15 @@ const PublicBannerCozyToCozyContent = ({ sharing }) => {
|
|
|
43
40
|
)
|
|
44
41
|
}
|
|
45
42
|
PublicBannerCozyToCozyContent.propTypes = {
|
|
46
|
-
sharing: PropTypes.object.isRequired
|
|
43
|
+
sharing: PropTypes.object.isRequired,
|
|
44
|
+
avatarURL: PropTypes.string.isRequired
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
const openExternalLink = url => (window.location = url)
|
|
50
48
|
|
|
51
49
|
const SharingBannerCozyToCozy = ({
|
|
52
50
|
sharing,
|
|
51
|
+
avatarURL,
|
|
53
52
|
isSharingShortcutCreated,
|
|
54
53
|
addSharingLink,
|
|
55
54
|
onClose
|
|
@@ -90,7 +89,7 @@ const SharingBannerCozyToCozy = ({
|
|
|
90
89
|
</>
|
|
91
90
|
}
|
|
92
91
|
>
|
|
93
|
-
<PublicBannerCozyToCozyContent sharing={sharing} />
|
|
92
|
+
<PublicBannerCozyToCozyContent sharing={sharing} avatarURL={avatarURL} />
|
|
94
93
|
</Alert>
|
|
95
94
|
)
|
|
96
95
|
}
|
|
@@ -162,6 +161,7 @@ SharingBannerByLink.propTypes = {
|
|
|
162
161
|
}
|
|
163
162
|
SharingBannerCozyToCozy.propTypes = {
|
|
164
163
|
sharing: PropTypes.object.isRequired,
|
|
164
|
+
avatarURL: PropTypes.string.isRequired,
|
|
165
165
|
isSharingShortcutCreated: PropTypes.bool.isRequired,
|
|
166
166
|
addSharingLink: PropTypes.string.isRequired,
|
|
167
167
|
onClose: PropTypes.func.isRequired
|
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import { render } from '@testing-library/react'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
|
|
4
|
-
import { useClient } from 'cozy-client'
|
|
5
4
|
import { BreakpointsProvider } from 'cozy-ui/transpiled/react/providers/Breakpoints'
|
|
6
5
|
import { useI18n } from 'twake-i18n'
|
|
7
6
|
|
|
8
7
|
import { SharingBannerCozyToCozy } from './PublicBanner'
|
|
9
8
|
|
|
10
9
|
jest.mock('twake-i18n')
|
|
11
|
-
jest.mock('cozy-client')
|
|
12
10
|
|
|
13
11
|
describe('PublicBanner', () => {
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
useClient.mockReturnValue({
|
|
16
|
-
options: { uri: 'http://cozy.localhost:8080' }
|
|
17
|
-
})
|
|
18
|
-
})
|
|
19
|
-
|
|
20
12
|
it('should not add dangerous html', () => {
|
|
21
13
|
// Given
|
|
22
14
|
useI18n.mockReturnValue({
|
|
@@ -35,6 +27,7 @@ describe('PublicBanner', () => {
|
|
|
35
27
|
<BreakpointsProvider>
|
|
36
28
|
<SharingBannerCozyToCozy
|
|
37
29
|
sharing={sharing}
|
|
30
|
+
avatarURL="http://cozy.localhost:8080/public/avatar?fallback=initials"
|
|
38
31
|
isSharingShortcutCreated={true}
|
|
39
32
|
addSharingLink="discoveryLink"
|
|
40
33
|
onClose={() => {}}
|
|
@@ -3,7 +3,11 @@ import React, { useState, useCallback } from 'react'
|
|
|
3
3
|
|
|
4
4
|
import { SharingBannerByLink, SharingBannerCozyToCozy } from './PublicBanner'
|
|
5
5
|
|
|
6
|
-
export const SharingBanner = ({
|
|
6
|
+
export const SharingBanner = ({
|
|
7
|
+
sharingInfos,
|
|
8
|
+
avatarURL,
|
|
9
|
+
hideCreateCozyAction
|
|
10
|
+
}) => {
|
|
7
11
|
const [isOpened, setIsOpened] = useState(true)
|
|
8
12
|
const onClose = useCallback(() => setIsOpened(false), [setIsOpened])
|
|
9
13
|
|
|
@@ -22,6 +26,7 @@ export const SharingBanner = ({ sharingInfos, hideCreateCozyAction }) => {
|
|
|
22
26
|
<SharingBannerCozyToCozy
|
|
23
27
|
isSharingShortcutCreated={isSharingShortcutCreated}
|
|
24
28
|
sharing={sharing}
|
|
29
|
+
avatarURL={avatarURL}
|
|
25
30
|
addSharingLink={addSharingLink}
|
|
26
31
|
onClose={onClose}
|
|
27
32
|
/>
|
|
@@ -31,6 +36,7 @@ export const SharingBanner = ({ sharingInfos, hideCreateCozyAction }) => {
|
|
|
31
36
|
|
|
32
37
|
SharingBanner.propTypes = {
|
|
33
38
|
sharingInfos: PropTypes.object,
|
|
39
|
+
avatarURL: PropTypes.string,
|
|
34
40
|
hideCreateCozyAction: PropTypes.bool
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import PropTypes from 'prop-types'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
|
|
4
|
+
import { useClient } from 'cozy-client'
|
|
5
|
+
|
|
4
6
|
import { SharingBanner } from './components/SharingBanner'
|
|
5
7
|
import { useSharingInfos } from './hooks/useSharingInfos'
|
|
6
8
|
import withLocales from '../../hoc/withLocales'
|
|
7
9
|
|
|
8
10
|
const Plugin = ({ previewPath, hideCreateCozyAction }) => {
|
|
11
|
+
const client = useClient()
|
|
9
12
|
const sharingInfos = useSharingInfos(previewPath)
|
|
10
13
|
return (
|
|
11
14
|
<SharingBanner
|
|
12
15
|
sharingInfos={sharingInfos}
|
|
16
|
+
avatarURL={`${client.options.uri}/public/avatar?fallback=initials`}
|
|
13
17
|
hideCreateCozyAction={hideCreateCozyAction}
|
|
14
18
|
/>
|
|
15
19
|
)
|
package/src/helpers/contacts.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { models } from 'cozy-client'
|
|
2
|
+
import flag from 'cozy-flags'
|
|
3
|
+
|
|
1
4
|
import { validateEmail } from '../helpers/email'
|
|
2
5
|
|
|
6
|
+
const ContactModel = models.contact
|
|
7
|
+
|
|
3
8
|
// Return the contact object if present
|
|
4
9
|
// Else check if a contact exist with this email address, if yes return it
|
|
5
10
|
// if not create it
|
|
@@ -59,3 +64,81 @@ export const getContactsFromGroupId = (contacts = [], groupId) => {
|
|
|
59
64
|
export const isContactToBeCreated = contact => {
|
|
60
65
|
return !contact._id
|
|
61
66
|
}
|
|
67
|
+
|
|
68
|
+
const isCurrentUser = contact => contact.me === true
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Computes the contacts and groups to suggest when adding recipients to a
|
|
72
|
+
* sharing. The unreachable contacts only count in the members of the groups,
|
|
73
|
+
* when flag sharing.show-recipient-groups is enabled.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} params
|
|
76
|
+
* @param {object[]} params.reachableContacts - Contacts that can be shared with
|
|
77
|
+
* @param {object[]} params.unreachableContactsWithGroups - Contacts that cannot be shared with but belong to a group
|
|
78
|
+
* @param {object[]} params.contactGroups - Groups of contacts
|
|
79
|
+
* @param {object[]} params.currentRecipients - Recipients already in the sharing
|
|
80
|
+
* @param {object[]} params.pendingRecipients - Recipients picked but not yet shared with
|
|
81
|
+
* @returns {object[]} The contacts, then the groups with their members
|
|
82
|
+
*/
|
|
83
|
+
export const computeContactsAndGroups = ({
|
|
84
|
+
reachableContacts,
|
|
85
|
+
unreachableContactsWithGroups,
|
|
86
|
+
contactGroups,
|
|
87
|
+
currentRecipients,
|
|
88
|
+
pendingRecipients
|
|
89
|
+
}) => {
|
|
90
|
+
const suggestedContacts = reachableContacts.filter(
|
|
91
|
+
contact =>
|
|
92
|
+
!isCurrentUser(contact) &&
|
|
93
|
+
// We do not want contact already in the sharing
|
|
94
|
+
!currentRecipients.find(
|
|
95
|
+
r => r.email === ContactModel.getPrimaryEmail(contact)
|
|
96
|
+
) &&
|
|
97
|
+
// We do not want contact currently in pending contacts (in chips)
|
|
98
|
+
!pendingRecipients.find(r => r._id === contact._id)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const unreachableContacts = unreachableContactsWithGroups.filter(
|
|
102
|
+
contact => !isCurrentUser(contact)
|
|
103
|
+
)
|
|
104
|
+
const currentRecipientGroups = currentRecipients
|
|
105
|
+
.map(({ id }) => id)
|
|
106
|
+
.filter(Boolean)
|
|
107
|
+
const suggestedGroups = contactGroups
|
|
108
|
+
.filter(
|
|
109
|
+
({ _id }) =>
|
|
110
|
+
!flag('sharing.show-recipient-groups') ||
|
|
111
|
+
!currentRecipientGroups.includes(_id)
|
|
112
|
+
)
|
|
113
|
+
.map(contactGroup => {
|
|
114
|
+
const reachableMembers = getContactsFromGroupId(
|
|
115
|
+
suggestedContacts,
|
|
116
|
+
contactGroup.id
|
|
117
|
+
).map(contact => ({
|
|
118
|
+
...contact,
|
|
119
|
+
isReachable: true
|
|
120
|
+
}))
|
|
121
|
+
|
|
122
|
+
if (flag('sharing.show-recipient-groups') !== true) {
|
|
123
|
+
return {
|
|
124
|
+
...contactGroup,
|
|
125
|
+
members: reachableMembers
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const unreachableMembers = getContactsFromGroupId(
|
|
130
|
+
unreachableContacts,
|
|
131
|
+
contactGroup.id
|
|
132
|
+
).map(contact => ({
|
|
133
|
+
...contact,
|
|
134
|
+
isReachable: false
|
|
135
|
+
}))
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
...contactGroup,
|
|
139
|
+
members: [...reachableMembers, ...unreachableMembers]
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
return [...suggestedContacts, ...suggestedGroups]
|
|
144
|
+
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import flag from 'cozy-flags'
|
|
2
|
+
|
|
3
|
+
import { computeContactsAndGroups, getOrCreateFromArray } from './contacts'
|
|
4
|
+
|
|
5
|
+
jest.mock('cozy-flags', () => ({
|
|
6
|
+
__esModule: true,
|
|
7
|
+
default: jest.fn(() => null)
|
|
8
|
+
}))
|
|
2
9
|
|
|
3
10
|
const findMock = jest
|
|
4
11
|
.fn()
|
|
@@ -51,3 +58,73 @@ describe('getOrCreateFromArray', () => {
|
|
|
51
58
|
])
|
|
52
59
|
})
|
|
53
60
|
})
|
|
61
|
+
|
|
62
|
+
describe('computeContactsAndGroups', () => {
|
|
63
|
+
const group = { id: 'friends', _id: 'friends', name: 'Friends' }
|
|
64
|
+
const inGroup = { relationships: { groups: { data: [{ _id: 'friends' }] } } }
|
|
65
|
+
const alice = {
|
|
66
|
+
_id: 'alice',
|
|
67
|
+
email: [{ address: 'alice@cozy.cc', primary: true }],
|
|
68
|
+
...inGroup
|
|
69
|
+
}
|
|
70
|
+
const bob = { _id: 'bob', email: [{ address: 'bob@cozy.cc' }], ...inGroup }
|
|
71
|
+
const me = { _id: 'me', me: true, ...inGroup }
|
|
72
|
+
const carol = { _id: 'carol', ...inGroup }
|
|
73
|
+
|
|
74
|
+
const setup = ({
|
|
75
|
+
currentRecipients = [],
|
|
76
|
+
pendingRecipients = [],
|
|
77
|
+
showRecipientGroups = null
|
|
78
|
+
} = {}) => {
|
|
79
|
+
flag.mockReturnValue(showRecipientGroups)
|
|
80
|
+
return computeContactsAndGroups({
|
|
81
|
+
reachableContacts: [alice, bob, me],
|
|
82
|
+
unreachableContactsWithGroups: [carol, me],
|
|
83
|
+
contactGroups: [group],
|
|
84
|
+
currentRecipients,
|
|
85
|
+
pendingRecipients
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
it('suggests the contacts, then the groups with their reachable members', () => {
|
|
90
|
+
const [first, second, friends] = setup()
|
|
91
|
+
|
|
92
|
+
expect([first._id, second._id, friends._id]).toEqual([
|
|
93
|
+
'alice',
|
|
94
|
+
'bob',
|
|
95
|
+
'friends'
|
|
96
|
+
])
|
|
97
|
+
expect(friends.members.map(m => [m._id, m.isReachable])).toEqual([
|
|
98
|
+
['alice', true],
|
|
99
|
+
['bob', true]
|
|
100
|
+
])
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('does not suggest the recipients already shared with or picked', () => {
|
|
104
|
+
const contactsAndGroups = setup({
|
|
105
|
+
currentRecipients: [{ email: 'alice@cozy.cc' }],
|
|
106
|
+
pendingRecipients: [bob]
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
expect(contactsAndGroups.map(c => c._id)).toEqual(['friends'])
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('adds the unreachable members when flag sharing.show-recipient-groups is enabled', () => {
|
|
113
|
+
const [, , friends] = setup({ showRecipientGroups: true })
|
|
114
|
+
|
|
115
|
+
expect(friends.members.map(m => [m._id, m.isReachable])).toEqual([
|
|
116
|
+
['alice', true],
|
|
117
|
+
['bob', true],
|
|
118
|
+
['carol', false]
|
|
119
|
+
])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('does not suggest the groups already in the sharing when flag sharing.show-recipient-groups is enabled', () => {
|
|
123
|
+
const contactsAndGroups = setup({
|
|
124
|
+
currentRecipients: [{ id: 'friends' }],
|
|
125
|
+
showRecipientGroups: true
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
expect(contactsAndGroups.map(c => c._id)).toEqual(['alice', 'bob'])
|
|
129
|
+
})
|
|
130
|
+
})
|