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.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -0
- package/dist/SharingOwnerAvatar.js +2 -2
- package/dist/SharingProvider.js +3 -1
- package/dist/components/Avatar/AvatarList.js +2 -2
- package/dist/components/Avatar/MemberAvatar.js +8 -33
- package/dist/components/Avatar/MemberAvatar.spec.js +17 -77
- package/dist/components/Avatar/MemberAvatarWrapper.js +20 -0
- package/dist/components/ConfirmTrustedRecipientsDialog.js +5 -1
- package/dist/components/ContactSuggestion.js +5 -2
- package/dist/components/Recipient/MemberRecipient.js +6 -3
- package/dist/components/Recipient/MemberRecipient.spec.js +1 -9
- package/dist/components/Recipient/MemberRecipientLite.js +2 -2
- package/dist/components/Recipient/PermissionTypeMenuWrapper.spec.js +3 -1
- package/dist/components/ShareAutosuggest.js +93 -16
- package/dist/components/ShareAutosuggest.spec.js +126 -3
- package/dist/components/ShareAutosuggest.stories.js +3 -0
- package/dist/components/ShareAutosuggestWrapper.js +65 -5
- package/dist/components/ShareAutosuggestWrapper.spec.js +113 -11
- package/dist/components/WhoHasAccessWrapper.js +6 -2
- package/dist/helpers/contacts.js +75 -16
- package/dist/helpers/contacts.spec.js +150 -1
- package/dist/helpers/recipients.js +38 -0
- package/dist/helpers/recipients.spec.js +53 -1
- package/dist/hooks/useResolveRecipients.js +6 -2
- package/dist/hooks/useResolveRecipients.spec.js +11 -2
- package/dist/index.js +1 -1
- package/dist/queries/queries.js +16 -3
- package/package.json +3 -3
- package/src/SharingOwnerAvatar.jsx +2 -2
- package/src/SharingProvider.jsx +6 -1
- package/src/components/Avatar/AvatarList.jsx +2 -2
- package/src/components/Avatar/MemberAvatar.jsx +4 -34
- package/src/components/Avatar/MemberAvatar.spec.jsx +13 -73
- package/src/components/Avatar/MemberAvatarWrapper.jsx +22 -0
- package/src/components/ConfirmTrustedRecipientsDialog.jsx +7 -0
- package/src/components/ContactSuggestion.jsx +8 -3
- package/src/components/Recipient/MemberRecipient.jsx +4 -2
- package/src/components/Recipient/MemberRecipient.spec.jsx +1 -8
- package/src/components/Recipient/MemberRecipientLite.jsx +2 -2
- package/src/components/Recipient/PermissionTypeMenuWrapper.spec.jsx +3 -1
- package/src/components/ShareAutosuggest.jsx +56 -9
- package/src/components/ShareAutosuggest.spec.jsx +60 -2
- package/src/components/ShareAutosuggest.stories.jsx +1 -0
- package/src/components/ShareAutosuggestWrapper.jsx +36 -4
- package/src/components/ShareAutosuggestWrapper.spec.jsx +78 -5
- package/src/components/WhoHasAccessWrapper.jsx +14 -2
- package/src/helpers/contacts.js +31 -1
- package/src/helpers/contacts.spec.js +102 -1
- package/src/helpers/recipients.js +40 -0
- package/src/helpers/recipients.spec.js +83 -1
- package/src/hooks/useResolveRecipients.js +5 -2
- package/src/hooks/useResolveRecipients.spec.js +8 -2
- package/src/index.jsx +1 -1
- 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/
|
|
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';
|
package/dist/queries/queries.js
CHANGED
|
@@ -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
|
-
}
|
|
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
|
|
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.
|
|
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": "
|
|
87
|
+
"gitHead": "dc527e69d5c65ec27dc4ad7b940e01248ee6120f"
|
|
88
88
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
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
|
-
<
|
|
11
|
+
<MemberAvatarWrapper recipient={getOwner(docId)} {...rest} />
|
|
12
12
|
)
|
|
13
13
|
}}
|
|
14
14
|
</SharingContext.Consumer>
|
package/src/SharingProvider.jsx
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
{
|
|
30
|
+
{avatarUrl && erroredSrc !== avatarUrl ? (
|
|
61
31
|
<img
|
|
62
32
|
width="100%"
|
|
63
33
|
height="100%"
|
|
64
|
-
src={
|
|
65
|
-
onError={() => setErroredSrc(
|
|
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
|
|
11
|
-
client.options = { uri: 'http://cozy.example.com' }
|
|
12
|
-
|
|
13
|
-
const setup = recipient =>
|
|
8
|
+
const setup = (recipient, avatarUrl = null) =>
|
|
14
9
|
render(
|
|
15
|
-
<AppLike
|
|
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
|
|
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
|
-
'
|
|
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
|
|
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
|
-
|
|
105
|
-
|
|
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()
|