cozy-sharing 40.2.0 → 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 (38) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/SharingOwnerAvatar.js +2 -2
  3. package/dist/components/Avatar/AvatarList.js +2 -2
  4. package/dist/components/Avatar/MemberAvatar.js +8 -33
  5. package/dist/components/Avatar/MemberAvatar.spec.js +17 -77
  6. package/dist/components/Avatar/MemberAvatarWrapper.js +20 -0
  7. package/dist/components/ConfirmTrustedRecipientsDialog.js +5 -1
  8. package/dist/components/ContactSuggestion.js +5 -2
  9. package/dist/components/Recipient/MemberRecipient.js +6 -3
  10. package/dist/components/Recipient/MemberRecipient.spec.js +1 -9
  11. package/dist/components/Recipient/MemberRecipientLite.js +2 -2
  12. package/dist/components/ShareAutosuggest.js +8 -4
  13. package/dist/components/ShareAutosuggest.spec.js +4 -1
  14. package/dist/components/ShareAutosuggest.stories.js +3 -0
  15. package/dist/components/ShareAutosuggestWrapper.js +5 -1
  16. package/dist/components/WhoHasAccessWrapper.js +6 -2
  17. package/dist/helpers/recipients.js +38 -0
  18. package/dist/helpers/recipients.spec.js +53 -1
  19. package/dist/index.js +1 -1
  20. package/package.json +3 -3
  21. package/src/SharingOwnerAvatar.jsx +2 -2
  22. package/src/components/Avatar/AvatarList.jsx +2 -2
  23. package/src/components/Avatar/MemberAvatar.jsx +4 -34
  24. package/src/components/Avatar/MemberAvatar.spec.jsx +13 -73
  25. package/src/components/Avatar/MemberAvatarWrapper.jsx +22 -0
  26. package/src/components/ConfirmTrustedRecipientsDialog.jsx +7 -0
  27. package/src/components/ContactSuggestion.jsx +8 -3
  28. package/src/components/Recipient/MemberRecipient.jsx +4 -2
  29. package/src/components/Recipient/MemberRecipient.spec.jsx +1 -8
  30. package/src/components/Recipient/MemberRecipientLite.jsx +2 -2
  31. package/src/components/ShareAutosuggest.jsx +13 -4
  32. package/src/components/ShareAutosuggest.spec.jsx +1 -0
  33. package/src/components/ShareAutosuggest.stories.jsx +1 -0
  34. package/src/components/ShareAutosuggestWrapper.jsx +2 -0
  35. package/src/components/WhoHasAccessWrapper.jsx +14 -2
  36. package/src/helpers/recipients.js +40 -0
  37. package/src/helpers/recipients.spec.js +83 -1
  38. package/src/index.jsx +1 -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.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
+
6
10
  # [40.2.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.9...cozy-sharing@40.2.0) (2026-10-28)
7
11
 
8
12
  ### Bug Fixes
@@ -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
  });
@@ -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, {
@@ -42,7 +42,8 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
42
42
  placeholder = _ref.placeholder,
43
43
  autoFocus = _ref.autoFocus,
44
44
  endAdornment = _ref.endAdornment,
45
- fetchContacts = _ref.fetchContacts;
45
+ fetchContacts = _ref.fetchContacts,
46
+ getAvatarUrl = _ref.getAvatarUrl;
46
47
 
47
48
  var _useState = useState(''),
48
49
  _useState2 = _slicedToArray(_useState, 2),
@@ -263,7 +264,8 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
263
264
  color: recipient.color
264
265
  }) : /*#__PURE__*/React.createElement(MemberAvatar, {
265
266
  size: "xs",
266
- recipient: recipient
267
+ recipient: recipient,
268
+ avatarUrl: getAvatarUrl(recipient)
267
269
  }),
268
270
  label: name,
269
271
  onDelete: function onDelete() {
@@ -283,7 +285,8 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
283
285
 
284
286
  var renderSuggestion = function renderSuggestion(contactOrGroup) {
285
287
  return /*#__PURE__*/React.createElement(ContactSuggestion, {
286
- contactOrGroup: contactOrGroup
288
+ contactOrGroup: contactOrGroup,
289
+ avatarUrl: getAvatarUrl(contactOrGroup)
287
290
  });
288
291
  };
289
292
 
@@ -331,6 +334,7 @@ ShareAutosuggest.propTypes = {
331
334
  placeholder: PropTypes.string,
332
335
  autoFocus: PropTypes.bool,
333
336
  endAdornment: PropTypes.node,
334
- fetchContacts: PropTypes.func
337
+ fetchContacts: PropTypes.func,
338
+ getAvatarUrl: PropTypes.func.isRequired
335
339
  };
336
340
  export default ShareAutosuggest;
@@ -28,7 +28,10 @@ describe('ShareAutosuggest', function () {
28
28
  recipients: [],
29
29
  onRemove: onRemove,
30
30
  enableCreateContact: enableCreateContact,
31
- fetchContacts: fetchContacts
31
+ fetchContacts: fetchContacts,
32
+ getAvatarUrl: function getAvatarUrl() {
33
+ return null;
34
+ }
32
35
  })));
33
36
  };
34
37
 
@@ -37,6 +37,9 @@ export var Default = {
37
37
  render: ShareAutosuggestRender,
38
38
  args: {
39
39
  loading: false,
40
+ getAvatarUrl: function getAvatarUrl() {
41
+ return null;
42
+ },
40
43
  contactsAndGroups: [{
41
44
  id: 'group1',
42
45
  name: 'Family',
@@ -13,6 +13,7 @@ import { models } from 'cozy-client';
13
13
  import flag from 'cozy-flags';
14
14
  import ShareAutosuggest from './ShareAutosuggest';
15
15
  import { computeContactsAndGroups, suggestContacts } from '../helpers/contacts';
16
+ import { makeAvatarUrl } from '../helpers/recipients';
16
17
  import { useSharingContext } from '../hooks/useSharingContext';
17
18
  import { DOCTYPE_GROUPS } from '../models';
18
19
  import { buildReachableContactsQuery, buildContactGroupsQuery, buildUnreachableContactsWithGroupsQuery } from '../queries/queries';
@@ -109,7 +110,10 @@ var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
109
110
  placeholder: placeholder,
110
111
  autoFocus: autoFocus,
111
112
  endAdornment: endAdornment,
112
- fetchContacts: contactsSuggestEnabled ? fetchContacts : undefined
113
+ fetchContacts: contactsSuggestEnabled ? fetchContacts : undefined,
114
+ getAvatarUrl: function getAvatarUrl(recipient) {
115
+ return makeAvatarUrl(recipient, client.options.uri);
116
+ }
113
117
  });
114
118
  };
115
119
 
@@ -7,7 +7,7 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
7
7
  import React from 'react';
8
8
  import { useClient, useQuery, hasQueryBeenLoaded } from 'cozy-client';
9
9
  import WhoHasAccess from './WhoHasAccess';
10
- import { hasMemberActions, markCurrentUser } from '../helpers/recipients';
10
+ import { addAvatarUrls, hasMemberActions, makeAvatarUrl, markCurrentUser } from '../helpers/recipients';
11
11
  import { useMemberPermissionType } from '../hooks/useMemberPermissionType';
12
12
  import { useSharingContext } from '../hooks/useSharingContext';
13
13
  import { buildInstanceSettingsQuery } from '../queries/queries'; // When the owner is not part of a sharing's members (eg. the folder only has
@@ -53,7 +53,7 @@ var WhoHasAccessWrapper = function WhoHasAccessWrapper(_ref) {
53
53
  revokeSelf = _useSharingContext.revokeSelf;
54
54
 
55
55
  var instanceUri = client.options.uri;
56
- var markedRecipients = markCurrentUser(recipients, instanceUri);
56
+ var markedRecipients = addAvatarUrls(markCurrentUser(recipients, instanceUri), instanceUri);
57
57
  var hasMemberMenus = markedRecipients.some(function (recipient) {
58
58
  return !recipient.members && !recipientsToBeConfirmed.some(function (user) {
59
59
  return user.email === recipient.email;
@@ -85,6 +85,10 @@ var WhoHasAccessWrapper = function WhoHasAccessWrapper(_ref) {
85
85
  status: 'owner',
86
86
  instance: instanceUri,
87
87
  avatarPath: OWNER_PUBLIC_AVATAR_PATH,
88
+ avatarUrl: makeAvatarUrl({
89
+ status: 'owner',
90
+ avatarPath: OWNER_PUBLIC_AVATAR_PATH
91
+ }, instanceUri),
88
92
  public_name: (_instanceSettingsResu = instanceSettingsResult.data) === null || _instanceSettingsResu === void 0 || (_instanceSettingsResu = _instanceSettingsResu.attributes) === null || _instanceSettingsResu === void 0 ? void 0 : _instanceSettingsResu.public_name
89
93
  } : null;
90
94
  var owner = ownerRecipient ? _objectSpread(_objectSpread({}, ownerRecipient), {}, {
@@ -5,6 +5,7 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
5
5
 
6
6
  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; }
7
7
 
8
+ import { getPrimaryCozy } from 'cozy-client/dist/models/contact';
8
9
  import flag from 'cozy-flags';
9
10
  import { DOCTYPE_GROUPS } from '../models';
10
11
  export var DEFAULT_DISPLAY_NAME = 'Share.contacts.defaultDisplayName';
@@ -141,4 +142,41 @@ export var hasMemberActions = function hasMemberActions(_ref2) {
141
142
  canManageSharing = _ref2.canManageSharing,
142
143
  canManageMembers = _ref2.canManageMembers;
143
144
  return canManageMembers && status !== 'owner' && (isCurrentUser && !isOwner || canManageSharing);
145
+ };
146
+ /**
147
+ * makeAvatarUrl derives the avatar image URL for the recipient.
148
+ * - If the recipient has an avatarPath (sharing context), it is
149
+ * used directly with a base URI prefix (unless it is already
150
+ * a full URL for a remote Cozy).
151
+ * - If the recipient has no avatarPath but has a cozy URL
152
+ * (contact context), it points to that Cozy's public avatar.
153
+ * - The status is used as a cache-buster in the URL so the
154
+ * browser refreshes the image when the sharing status changes.
155
+ * For non-sharing recipients, 'contact' is the stable default.
156
+ */
157
+
158
+ export var makeAvatarUrl = function makeAvatarUrl(recipient, instanceUri) {
159
+ var avatarPath = recipient.avatarPath;
160
+ var cozyUrl = !avatarPath ? getPrimaryCozy(recipient) : null;
161
+ if (!avatarPath && !cozyUrl) return null;
162
+ var finalPath = avatarPath || "".concat(cozyUrl.replace(/\/$/, ''), "/public/avatar?fallback=initials");
163
+ var status = recipient.status || 'contact';
164
+ var isFullPath = finalPath.startsWith('http');
165
+ var base = isFullPath ? '' : instanceUri;
166
+ return "".concat(base).concat(finalPath).concat(finalPath.includes('?') ? '&' : '?', "v=").concat(status);
167
+ };
168
+ /**
169
+ * Adds to the recipients, except groups, the URL of their avatar
170
+ *
171
+ * @param {object[]} recipients - Recipients of a sharing
172
+ * @param {string} instanceUri - URI of the current user's instance
173
+ * @returns {object[]} The recipients with their `avatarUrl`
174
+ */
175
+
176
+ export var addAvatarUrls = function addAvatarUrls(recipients, instanceUri) {
177
+ return recipients.map(function (recipient) {
178
+ return recipient.members ? recipient : _objectSpread(_objectSpread({}, recipient), {}, {
179
+ avatarUrl: makeAvatarUrl(recipient, instanceUri)
180
+ });
181
+ });
144
182
  };
@@ -5,7 +5,7 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
5
5
 
6
6
  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; }
7
7
 
8
- import { hasMemberActions, markCurrentUser } from './recipients';
8
+ import { addAvatarUrls, hasMemberActions, makeAvatarUrl, markCurrentUser } from './recipients';
9
9
  var instanceUri = 'https://alice.cozy.cc';
10
10
  describe('markCurrentUser', function () {
11
11
  it('flags the recipient whose instance is the current one', function () {
@@ -86,4 +86,56 @@ describe('hasMemberActions', function () {
86
86
  status: 'ready'
87
87
  }))).toBe(false);
88
88
  });
89
+ });
90
+ describe('makeAvatarUrl', function () {
91
+ var instanceUri = 'http://cozy.example.com';
92
+ it('prefixes the avatarPath with the instance URI and the status', function () {
93
+ expect(makeAvatarUrl({
94
+ status: 'owner',
95
+ avatarPath: '/sharings/123/recipients/0/avatar'
96
+ }, instanceUri)).toBe('http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner');
97
+ });
98
+ it('appends the status with & when avatarPath already has a query string', function () {
99
+ expect(makeAvatarUrl({
100
+ status: 'owner',
101
+ avatarPath: '/public/avatar?fallback=initials'
102
+ }, instanceUri)).toBe('http://cozy.example.com/public/avatar?fallback=initials&v=owner');
103
+ });
104
+ it('does not prefix a full URL avatarPath with the instance URI', function () {
105
+ expect(makeAvatarUrl({
106
+ status: 'contact',
107
+ avatarPath: 'https://jonsnow.mycozy.cloud/public/avatar?fallback=initials'
108
+ }, instanceUri)).toBe('https://jonsnow.mycozy.cloud/public/avatar?fallback=initials&v=contact');
109
+ });
110
+ it('points to the public avatar of the contact cozy when there is no avatarPath', function () {
111
+ expect(makeAvatarUrl({
112
+ _type: 'io.cozy.contacts',
113
+ cozy: [{
114
+ url: 'https://jonsnow.mycozy.cloud'
115
+ }]
116
+ }, instanceUri)).toBe('https://jonsnow.mycozy.cloud/public/avatar?fallback=initials&v=contact');
117
+ });
118
+ it('returns null when there is no avatarPath and no cozy URL', function () {
119
+ expect(makeAvatarUrl({
120
+ _type: 'io.cozy.contacts',
121
+ public_name: 'Bob'
122
+ }, instanceUri)).toBe(null);
123
+ });
124
+ });
125
+ describe('addAvatarUrls', function () {
126
+ it('adds the avatar URL to the members but not to the groups', function () {
127
+ var _addAvatarUrls = addAvatarUrls([{
128
+ status: 'ready',
129
+ avatarPath: '/sharings/123/recipients/1/avatar'
130
+ }, {
131
+ name: 'Team',
132
+ members: []
133
+ }], 'http://cozy.example.com'),
134
+ _addAvatarUrls2 = _slicedToArray(_addAvatarUrls, 2),
135
+ member = _addAvatarUrls2[0],
136
+ group = _addAvatarUrls2[1];
137
+
138
+ expect(member.avatarUrl).toBe('http://cozy.example.com/sharings/123/recipients/1/avatar?v=ready');
139
+ expect(group).not.toHaveProperty('avatarUrl');
140
+ });
89
141
  });