cozy-sharing 40.1.9 → 40.2.0
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 +10 -0
- package/README.md +1 -0
- package/dist/SharingProvider.js +3 -1
- package/dist/components/Recipient/PermissionTypeMenuWrapper.spec.js +3 -1
- package/dist/components/ShareAutosuggest.js +87 -14
- package/dist/components/ShareAutosuggest.spec.js +123 -3
- package/dist/components/ShareAutosuggestWrapper.js +61 -5
- package/dist/components/ShareAutosuggestWrapper.spec.js +113 -11
- package/dist/helpers/contacts.js +75 -16
- package/dist/helpers/contacts.spec.js +150 -1
- package/dist/hooks/useResolveRecipients.js +6 -2
- package/dist/hooks/useResolveRecipients.spec.js +11 -2
- package/dist/queries/queries.js +16 -3
- package/package.json +3 -3
- package/src/SharingProvider.jsx +6 -1
- package/src/components/Recipient/PermissionTypeMenuWrapper.spec.jsx +3 -1
- package/src/components/ShareAutosuggest.jsx +45 -7
- package/src/components/ShareAutosuggest.spec.jsx +59 -2
- package/src/components/ShareAutosuggestWrapper.jsx +34 -4
- package/src/components/ShareAutosuggestWrapper.spec.jsx +78 -5
- package/src/helpers/contacts.js +31 -1
- package/src/helpers/contacts.spec.js +102 -1
- package/src/hooks/useResolveRecipients.js +5 -2
- package/src/hooks/useResolveRecipients.spec.js +8 -2
- package/src/queries/queries.js +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
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.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.9...cozy-sharing@40.2.0) (2026-10-28)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **cozy-sharing:** Ignore the email case when filtering suggestions ([4057cf0](https://github.com/cozy/cozy-libs/commit/4057cf010f54c646d8518aa787e03be445b60df2))
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- **cozy-sharing:** Suggest recipients from the stack ([8b06972](https://github.com/cozy/cozy-libs/commit/8b0697256b118c365ca9f7315aae3f00eef6ebd6)), closes [linagora/twake-workplace-private#1929](https://github.com/linagora/twake-workplace-private/issues/1929)
|
|
15
|
+
|
|
6
16
|
## [40.1.9](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.8...cozy-sharing@40.1.9) (2026-10-28)
|
|
7
17
|
|
|
8
18
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ import SharingProvider from 'cozy-sharing'
|
|
|
40
40
|
| `onShared` | no | Callback fired after a share is created |
|
|
41
41
|
| `isPublic` | no | Set to `true` when rendering a public page (disables fetching) |
|
|
42
42
|
| `previewPath` | no | Custom preview path sent to the share creation |
|
|
43
|
+
| `contactsSuggestEnabled` | no | Suggest the recipients with `GET /contacts/suggest` instead of loading every contact |
|
|
43
44
|
|
|
44
45
|
### Built-in components
|
|
45
46
|
|
package/dist/SharingProvider.js
CHANGED
|
@@ -1451,7 +1451,9 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
|
|
|
1451
1451
|
// but if we don't, consumers don't rerender when the state changes after loading the sharings,
|
|
1452
1452
|
// probably because the state object remains the same...
|
|
1453
1453
|
return /*#__PURE__*/React.createElement(SharingContext.Provider, {
|
|
1454
|
-
value: _objectSpread({}, this.state)
|
|
1454
|
+
value: _objectSpread(_objectSpread({}, this.state), {}, {
|
|
1455
|
+
contactsSuggestEnabled: this.props.contactsSuggestEnabled
|
|
1456
|
+
})
|
|
1455
1457
|
}, this.props.children);
|
|
1456
1458
|
}
|
|
1457
1459
|
}]);
|
|
@@ -208,7 +208,9 @@ describe('PermissionTypeMenu component', function () {
|
|
|
208
208
|
}]
|
|
209
209
|
};
|
|
210
210
|
beforeEach(function () {
|
|
211
|
-
flag.
|
|
211
|
+
flag.mockImplementation(function (name) {
|
|
212
|
+
return name === 'drive.federated-shared-folder.enabled';
|
|
213
|
+
});
|
|
212
214
|
mockHasSharedParent.mockReturnValue(true);
|
|
213
215
|
mockGetSharingById.mockReturnValue({
|
|
214
216
|
id: 'sharing-123',
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
3
|
+
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
4
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
5
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
3
6
|
import PropTypes from 'prop-types';
|
|
4
|
-
import React, { useState, useRef } from 'react';
|
|
7
|
+
import React, { useEffect, useState, useRef } from 'react';
|
|
5
8
|
import Autosuggest from 'react-autosuggest';
|
|
6
9
|
import Chip from 'cozy-ui/transpiled/react/Chips';
|
|
7
10
|
import { Spinner } from 'cozy-ui/transpiled/react/Spinner';
|
|
@@ -10,6 +13,7 @@ import { MemberAvatar } from './Avatar/MemberAvatar';
|
|
|
10
13
|
import ContactSuggestion from './ContactSuggestion';
|
|
11
14
|
import { isContactToBeCreated } from '../helpers/contacts';
|
|
12
15
|
import { extractEmails, validateEmail } from '../helpers/email';
|
|
16
|
+
import logger from '../logger';
|
|
13
17
|
import { getDisplayName, DOCTYPE_CONTACTS, DOCTYPE_GROUPS } from '../models';
|
|
14
18
|
var styles = {
|
|
15
19
|
"container": "autosuggest__container___1Yro7",
|
|
@@ -24,6 +28,8 @@ var styles = {
|
|
|
24
28
|
"endAdornment": "autosuggest__endAdornment___2EwTN"
|
|
25
29
|
};
|
|
26
30
|
import { contactOrGroupMatch } from '../suggestionMatchers';
|
|
31
|
+
var FETCH_CONTACTS_DEBOUNCE = 300;
|
|
32
|
+
var FETCH_CONTACTS_MIN_LENGTH = 3;
|
|
27
33
|
|
|
28
34
|
var ShareAutosuggest = function ShareAutosuggest(_ref) {
|
|
29
35
|
var loading = _ref.loading,
|
|
@@ -35,7 +41,8 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
|
|
|
35
41
|
enableCreateContact = _ref.enableCreateContact,
|
|
36
42
|
placeholder = _ref.placeholder,
|
|
37
43
|
autoFocus = _ref.autoFocus,
|
|
38
|
-
endAdornment = _ref.endAdornment
|
|
44
|
+
endAdornment = _ref.endAdornment,
|
|
45
|
+
fetchContacts = _ref.fetchContacts;
|
|
39
46
|
|
|
40
47
|
var _useState = useState(''),
|
|
41
48
|
_useState2 = _slicedToArray(_useState, 2),
|
|
@@ -57,8 +64,20 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
|
|
|
57
64
|
isLoadingDisplayed = _useState8[0],
|
|
58
65
|
setLoadingDisplayed = _useState8[1];
|
|
59
66
|
|
|
67
|
+
var _useState9 = useState(false),
|
|
68
|
+
_useState10 = _slicedToArray(_useState9, 2),
|
|
69
|
+
isFetching = _useState10[0],
|
|
70
|
+
setIsFetching = _useState10[1];
|
|
71
|
+
|
|
60
72
|
var autosuggestRef = useRef(null);
|
|
61
73
|
var containerRef = useRef(null);
|
|
74
|
+
var fetchTimeoutRef = useRef(null);
|
|
75
|
+
var fetchedValueRef = useRef(null);
|
|
76
|
+
useEffect(function () {
|
|
77
|
+
return function () {
|
|
78
|
+
return clearTimeout(fetchTimeoutRef.current);
|
|
79
|
+
};
|
|
80
|
+
}, []);
|
|
62
81
|
|
|
63
82
|
var computeSuggestions = function computeSuggestions(value) {
|
|
64
83
|
var inputValue = value.trim().toLowerCase();
|
|
@@ -67,10 +86,7 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
|
|
|
67
86
|
});
|
|
68
87
|
};
|
|
69
88
|
|
|
70
|
-
var
|
|
71
|
-
var value = _ref2.value;
|
|
72
|
-
var computedSuggestions = computeSuggestions(value);
|
|
73
|
-
|
|
89
|
+
var showSuggestions = function showSuggestions(computedSuggestions, value) {
|
|
74
90
|
if (computedSuggestions.length > 0) {
|
|
75
91
|
setSuggestions(computedSuggestions);
|
|
76
92
|
} else if (enableCreateContact && validateEmail(value)) {
|
|
@@ -83,7 +99,63 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
|
|
|
83
99
|
}
|
|
84
100
|
};
|
|
85
101
|
|
|
102
|
+
var onSuggestionsFetchRequested = function onSuggestionsFetchRequested(_ref2) {
|
|
103
|
+
var value = _ref2.value;
|
|
104
|
+
var computedSuggestions = computeSuggestions(value);
|
|
105
|
+
clearTimeout(fetchTimeoutRef.current);
|
|
106
|
+
fetchedValueRef.current = value;
|
|
107
|
+
|
|
108
|
+
if (!fetchContacts || value.trim().length < FETCH_CONTACTS_MIN_LENGTH) {
|
|
109
|
+
setIsFetching(false);
|
|
110
|
+
showSuggestions(computedSuggestions, value);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setIsFetching(true);
|
|
115
|
+
fetchTimeoutRef.current = setTimeout( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
|
|
116
|
+
var contacts;
|
|
117
|
+
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
118
|
+
while (1) switch (_context.prev = _context.next) {
|
|
119
|
+
case 0:
|
|
120
|
+
contacts = [];
|
|
121
|
+
_context.prev = 1;
|
|
122
|
+
_context.next = 4;
|
|
123
|
+
return fetchContacts(value.trim());
|
|
124
|
+
|
|
125
|
+
case 4:
|
|
126
|
+
contacts = _context.sent;
|
|
127
|
+
_context.next = 10;
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 7:
|
|
131
|
+
_context.prev = 7;
|
|
132
|
+
_context.t0 = _context["catch"](1);
|
|
133
|
+
logger.error('Failed to fetch contact suggestions', _context.t0);
|
|
134
|
+
|
|
135
|
+
case 10:
|
|
136
|
+
if (!(fetchedValueRef.current !== value)) {
|
|
137
|
+
_context.next = 12;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return _context.abrupt("return");
|
|
142
|
+
|
|
143
|
+
case 12:
|
|
144
|
+
setIsFetching(false);
|
|
145
|
+
showSuggestions([].concat(_toConsumableArray(contacts), _toConsumableArray(computedSuggestions)), value);
|
|
146
|
+
|
|
147
|
+
case 14:
|
|
148
|
+
case "end":
|
|
149
|
+
return _context.stop();
|
|
150
|
+
}
|
|
151
|
+
}, _callee, null, [[1, 7]]);
|
|
152
|
+
})), FETCH_CONTACTS_DEBOUNCE);
|
|
153
|
+
};
|
|
154
|
+
|
|
86
155
|
var onSuggestionsClearRequested = function onSuggestionsClearRequested() {
|
|
156
|
+
clearTimeout(fetchTimeoutRef.current);
|
|
157
|
+
fetchedValueRef.current = null;
|
|
158
|
+
setIsFetching(false);
|
|
87
159
|
setSuggestions([]);
|
|
88
160
|
};
|
|
89
161
|
|
|
@@ -91,9 +163,9 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
|
|
|
91
163
|
setIsPasted(true);
|
|
92
164
|
};
|
|
93
165
|
|
|
94
|
-
var onAutosuggestChange = function onAutosuggestChange(event,
|
|
95
|
-
var newValue =
|
|
96
|
-
method =
|
|
166
|
+
var onAutosuggestChange = function onAutosuggestChange(event, _ref4) {
|
|
167
|
+
var newValue = _ref4.newValue,
|
|
168
|
+
method = _ref4.method;
|
|
97
169
|
|
|
98
170
|
if (isPasted) {
|
|
99
171
|
var emails = extractEmails(newValue);
|
|
@@ -126,9 +198,9 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
|
|
|
126
198
|
}
|
|
127
199
|
};
|
|
128
200
|
|
|
129
|
-
var onKeyUp = function onKeyUp(
|
|
130
|
-
var key =
|
|
131
|
-
keyCode =
|
|
201
|
+
var onKeyUp = function onKeyUp(_ref5) {
|
|
202
|
+
var key = _ref5.key,
|
|
203
|
+
keyCode = _ref5.keyCode;
|
|
132
204
|
|
|
133
205
|
if (recipients.length > 0 && inputValue === '' && (key === 'Backspace' || keyCode === 8 || key === 'Delete' || keyCode === 46)) {
|
|
134
206
|
document.getElementById("recipient_".concat(recipients.length - 1)).focus();
|
|
@@ -202,7 +274,7 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
|
|
|
202
274
|
}), /*#__PURE__*/React.createElement("input", _extends({}, inputProps, {
|
|
203
275
|
onKeyPress: onKeyPress,
|
|
204
276
|
onKeyUp: onKeyUp
|
|
205
|
-
})), loading && isLoadingDisplayed ? /*#__PURE__*/React.createElement(Spinner, {
|
|
277
|
+
})), (loading || isFetching) && isLoadingDisplayed ? /*#__PURE__*/React.createElement(Spinner, {
|
|
206
278
|
color: "var(--primaryColor)"
|
|
207
279
|
}) : null, endAdornment ? /*#__PURE__*/React.createElement("div", {
|
|
208
280
|
className: styles['endAdornment']
|
|
@@ -258,6 +330,7 @@ ShareAutosuggest.propTypes = {
|
|
|
258
330
|
enableCreateContact: PropTypes.bool,
|
|
259
331
|
placeholder: PropTypes.string,
|
|
260
332
|
autoFocus: PropTypes.bool,
|
|
261
|
-
endAdornment: PropTypes.node
|
|
333
|
+
endAdornment: PropTypes.node,
|
|
334
|
+
fetchContacts: PropTypes.func
|
|
262
335
|
};
|
|
263
336
|
export default ShareAutosuggest;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
3
|
+
import { act, render, fireEvent, screen } from '@testing-library/react';
|
|
2
4
|
import React from 'react';
|
|
3
5
|
import ShareAutosuggest from './ShareAutosuggest';
|
|
4
6
|
import AppLike from '../../test/AppLike';
|
|
@@ -16,7 +18,8 @@ describe('ShareAutosuggest', function () {
|
|
|
16
18
|
_ref$loading = _ref.loading,
|
|
17
19
|
loading = _ref$loading === void 0 ? false : _ref$loading,
|
|
18
20
|
_ref$enableCreateCont = _ref.enableCreateContact,
|
|
19
|
-
enableCreateContact = _ref$enableCreateCont === void 0 ? true : _ref$enableCreateCont
|
|
21
|
+
enableCreateContact = _ref$enableCreateCont === void 0 ? true : _ref$enableCreateCont,
|
|
22
|
+
fetchContacts = _ref.fetchContacts;
|
|
20
23
|
render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(ShareAutosuggest, {
|
|
21
24
|
loading: loading,
|
|
22
25
|
contactsAndGroups: [],
|
|
@@ -24,7 +27,8 @@ describe('ShareAutosuggest', function () {
|
|
|
24
27
|
onPick: onPick,
|
|
25
28
|
recipients: [],
|
|
26
29
|
onRemove: onRemove,
|
|
27
|
-
enableCreateContact: enableCreateContact
|
|
30
|
+
enableCreateContact: enableCreateContact,
|
|
31
|
+
fetchContacts: fetchContacts
|
|
28
32
|
})));
|
|
29
33
|
};
|
|
30
34
|
|
|
@@ -123,4 +127,120 @@ describe('ShareAutosuggest', function () {
|
|
|
123
127
|
});
|
|
124
128
|
expect(screen.queryByText('loading')).toBeNull();
|
|
125
129
|
});
|
|
130
|
+
describe('with fetchContacts', function () {
|
|
131
|
+
beforeEach(function () {
|
|
132
|
+
jest.useFakeTimers(); // Not implemented by jsdom, called when a suggestion is highlighted
|
|
133
|
+
|
|
134
|
+
Element.prototype.scrollIntoView = jest.fn();
|
|
135
|
+
});
|
|
136
|
+
afterEach(function () {
|
|
137
|
+
jest.useRealTimers();
|
|
138
|
+
});
|
|
139
|
+
it('should suggest the fetched contacts, debounced and from 3 characters', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
|
|
140
|
+
var fetchContacts, inputNode;
|
|
141
|
+
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
142
|
+
while (1) switch (_context.prev = _context.next) {
|
|
143
|
+
case 0:
|
|
144
|
+
fetchContacts = jest.fn().mockResolvedValue([{
|
|
145
|
+
_id: 'org-member-1',
|
|
146
|
+
_type: 'io.cozy.contacts',
|
|
147
|
+
displayName: 'Jean Dupont',
|
|
148
|
+
email: [{
|
|
149
|
+
address: 'jean@org.example',
|
|
150
|
+
primary: true
|
|
151
|
+
}]
|
|
152
|
+
}]);
|
|
153
|
+
setup({
|
|
154
|
+
onPick: jest.fn(),
|
|
155
|
+
onRemove: jest.fn(),
|
|
156
|
+
fetchContacts: fetchContacts
|
|
157
|
+
});
|
|
158
|
+
inputNode = screen.getByPlaceholderText('myPlaceHolder');
|
|
159
|
+
fireEvent.change(inputNode, {
|
|
160
|
+
target: {
|
|
161
|
+
value: 'du'
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
act(function () {
|
|
165
|
+
return jest.runAllTimers();
|
|
166
|
+
});
|
|
167
|
+
expect(fetchContacts).not.toHaveBeenCalled();
|
|
168
|
+
fireEvent.change(inputNode, {
|
|
169
|
+
target: {
|
|
170
|
+
value: 'dup'
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
fireEvent.change(inputNode, {
|
|
174
|
+
target: {
|
|
175
|
+
value: 'dupo'
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
act(function () {
|
|
179
|
+
return jest.runAllTimers();
|
|
180
|
+
});
|
|
181
|
+
expect(fetchContacts).toHaveBeenCalledTimes(1);
|
|
182
|
+
expect(fetchContacts).toHaveBeenCalledWith('dupo');
|
|
183
|
+
_context.t0 = expect;
|
|
184
|
+
_context.next = 14;
|
|
185
|
+
return screen.findByText('Jean Dupont');
|
|
186
|
+
|
|
187
|
+
case 14:
|
|
188
|
+
_context.t1 = _context.sent;
|
|
189
|
+
(0, _context.t0)(_context.t1).toBeInTheDocument();
|
|
190
|
+
|
|
191
|
+
case 16:
|
|
192
|
+
case "end":
|
|
193
|
+
return _context.stop();
|
|
194
|
+
}
|
|
195
|
+
}, _callee);
|
|
196
|
+
})));
|
|
197
|
+
it('should show the loading indicator until the contacts are fetched', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
|
|
198
|
+
var resolveFetch, fetchContacts, inputNode;
|
|
199
|
+
return _regeneratorRuntime.wrap(function _callee3$(_context3) {
|
|
200
|
+
while (1) switch (_context3.prev = _context3.next) {
|
|
201
|
+
case 0:
|
|
202
|
+
fetchContacts = jest.fn(function () {
|
|
203
|
+
return new Promise(function (resolve) {
|
|
204
|
+
return resolveFetch = resolve;
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
setup({
|
|
208
|
+
onPick: jest.fn(),
|
|
209
|
+
onRemove: jest.fn(),
|
|
210
|
+
fetchContacts: fetchContacts
|
|
211
|
+
});
|
|
212
|
+
inputNode = screen.getByPlaceholderText('myPlaceHolder');
|
|
213
|
+
fireEvent.change(inputNode, {
|
|
214
|
+
target: {
|
|
215
|
+
value: 'dup'
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
expect(screen.getByText('loading')).toBeInTheDocument();
|
|
219
|
+
act(function () {
|
|
220
|
+
return jest.runAllTimers();
|
|
221
|
+
});
|
|
222
|
+
_context3.next = 8;
|
|
223
|
+
return act( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
|
|
224
|
+
return _regeneratorRuntime.wrap(function _callee2$(_context2) {
|
|
225
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
226
|
+
case 0:
|
|
227
|
+
return _context2.abrupt("return", resolveFetch([]));
|
|
228
|
+
|
|
229
|
+
case 1:
|
|
230
|
+
case "end":
|
|
231
|
+
return _context2.stop();
|
|
232
|
+
}
|
|
233
|
+
}, _callee2);
|
|
234
|
+
})));
|
|
235
|
+
|
|
236
|
+
case 8:
|
|
237
|
+
expect(screen.queryByText('loading')).toBeNull();
|
|
238
|
+
|
|
239
|
+
case 9:
|
|
240
|
+
case "end":
|
|
241
|
+
return _context3.stop();
|
|
242
|
+
}
|
|
243
|
+
}, _callee3);
|
|
244
|
+
})));
|
|
245
|
+
});
|
|
126
246
|
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
1
2
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
2
4
|
|
|
3
5
|
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
6
|
|
|
@@ -6,18 +8,25 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
|
|
|
6
8
|
|
|
7
9
|
import PropTypes from 'prop-types';
|
|
8
10
|
import React from 'react';
|
|
9
|
-
import { useQueryAll, isQueryLoading } from 'cozy-client';
|
|
11
|
+
import { useClient, useQueryAll, isQueryLoading } from 'cozy-client';
|
|
12
|
+
import { models } from 'cozy-client';
|
|
10
13
|
import flag from 'cozy-flags';
|
|
11
14
|
import ShareAutosuggest from './ShareAutosuggest';
|
|
12
|
-
import { computeContactsAndGroups } from '../helpers/contacts';
|
|
15
|
+
import { computeContactsAndGroups, suggestContacts } from '../helpers/contacts';
|
|
16
|
+
import { useSharingContext } from '../hooks/useSharingContext';
|
|
17
|
+
import { DOCTYPE_GROUPS } from '../models';
|
|
13
18
|
import { buildReachableContactsQuery, buildContactGroupsQuery, buildUnreachableContactsWithGroupsQuery } from '../queries/queries';
|
|
19
|
+
var ContactModel = models.contact;
|
|
14
20
|
/**
|
|
15
21
|
* ShareAutosuggestWrapper is responsible for fetching contacts and groups.
|
|
16
22
|
* We retrieve all the contacts that are reachable and the groups they belong to.
|
|
17
23
|
* In order to display the total number of members in each group, we also retrieve the contacts that are not reachable.
|
|
24
|
+
* With contactsSuggestEnabled, the stack suggests the contacts and we only retrieve the group members.
|
|
18
25
|
*/
|
|
19
26
|
|
|
20
27
|
var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
|
|
28
|
+
var _useSharingContext;
|
|
29
|
+
|
|
21
30
|
var currentRecipients = _ref.currentRecipients,
|
|
22
31
|
recipients = _ref.recipients,
|
|
23
32
|
placeholder = _ref.placeholder,
|
|
@@ -27,7 +36,11 @@ var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
|
|
|
27
36
|
disabled = _ref.disabled,
|
|
28
37
|
autoFocus = _ref.autoFocus,
|
|
29
38
|
endAdornment = _ref.endAdornment;
|
|
30
|
-
var
|
|
39
|
+
var client = useClient();
|
|
40
|
+
var contactsSuggestEnabled = (_useSharingContext = useSharingContext()) === null || _useSharingContext === void 0 ? void 0 : _useSharingContext.contactsSuggestEnabled;
|
|
41
|
+
var reachableContactsQuery = buildReachableContactsQuery({
|
|
42
|
+
inGroups: contactsSuggestEnabled
|
|
43
|
+
});
|
|
31
44
|
var reachableContactsResult = useQueryAll(reachableContactsQuery.definition, reachableContactsQuery.options);
|
|
32
45
|
var unreachableContactsWithGroupsQuery = buildUnreachableContactsWithGroupsQuery();
|
|
33
46
|
var unreachableContactsWithGroupsResult = useQueryAll(unreachableContactsWithGroupsQuery.definition, _objectSpread(_objectSpread({}, unreachableContactsWithGroupsQuery.options), {}, {
|
|
@@ -36,13 +49,55 @@ var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
|
|
|
36
49
|
var contactGroupsQuery = buildContactGroupsQuery();
|
|
37
50
|
var contactGroupsResult = useQueryAll(contactGroupsQuery.definition, contactGroupsQuery.options);
|
|
38
51
|
var isLoading = isQueryLoading(reachableContactsResult) || reachableContactsResult.hasMore || isQueryLoading(contactGroupsResult) || contactGroupsResult.hasMore || flag('sharing.show-recipient-groups') && (isQueryLoading(unreachableContactsWithGroupsResult) || unreachableContactsWithGroupsResult.hasMore);
|
|
39
|
-
var
|
|
52
|
+
var suggestedContactsAndGroups = computeContactsAndGroups({
|
|
40
53
|
reachableContacts: reachableContactsResult.data || [],
|
|
41
54
|
unreachableContactsWithGroups: unreachableContactsWithGroupsResult.data || [],
|
|
42
55
|
contactGroups: contactGroupsResult.data || [],
|
|
43
56
|
currentRecipients: currentRecipients,
|
|
44
57
|
pendingRecipients: recipients
|
|
45
58
|
});
|
|
59
|
+
var contactsAndGroups = contactsSuggestEnabled ? suggestedContactsAndGroups.filter(function (_ref2) {
|
|
60
|
+
var _type = _ref2._type;
|
|
61
|
+
return _type === DOCTYPE_GROUPS;
|
|
62
|
+
}) : suggestedContactsAndGroups; // The stack matches the emails whatever their case
|
|
63
|
+
|
|
64
|
+
var normalizeEmail = function normalizeEmail(email) {
|
|
65
|
+
return email === null || email === void 0 ? void 0 : email.trim().toLowerCase();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
var isRecipient = function isRecipient(email) {
|
|
69
|
+
return currentRecipients.some(function (r) {
|
|
70
|
+
return normalizeEmail(r.email) === email;
|
|
71
|
+
}) || recipients.some(function (r) {
|
|
72
|
+
return normalizeEmail(ContactModel.getPrimaryEmail(r)) === email;
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
var fetchContacts = /*#__PURE__*/function () {
|
|
77
|
+
var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(q) {
|
|
78
|
+
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
79
|
+
while (1) switch (_context.prev = _context.next) {
|
|
80
|
+
case 0:
|
|
81
|
+
_context.next = 2;
|
|
82
|
+
return suggestContacts(client, q);
|
|
83
|
+
|
|
84
|
+
case 2:
|
|
85
|
+
return _context.abrupt("return", _context.sent.filter(function (contact) {
|
|
86
|
+
return !isRecipient(normalizeEmail(ContactModel.getPrimaryEmail(contact)));
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
case 3:
|
|
90
|
+
case "end":
|
|
91
|
+
return _context.stop();
|
|
92
|
+
}
|
|
93
|
+
}, _callee);
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
return function fetchContacts(_x) {
|
|
97
|
+
return _ref3.apply(this, arguments);
|
|
98
|
+
};
|
|
99
|
+
}();
|
|
100
|
+
|
|
46
101
|
return /*#__PURE__*/React.createElement(ShareAutosuggest, {
|
|
47
102
|
loading: isLoading,
|
|
48
103
|
disabled: disabled,
|
|
@@ -53,7 +108,8 @@ var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
|
|
|
53
108
|
enableCreateContact: enableCreateContact,
|
|
54
109
|
placeholder: placeholder,
|
|
55
110
|
autoFocus: autoFocus,
|
|
56
|
-
endAdornment: endAdornment
|
|
111
|
+
endAdornment: endAdornment,
|
|
112
|
+
fetchContacts: contactsSuggestEnabled ? fetchContacts : undefined
|
|
57
113
|
});
|
|
58
114
|
};
|
|
59
115
|
|
|
@@ -14,11 +14,13 @@ jest.mock('cozy-flags', function () {
|
|
|
14
14
|
})
|
|
15
15
|
};
|
|
16
16
|
});
|
|
17
|
+
var mockAutosuggestProps;
|
|
17
18
|
jest.mock('./ShareAutosuggest', function () {
|
|
18
19
|
return {
|
|
19
20
|
__esModule: true,
|
|
20
|
-
default: function _default(
|
|
21
|
-
|
|
21
|
+
default: function _default(props) {
|
|
22
|
+
mockAutosuggestProps = props;
|
|
23
|
+
var contactsAndGroups = props.contactsAndGroups;
|
|
22
24
|
return /*#__PURE__*/React.createElement("ul", null, contactsAndGroups.map(function (contactOrGroup) {
|
|
23
25
|
if (contactOrGroup._type === 'io.cozy.contacts') {
|
|
24
26
|
return /*#__PURE__*/React.createElement("li", {
|
|
@@ -34,14 +36,16 @@ jest.mock('./ShareAutosuggest', function () {
|
|
|
34
36
|
};
|
|
35
37
|
});
|
|
36
38
|
describe('ShareAutosuggestWrapper component', function () {
|
|
37
|
-
var setup = function setup(
|
|
38
|
-
var contacts =
|
|
39
|
-
contactGroups =
|
|
40
|
-
unreachableContact =
|
|
39
|
+
var setup = function setup(_ref) {
|
|
40
|
+
var contacts = _ref.contacts,
|
|
41
|
+
contactGroups = _ref.contactGroups,
|
|
42
|
+
unreachableContact = _ref.unreachableContact,
|
|
43
|
+
groupMembers = _ref.groupMembers;
|
|
41
44
|
|
|
42
|
-
var
|
|
43
|
-
currentRecipients =
|
|
44
|
-
recipients =
|
|
45
|
+
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
46
|
+
currentRecipients = _ref2.currentRecipients,
|
|
47
|
+
recipients = _ref2.recipients,
|
|
48
|
+
contactsSuggestEnabled = _ref2.contactsSuggestEnabled;
|
|
45
49
|
|
|
46
50
|
var mockClient = createMockClient({
|
|
47
51
|
queries: {
|
|
@@ -56,11 +60,18 @@ describe('ShareAutosuggestWrapper component', function () {
|
|
|
56
60
|
'io.cozy.contacts/unreachable-with-groups': {
|
|
57
61
|
doctype: 'io.cozy.contacts',
|
|
58
62
|
data: unreachableContact
|
|
63
|
+
},
|
|
64
|
+
'io.cozy.contacts/reachable-with-groups': {
|
|
65
|
+
doctype: 'io.cozy.contacts',
|
|
66
|
+
data: groupMembers
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
});
|
|
62
70
|
render( /*#__PURE__*/React.createElement(AppLike, {
|
|
63
|
-
client: mockClient
|
|
71
|
+
client: mockClient,
|
|
72
|
+
sharingContextValue: {
|
|
73
|
+
contactsSuggestEnabled: contactsSuggestEnabled
|
|
74
|
+
}
|
|
64
75
|
}, /*#__PURE__*/React.createElement(ShareAutosuggestWrapper, {
|
|
65
76
|
onPick: function onPick() {},
|
|
66
77
|
onRemove: function onRemove() {},
|
|
@@ -68,6 +79,7 @@ describe('ShareAutosuggestWrapper component', function () {
|
|
|
68
79
|
currentRecipients: currentRecipients,
|
|
69
80
|
recipients: recipients
|
|
70
81
|
})));
|
|
82
|
+
return mockClient;
|
|
71
83
|
};
|
|
72
84
|
|
|
73
85
|
it('should include groups and contacts when are loaded', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
|
|
@@ -240,7 +252,9 @@ describe('ShareAutosuggestWrapper component', function () {
|
|
|
240
252
|
return _regeneratorRuntime.wrap(function _callee4$(_context4) {
|
|
241
253
|
while (1) switch (_context4.prev = _context4.next) {
|
|
242
254
|
case 0:
|
|
243
|
-
flag.
|
|
255
|
+
flag.mockImplementation(function (name) {
|
|
256
|
+
return name === 'sharing.show-recipient-groups';
|
|
257
|
+
});
|
|
244
258
|
contacts = [{
|
|
245
259
|
id: 'df563cc4-6440',
|
|
246
260
|
_id: 'df563cc4-6440',
|
|
@@ -307,4 +321,92 @@ describe('ShareAutosuggestWrapper component', function () {
|
|
|
307
321
|
}
|
|
308
322
|
}, _callee4);
|
|
309
323
|
})));
|
|
324
|
+
describe('with contactsSuggestEnabled', function () {
|
|
325
|
+
beforeEach(function () {
|
|
326
|
+
flag.mockReset();
|
|
327
|
+
});
|
|
328
|
+
it('should suggest only groups, with the count of their members', function () {
|
|
329
|
+
var groupMembers = [{
|
|
330
|
+
id: '5a3b4ccf-c257',
|
|
331
|
+
_id: '5a3b4ccf-c257',
|
|
332
|
+
_type: 'io.cozy.contacts',
|
|
333
|
+
name: {
|
|
334
|
+
givenName: 'Teagan',
|
|
335
|
+
familyName: 'Wolf'
|
|
336
|
+
},
|
|
337
|
+
relationships: {
|
|
338
|
+
groups: {
|
|
339
|
+
data: [{
|
|
340
|
+
_id: 'fe86af20-c6c5',
|
|
341
|
+
_type: 'io.cozy.contacts.groups'
|
|
342
|
+
}]
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}];
|
|
346
|
+
var contactGroups = [{
|
|
347
|
+
id: 'fe86af20-c6c5',
|
|
348
|
+
_id: 'fe86af20-c6c5',
|
|
349
|
+
_type: 'io.cozy.contacts.groups',
|
|
350
|
+
name: 'Friends'
|
|
351
|
+
}];
|
|
352
|
+
setup({
|
|
353
|
+
contactGroups: contactGroups,
|
|
354
|
+
groupMembers: groupMembers
|
|
355
|
+
}, {
|
|
356
|
+
contactsSuggestEnabled: true
|
|
357
|
+
});
|
|
358
|
+
expect(screen.getByText('Friends - 1 members')).toBeInTheDocument();
|
|
359
|
+
expect(screen.queryByText('Teagan Wolf')).not.toBeInTheDocument();
|
|
360
|
+
});
|
|
361
|
+
it('should fetch suggestions without the current and pending recipients, whatever the case of their email', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
|
|
362
|
+
var suggestion, mockClient, fetchJSON, contacts;
|
|
363
|
+
return _regeneratorRuntime.wrap(function _callee5$(_context5) {
|
|
364
|
+
while (1) switch (_context5.prev = _context5.next) {
|
|
365
|
+
case 0:
|
|
366
|
+
suggestion = function suggestion(email) {
|
|
367
|
+
return {
|
|
368
|
+
type: 'io.cozy.contacts',
|
|
369
|
+
attributes: {
|
|
370
|
+
fullname: email,
|
|
371
|
+
email: [{
|
|
372
|
+
address: email
|
|
373
|
+
}]
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
mockClient = setup({
|
|
379
|
+
contactGroups: [],
|
|
380
|
+
groupMembers: []
|
|
381
|
+
}, {
|
|
382
|
+
currentRecipients: [{
|
|
383
|
+
email: 'Current@Cozy.cc'
|
|
384
|
+
}],
|
|
385
|
+
recipients: [{
|
|
386
|
+
email: 'pending@cozy.cc'
|
|
387
|
+
}],
|
|
388
|
+
contactsSuggestEnabled: true
|
|
389
|
+
});
|
|
390
|
+
fetchJSON = jest.fn().mockResolvedValue({
|
|
391
|
+
data: [suggestion('current@cozy.cc'), suggestion('PENDING@cozy.cc'), suggestion('new@cozy.cc')]
|
|
392
|
+
});
|
|
393
|
+
jest.spyOn(mockClient, 'getStackClient').mockReturnValue({
|
|
394
|
+
fetchJSON: fetchJSON
|
|
395
|
+
});
|
|
396
|
+
_context5.next = 6;
|
|
397
|
+
return mockAutosuggestProps.fetchContacts('coz');
|
|
398
|
+
|
|
399
|
+
case 6:
|
|
400
|
+
contacts = _context5.sent;
|
|
401
|
+
expect(contacts.map(function (c) {
|
|
402
|
+
return c.fullname;
|
|
403
|
+
})).toEqual(['new@cozy.cc']);
|
|
404
|
+
|
|
405
|
+
case 8:
|
|
406
|
+
case "end":
|
|
407
|
+
return _context5.stop();
|
|
408
|
+
}
|
|
409
|
+
}, _callee5);
|
|
410
|
+
})));
|
|
411
|
+
});
|
|
310
412
|
});
|