cozy-sharing 33.0.0 → 33.0.2
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 +12 -0
- package/dist/components/FederatedFolder/FederatedFolderModal.js +2 -1
- package/dist/components/ShareAutosuggest.js +20 -10
- package/dist/components/ShareAutosuggest.spec.js +26 -2
- package/dist/components/ShareByEmail.js +3 -0
- package/dist/components/ShareByEmail.spec.js +2 -1
- package/dist/components/ShareDialogCozyToCozy.js +2 -1
- package/dist/components/ShareDialogCozyToCozy.spec.js +2 -2
- package/dist/components/ShareRecipientsInput.js +12 -1
- package/dist/components/ShareRecipientsInput.spec.js +68 -5
- package/dist/helpers/contacts.js +12 -0
- package/dist/suggestionMatchers.js +5 -1
- package/package.json +2 -2
- package/src/components/FederatedFolder/FederatedFolderModal.jsx +1 -0
- package/src/components/ShareAutosuggest.jsx +24 -27
- package/src/components/ShareAutosuggest.spec.jsx +20 -1
- package/src/components/ShareByEmail.jsx +3 -0
- package/src/components/ShareByEmail.spec.jsx +1 -0
- package/src/components/ShareDialogCozyToCozy.jsx +1 -0
- package/src/components/ShareDialogCozyToCozy.spec.jsx +1 -0
- package/src/components/ShareRecipientsInput.jsx +15 -1
- package/src/components/ShareRecipientsInput.spec.jsx +44 -1
- package/src/helpers/contacts.js +12 -0
- package/src/suggestionMatchers.js +16 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
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
|
+
## [33.0.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.0.1...cozy-sharing@33.0.2) (2026-06-02)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- Avoid suggesting an already added or a pending recipient ([a9f7b19](https://github.com/cozy/cozy-libs/commit/a9f7b1934ef9d666b9515fe1cf28538a466bf1ff))
|
|
11
|
+
|
|
12
|
+
## [33.0.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.0.0...cozy-sharing@33.0.1) (2026-06-02)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- Disable new contact creation for shared drives forms ([08abf8a](https://github.com/cozy/cozy-libs/commit/08abf8a21a8d37db190866d68a73744b8bcbf0aa))
|
|
17
|
+
|
|
6
18
|
# [33.0.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@32.3.2...cozy-sharing@33.0.0) (2026-06-02)
|
|
7
19
|
|
|
8
20
|
### Bug Fixes
|
|
@@ -258,7 +258,8 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
|
|
|
258
258
|
pendingRecipients: pendingRecipients,
|
|
259
259
|
onPendingRecipientsChange: setPendingRecipients,
|
|
260
260
|
selectedOption: selectedOption,
|
|
261
|
-
onSelectedOptionChange: setSelectedOption
|
|
261
|
+
onSelectedOptionChange: setSelectedOption,
|
|
262
|
+
enableCreateContact: true
|
|
262
263
|
})), /*#__PURE__*/React.createElement(WhoHasAccess, {
|
|
263
264
|
isOwner: true,
|
|
264
265
|
isSharedDrive: true,
|
|
@@ -8,6 +8,7 @@ import Chip from 'cozy-ui/transpiled/react/Chips';
|
|
|
8
8
|
import { Spinner } from 'cozy-ui/transpiled/react/Spinner';
|
|
9
9
|
import { GroupAvatar } from './Avatar/GroupAvatar';
|
|
10
10
|
import ContactSuggestion from './ContactSuggestion';
|
|
11
|
+
import { isContactToBeCreated } from '../helpers/contacts';
|
|
11
12
|
import { extractEmails, validateEmail } from '../helpers/email';
|
|
12
13
|
import { getDisplayName, getInitials, Contact, Group } from '../models';
|
|
13
14
|
var styles = {
|
|
@@ -22,15 +23,16 @@ var styles = {
|
|
|
22
23
|
"recipientChip": "autosuggest__recipientChip___d5I3R",
|
|
23
24
|
"endAdornment": "autosuggest__endAdornment___2EwTN"
|
|
24
25
|
};
|
|
25
|
-
import {
|
|
26
|
+
import { contactOrGroupMatch } from '../suggestionMatchers';
|
|
26
27
|
|
|
27
|
-
var
|
|
28
|
+
var ShareAutosuggest = function ShareAutosuggest(_ref) {
|
|
28
29
|
var loading = _ref.loading,
|
|
29
30
|
disabled = _ref.disabled,
|
|
30
31
|
contactsAndGroups = _ref.contactsAndGroups,
|
|
31
32
|
recipients = _ref.recipients,
|
|
32
33
|
onPick = _ref.onPick,
|
|
33
34
|
onRemove = _ref.onRemove,
|
|
35
|
+
enableCreateContact = _ref.enableCreateContact,
|
|
34
36
|
placeholder = _ref.placeholder,
|
|
35
37
|
endAdornment = _ref.endAdornment;
|
|
36
38
|
|
|
@@ -60,18 +62,24 @@ var ShareAutocomplete = function ShareAutocomplete(_ref) {
|
|
|
60
62
|
var computeSuggestions = function computeSuggestions(value) {
|
|
61
63
|
var inputValue = value.trim().toLowerCase();
|
|
62
64
|
return inputValue.length === 0 ? [] : contactsAndGroups.filter(function (contactOrGroup) {
|
|
63
|
-
return
|
|
65
|
+
return contactOrGroupMatch(inputValue, contactOrGroup);
|
|
64
66
|
});
|
|
65
67
|
};
|
|
66
68
|
|
|
67
69
|
var onSuggestionsFetchRequested = function onSuggestionsFetchRequested(_ref2) {
|
|
68
70
|
var value = _ref2.value;
|
|
69
71
|
var computedSuggestions = computeSuggestions(value);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
72
|
+
|
|
73
|
+
if (computedSuggestions.length > 0) {
|
|
74
|
+
setSuggestions(computedSuggestions);
|
|
75
|
+
} else if (enableCreateContact && validateEmail(value)) {
|
|
76
|
+
setSuggestions([{
|
|
77
|
+
email: value,
|
|
78
|
+
_type: Contact.doctype
|
|
79
|
+
}]);
|
|
80
|
+
} else {
|
|
81
|
+
setSuggestions([]);
|
|
82
|
+
}
|
|
75
83
|
};
|
|
76
84
|
|
|
77
85
|
var onSuggestionsClearRequested = function onSuggestionsClearRequested() {
|
|
@@ -142,6 +150,7 @@ var ShareAutocomplete = function ShareAutocomplete(_ref) {
|
|
|
142
150
|
var onAutosuggestPick = function onAutosuggestPick(value) {
|
|
143
151
|
var _autosuggestRef$curre;
|
|
144
152
|
|
|
153
|
+
if (isContactToBeCreated(value) && !enableCreateContact) return;
|
|
145
154
|
onPick(value);
|
|
146
155
|
setInputValue('');
|
|
147
156
|
autosuggestRef === null || autosuggestRef === void 0 || (_autosuggestRef$curre = autosuggestRef.current) === null || _autosuggestRef$curre === void 0 || (_autosuggestRef$curre = _autosuggestRef$curre.input) === null || _autosuggestRef$curre === void 0 || _autosuggestRef$curre.focus();
|
|
@@ -234,14 +243,15 @@ var ShareAutocomplete = function ShareAutocomplete(_ref) {
|
|
|
234
243
|
}));
|
|
235
244
|
};
|
|
236
245
|
|
|
237
|
-
|
|
246
|
+
ShareAutosuggest.propTypes = {
|
|
238
247
|
loading: PropTypes.bool,
|
|
239
248
|
disabled: PropTypes.bool,
|
|
240
249
|
contactsAndGroups: PropTypes.array,
|
|
241
250
|
recipients: PropTypes.array.isRequired,
|
|
242
251
|
onPick: PropTypes.func.isRequired,
|
|
243
252
|
onRemove: PropTypes.func.isRequired,
|
|
253
|
+
enableCreateContact: PropTypes.bool,
|
|
244
254
|
placeholder: PropTypes.string,
|
|
245
255
|
endAdornment: PropTypes.node
|
|
246
256
|
};
|
|
247
|
-
export default
|
|
257
|
+
export default ShareAutosuggest;
|
|
@@ -14,14 +14,17 @@ describe('ShareAutosuggest', function () {
|
|
|
14
14
|
var onPick = _ref.onPick,
|
|
15
15
|
onRemove = _ref.onRemove,
|
|
16
16
|
_ref$loading = _ref.loading,
|
|
17
|
-
loading = _ref$loading === void 0 ? false : _ref$loading
|
|
17
|
+
loading = _ref$loading === void 0 ? false : _ref$loading,
|
|
18
|
+
_ref$enableCreateCont = _ref.enableCreateContact,
|
|
19
|
+
enableCreateContact = _ref$enableCreateCont === void 0 ? true : _ref$enableCreateCont;
|
|
18
20
|
render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(ShareAutosuggest, {
|
|
19
21
|
loading: loading,
|
|
20
22
|
contactsAndGroups: [],
|
|
21
23
|
placeholder: "myPlaceHolder",
|
|
22
24
|
onPick: onPick,
|
|
23
25
|
recipients: [],
|
|
24
|
-
onRemove: onRemove
|
|
26
|
+
onRemove: onRemove,
|
|
27
|
+
enableCreateContact: enableCreateContact
|
|
25
28
|
})));
|
|
26
29
|
};
|
|
27
30
|
|
|
@@ -78,6 +81,27 @@ describe('ShareAutosuggest', function () {
|
|
|
78
81
|
email: 'quentin.valmori@cozycloud.cc'
|
|
79
82
|
});
|
|
80
83
|
});
|
|
84
|
+
it('should not call onPick for new contacts when enableCreateContact is false', function () {
|
|
85
|
+
var onPick = jest.fn();
|
|
86
|
+
var onRemove = jest.fn();
|
|
87
|
+
setup({
|
|
88
|
+
onPick: onPick,
|
|
89
|
+
onRemove: onRemove,
|
|
90
|
+
enableCreateContact: false
|
|
91
|
+
});
|
|
92
|
+
var inputNode = screen.getByPlaceholderText('myPlaceHolder');
|
|
93
|
+
fireEvent.change(inputNode, {
|
|
94
|
+
target: {
|
|
95
|
+
value: 'new@cozycloud.cc'
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
fireEvent.keyPress(inputNode, {
|
|
99
|
+
key: 'Enter',
|
|
100
|
+
keyCode: 13,
|
|
101
|
+
charCode: 13
|
|
102
|
+
});
|
|
103
|
+
expect(onPick).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
81
105
|
it('should show loading indicator when loading prop is true (autoFocus triggers onFocus on mount)', function () {
|
|
82
106
|
var onPick = jest.fn();
|
|
83
107
|
var onRemove = jest.fn();
|
|
@@ -33,6 +33,7 @@ export var ShareByEmail = function ShareByEmail(_ref) {
|
|
|
33
33
|
onPendingRecipientsChange = _ref.onPendingRecipientsChange,
|
|
34
34
|
selectedOption = _ref.selectedOption,
|
|
35
35
|
onSelectedOptionChange = _ref.onSelectedOptionChange,
|
|
36
|
+
enableCreateContact = _ref.enableCreateContact,
|
|
36
37
|
sharing = _ref.sharing;
|
|
37
38
|
|
|
38
39
|
var _useI18n = useI18n(),
|
|
@@ -71,6 +72,7 @@ export var ShareByEmail = function ShareByEmail(_ref) {
|
|
|
71
72
|
placeholder: pendingRecipients.length === 0 ? t("".concat(documentType, ".share.shareByEmail.emailPlaceholder")) : '',
|
|
72
73
|
onPick: onRecipientPick,
|
|
73
74
|
onRemove: onRecipientRemove,
|
|
75
|
+
enableCreateContact: enableCreateContact,
|
|
74
76
|
currentRecipients: currentRecipients,
|
|
75
77
|
recipients: pendingRecipients,
|
|
76
78
|
endAdornment: /*#__PURE__*/React.createElement(ShareTypeSelect, {
|
|
@@ -88,6 +90,7 @@ ShareByEmail.propTypes = {
|
|
|
88
90
|
onPendingRecipientsChange: PropTypes.func.isRequired,
|
|
89
91
|
selectedOption: PropTypes.oneOf(['readWrite', 'readOnly']).isRequired,
|
|
90
92
|
onSelectedOptionChange: PropTypes.func.isRequired,
|
|
93
|
+
enableCreateContact: PropTypes.bool,
|
|
91
94
|
sharing: PropTypes.object
|
|
92
95
|
};
|
|
93
96
|
export default ShareByEmail;
|
|
@@ -25,7 +25,8 @@ describe('ShareByEmail (controlled)', function () {
|
|
|
25
25
|
pendingRecipients: [],
|
|
26
26
|
onPendingRecipientsChange: onPendingRecipientsChange,
|
|
27
27
|
selectedOption: 'readWrite',
|
|
28
|
-
onSelectedOptionChange: onSelectedOptionChange
|
|
28
|
+
onSelectedOptionChange: onSelectedOptionChange,
|
|
29
|
+
enableCreateContact: true
|
|
29
30
|
}, overrides);
|
|
30
31
|
|
|
31
32
|
var utils = render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(ShareByEmail, props)));
|
|
@@ -91,7 +91,8 @@ var SharingContent = function SharingContent(_ref) {
|
|
|
91
91
|
pendingRecipients: pendingRecipients,
|
|
92
92
|
onPendingRecipientsChange: onPendingRecipientsChange,
|
|
93
93
|
selectedOption: selectedOption,
|
|
94
|
-
onSelectedOptionChange: onSelectedOptionChange
|
|
94
|
+
onSelectedOptionChange: onSelectedOptionChange,
|
|
95
|
+
enableCreateContact: true
|
|
95
96
|
})), showWhoHasAccess && /*#__PURE__*/React.createElement(WhoHasAccess, {
|
|
96
97
|
document: document,
|
|
97
98
|
documentType: documentType,
|
|
@@ -39,11 +39,11 @@ jest.mock('../hooks/useSharingContext', function () {
|
|
|
39
39
|
};
|
|
40
40
|
});
|
|
41
41
|
jest.mock('../helpers/contacts', function () {
|
|
42
|
-
return {
|
|
42
|
+
return _objectSpread(_objectSpread({}, jest.requireActual('../helpers/contacts')), {}, {
|
|
43
43
|
getOrCreateFromArray: jest.fn(function (client, recipients) {
|
|
44
44
|
return Promise.resolve(recipients);
|
|
45
45
|
})
|
|
46
|
-
};
|
|
46
|
+
});
|
|
47
47
|
});
|
|
48
48
|
describe('ShareDialogCozyToCozy', function () {
|
|
49
49
|
var client = createMockClient({});
|
|
@@ -8,10 +8,12 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
|
|
|
8
8
|
import PropTypes from 'prop-types';
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import { useQueryAll, isQueryLoading } from 'cozy-client';
|
|
11
|
+
import { models } from 'cozy-client';
|
|
11
12
|
import flag from 'cozy-flags';
|
|
12
13
|
import ShareAutosuggest from './ShareAutosuggest';
|
|
13
14
|
import { getContactsFromGroupId } from '../helpers/contacts';
|
|
14
15
|
import { buildReachableContactsQuery, buildContactGroupsQuery, buildUnreachableContactsWithGroupsQuery } from '../queries/queries';
|
|
16
|
+
var ContactModel = models.contact;
|
|
15
17
|
/**
|
|
16
18
|
* ShareRecipientsInput is responsible for fetching contacts and groups.
|
|
17
19
|
* We retrieve all the contacts that are reachable and the groups they belong to.
|
|
@@ -28,6 +30,7 @@ var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
|
|
|
28
30
|
placeholder = _ref.placeholder,
|
|
29
31
|
onPick = _ref.onPick,
|
|
30
32
|
onRemove = _ref.onRemove,
|
|
33
|
+
enableCreateContact = _ref.enableCreateContact,
|
|
31
34
|
disabled = _ref.disabled,
|
|
32
35
|
endAdornment = _ref.endAdornment;
|
|
33
36
|
var reachableContactsQuery = buildReachableContactsQuery();
|
|
@@ -40,7 +43,13 @@ var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
|
|
|
40
43
|
var contactGroupsResult = useQueryAll(contactGroupsQuery.definition, contactGroupsQuery.options);
|
|
41
44
|
var isLoading = isQueryLoading(reachableContactsResult) || reachableContactsResult.hasMore || isQueryLoading(contactGroupsResult) || contactGroupsResult.hasMore || flag('sharing.show-recipient-groups') && (isQueryLoading(unreachableContactsWithGroupsResult) || unreachableContactsWithGroupsResult.hasMore);
|
|
42
45
|
var reachableContacts = (reachableContactsResult.data || []).filter(function (contact) {
|
|
43
|
-
return !isCurrentUser(contact)
|
|
46
|
+
return !isCurrentUser(contact) && // We do not want contact already in the sharing
|
|
47
|
+
!currentRecipients.find(function (r) {
|
|
48
|
+
return r.email === ContactModel.getPrimaryEmail(contact);
|
|
49
|
+
}) && // We do not want contact currently in pending contacts (in chips)
|
|
50
|
+
!recipients.find(function (r) {
|
|
51
|
+
return r._id === contact._id;
|
|
52
|
+
});
|
|
44
53
|
});
|
|
45
54
|
var unreachableContactsWithGroups = (unreachableContactsWithGroupsResult.data || []).filter(function (contact) {
|
|
46
55
|
return !isCurrentUser(contact);
|
|
@@ -82,6 +91,7 @@ var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
|
|
|
82
91
|
recipients: recipients,
|
|
83
92
|
onPick: onPick,
|
|
84
93
|
onRemove: onRemove,
|
|
94
|
+
enableCreateContact: enableCreateContact,
|
|
85
95
|
placeholder: placeholder,
|
|
86
96
|
endAdornment: endAdornment
|
|
87
97
|
});
|
|
@@ -93,6 +103,7 @@ ShareRecipientsInput.propTypes = {
|
|
|
93
103
|
placeholder: PropTypes.string,
|
|
94
104
|
onPick: PropTypes.func.isRequired,
|
|
95
105
|
onRemove: PropTypes.func.isRequired,
|
|
106
|
+
enableCreateContact: PropTypes.bool,
|
|
96
107
|
disabled: PropTypes.bool,
|
|
97
108
|
endAdornment: PropTypes.node
|
|
98
109
|
};
|
|
@@ -38,6 +38,11 @@ describe('ShareRecipientsInput component', function () {
|
|
|
38
38
|
var contacts = _ref2.contacts,
|
|
39
39
|
contactGroups = _ref2.contactGroups,
|
|
40
40
|
unreachableContact = _ref2.unreachableContact;
|
|
41
|
+
|
|
42
|
+
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
43
|
+
currentRecipients = _ref3.currentRecipients,
|
|
44
|
+
recipients = _ref3.recipients;
|
|
45
|
+
|
|
41
46
|
var mockClient = createMockClient({
|
|
42
47
|
queries: {
|
|
43
48
|
'io.cozy.contacts/reachable': {
|
|
@@ -59,7 +64,9 @@ describe('ShareRecipientsInput component', function () {
|
|
|
59
64
|
}, /*#__PURE__*/React.createElement(ShareRecipientsInput, {
|
|
60
65
|
onPick: function onPick() {},
|
|
61
66
|
onRemove: function onRemove() {},
|
|
62
|
-
placeholder: "Enter recipients here"
|
|
67
|
+
placeholder: "Enter recipients here",
|
|
68
|
+
currentRecipients: currentRecipients,
|
|
69
|
+
recipients: recipients
|
|
63
70
|
})));
|
|
64
71
|
};
|
|
65
72
|
|
|
@@ -172,10 +179,66 @@ describe('ShareRecipientsInput component', function () {
|
|
|
172
179
|
}
|
|
173
180
|
}, _callee2);
|
|
174
181
|
})));
|
|
175
|
-
it('should
|
|
176
|
-
var contacts, contactGroups,
|
|
182
|
+
it('should exclude already added recipients from suggestions', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
|
|
183
|
+
var contacts, contactGroups, currentRecipients, recipients;
|
|
177
184
|
return _regeneratorRuntime.wrap(function _callee3$(_context3) {
|
|
178
185
|
while (1) switch (_context3.prev = _context3.next) {
|
|
186
|
+
case 0:
|
|
187
|
+
contacts = [{
|
|
188
|
+
id: 'df563cc4-6440',
|
|
189
|
+
_id: 'df563cc4-6440',
|
|
190
|
+
_type: 'io.cozy.contacts',
|
|
191
|
+
name: {
|
|
192
|
+
givenName: 'Michale',
|
|
193
|
+
familyName: 'Russel'
|
|
194
|
+
},
|
|
195
|
+
email: [{
|
|
196
|
+
address: 'michale@example.com',
|
|
197
|
+
primary: true
|
|
198
|
+
}]
|
|
199
|
+
}, {
|
|
200
|
+
id: '5a3b4ccf-c257',
|
|
201
|
+
_id: '5a3b4ccf-c257',
|
|
202
|
+
_type: 'io.cozy.contacts',
|
|
203
|
+
name: {
|
|
204
|
+
givenName: 'Teagan',
|
|
205
|
+
familyName: 'Wolf'
|
|
206
|
+
},
|
|
207
|
+
email: [{
|
|
208
|
+
address: 'teagan@example.com',
|
|
209
|
+
primary: true
|
|
210
|
+
}]
|
|
211
|
+
}];
|
|
212
|
+
contactGroups = [];
|
|
213
|
+
currentRecipients = [{
|
|
214
|
+
id: 'recipient-1',
|
|
215
|
+
email: 'michale@example.com'
|
|
216
|
+
}];
|
|
217
|
+
recipients = [{
|
|
218
|
+
_id: '5a3b4ccf-c257'
|
|
219
|
+
}];
|
|
220
|
+
setup({
|
|
221
|
+
contacts: contacts,
|
|
222
|
+
contactGroups: contactGroups
|
|
223
|
+
}, {
|
|
224
|
+
currentRecipients: currentRecipients,
|
|
225
|
+
recipients: recipients
|
|
226
|
+
}); // Michale is in currentRecipients by email, should be filtered out
|
|
227
|
+
|
|
228
|
+
expect(screen.queryByText('Michale Russel')).not.toBeInTheDocument(); // Teagan is in recipients by _id, should be filtered out
|
|
229
|
+
|
|
230
|
+
expect(screen.queryByText('Teagan Wolf')).not.toBeInTheDocument();
|
|
231
|
+
|
|
232
|
+
case 7:
|
|
233
|
+
case "end":
|
|
234
|
+
return _context3.stop();
|
|
235
|
+
}
|
|
236
|
+
}, _callee3);
|
|
237
|
+
})));
|
|
238
|
+
it('should include unreachable members when flag sharing.show-recipient-groups is activated', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
|
|
239
|
+
var contacts, contactGroups, unreachableContact;
|
|
240
|
+
return _regeneratorRuntime.wrap(function _callee4$(_context4) {
|
|
241
|
+
while (1) switch (_context4.prev = _context4.next) {
|
|
179
242
|
case 0:
|
|
180
243
|
flag.mockReturnValue(true);
|
|
181
244
|
contacts = [{
|
|
@@ -240,8 +303,8 @@ describe('ShareRecipientsInput component', function () {
|
|
|
240
303
|
|
|
241
304
|
case 8:
|
|
242
305
|
case "end":
|
|
243
|
-
return
|
|
306
|
+
return _context4.stop();
|
|
244
307
|
}
|
|
245
|
-
},
|
|
308
|
+
}, _callee4);
|
|
246
309
|
})));
|
|
247
310
|
});
|
package/dist/helpers/contacts.js
CHANGED
|
@@ -102,4 +102,16 @@ export var getContactsFromGroupId = function getContactsFromGroupId() {
|
|
|
102
102
|
return group._id === groupId;
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Returns true when a contact does not exist. A contact without
|
|
108
|
+
* an `_id` is always a placeholder that must be created before sharing.
|
|
109
|
+
*
|
|
110
|
+
* Used to decide whether the autosuggest input should allow creating
|
|
111
|
+
* a new contact on the fly or should block the pick (depending on the
|
|
112
|
+
* `enableCreateContact` flag).
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
export var isContactToBeCreated = function isContactToBeCreated(contact) {
|
|
116
|
+
return !contact._id;
|
|
105
117
|
};
|
|
@@ -40,4 +40,8 @@ var fullnameMatch = function fullnameMatch(input, contact) {
|
|
|
40
40
|
return fullnameInput.test(contact.fullname);
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
var contactOrGroupMatch = function contactOrGroupMatch(input, contact) {
|
|
44
|
+
return groupNameMatch(input, contact) || fullnameMatch(input, contact) || emailMatch(input, contact) || cozyUrlMatch(input, contact);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export { emailMatch, cozyUrlMatch, groupNameMatch, fullnameMatch, contactOrGroupMatch };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-sharing",
|
|
3
|
-
"version": "33.0.
|
|
3
|
+
"version": "33.0.2",
|
|
4
4
|
"description": "Provides sharing login for React applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "Cozy",
|
|
@@ -83,5 +83,5 @@
|
|
|
83
83
|
"sideEffects": [
|
|
84
84
|
"*.css"
|
|
85
85
|
],
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "0a14cfca6a608f4a40d2301c370b176ecbaeee4c"
|
|
87
87
|
}
|
|
@@ -8,23 +8,20 @@ import { Spinner } from 'cozy-ui/transpiled/react/Spinner'
|
|
|
8
8
|
|
|
9
9
|
import { GroupAvatar } from './Avatar/GroupAvatar'
|
|
10
10
|
import ContactSuggestion from './ContactSuggestion'
|
|
11
|
+
import { isContactToBeCreated } from '../helpers/contacts'
|
|
11
12
|
import { extractEmails, validateEmail } from '../helpers/email'
|
|
12
13
|
import { getDisplayName, getInitials, Contact, Group } from '../models'
|
|
13
14
|
import styles from '../styles/autosuggest.styl'
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
groupNameMatch,
|
|
18
|
-
fullnameMatch
|
|
19
|
-
} from '../suggestionMatchers'
|
|
20
|
-
|
|
21
|
-
const ShareAutocomplete = ({
|
|
15
|
+
import { contactOrGroupMatch } from '../suggestionMatchers'
|
|
16
|
+
|
|
17
|
+
const ShareAutosuggest = ({
|
|
22
18
|
loading,
|
|
23
19
|
disabled,
|
|
24
20
|
contactsAndGroups,
|
|
25
21
|
recipients,
|
|
26
22
|
onPick,
|
|
27
23
|
onRemove,
|
|
24
|
+
enableCreateContact,
|
|
28
25
|
placeholder,
|
|
29
26
|
endAdornment
|
|
30
27
|
}) => {
|
|
@@ -41,29 +38,26 @@ const ShareAutocomplete = ({
|
|
|
41
38
|
|
|
42
39
|
return inputValue.length === 0
|
|
43
40
|
? []
|
|
44
|
-
: contactsAndGroups.filter(
|
|
45
|
-
contactOrGroup
|
|
46
|
-
groupNameMatch(inputValue, contactOrGroup) ||
|
|
47
|
-
fullnameMatch(inputValue, contactOrGroup) ||
|
|
48
|
-
emailMatch(inputValue, contactOrGroup) ||
|
|
49
|
-
cozyUrlMatch(inputValue, contactOrGroup)
|
|
41
|
+
: contactsAndGroups.filter(contactOrGroup =>
|
|
42
|
+
contactOrGroupMatch(inputValue, contactOrGroup)
|
|
50
43
|
)
|
|
51
44
|
}
|
|
52
45
|
|
|
53
46
|
const onSuggestionsFetchRequested = ({ value }) => {
|
|
54
47
|
const computedSuggestions = computeSuggestions(value)
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
computedSuggestions
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
49
|
+
if (computedSuggestions.length > 0) {
|
|
50
|
+
setSuggestions(computedSuggestions)
|
|
51
|
+
} else if (enableCreateContact && validateEmail(value)) {
|
|
52
|
+
setSuggestions([
|
|
53
|
+
{
|
|
54
|
+
email: value,
|
|
55
|
+
_type: Contact.doctype
|
|
56
|
+
}
|
|
57
|
+
])
|
|
58
|
+
} else {
|
|
59
|
+
setSuggestions([])
|
|
60
|
+
}
|
|
67
61
|
}
|
|
68
62
|
|
|
69
63
|
const onSuggestionsClearRequested = () => {
|
|
@@ -130,6 +124,8 @@ const ShareAutocomplete = ({
|
|
|
130
124
|
}
|
|
131
125
|
|
|
132
126
|
const onAutosuggestPick = value => {
|
|
127
|
+
if (isContactToBeCreated(value) && !enableCreateContact) return
|
|
128
|
+
|
|
133
129
|
onPick(value)
|
|
134
130
|
setInputValue('')
|
|
135
131
|
autosuggestRef?.current?.input?.focus()
|
|
@@ -221,15 +217,16 @@ const ShareAutocomplete = ({
|
|
|
221
217
|
)
|
|
222
218
|
}
|
|
223
219
|
|
|
224
|
-
|
|
220
|
+
ShareAutosuggest.propTypes = {
|
|
225
221
|
loading: PropTypes.bool,
|
|
226
222
|
disabled: PropTypes.bool,
|
|
227
223
|
contactsAndGroups: PropTypes.array,
|
|
228
224
|
recipients: PropTypes.array.isRequired,
|
|
229
225
|
onPick: PropTypes.func.isRequired,
|
|
230
226
|
onRemove: PropTypes.func.isRequired,
|
|
227
|
+
enableCreateContact: PropTypes.bool,
|
|
231
228
|
placeholder: PropTypes.string,
|
|
232
229
|
endAdornment: PropTypes.node
|
|
233
230
|
}
|
|
234
231
|
|
|
235
|
-
export default
|
|
232
|
+
export default ShareAutosuggest
|
|
@@ -9,7 +9,12 @@ jest.mock('cozy-ui/transpiled/react/Spinner', () => ({
|
|
|
9
9
|
}))
|
|
10
10
|
|
|
11
11
|
describe('ShareAutosuggest', () => {
|
|
12
|
-
const setup = ({
|
|
12
|
+
const setup = ({
|
|
13
|
+
onPick,
|
|
14
|
+
onRemove,
|
|
15
|
+
loading = false,
|
|
16
|
+
enableCreateContact = true
|
|
17
|
+
}) => {
|
|
13
18
|
render(
|
|
14
19
|
<AppLike>
|
|
15
20
|
<ShareAutosuggest
|
|
@@ -19,6 +24,7 @@ describe('ShareAutosuggest', () => {
|
|
|
19
24
|
onPick={onPick}
|
|
20
25
|
recipients={[]}
|
|
21
26
|
onRemove={onRemove}
|
|
27
|
+
enableCreateContact={enableCreateContact}
|
|
22
28
|
/>
|
|
23
29
|
</AppLike>
|
|
24
30
|
)
|
|
@@ -51,6 +57,19 @@ describe('ShareAutosuggest', () => {
|
|
|
51
57
|
})
|
|
52
58
|
})
|
|
53
59
|
|
|
60
|
+
it('should not call onPick for new contacts when enableCreateContact is false', () => {
|
|
61
|
+
const onPick = jest.fn()
|
|
62
|
+
const onRemove = jest.fn()
|
|
63
|
+
|
|
64
|
+
setup({ onPick, onRemove, enableCreateContact: false })
|
|
65
|
+
|
|
66
|
+
const inputNode = screen.getByPlaceholderText('myPlaceHolder')
|
|
67
|
+
|
|
68
|
+
fireEvent.change(inputNode, { target: { value: 'new@cozycloud.cc' } })
|
|
69
|
+
fireEvent.keyPress(inputNode, { key: 'Enter', keyCode: 13, charCode: 13 })
|
|
70
|
+
expect(onPick).not.toHaveBeenCalled()
|
|
71
|
+
})
|
|
72
|
+
|
|
54
73
|
it('should show loading indicator when loading prop is true (autoFocus triggers onFocus on mount)', () => {
|
|
55
74
|
const onPick = jest.fn()
|
|
56
75
|
const onRemove = jest.fn()
|
|
@@ -21,6 +21,7 @@ export const ShareByEmail = ({
|
|
|
21
21
|
onPendingRecipientsChange,
|
|
22
22
|
selectedOption,
|
|
23
23
|
onSelectedOptionChange,
|
|
24
|
+
enableCreateContact,
|
|
24
25
|
sharing
|
|
25
26
|
}) => {
|
|
26
27
|
const { t } = useI18n()
|
|
@@ -66,6 +67,7 @@ export const ShareByEmail = ({
|
|
|
66
67
|
}
|
|
67
68
|
onPick={onRecipientPick}
|
|
68
69
|
onRemove={onRecipientRemove}
|
|
70
|
+
enableCreateContact={enableCreateContact}
|
|
69
71
|
currentRecipients={currentRecipients}
|
|
70
72
|
recipients={pendingRecipients}
|
|
71
73
|
endAdornment={
|
|
@@ -89,6 +91,7 @@ ShareByEmail.propTypes = {
|
|
|
89
91
|
onPendingRecipientsChange: PropTypes.func.isRequired,
|
|
90
92
|
selectedOption: PropTypes.oneOf(['readWrite', 'readOnly']).isRequired,
|
|
91
93
|
onSelectedOptionChange: PropTypes.func.isRequired,
|
|
94
|
+
enableCreateContact: PropTypes.bool,
|
|
92
95
|
sharing: PropTypes.object
|
|
93
96
|
}
|
|
94
97
|
|
|
@@ -2,6 +2,7 @@ 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'
|
|
5
6
|
import flag from 'cozy-flags'
|
|
6
7
|
|
|
7
8
|
import ShareAutosuggest from './ShareAutosuggest'
|
|
@@ -12,6 +13,8 @@ import {
|
|
|
12
13
|
buildUnreachableContactsWithGroupsQuery
|
|
13
14
|
} from '../queries/queries'
|
|
14
15
|
|
|
16
|
+
const ContactModel = models.contact
|
|
17
|
+
|
|
15
18
|
/**
|
|
16
19
|
* ShareRecipientsInput is responsible for fetching contacts and groups.
|
|
17
20
|
* We retrieve all the contacts that are reachable and the groups they belong to.
|
|
@@ -25,6 +28,7 @@ const ShareRecipientsInput = ({
|
|
|
25
28
|
placeholder,
|
|
26
29
|
onPick,
|
|
27
30
|
onRemove,
|
|
31
|
+
enableCreateContact,
|
|
28
32
|
disabled,
|
|
29
33
|
endAdornment
|
|
30
34
|
}) => {
|
|
@@ -60,8 +64,16 @@ const ShareRecipientsInput = ({
|
|
|
60
64
|
unreachableContactsWithGroupsResult.hasMore))
|
|
61
65
|
|
|
62
66
|
const reachableContacts = (reachableContactsResult.data || []).filter(
|
|
63
|
-
contact =>
|
|
67
|
+
contact =>
|
|
68
|
+
!isCurrentUser(contact) &&
|
|
69
|
+
// We do not want contact already in the sharing
|
|
70
|
+
!currentRecipients.find(
|
|
71
|
+
r => r.email === ContactModel.getPrimaryEmail(contact)
|
|
72
|
+
) &&
|
|
73
|
+
// We do not want contact currently in pending contacts (in chips)
|
|
74
|
+
!recipients.find(r => r._id === contact._id)
|
|
64
75
|
)
|
|
76
|
+
|
|
65
77
|
const unreachableContactsWithGroups = (
|
|
66
78
|
unreachableContactsWithGroupsResult.data || []
|
|
67
79
|
).filter(contact => !isCurrentUser(contact))
|
|
@@ -114,6 +126,7 @@ const ShareRecipientsInput = ({
|
|
|
114
126
|
recipients={recipients}
|
|
115
127
|
onPick={onPick}
|
|
116
128
|
onRemove={onRemove}
|
|
129
|
+
enableCreateContact={enableCreateContact}
|
|
117
130
|
placeholder={placeholder}
|
|
118
131
|
endAdornment={endAdornment}
|
|
119
132
|
/>
|
|
@@ -126,6 +139,7 @@ ShareRecipientsInput.propTypes = {
|
|
|
126
139
|
placeholder: PropTypes.string,
|
|
127
140
|
onPick: PropTypes.func.isRequired,
|
|
128
141
|
onRemove: PropTypes.func.isRequired,
|
|
142
|
+
enableCreateContact: PropTypes.bool,
|
|
129
143
|
disabled: PropTypes.bool,
|
|
130
144
|
endAdornment: PropTypes.node
|
|
131
145
|
}
|
|
@@ -38,7 +38,10 @@ jest.mock('./ShareAutosuggest', () => ({
|
|
|
38
38
|
}))
|
|
39
39
|
|
|
40
40
|
describe('ShareRecipientsInput component', () => {
|
|
41
|
-
const setup = (
|
|
41
|
+
const setup = (
|
|
42
|
+
{ contacts, contactGroups, unreachableContact },
|
|
43
|
+
{ currentRecipients, recipients } = {}
|
|
44
|
+
) => {
|
|
42
45
|
const mockClient = createMockClient({
|
|
43
46
|
queries: {
|
|
44
47
|
'io.cozy.contacts/reachable': {
|
|
@@ -61,6 +64,8 @@ describe('ShareRecipientsInput component', () => {
|
|
|
61
64
|
onPick={() => {}}
|
|
62
65
|
onRemove={() => {}}
|
|
63
66
|
placeholder="Enter recipients here"
|
|
67
|
+
currentRecipients={currentRecipients}
|
|
68
|
+
recipients={recipients}
|
|
64
69
|
/>
|
|
65
70
|
</AppLike>
|
|
66
71
|
)
|
|
@@ -175,6 +180,44 @@ describe('ShareRecipientsInput component', () => {
|
|
|
175
180
|
expect(screen.getByText('Friends - 1 members')).toBeInTheDocument()
|
|
176
181
|
})
|
|
177
182
|
|
|
183
|
+
it('should exclude already added recipients from suggestions', async () => {
|
|
184
|
+
const contacts = [
|
|
185
|
+
{
|
|
186
|
+
id: 'df563cc4-6440',
|
|
187
|
+
_id: 'df563cc4-6440',
|
|
188
|
+
_type: 'io.cozy.contacts',
|
|
189
|
+
name: {
|
|
190
|
+
givenName: 'Michale',
|
|
191
|
+
familyName: 'Russel'
|
|
192
|
+
},
|
|
193
|
+
email: [{ address: 'michale@example.com', primary: true }]
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: '5a3b4ccf-c257',
|
|
197
|
+
_id: '5a3b4ccf-c257',
|
|
198
|
+
_type: 'io.cozy.contacts',
|
|
199
|
+
name: {
|
|
200
|
+
givenName: 'Teagan',
|
|
201
|
+
familyName: 'Wolf'
|
|
202
|
+
},
|
|
203
|
+
email: [{ address: 'teagan@example.com', primary: true }]
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
const contactGroups = []
|
|
208
|
+
const currentRecipients = [
|
|
209
|
+
{ id: 'recipient-1', email: 'michale@example.com' }
|
|
210
|
+
]
|
|
211
|
+
const recipients = [{ _id: '5a3b4ccf-c257' }]
|
|
212
|
+
|
|
213
|
+
setup({ contacts, contactGroups }, { currentRecipients, recipients })
|
|
214
|
+
|
|
215
|
+
// Michale is in currentRecipients by email, should be filtered out
|
|
216
|
+
expect(screen.queryByText('Michale Russel')).not.toBeInTheDocument()
|
|
217
|
+
// Teagan is in recipients by _id, should be filtered out
|
|
218
|
+
expect(screen.queryByText('Teagan Wolf')).not.toBeInTheDocument()
|
|
219
|
+
})
|
|
220
|
+
|
|
178
221
|
it('should include unreachable members when flag sharing.show-recipient-groups is activated', async () => {
|
|
179
222
|
flag.mockReturnValue(true)
|
|
180
223
|
|
package/src/helpers/contacts.js
CHANGED
|
@@ -47,3 +47,15 @@ export const getContactsFromGroupId = (contacts = [], groupId) => {
|
|
|
47
47
|
)
|
|
48
48
|
})
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns true when a contact does not exist. A contact without
|
|
53
|
+
* an `_id` is always a placeholder that must be created before sharing.
|
|
54
|
+
*
|
|
55
|
+
* Used to decide whether the autosuggest input should allow creating
|
|
56
|
+
* a new contact on the fly or should block the pick (depending on the
|
|
57
|
+
* `enableCreateContact` flag).
|
|
58
|
+
*/
|
|
59
|
+
export const isContactToBeCreated = contact => {
|
|
60
|
+
return !contact._id
|
|
61
|
+
}
|
|
@@ -34,4 +34,19 @@ const fullnameMatch = (input, contact) => {
|
|
|
34
34
|
return fullnameInput.test(contact.fullname)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
const contactOrGroupMatch = (input, contact) => {
|
|
38
|
+
return (
|
|
39
|
+
groupNameMatch(input, contact) ||
|
|
40
|
+
fullnameMatch(input, contact) ||
|
|
41
|
+
emailMatch(input, contact) ||
|
|
42
|
+
cozyUrlMatch(input, contact)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
emailMatch,
|
|
48
|
+
cozyUrlMatch,
|
|
49
|
+
groupNameMatch,
|
|
50
|
+
fullnameMatch,
|
|
51
|
+
contactOrGroupMatch
|
|
52
|
+
}
|