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
@@ -8,7 +8,7 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
8
8
 
9
9
  import _regeneratorRuntime from "@babel/runtime/regenerator";
10
10
  import flag from 'cozy-flags';
11
- import { computeContactsAndGroups, getOrCreateFromArray } from './contacts';
11
+ import { computeContactsAndGroups, getOrCreateFromArray, suggestContacts } from './contacts';
12
12
  jest.mock('cozy-flags', function () {
13
13
  return {
14
14
  __esModule: true,
@@ -184,4 +184,153 @@ describe('computeContactsAndGroups', function () {
184
184
  return c._id;
185
185
  })).toEqual(['alice', 'bob']);
186
186
  });
187
+ });
188
+ describe('getOrCreateFromArray with contactsSuggestEnabled', function () {
189
+ it('should send people by email and keep groups, without creating contacts', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
190
+ var createContact, group, recipients;
191
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
192
+ while (1) switch (_context2.prev = _context2.next) {
193
+ case 0:
194
+ createContact = jest.fn();
195
+ group = {
196
+ _id: 'group-1',
197
+ _type: 'io.cozy.contacts.groups'
198
+ };
199
+ _context2.next = 4;
200
+ return getOrCreateFromArray(mockClient, [{
201
+ _id: 'org-member-1',
202
+ _type: 'io.cozy.contacts',
203
+ email: [{
204
+ address: 'member@org.example',
205
+ primary: true
206
+ }],
207
+ meta: {
208
+ kind: 'member'
209
+ }
210
+ }, {
211
+ email: 'unknown@friends.example'
212
+ }, group], createContact, true);
213
+
214
+ case 4:
215
+ recipients = _context2.sent;
216
+ expect(recipients).toEqual([{
217
+ _type: 'io.cozy.contacts',
218
+ email: 'member@org.example'
219
+ }, {
220
+ _type: 'io.cozy.contacts',
221
+ email: 'unknown@friends.example'
222
+ }, group]);
223
+ expect(createContact).not.toHaveBeenCalled();
224
+
225
+ case 7:
226
+ case "end":
227
+ return _context2.stop();
228
+ }
229
+ }, _callee2);
230
+ })));
231
+ it('should keep a contact without email by id, and drop a recipient with neither', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
232
+ var cozyOnlyContact, recipients;
233
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
234
+ while (1) switch (_context3.prev = _context3.next) {
235
+ case 0:
236
+ cozyOnlyContact = {
237
+ _id: 'contact-1',
238
+ _type: 'io.cozy.contacts',
239
+ cozy: [{
240
+ url: 'https://bob.example'
241
+ }]
242
+ };
243
+ _context3.next = 3;
244
+ return getOrCreateFromArray(mockClient, [cozyOnlyContact, {
245
+ instance: 'https://alice.example'
246
+ }], jest.fn(), true);
247
+
248
+ case 3:
249
+ recipients = _context3.sent;
250
+ expect(recipients).toEqual([cozyOnlyContact, undefined]);
251
+
252
+ case 5:
253
+ case "end":
254
+ return _context3.stop();
255
+ }
256
+ }, _callee3);
257
+ })));
258
+ });
259
+ describe('suggestContacts', function () {
260
+ it('should map the suggestions of the stack to contacts', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
261
+ var fetchJSON, client, contacts;
262
+ return _regeneratorRuntime.wrap(function _callee4$(_context4) {
263
+ while (1) switch (_context4.prev = _context4.next) {
264
+ case 0:
265
+ fetchJSON = jest.fn().mockResolvedValue({
266
+ data: [{
267
+ type: 'io.cozy.contacts',
268
+ id: 'org-member-1',
269
+ attributes: {
270
+ fullname: 'Jean Dupont',
271
+ name: {
272
+ givenName: 'Jean',
273
+ familyName: 'Dupont'
274
+ },
275
+ email: [{
276
+ address: 'jean@org.example',
277
+ primary: true
278
+ }]
279
+ },
280
+ meta: {
281
+ kind: 'member'
282
+ }
283
+ }, {
284
+ type: 'io.cozy.contacts',
285
+ attributes: {
286
+ fullname: 'Paul Dupuis',
287
+ email: [{
288
+ address: 'paul@friends.example',
289
+ primary: true
290
+ }]
291
+ }
292
+ }]
293
+ });
294
+ client = {
295
+ getStackClient: function getStackClient() {
296
+ return {
297
+ fetchJSON: fetchJSON
298
+ };
299
+ }
300
+ };
301
+ _context4.next = 4;
302
+ return suggestContacts(client, 'dup & co');
303
+
304
+ case 4:
305
+ contacts = _context4.sent;
306
+ expect(fetchJSON).toHaveBeenCalledWith('GET', '/contacts/suggest?q=dup%20%26%20co');
307
+ expect(contacts).toEqual([{
308
+ _id: 'org-member-1',
309
+ id: 'org-member-1',
310
+ _type: 'io.cozy.contacts',
311
+ fullname: 'Jean Dupont',
312
+ name: {
313
+ givenName: 'Jean',
314
+ familyName: 'Dupont'
315
+ },
316
+ email: [{
317
+ address: 'jean@org.example',
318
+ primary: true
319
+ }]
320
+ }, {
321
+ _type: 'io.cozy.contacts',
322
+ fullname: 'Paul Dupuis',
323
+ displayName: 'Paul Dupuis',
324
+ email: [{
325
+ address: 'paul@friends.example',
326
+ primary: true
327
+ }]
328
+ }]);
329
+
330
+ case 7:
331
+ case "end":
332
+ return _context4.stop();
333
+ }
334
+ }, _callee4);
335
+ })));
187
336
  });
@@ -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
  });
@@ -2,6 +2,7 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
3
  import { useCallback } from 'react';
4
4
  import { useClient } from 'cozy-client';
5
+ import { useSharingContext } from './useSharingContext';
5
6
  import { getOrCreateFromArray } from '../helpers/contacts';
6
7
  import { DOCTYPE_CONTACTS } from '../models';
7
8
  /**
@@ -13,7 +14,10 @@ import { DOCTYPE_CONTACTS } from '../models';
13
14
  */
14
15
 
15
16
  export var useResolveRecipients = function useResolveRecipients() {
17
+ var _useSharingContext;
18
+
16
19
  var client = useClient();
20
+ var contactsSuggestEnabled = (_useSharingContext = useSharingContext()) === null || _useSharingContext === void 0 ? void 0 : _useSharingContext.contactsSuggestEnabled;
17
21
  return useCallback( /*#__PURE__*/function () {
18
22
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(pendingRecipients) {
19
23
  var selectedOption,
@@ -29,7 +33,7 @@ export var useResolveRecipients = function useResolveRecipients() {
29
33
  return client.create(DOCTYPE_CONTACTS, contact);
30
34
  };
31
35
  _context.next = 4;
32
- return getOrCreateFromArray(client, pendingRecipients, createContact);
36
+ return getOrCreateFromArray(client, pendingRecipients, createContact, contactsSuggestEnabled);
33
37
 
34
38
  case 4:
35
39
  contacts = _context.sent;
@@ -49,5 +53,5 @@ export var useResolveRecipients = function useResolveRecipients() {
49
53
  return function (_x) {
50
54
  return _ref.apply(this, arguments);
51
55
  };
52
- }(), [client]);
56
+ }(), [client, contactsSuggestEnabled]);
53
57
  };
@@ -18,6 +18,15 @@ jest.mock('../helpers/contacts', function () {
18
18
  getOrCreateFromArray: jest.fn()
19
19
  };
20
20
  });
21
+ jest.mock('./useSharingContext', function () {
22
+ return {
23
+ useSharingContext: function useSharingContext() {
24
+ return {
25
+ contactsSuggestEnabled: true
26
+ };
27
+ }
28
+ };
29
+ });
21
30
  var contacts = [{
22
31
  _id: 'alice'
23
32
  }, {
@@ -102,7 +111,7 @@ describe('useResolveRecipients', function () {
102
111
  address: 'alice@cozy.cc'
103
112
  }]
104
113
  });
105
- expect(getOrCreateFromArray).toHaveBeenCalledWith(mockClient, pendingRecipients, expect.any(Function));
114
+ expect(getOrCreateFromArray).toHaveBeenCalledWith(mockClient, pendingRecipients, expect.any(Function), true);
106
115
  expect(mockClient.create).toHaveBeenCalledWith('io.cozy.contacts', {
107
116
  email: [{
108
117
  address: 'alice@cozy.cc'
@@ -129,7 +138,7 @@ describe('useResolveRecipients', function () {
129
138
  return result.current([], 'readWrite', createContact);
130
139
 
131
140
  case 4:
132
- expect(getOrCreateFromArray).toHaveBeenCalledWith(mockClient, [], createContact);
141
+ expect(getOrCreateFromArray).toHaveBeenCalledWith(mockClient, [], createContact, true);
133
142
 
134
143
  case 5:
135
144
  case "end":
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ export { SharingContext, withLocales };
6
6
  export { useSharingContext } from './hooks/useSharingContext';
7
7
  export { useFetchDocumentPath } from './hooks/useFetchDocumentPath';
8
8
  export { GroupAvatar } from './components/Avatar/GroupAvatar';
9
- export { MemberAvatar } from './components/Avatar/MemberAvatar';
9
+ export { MemberAvatarWrapper as MemberAvatar } from './components/Avatar/MemberAvatarWrapper';
10
10
  export { SharedDocument } from './SharedDocument';
11
11
  export { SharedStatus } from './SharedStatus';
12
12
  export { SharedBadge } from './SharedBadge';
@@ -1,3 +1,9 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+
3
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
4
+
5
+ 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; }
6
+
1
7
  import { Q, fetchPolicies } from 'cozy-client';
2
8
  import { DOCTYPE_CONTACTS, DOCTYPE_GROUPS } from '../models';
3
9
  var DEFAULT_CACHE_TIMEOUT_QUERIES = 9 * 60 * 1000;
@@ -12,12 +18,15 @@ export var buildSharingsByIdQuery = function buildSharingsByIdQuery(sharingId) {
12
18
  };
13
19
  };
14
20
  export var buildReachableContactsQuery = function buildReachableContactsQuery() {
21
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
22
+ inGroups = _ref.inGroups;
23
+
15
24
  return {
16
25
  definition: Q(DOCTYPE_CONTACTS).where({
17
26
  _id: {
18
27
  $gt: null
19
28
  }
20
- }).partialIndex({
29
+ }).partialIndex(_objectSpread({
21
30
  trashed: {
22
31
  $or: [{
23
32
  $eq: false
@@ -38,9 +47,13 @@ export var buildReachableContactsQuery = function buildReachableContactsQuery()
38
47
  }
39
48
  }
40
49
  }]
41
- }).indexFields(['_id']).limitBy(1000),
50
+ }, inGroups && {
51
+ 'relationships.groups.data': {
52
+ $exists: true
53
+ }
54
+ })).indexFields(['_id']).limitBy(1000),
42
55
  options: {
43
- as: 'io.cozy.contacts/reachable'
56
+ as: inGroups ? 'io.cozy.contacts/reachable-with-groups' : 'io.cozy.contacts/reachable'
44
57
  }
45
58
  };
46
59
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "40.1.9",
3
+ "version": "40.2.1",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -58,7 +58,7 @@
58
58
  "cozy-intent": "^2.31.1",
59
59
  "cozy-minilog": "^3.10.1",
60
60
  "cozy-ui": "^140.5.0",
61
- "cozy-ui-plus": "^14.1.4",
61
+ "cozy-ui-plus": "^14.1.6",
62
62
  "jest": "30.3.0",
63
63
  "jest-environment-jsdom": "30.3.0",
64
64
  "react": "16.12.0",
@@ -84,5 +84,5 @@
84
84
  "sideEffects": [
85
85
  "*.css"
86
86
  ],
87
- "gitHead": "4dc5561f74fe7d2d000b112026294e208221a4d7"
87
+ "gitHead": "dc527e69d5c65ec27dc4ad7b940e01248ee6120f"
88
88
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
 
3
- import { MemberAvatar } from './components/Avatar/MemberAvatar'
3
+ import { MemberAvatarWrapper } from './components/Avatar/MemberAvatarWrapper'
4
4
  import SharingContext from './context'
5
5
  import withLocales from './hoc/withLocales'
6
6
 
@@ -8,7 +8,7 @@ export const SharingOwnerAvatar = withLocales(({ docId, ...rest }) => (
8
8
  <SharingContext.Consumer>
9
9
  {({ byDocId, getOwner } = {}) => {
10
10
  return !byDocId || !byDocId[docId] ? null : (
11
- <MemberAvatar recipient={getOwner(docId)} {...rest} />
11
+ <MemberAvatarWrapper recipient={getOwner(docId)} {...rest} />
12
12
  )
13
13
  }}
14
14
  </SharingContext.Consumer>
@@ -745,7 +745,12 @@ export class SharingProvider extends Component {
745
745
  // but if we don't, consumers don't rerender when the state changes after loading the sharings,
746
746
  // probably because the state object remains the same...
747
747
  return (
748
- <SharingContext.Provider value={{ ...this.state }}>
748
+ <SharingContext.Provider
749
+ value={{
750
+ ...this.state,
751
+ contactsSuggestEnabled: this.props.contactsSuggestEnabled
752
+ }}
753
+ >
749
754
  {this.props.children}
750
755
  </SharingContext.Provider>
751
756
  )
@@ -7,7 +7,7 @@ import Avatar from 'cozy-ui/transpiled/react/Avatar'
7
7
 
8
8
  import { ExtraAvatar } from './ExtraAvatar'
9
9
  import { GroupAvatar } from './GroupAvatar'
10
- import { MemberAvatar } from './MemberAvatar'
10
+ import { MemberAvatarWrapper } from './MemberAvatarWrapper'
11
11
  import { getDisplayName } from '../../models'
12
12
  import styles from '../../styles/recipient.styl'
13
13
 
@@ -104,7 +104,7 @@ const AvatarList = ({
104
104
  }`}
105
105
  key={recipient.index}
106
106
  >
107
- <MemberAvatar recipient={recipient} size={size} />
107
+ <MemberAvatarWrapper recipient={recipient} size={size} />
108
108
  </span>
109
109
  )
110
110
  })}
@@ -1,39 +1,11 @@
1
1
  import React, { useState } from 'react'
2
2
 
3
- import { useClient } from 'cozy-client'
4
- import { getPrimaryCozy } from 'cozy-client/dist/models/contact'
5
3
  import Avatar from 'cozy-ui/transpiled/react/Avatar'
6
4
 
7
5
  import logger from '../../logger'
8
6
  import { getInitials } from '../../models'
9
7
 
10
- /**
11
- * makeAvatarUrl derives the avatar image URL for the recipient.
12
- * - If the recipient has an avatarPath (sharing context), it is
13
- * used directly with a base URI prefix (unless it is already
14
- * a full URL for a remote Cozy).
15
- * - If the recipient has no avatarPath but has a cozy URL
16
- * (contact context), it points to that Cozy's public avatar.
17
- * - The status is used as a cache-buster in the URL so the
18
- * browser refreshes the image when the sharing status changes.
19
- * For non-sharing recipients, 'contact' is the stable default.
20
- */
21
- const makeAvatarUrl = (recipient, instanceUri) => {
22
- const avatarPath = recipient.avatarPath
23
- const cozyUrl = !avatarPath ? getPrimaryCozy(recipient) : null
24
- if (!avatarPath && !cozyUrl) return null
25
-
26
- const finalPath =
27
- avatarPath ||
28
- `${cozyUrl.replace(/\/$/, '')}/public/avatar?fallback=initials`
29
- const status = recipient.status || 'contact'
30
- const isFullPath = finalPath.startsWith('http')
31
- const base = isFullPath ? '' : instanceUri
32
- return `${base}${finalPath}${finalPath.includes('?') ? '&' : '?'}v=${status}`
33
- }
34
-
35
- const MemberAvatar = ({ recipient, ...rest }) => {
36
- const client = useClient()
8
+ const MemberAvatar = ({ recipient, avatarUrl, ...rest }) => {
37
9
  // Remember which image url failed to load so we can fall back to the
38
10
  // initials. Storing the url (instead of a boolean) means a new url is
39
11
  // retried automatically, without an effect to reset the error state.
@@ -49,20 +21,18 @@ const MemberAvatar = ({ recipient, ...rest }) => {
49
21
  return null
50
22
  }
51
23
 
52
- const image = makeAvatarUrl(recipient, client.options.uri)
53
-
54
24
  // The avatar can become unreachable (eg. the related sharing is revoked
55
25
  // while its members are still rendered), in which case the image request
56
26
  // fails. Without a fallback the browser shows a broken-image icon, so we
57
27
  // render the initials instead.
58
28
  return (
59
29
  <Avatar {...rest}>
60
- {image && erroredSrc !== image ? (
30
+ {avatarUrl && erroredSrc !== avatarUrl ? (
61
31
  <img
62
32
  width="100%"
63
33
  height="100%"
64
- src={image}
65
- onError={() => setErroredSrc(image)}
34
+ src={avatarUrl}
35
+ onError={() => setErroredSrc(avatarUrl)}
66
36
  />
67
37
  ) : (
68
38
  getInitials(recipient)
@@ -1,19 +1,14 @@
1
1
  import { render, fireEvent } from '@testing-library/react'
2
2
  import React from 'react'
3
3
 
4
- import { createMockClient } from 'cozy-client'
5
-
6
4
  import { MemberAvatar } from './MemberAvatar'
7
5
  import AppLike from '../../../test/AppLike'
8
6
 
9
7
  describe('MemberAvatar', () => {
10
- const client = createMockClient({})
11
- client.options = { uri: 'http://cozy.example.com' }
12
-
13
- const setup = recipient =>
8
+ const setup = (recipient, avatarUrl = null) =>
14
9
  render(
15
- <AppLike client={client}>
16
- <MemberAvatar recipient={recipient} />
10
+ <AppLike>
11
+ <MemberAvatar recipient={recipient} avatarUrl={avatarUrl} />
17
12
  </AppLike>
18
13
  )
19
14
 
@@ -26,59 +21,18 @@ describe('MemberAvatar', () => {
26
21
  expect(container.querySelector('img')).toBeNull()
27
22
  })
28
23
 
29
- it('renders the avatar image when the recipient has an avatarPath and a status', () => {
30
- const { container } = setup({
31
- public_name: 'Bob',
32
- status: 'owner',
33
- avatarPath: '/sharings/123/recipients/0/avatar'
34
- })
35
-
36
- const img = container.querySelector('img')
37
- expect(img).toBeTruthy()
38
- expect(img.getAttribute('src')).toBe(
24
+ it('renders the avatar image', () => {
25
+ const { container } = setup(
26
+ { public_name: 'Bob', status: 'owner' },
39
27
  'http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner'
40
28
  )
41
- })
42
-
43
- it('appends the cache-busting param with & when avatarPath already has a query string', () => {
44
- const { container } = setup({
45
- public_name: 'Bob',
46
- status: 'owner',
47
- avatarPath: '/public/avatar?fallback=initials'
48
- })
49
-
50
- expect(container.querySelector('img').getAttribute('src')).toBe(
51
- 'http://cozy.example.com/public/avatar?fallback=initials&v=owner'
52
- )
53
- })
54
-
55
- it('renders a full URL avatarPath without prefixing the local instance URI', () => {
56
- const { container } = setup({
57
- public_name: 'Jon Snow',
58
- status: 'contact',
59
- avatarPath: 'https://jonsnow.mycozy.cloud/public/avatar?fallback=initials'
60
- })
61
29
 
62
30
  expect(container.querySelector('img').getAttribute('src')).toBe(
63
- 'https://jonsnow.mycozy.cloud/public/avatar?fallback=initials&v=contact'
64
- )
65
- })
66
-
67
- it('derives avatarPath from cozy URL when avatarPath is not set', () => {
68
- const { container } = setup({
69
- _type: 'io.cozy.contacts',
70
- name: { givenName: 'Jon', familyName: 'Snow' },
71
- cozy: [{ url: 'https://jonsnow.mycozy.cloud' }]
72
- })
73
-
74
- const img = container.querySelector('img')
75
- expect(img).toBeTruthy()
76
- expect(img.getAttribute('src')).toBe(
77
- 'https://jonsnow.mycozy.cloud/public/avatar?fallback=initials&v=contact'
31
+ 'http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner'
78
32
  )
79
33
  })
80
34
 
81
- it('renders initials when there is no avatarPath and no cozy URL', () => {
35
+ it('renders the initials when there is no avatar URL', () => {
82
36
  const { container, getByText } = setup({
83
37
  _type: 'io.cozy.contacts',
84
38
  name: { givenName: 'Jon', familyName: 'Snow' }
@@ -88,27 +42,13 @@ describe('MemberAvatar', () => {
88
42
  expect(getByText('JS')).toBeTruthy()
89
43
  })
90
44
 
91
- it('renders the initials when there is no avatarPath', () => {
92
- const { container, getByText } = setup({
93
- public_name: 'Bob',
94
- status: 'owner'
95
- })
96
-
97
- expect(container.querySelector('img')).toBeNull()
98
- expect(getByText('B')).toBeTruthy()
99
- })
100
-
101
45
  it('falls back to the initials when the avatar image fails to load', () => {
102
- const { container, getByText } = setup({
103
- public_name: 'Bob',
104
- status: 'owner',
105
- avatarPath: '/sharings/123/recipients/0/avatar'
106
- })
107
-
108
- const img = container.querySelector('img')
109
- expect(img).toBeTruthy()
46
+ const { container, getByText } = setup(
47
+ { public_name: 'Bob', status: 'owner' },
48
+ 'http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner'
49
+ )
110
50
 
111
- fireEvent.error(img)
51
+ fireEvent.error(container.querySelector('img'))
112
52
 
113
53
  expect(container.querySelector('img')).toBeNull()
114
54
  expect(getByText('B')).toBeTruthy()