cozy-sharing 40.1.9 → 40.2.1

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -0
  3. package/dist/SharingOwnerAvatar.js +2 -2
  4. package/dist/SharingProvider.js +3 -1
  5. package/dist/components/Avatar/AvatarList.js +2 -2
  6. package/dist/components/Avatar/MemberAvatar.js +8 -33
  7. package/dist/components/Avatar/MemberAvatar.spec.js +17 -77
  8. package/dist/components/Avatar/MemberAvatarWrapper.js +20 -0
  9. package/dist/components/ConfirmTrustedRecipientsDialog.js +5 -1
  10. package/dist/components/ContactSuggestion.js +5 -2
  11. package/dist/components/Recipient/MemberRecipient.js +6 -3
  12. package/dist/components/Recipient/MemberRecipient.spec.js +1 -9
  13. package/dist/components/Recipient/MemberRecipientLite.js +2 -2
  14. package/dist/components/Recipient/PermissionTypeMenuWrapper.spec.js +3 -1
  15. package/dist/components/ShareAutosuggest.js +93 -16
  16. package/dist/components/ShareAutosuggest.spec.js +126 -3
  17. package/dist/components/ShareAutosuggest.stories.js +3 -0
  18. package/dist/components/ShareAutosuggestWrapper.js +65 -5
  19. package/dist/components/ShareAutosuggestWrapper.spec.js +113 -11
  20. package/dist/components/WhoHasAccessWrapper.js +6 -2
  21. package/dist/helpers/contacts.js +75 -16
  22. package/dist/helpers/contacts.spec.js +150 -1
  23. package/dist/helpers/recipients.js +38 -0
  24. package/dist/helpers/recipients.spec.js +53 -1
  25. package/dist/hooks/useResolveRecipients.js +6 -2
  26. package/dist/hooks/useResolveRecipients.spec.js +11 -2
  27. package/dist/index.js +1 -1
  28. package/dist/queries/queries.js +16 -3
  29. package/package.json +3 -3
  30. package/src/SharingOwnerAvatar.jsx +2 -2
  31. package/src/SharingProvider.jsx +6 -1
  32. package/src/components/Avatar/AvatarList.jsx +2 -2
  33. package/src/components/Avatar/MemberAvatar.jsx +4 -34
  34. package/src/components/Avatar/MemberAvatar.spec.jsx +13 -73
  35. package/src/components/Avatar/MemberAvatarWrapper.jsx +22 -0
  36. package/src/components/ConfirmTrustedRecipientsDialog.jsx +7 -0
  37. package/src/components/ContactSuggestion.jsx +8 -3
  38. package/src/components/Recipient/MemberRecipient.jsx +4 -2
  39. package/src/components/Recipient/MemberRecipient.spec.jsx +1 -8
  40. package/src/components/Recipient/MemberRecipientLite.jsx +2 -2
  41. package/src/components/Recipient/PermissionTypeMenuWrapper.spec.jsx +3 -1
  42. package/src/components/ShareAutosuggest.jsx +56 -9
  43. package/src/components/ShareAutosuggest.spec.jsx +60 -2
  44. package/src/components/ShareAutosuggest.stories.jsx +1 -0
  45. package/src/components/ShareAutosuggestWrapper.jsx +36 -4
  46. package/src/components/ShareAutosuggestWrapper.spec.jsx +78 -5
  47. package/src/components/WhoHasAccessWrapper.jsx +14 -2
  48. package/src/helpers/contacts.js +31 -1
  49. package/src/helpers/contacts.spec.js +102 -1
  50. package/src/helpers/recipients.js +40 -0
  51. package/src/helpers/recipients.spec.js +83 -1
  52. package/src/hooks/useResolveRecipients.js +5 -2
  53. package/src/hooks/useResolveRecipients.spec.js +8 -2
  54. package/src/index.jsx +1 -1
  55. package/src/queries/queries.js +6 -3
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
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.2.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.2.0...cozy-sharing@40.2.1) (2026-10-28)
7
+
8
+ **Note:** Version bump only for package cozy-sharing
9
+
10
+ # [40.2.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.9...cozy-sharing@40.2.0) (2026-10-28)
11
+
12
+ ### Bug Fixes
13
+
14
+ - **cozy-sharing:** Ignore the email case when filtering suggestions ([4057cf0](https://github.com/cozy/cozy-libs/commit/4057cf010f54c646d8518aa787e03be445b60df2))
15
+
16
+ ### Features
17
+
18
+ - **cozy-sharing:** Suggest recipients from the stack ([8b06972](https://github.com/cozy/cozy-libs/commit/8b0697256b118c365ca9f7315aae3f00eef6ebd6)), closes [linagora/twake-workplace-private#1929](https://github.com/linagora/twake-workplace-private/issues/1929)
19
+
6
20
  ## [40.1.9](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.8...cozy-sharing@40.1.9) (2026-10-28)
7
21
 
8
22
  ### Bug Fixes
package/README.md CHANGED
@@ -40,6 +40,7 @@ import SharingProvider from 'cozy-sharing'
40
40
  | `onShared` | no | Callback fired after a share is created |
41
41
  | `isPublic` | no | Set to `true` when rendering a public page (disables fetching) |
42
42
  | `previewPath` | no | Custom preview path sent to the share creation |
43
+ | `contactsSuggestEnabled` | no | Suggest the recipients with `GET /contacts/suggest` instead of loading every contact |
43
44
 
44
45
  ### Built-in components
45
46
 
@@ -2,7 +2,7 @@ import _extends from "@babel/runtime/helpers/extends";
2
2
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
3
  var _excluded = ["docId"];
4
4
  import React from 'react';
5
- import { MemberAvatar } from './components/Avatar/MemberAvatar';
5
+ import { MemberAvatarWrapper } from './components/Avatar/MemberAvatarWrapper';
6
6
  import SharingContext from './context';
7
7
  import withLocales from './hoc/withLocales';
8
8
  export var SharingOwnerAvatar = withLocales(function (_ref) {
@@ -14,7 +14,7 @@ export var SharingOwnerAvatar = withLocales(function (_ref) {
14
14
  byDocId = _ref2.byDocId,
15
15
  getOwner = _ref2.getOwner;
16
16
 
17
- return !byDocId || !byDocId[docId] ? null : /*#__PURE__*/React.createElement(MemberAvatar, _extends({
17
+ return !byDocId || !byDocId[docId] ? null : /*#__PURE__*/React.createElement(MemberAvatarWrapper, _extends({
18
18
  recipient: getOwner(docId)
19
19
  }, rest));
20
20
  });
@@ -1451,7 +1451,9 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
1451
1451
  // but if we don't, consumers don't rerender when the state changes after loading the sharings,
1452
1452
  // probably because the state object remains the same...
1453
1453
  return /*#__PURE__*/React.createElement(SharingContext.Provider, {
1454
- value: _objectSpread({}, this.state)
1454
+ value: _objectSpread(_objectSpread({}, this.state), {}, {
1455
+ contactsSuggestEnabled: this.props.contactsSuggestEnabled
1456
+ })
1455
1457
  }, this.props.children);
1456
1458
  }
1457
1459
  }]);
@@ -6,7 +6,7 @@ import { useClient } from 'cozy-client';
6
6
  import Avatar from 'cozy-ui/transpiled/react/Avatar';
7
7
  import { ExtraAvatar } from './ExtraAvatar';
8
8
  import { GroupAvatar } from './GroupAvatar';
9
- import { MemberAvatar } from './MemberAvatar';
9
+ import { MemberAvatarWrapper } from './MemberAvatarWrapper';
10
10
  import { getDisplayName } from '../../models';
11
11
  var styles = {
12
12
  "recipient": "recipient__recipient___2DmZI",
@@ -96,7 +96,7 @@ var AvatarList = function AvatarList(_ref2) {
96
96
  return /*#__PURE__*/React.createElement("span", {
97
97
  "data-testid": "AvatarList-avatar".concat(recipient.status === 'owner' ? '-owner' : ''),
98
98
  key: recipient.index
99
- }, /*#__PURE__*/React.createElement(MemberAvatar, {
99
+ }, /*#__PURE__*/React.createElement(MemberAvatarWrapper, {
100
100
  recipient: recipient,
101
101
  size: size
102
102
  }));
@@ -1,49 +1,25 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
4
- var _excluded = ["recipient"];
4
+ var _excluded = ["recipient", "avatarUrl"];
5
5
 
6
6
  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; }
7
7
 
8
8
  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; }
9
9
 
10
10
  import React, { useState } from 'react';
11
- import { useClient } from 'cozy-client';
12
- import { getPrimaryCozy } from 'cozy-client/dist/models/contact';
13
11
  import Avatar from 'cozy-ui/transpiled/react/Avatar';
14
12
  import logger from '../../logger';
15
13
  import { getInitials } from '../../models';
16
- /**
17
- * makeAvatarUrl derives the avatar image URL for the recipient.
18
- * - If the recipient has an avatarPath (sharing context), it is
19
- * used directly with a base URI prefix (unless it is already
20
- * a full URL for a remote Cozy).
21
- * - If the recipient has no avatarPath but has a cozy URL
22
- * (contact context), it points to that Cozy's public avatar.
23
- * - The status is used as a cache-buster in the URL so the
24
- * browser refreshes the image when the sharing status changes.
25
- * For non-sharing recipients, 'contact' is the stable default.
26
- */
27
-
28
- var makeAvatarUrl = function makeAvatarUrl(recipient, instanceUri) {
29
- var avatarPath = recipient.avatarPath;
30
- var cozyUrl = !avatarPath ? getPrimaryCozy(recipient) : null;
31
- if (!avatarPath && !cozyUrl) return null;
32
- var finalPath = avatarPath || "".concat(cozyUrl.replace(/\/$/, ''), "/public/avatar?fallback=initials");
33
- var status = recipient.status || 'contact';
34
- var isFullPath = finalPath.startsWith('http');
35
- var base = isFullPath ? '' : instanceUri;
36
- return "".concat(base).concat(finalPath).concat(finalPath.includes('?') ? '&' : '?', "v=").concat(status);
37
- };
38
14
 
39
15
  var MemberAvatar = function MemberAvatar(_ref) {
40
16
  var recipient = _ref.recipient,
17
+ avatarUrl = _ref.avatarUrl,
41
18
  rest = _objectWithoutProperties(_ref, _excluded);
42
19
 
43
- var client = useClient(); // Remember which image url failed to load so we can fall back to the
20
+ // Remember which image url failed to load so we can fall back to the
44
21
  // initials. Storing the url (instead of a boolean) means a new url is
45
22
  // retried automatically, without an effect to reset the error state.
46
-
47
23
  var _useState = useState(null),
48
24
  _useState2 = _slicedToArray(_useState, 2),
49
25
  erroredSrc = _useState2[0],
@@ -59,19 +35,18 @@ var MemberAvatar = function MemberAvatar(_ref) {
59
35
  recipient: recipient
60
36
  }, rest));
61
37
  return null;
62
- }
63
-
64
- var image = makeAvatarUrl(recipient, client.options.uri); // The avatar can become unreachable (eg. the related sharing is revoked
38
+ } // The avatar can become unreachable (eg. the related sharing is revoked
65
39
  // while its members are still rendered), in which case the image request
66
40
  // fails. Without a fallback the browser shows a broken-image icon, so we
67
41
  // render the initials instead.
68
42
 
69
- return /*#__PURE__*/React.createElement(Avatar, rest, image && erroredSrc !== image ? /*#__PURE__*/React.createElement("img", {
43
+
44
+ return /*#__PURE__*/React.createElement(Avatar, rest, avatarUrl && erroredSrc !== avatarUrl ? /*#__PURE__*/React.createElement("img", {
70
45
  width: "100%",
71
46
  height: "100%",
72
- src: image,
47
+ src: avatarUrl,
73
48
  onError: function onError() {
74
- return setErroredSrc(image);
49
+ return setErroredSrc(avatarUrl);
75
50
  }
76
51
  }) : getInitials(recipient));
77
52
  };
@@ -1,19 +1,13 @@
1
1
  import { render, fireEvent } from '@testing-library/react';
2
2
  import React from 'react';
3
- import { createMockClient } from 'cozy-client';
4
3
  import { MemberAvatar } from './MemberAvatar';
5
4
  import AppLike from '../../../test/AppLike';
6
5
  describe('MemberAvatar', function () {
7
- var client = createMockClient({});
8
- client.options = {
9
- uri: 'http://cozy.example.com'
10
- };
11
-
12
6
  var setup = function setup(recipient) {
13
- return render( /*#__PURE__*/React.createElement(AppLike, {
14
- client: client
15
- }, /*#__PURE__*/React.createElement(MemberAvatar, {
16
- recipient: recipient
7
+ var avatarUrl = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
8
+ return render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(MemberAvatar, {
9
+ recipient: recipient,
10
+ avatarUrl: avatarUrl
17
11
  })));
18
12
  };
19
13
 
@@ -26,92 +20,38 @@ describe('MemberAvatar', function () {
26
20
  expect(container.querySelector('[class*="MuiAvatar-root"]')).toBeNull();
27
21
  expect(container.querySelector('img')).toBeNull();
28
22
  });
29
- it('renders the avatar image when the recipient has an avatarPath and a status', function () {
23
+ it('renders the avatar image', function () {
30
24
  var _setup2 = setup({
31
25
  public_name: 'Bob',
32
- status: 'owner',
33
- avatarPath: '/sharings/123/recipients/0/avatar'
34
- }),
26
+ status: 'owner'
27
+ }, 'http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner'),
35
28
  container = _setup2.container;
36
29
 
37
- var img = container.querySelector('img');
38
- expect(img).toBeTruthy();
39
- expect(img.getAttribute('src')).toBe('http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner');
30
+ expect(container.querySelector('img').getAttribute('src')).toBe('http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner');
40
31
  });
41
- it('appends the cache-busting param with & when avatarPath already has a query string', function () {
32
+ it('renders the initials when there is no avatar URL', function () {
42
33
  var _setup3 = setup({
43
- public_name: 'Bob',
44
- status: 'owner',
45
- avatarPath: '/public/avatar?fallback=initials'
46
- }),
47
- container = _setup3.container;
48
-
49
- expect(container.querySelector('img').getAttribute('src')).toBe('http://cozy.example.com/public/avatar?fallback=initials&v=owner');
50
- });
51
- it('renders a full URL avatarPath without prefixing the local instance URI', function () {
52
- var _setup4 = setup({
53
- public_name: 'Jon Snow',
54
- status: 'contact',
55
- avatarPath: 'https://jonsnow.mycozy.cloud/public/avatar?fallback=initials'
56
- }),
57
- container = _setup4.container;
58
-
59
- expect(container.querySelector('img').getAttribute('src')).toBe('https://jonsnow.mycozy.cloud/public/avatar?fallback=initials&v=contact');
60
- });
61
- it('derives avatarPath from cozy URL when avatarPath is not set', function () {
62
- var _setup5 = setup({
63
- _type: 'io.cozy.contacts',
64
- name: {
65
- givenName: 'Jon',
66
- familyName: 'Snow'
67
- },
68
- cozy: [{
69
- url: 'https://jonsnow.mycozy.cloud'
70
- }]
71
- }),
72
- container = _setup5.container;
73
-
74
- var img = container.querySelector('img');
75
- expect(img).toBeTruthy();
76
- expect(img.getAttribute('src')).toBe('https://jonsnow.mycozy.cloud/public/avatar?fallback=initials&v=contact');
77
- });
78
- it('renders initials when there is no avatarPath and no cozy URL', function () {
79
- var _setup6 = setup({
80
34
  _type: 'io.cozy.contacts',
81
35
  name: {
82
36
  givenName: 'Jon',
83
37
  familyName: 'Snow'
84
38
  }
85
39
  }),
86
- container = _setup6.container,
87
- getByText = _setup6.getByText;
40
+ container = _setup3.container,
41
+ getByText = _setup3.getByText;
88
42
 
89
43
  expect(container.querySelector('img')).toBeNull();
90
44
  expect(getByText('JS')).toBeTruthy();
91
45
  });
92
- it('renders the initials when there is no avatarPath', function () {
93
- var _setup7 = setup({
94
- public_name: 'Bob',
95
- status: 'owner'
96
- }),
97
- container = _setup7.container,
98
- getByText = _setup7.getByText;
99
-
100
- expect(container.querySelector('img')).toBeNull();
101
- expect(getByText('B')).toBeTruthy();
102
- });
103
46
  it('falls back to the initials when the avatar image fails to load', function () {
104
- var _setup8 = setup({
47
+ var _setup4 = setup({
105
48
  public_name: 'Bob',
106
- status: 'owner',
107
- avatarPath: '/sharings/123/recipients/0/avatar'
108
- }),
109
- container = _setup8.container,
110
- getByText = _setup8.getByText;
49
+ status: 'owner'
50
+ }, 'http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner'),
51
+ container = _setup4.container,
52
+ getByText = _setup4.getByText;
111
53
 
112
- var img = container.querySelector('img');
113
- expect(img).toBeTruthy();
114
- fireEvent.error(img);
54
+ fireEvent.error(container.querySelector('img'));
115
55
  expect(container.querySelector('img')).toBeNull();
116
56
  expect(getByText('B')).toBeTruthy();
117
57
  });
@@ -0,0 +1,20 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
+ var _excluded = ["recipient"];
4
+ import React from 'react';
5
+ import { useClient } from 'cozy-client';
6
+ import { MemberAvatar } from './MemberAvatar';
7
+ import { makeAvatarUrl } from '../../helpers/recipients';
8
+
9
+ var MemberAvatarWrapper = function MemberAvatarWrapper(_ref) {
10
+ var recipient = _ref.recipient,
11
+ avatarProps = _objectWithoutProperties(_ref, _excluded);
12
+
13
+ var client = useClient();
14
+ return /*#__PURE__*/React.createElement(MemberAvatar, _extends({
15
+ recipient: recipient,
16
+ avatarUrl: recipient ? makeAvatarUrl(recipient, client.options.uri) : null
17
+ }, avatarProps));
18
+ };
19
+
20
+ export { MemberAvatarWrapper };
@@ -2,12 +2,14 @@ import _objectDestructuringEmpty from "@babel/runtime/helpers/objectDestructurin
2
2
  import _extends from "@babel/runtime/helpers/extends";
3
3
  import cx from 'classnames';
4
4
  import React from 'react';
5
+ import { useClient } from 'cozy-client';
5
6
  import List from 'cozy-ui/transpiled/react/List';
6
7
  import Typography from 'cozy-ui/transpiled/react/Typography';
7
8
  import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints';
8
9
  import { useI18n } from 'twake-i18n';
9
10
  import MemberRecipient from './Recipient/MemberRecipient';
10
11
  import ShareDialogTwoStepsConfirmationContainer from './ShareDialogTwoStepsConfirmationContainer';
12
+ import { makeAvatarUrl } from '../helpers/recipients';
11
13
  /**
12
14
  * Displays the sharing interface that can be used when ConfirmTrustedRecipientsDialog is in sharing state
13
15
  */
@@ -22,6 +24,7 @@ var SharingContent = function SharingContent(_ref) {
22
24
  var _useBreakpoints = useBreakpoints(),
23
25
  isMobile = _useBreakpoints.isMobile;
24
26
 
27
+ var client = useClient();
25
28
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Typography, {
26
29
  variant: "body1",
27
30
  className: cx('u-mb-half', isMobile ? 'u-ph-1 u-mt-1' : 'u-ph-2 u-mt-1-half')
@@ -32,7 +35,8 @@ var SharingContent = function SharingContent(_ref) {
32
35
  key: "key_r_".concat(recipientConfirmationData.id),
33
36
  isOwner: false,
34
37
  recipientConfirmationData: recipientConfirmationData,
35
- verifyRecipient: verifyRecipient
38
+ verifyRecipient: verifyRecipient,
39
+ avatarUrl: makeAvatarUrl(recipientConfirmationData, client.options.uri)
36
40
  }));
37
41
  })));
38
42
  };
@@ -9,7 +9,8 @@ import { DOCTYPE_GROUPS, contactPropType, groupPropType, getDisplayName } from '
9
9
  import { GroupAvatar } from './Avatar/GroupAvatar';
10
10
  import { MemberAvatar } from './Avatar/MemberAvatar';
11
11
  export var ContactSuggestion = function ContactSuggestion(_ref) {
12
- var contactOrGroup = _ref.contactOrGroup;
12
+ var contactOrGroup = _ref.contactOrGroup,
13
+ avatarUrl = _ref.avatarUrl;
13
14
 
14
15
  var _useI18n = useI18n(),
15
16
  t = _useI18n.t;
@@ -34,6 +35,7 @@ export var ContactSuggestion = function ContactSuggestion(_ref) {
34
35
  button: true
35
36
  }, /*#__PURE__*/React.createElement(ListItemIcon, null, /*#__PURE__*/React.createElement(MemberAvatar, {
36
37
  recipient: contactOrGroup,
38
+ avatarUrl: avatarUrl,
37
39
  size: "m"
38
40
  })), /*#__PURE__*/React.createElement(ListItemText, {
39
41
  primary: name,
@@ -46,6 +48,7 @@ var newUnknownContactProptypes = PropTypes.shape({
46
48
  });
47
49
  ContactSuggestion.propTypes = {
48
50
  contactOrGroup: PropTypes.oneOfType([contactPropType, groupPropType, newUnknownContactProptypes]).isRequired,
49
- contacts: PropTypes.arrayOf(contactPropType)
51
+ contacts: PropTypes.arrayOf(contactPropType),
52
+ avatarUrl: PropTypes.string
50
53
  };
51
54
  export default ContactSuggestion;
@@ -1,6 +1,6 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
- var _excluded = ["instance", "isOwner", "status", "isCurrentUser", "recipientConfirmationData", "verifyRecipient", "fadeIn", "isOrgSharedDrive"];
3
+ var _excluded = ["instance", "isOwner", "status", "isCurrentUser", "recipientConfirmationData", "verifyRecipient", "fadeIn", "isOrgSharedDrive", "avatarUrl"];
4
4
  import PropTypes from 'prop-types';
5
5
  import React from 'react';
6
6
  import Fade from 'cozy-ui/transpiled/react/Fade';
@@ -33,6 +33,7 @@ var MemberRecipient = function MemberRecipient(props) {
33
33
  verifyRecipient = props.verifyRecipient,
34
34
  fadeIn = props.fadeIn,
35
35
  isOrgSharedDrive = props.isOrgSharedDrive,
36
+ avatarUrl = props.avatarUrl,
36
37
  rest = _objectWithoutProperties(props, _excluded);
37
38
 
38
39
  var isMe = isOwner && status === 'owner' || isCurrentUser;
@@ -55,7 +56,8 @@ var MemberRecipient = function MemberRecipient(props) {
55
56
  size: "small"
56
57
  }, /*#__PURE__*/React.createElement(ListItemIcon, null, /*#__PURE__*/React.createElement(MemberAvatar, {
57
58
  size: "m",
58
- recipient: props
59
+ recipient: props,
60
+ avatarUrl: avatarUrl
59
61
  })), /*#__PURE__*/React.createElement(ListItemText, {
60
62
  primary: /*#__PURE__*/React.createElement(Typography, {
61
63
  variant: "body1"
@@ -78,6 +80,7 @@ MemberRecipient.propTypes = {
78
80
  isCurrentUser: PropTypes.bool,
79
81
  recipientConfirmationData: PropTypes.object,
80
82
  verifyRecipient: PropTypes.func,
81
- isOrgSharedDrive: PropTypes.bool
83
+ isOrgSharedDrive: PropTypes.bool,
84
+ avatarUrl: PropTypes.string
82
85
  };
83
86
  export default MemberRecipient;
@@ -2,19 +2,11 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
3
  import { render, fireEvent } from '@testing-library/react';
4
4
  import React from 'react';
5
- import { createMockClient } from 'cozy-client';
6
5
  import MemberRecipient from './MemberRecipient';
7
6
  import AppLike from '../../../test/AppLike';
8
7
  describe('MemberRecipient component', function () {
9
- var client = createMockClient({});
10
- client.options = {
11
- uri: 'foo.mycozy.cloud'
12
- };
13
-
14
8
  var setup = function setup(props) {
15
- return render( /*#__PURE__*/React.createElement(AppLike, {
16
- client: client
17
- }, /*#__PURE__*/React.createElement(MemberRecipient, props)));
9
+ return render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(MemberRecipient, props)));
18
10
  };
19
11
 
20
12
  var createRangeBackup;
@@ -13,7 +13,7 @@ import MemberRecipientStatus from './MemberRecipientStatus';
13
13
  import { DEFAULT_DISPLAY_NAME } from '../../helpers/recipients';
14
14
  import withLocales from '../../hoc/withLocales';
15
15
  import { getDisplayName } from '../../models';
16
- import { MemberAvatar } from '../Avatar/MemberAvatar';
16
+ import { MemberAvatarWrapper } from '../Avatar/MemberAvatarWrapper';
17
17
 
18
18
  var MemberRecipientLite = function MemberRecipientLite(_ref) {
19
19
  var _recipient$type;
@@ -31,7 +31,7 @@ var MemberRecipientLite = function MemberRecipientLite(_ref) {
31
31
  return /*#__PURE__*/React.createElement(ListItem, {
32
32
  size: "small",
33
33
  ellipsis: true
34
- }, /*#__PURE__*/React.createElement(ListItemIcon, null, /*#__PURE__*/React.createElement(MemberAvatar, _extends({
34
+ }, /*#__PURE__*/React.createElement(ListItemIcon, null, /*#__PURE__*/React.createElement(MemberAvatarWrapper, _extends({
35
35
  size: "m",
36
36
  recipient: recipient
37
37
  }, props))), /*#__PURE__*/React.createElement(ListItemText, {
@@ -208,7 +208,9 @@ describe('PermissionTypeMenu component', function () {
208
208
  }]
209
209
  };
210
210
  beforeEach(function () {
211
- flag.mockReturnValue(true);
211
+ flag.mockImplementation(function (name) {
212
+ return name === 'drive.federated-shared-folder.enabled';
213
+ });
212
214
  mockHasSharedParent.mockReturnValue(true);
213
215
  mockGetSharingById.mockReturnValue({
214
216
  id: 'sharing-123',
@@ -1,7 +1,10 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
4
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
5
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
3
6
  import PropTypes from 'prop-types';
4
- import React, { useState, useRef } from 'react';
7
+ import React, { useEffect, useState, useRef } from 'react';
5
8
  import Autosuggest from 'react-autosuggest';
6
9
  import Chip from 'cozy-ui/transpiled/react/Chips';
7
10
  import { Spinner } from 'cozy-ui/transpiled/react/Spinner';
@@ -10,6 +13,7 @@ import { MemberAvatar } from './Avatar/MemberAvatar';
10
13
  import ContactSuggestion from './ContactSuggestion';
11
14
  import { isContactToBeCreated } from '../helpers/contacts';
12
15
  import { extractEmails, validateEmail } from '../helpers/email';
16
+ import logger from '../logger';
13
17
  import { getDisplayName, DOCTYPE_CONTACTS, DOCTYPE_GROUPS } from '../models';
14
18
  var styles = {
15
19
  "container": "autosuggest__container___1Yro7",
@@ -24,6 +28,8 @@ var styles = {
24
28
  "endAdornment": "autosuggest__endAdornment___2EwTN"
25
29
  };
26
30
  import { contactOrGroupMatch } from '../suggestionMatchers';
31
+ var FETCH_CONTACTS_DEBOUNCE = 300;
32
+ var FETCH_CONTACTS_MIN_LENGTH = 3;
27
33
 
28
34
  var ShareAutosuggest = function ShareAutosuggest(_ref) {
29
35
  var loading = _ref.loading,
@@ -35,7 +41,9 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
35
41
  enableCreateContact = _ref.enableCreateContact,
36
42
  placeholder = _ref.placeholder,
37
43
  autoFocus = _ref.autoFocus,
38
- endAdornment = _ref.endAdornment;
44
+ endAdornment = _ref.endAdornment,
45
+ fetchContacts = _ref.fetchContacts,
46
+ getAvatarUrl = _ref.getAvatarUrl;
39
47
 
40
48
  var _useState = useState(''),
41
49
  _useState2 = _slicedToArray(_useState, 2),
@@ -57,8 +65,20 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
57
65
  isLoadingDisplayed = _useState8[0],
58
66
  setLoadingDisplayed = _useState8[1];
59
67
 
68
+ var _useState9 = useState(false),
69
+ _useState10 = _slicedToArray(_useState9, 2),
70
+ isFetching = _useState10[0],
71
+ setIsFetching = _useState10[1];
72
+
60
73
  var autosuggestRef = useRef(null);
61
74
  var containerRef = useRef(null);
75
+ var fetchTimeoutRef = useRef(null);
76
+ var fetchedValueRef = useRef(null);
77
+ useEffect(function () {
78
+ return function () {
79
+ return clearTimeout(fetchTimeoutRef.current);
80
+ };
81
+ }, []);
62
82
 
63
83
  var computeSuggestions = function computeSuggestions(value) {
64
84
  var inputValue = value.trim().toLowerCase();
@@ -67,10 +87,7 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
67
87
  });
68
88
  };
69
89
 
70
- var onSuggestionsFetchRequested = function onSuggestionsFetchRequested(_ref2) {
71
- var value = _ref2.value;
72
- var computedSuggestions = computeSuggestions(value);
73
-
90
+ var showSuggestions = function showSuggestions(computedSuggestions, value) {
74
91
  if (computedSuggestions.length > 0) {
75
92
  setSuggestions(computedSuggestions);
76
93
  } else if (enableCreateContact && validateEmail(value)) {
@@ -83,7 +100,63 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
83
100
  }
84
101
  };
85
102
 
103
+ var onSuggestionsFetchRequested = function onSuggestionsFetchRequested(_ref2) {
104
+ var value = _ref2.value;
105
+ var computedSuggestions = computeSuggestions(value);
106
+ clearTimeout(fetchTimeoutRef.current);
107
+ fetchedValueRef.current = value;
108
+
109
+ if (!fetchContacts || value.trim().length < FETCH_CONTACTS_MIN_LENGTH) {
110
+ setIsFetching(false);
111
+ showSuggestions(computedSuggestions, value);
112
+ return;
113
+ }
114
+
115
+ setIsFetching(true);
116
+ fetchTimeoutRef.current = setTimeout( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
117
+ var contacts;
118
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
119
+ while (1) switch (_context.prev = _context.next) {
120
+ case 0:
121
+ contacts = [];
122
+ _context.prev = 1;
123
+ _context.next = 4;
124
+ return fetchContacts(value.trim());
125
+
126
+ case 4:
127
+ contacts = _context.sent;
128
+ _context.next = 10;
129
+ break;
130
+
131
+ case 7:
132
+ _context.prev = 7;
133
+ _context.t0 = _context["catch"](1);
134
+ logger.error('Failed to fetch contact suggestions', _context.t0);
135
+
136
+ case 10:
137
+ if (!(fetchedValueRef.current !== value)) {
138
+ _context.next = 12;
139
+ break;
140
+ }
141
+
142
+ return _context.abrupt("return");
143
+
144
+ case 12:
145
+ setIsFetching(false);
146
+ showSuggestions([].concat(_toConsumableArray(contacts), _toConsumableArray(computedSuggestions)), value);
147
+
148
+ case 14:
149
+ case "end":
150
+ return _context.stop();
151
+ }
152
+ }, _callee, null, [[1, 7]]);
153
+ })), FETCH_CONTACTS_DEBOUNCE);
154
+ };
155
+
86
156
  var onSuggestionsClearRequested = function onSuggestionsClearRequested() {
157
+ clearTimeout(fetchTimeoutRef.current);
158
+ fetchedValueRef.current = null;
159
+ setIsFetching(false);
87
160
  setSuggestions([]);
88
161
  };
89
162
 
@@ -91,9 +164,9 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
91
164
  setIsPasted(true);
92
165
  };
93
166
 
94
- var onAutosuggestChange = function onAutosuggestChange(event, _ref3) {
95
- var newValue = _ref3.newValue,
96
- method = _ref3.method;
167
+ var onAutosuggestChange = function onAutosuggestChange(event, _ref4) {
168
+ var newValue = _ref4.newValue,
169
+ method = _ref4.method;
97
170
 
98
171
  if (isPasted) {
99
172
  var emails = extractEmails(newValue);
@@ -126,9 +199,9 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
126
199
  }
127
200
  };
128
201
 
129
- var onKeyUp = function onKeyUp(_ref4) {
130
- var key = _ref4.key,
131
- keyCode = _ref4.keyCode;
202
+ var onKeyUp = function onKeyUp(_ref5) {
203
+ var key = _ref5.key,
204
+ keyCode = _ref5.keyCode;
132
205
 
133
206
  if (recipients.length > 0 && inputValue === '' && (key === 'Backspace' || keyCode === 8 || key === 'Delete' || keyCode === 46)) {
134
207
  document.getElementById("recipient_".concat(recipients.length - 1)).focus();
@@ -191,7 +264,8 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
191
264
  color: recipient.color
192
265
  }) : /*#__PURE__*/React.createElement(MemberAvatar, {
193
266
  size: "xs",
194
- recipient: recipient
267
+ recipient: recipient,
268
+ avatarUrl: getAvatarUrl(recipient)
195
269
  }),
196
270
  label: name,
197
271
  onDelete: function onDelete() {
@@ -202,7 +276,7 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
202
276
  }), /*#__PURE__*/React.createElement("input", _extends({}, inputProps, {
203
277
  onKeyPress: onKeyPress,
204
278
  onKeyUp: onKeyUp
205
- })), loading && isLoadingDisplayed ? /*#__PURE__*/React.createElement(Spinner, {
279
+ })), (loading || isFetching) && isLoadingDisplayed ? /*#__PURE__*/React.createElement(Spinner, {
206
280
  color: "var(--primaryColor)"
207
281
  }) : null, endAdornment ? /*#__PURE__*/React.createElement("div", {
208
282
  className: styles['endAdornment']
@@ -211,7 +285,8 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
211
285
 
212
286
  var renderSuggestion = function renderSuggestion(contactOrGroup) {
213
287
  return /*#__PURE__*/React.createElement(ContactSuggestion, {
214
- contactOrGroup: contactOrGroup
288
+ contactOrGroup: contactOrGroup,
289
+ avatarUrl: getAvatarUrl(contactOrGroup)
215
290
  });
216
291
  };
217
292
 
@@ -258,6 +333,8 @@ ShareAutosuggest.propTypes = {
258
333
  enableCreateContact: PropTypes.bool,
259
334
  placeholder: PropTypes.string,
260
335
  autoFocus: PropTypes.bool,
261
- endAdornment: PropTypes.node
336
+ endAdornment: PropTypes.node,
337
+ fetchContacts: PropTypes.func,
338
+ getAvatarUrl: PropTypes.func.isRequired
262
339
  };
263
340
  export default ShareAutosuggest;