cozy-sharing 33.0.1 → 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 CHANGED
@@ -3,6 +3,12 @@
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
+
6
12
  ## [33.0.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.0.0...cozy-sharing@33.0.1) (2026-06-02)
7
13
 
8
14
  ### Bug Fixes
@@ -23,9 +23,9 @@ var styles = {
23
23
  "recipientChip": "autosuggest__recipientChip___d5I3R",
24
24
  "endAdornment": "autosuggest__endAdornment___2EwTN"
25
25
  };
26
- import { cozyUrlMatch, emailMatch, groupNameMatch, fullnameMatch } from '../suggestionMatchers';
26
+ import { contactOrGroupMatch } from '../suggestionMatchers';
27
27
 
28
- var ShareAutocomplete = function ShareAutocomplete(_ref) {
28
+ var ShareAutosuggest = function ShareAutosuggest(_ref) {
29
29
  var loading = _ref.loading,
30
30
  disabled = _ref.disabled,
31
31
  contactsAndGroups = _ref.contactsAndGroups,
@@ -62,7 +62,7 @@ var ShareAutocomplete = function ShareAutocomplete(_ref) {
62
62
  var computeSuggestions = function computeSuggestions(value) {
63
63
  var inputValue = value.trim().toLowerCase();
64
64
  return inputValue.length === 0 ? [] : contactsAndGroups.filter(function (contactOrGroup) {
65
- return groupNameMatch(inputValue, contactOrGroup) || fullnameMatch(inputValue, contactOrGroup) || emailMatch(inputValue, contactOrGroup) || cozyUrlMatch(inputValue, contactOrGroup);
65
+ return contactOrGroupMatch(inputValue, contactOrGroup);
66
66
  });
67
67
  };
68
68
 
@@ -243,7 +243,7 @@ var ShareAutocomplete = function ShareAutocomplete(_ref) {
243
243
  }));
244
244
  };
245
245
 
246
- ShareAutocomplete.propTypes = {
246
+ ShareAutosuggest.propTypes = {
247
247
  loading: PropTypes.bool,
248
248
  disabled: PropTypes.bool,
249
249
  contactsAndGroups: PropTypes.array,
@@ -254,4 +254,4 @@ ShareAutocomplete.propTypes = {
254
254
  placeholder: PropTypes.string,
255
255
  endAdornment: PropTypes.node
256
256
  };
257
- export default ShareAutocomplete;
257
+ export default ShareAutosuggest;
@@ -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.
@@ -41,7 +43,13 @@ var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
41
43
  var contactGroupsResult = useQueryAll(contactGroupsQuery.definition, contactGroupsQuery.options);
42
44
  var isLoading = isQueryLoading(reachableContactsResult) || reachableContactsResult.hasMore || isQueryLoading(contactGroupsResult) || contactGroupsResult.hasMore || flag('sharing.show-recipient-groups') && (isQueryLoading(unreachableContactsWithGroupsResult) || unreachableContactsWithGroupsResult.hasMore);
43
45
  var reachableContacts = (reachableContactsResult.data || []).filter(function (contact) {
44
- 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
+ });
45
53
  });
46
54
  var unreachableContactsWithGroups = (unreachableContactsWithGroupsResult.data || []).filter(function (contact) {
47
55
  return !isCurrentUser(contact);
@@ -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 include unreachable members when flag sharing.show-recipient-groups is activated', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
176
- var contacts, contactGroups, unreachableContact;
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 _context3.stop();
306
+ return _context4.stop();
244
307
  }
245
- }, _callee3);
308
+ }, _callee4);
246
309
  })));
247
310
  });
@@ -40,4 +40,8 @@ var fullnameMatch = function fullnameMatch(input, contact) {
40
40
  return fullnameInput.test(contact.fullname);
41
41
  };
42
42
 
43
- export { emailMatch, cozyUrlMatch, groupNameMatch, fullnameMatch };
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.1",
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": "546412325d385a075b531e7c735a0082d6864040"
86
+ "gitHead": "0a14cfca6a608f4a40d2301c370b176ecbaeee4c"
87
87
  }
@@ -12,14 +12,9 @@ import { isContactToBeCreated } from '../helpers/contacts'
12
12
  import { extractEmails, validateEmail } from '../helpers/email'
13
13
  import { getDisplayName, getInitials, Contact, Group } from '../models'
14
14
  import styles from '../styles/autosuggest.styl'
15
- import {
16
- cozyUrlMatch,
17
- emailMatch,
18
- groupNameMatch,
19
- fullnameMatch
20
- } from '../suggestionMatchers'
21
-
22
- const ShareAutocomplete = ({
15
+ import { contactOrGroupMatch } from '../suggestionMatchers'
16
+
17
+ const ShareAutosuggest = ({
23
18
  loading,
24
19
  disabled,
25
20
  contactsAndGroups,
@@ -43,12 +38,8 @@ const ShareAutocomplete = ({
43
38
 
44
39
  return inputValue.length === 0
45
40
  ? []
46
- : contactsAndGroups.filter(
47
- contactOrGroup =>
48
- groupNameMatch(inputValue, contactOrGroup) ||
49
- fullnameMatch(inputValue, contactOrGroup) ||
50
- emailMatch(inputValue, contactOrGroup) ||
51
- cozyUrlMatch(inputValue, contactOrGroup)
41
+ : contactsAndGroups.filter(contactOrGroup =>
42
+ contactOrGroupMatch(inputValue, contactOrGroup)
52
43
  )
53
44
  }
54
45
 
@@ -226,7 +217,7 @@ const ShareAutocomplete = ({
226
217
  )
227
218
  }
228
219
 
229
- ShareAutocomplete.propTypes = {
220
+ ShareAutosuggest.propTypes = {
230
221
  loading: PropTypes.bool,
231
222
  disabled: PropTypes.bool,
232
223
  contactsAndGroups: PropTypes.array,
@@ -238,4 +229,4 @@ ShareAutocomplete.propTypes = {
238
229
  endAdornment: PropTypes.node
239
230
  }
240
231
 
241
- export default ShareAutocomplete
232
+ export default ShareAutosuggest
@@ -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.
@@ -61,8 +64,16 @@ const ShareRecipientsInput = ({
61
64
  unreachableContactsWithGroupsResult.hasMore))
62
65
 
63
66
  const reachableContacts = (reachableContactsResult.data || []).filter(
64
- contact => !isCurrentUser(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)
65
75
  )
76
+
66
77
  const unreachableContactsWithGroups = (
67
78
  unreachableContactsWithGroupsResult.data || []
68
79
  ).filter(contact => !isCurrentUser(contact))
@@ -38,7 +38,10 @@ jest.mock('./ShareAutosuggest', () => ({
38
38
  }))
39
39
 
40
40
  describe('ShareRecipientsInput component', () => {
41
- const setup = ({ contacts, contactGroups, unreachableContact }) => {
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
 
@@ -34,4 +34,19 @@ const fullnameMatch = (input, contact) => {
34
34
  return fullnameInput.test(contact.fullname)
35
35
  }
36
36
 
37
- export { emailMatch, cozyUrlMatch, groupNameMatch, fullnameMatch }
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
+ }