@atlaskit/link-picker 1.6.1 → 1.7.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cjs/{ui/error-boundary-fallback → common}/generic-error-svg/index.js +0 -0
  3. package/dist/cjs/{ui/error-boundary-fallback → common}/generic-error-svg/styled.js +0 -0
  4. package/dist/cjs/services/use-plugins/index.js +32 -35
  5. package/dist/cjs/services/use-search-query/index.js +41 -0
  6. package/dist/cjs/ui/error-boundary-fallback/index.js +1 -1
  7. package/dist/cjs/ui/link-picker/index.js +56 -40
  8. package/dist/cjs/ui/link-picker/link-search-error/index.js +27 -0
  9. package/dist/cjs/ui/link-picker/messages.js +10 -0
  10. package/dist/cjs/ui/link-picker/url.js +0 -5
  11. package/dist/cjs/version.json +1 -1
  12. package/dist/es2019/{ui/error-boundary-fallback → common}/generic-error-svg/index.js +0 -0
  13. package/dist/es2019/{ui/error-boundary-fallback → common}/generic-error-svg/styled.js +0 -0
  14. package/dist/es2019/services/use-plugins/index.js +28 -38
  15. package/dist/es2019/services/use-search-query/index.js +32 -0
  16. package/dist/es2019/ui/error-boundary-fallback/index.js +1 -1
  17. package/dist/es2019/ui/link-picker/index.js +50 -39
  18. package/dist/es2019/ui/link-picker/link-search-error/index.js +10 -0
  19. package/dist/es2019/ui/link-picker/messages.js +10 -0
  20. package/dist/es2019/ui/link-picker/url.js +0 -5
  21. package/dist/es2019/version.json +1 -1
  22. package/dist/esm/{ui/error-boundary-fallback → common}/generic-error-svg/index.js +0 -0
  23. package/dist/esm/{ui/error-boundary-fallback → common}/generic-error-svg/styled.js +0 -0
  24. package/dist/esm/services/use-plugins/index.js +33 -35
  25. package/dist/esm/services/use-search-query/index.js +32 -0
  26. package/dist/esm/ui/error-boundary-fallback/index.js +1 -1
  27. package/dist/esm/ui/link-picker/index.js +52 -39
  28. package/dist/esm/ui/link-picker/link-search-error/index.js +14 -0
  29. package/dist/esm/ui/link-picker/messages.js +10 -0
  30. package/dist/esm/ui/link-picker/url.js +0 -5
  31. package/dist/esm/version.json +1 -1
  32. package/dist/types/{ui/error-boundary-fallback → common}/generic-error-svg/index.d.ts +0 -0
  33. package/dist/types/{ui/error-boundary-fallback → common}/generic-error-svg/styled.d.ts +0 -0
  34. package/dist/types/index.d.ts +1 -1
  35. package/dist/types/services/use-plugins/index.d.ts +6 -5
  36. package/dist/types/services/use-search-query/index.d.ts +3 -0
  37. package/dist/types/ui/link-picker/index.d.ts +2 -0
  38. package/dist/types/ui/link-picker/link-search-error/index.d.ts +4 -0
  39. package/dist/types/ui/link-picker/messages.d.ts +10 -0
  40. package/dist/types/ui/types.d.ts +5 -2
  41. package/package.json +4 -4
  42. package/report.api.md +11 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @atlaskit/link-picker
2
2
 
3
+ ## 1.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`d0feee9b4ad`](https://bitbucket.org/atlassian/atlassian-frontend/commits/d0feee9b4ad) - [ux] Add a UI error message to display when the link picker plugin resolve throws an error
8
+
3
9
  ## 1.6.1
4
10
 
5
11
  ### Patch Changes
@@ -19,8 +19,6 @@ var _react = require("react");
19
19
 
20
20
  var _linkPicker = require("../../ui/link-picker");
21
21
 
22
- var _url = require("../../ui/link-picker/url");
23
-
24
22
  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; }
25
23
 
26
24
  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) { (0, _defineProperty2.default)(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; }
@@ -29,56 +27,46 @@ function reducer(state, payload) {
29
27
  return _objectSpread(_objectSpread({}, state), payload);
30
28
  }
31
29
 
32
- function usePlugins(state, plugins) {
30
+ function usePlugins(state, activeTab, plugins) {
31
+ var _useState = (0, _react.useState)(0),
32
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
33
+ retries = _useState2[0],
34
+ setRetries = _useState2[1];
35
+
33
36
  var _useReducer = (0, _react.useReducer)(reducer, {
34
- items: [],
37
+ items: null,
38
+ error: null,
35
39
  isLoading: false
36
40
  }),
37
41
  _useReducer2 = (0, _slicedToArray2.default)(_useReducer, 2),
38
42
  pluginState = _useReducer2[0],
39
43
  dispatch = _useReducer2[1];
40
44
 
41
- var url = state.url,
42
- selectedIndex = state.selectedIndex;
43
45
  var items = pluginState.items,
44
- isLoading = pluginState.isLoading;
45
- var activePlugin = !plugins || plugins.length === 0 ? null : plugins[state.activeTab];
46
+ isLoading = pluginState.isLoading,
47
+ error = pluginState.error;
46
48
  var queryVersion = (0, _react.useRef)(0);
47
- var pluginRef = (0, _react.useRef)(activePlugin);
48
- var selectedItem = items[selectedIndex];
49
- var isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
49
+ var activePlugin = plugins === null || plugins === void 0 ? void 0 : plugins[activeTab];
50
50
  (0, _react.useEffect)(function () {
51
51
  if (!activePlugin) {
52
52
  return;
53
53
  }
54
54
 
55
- if (isSelectedItem) {
56
- return;
57
- }
58
-
59
- if ((0, _url.isSafeUrl)(url) && url.length) {
55
+ if (!state) {
60
56
  dispatch({
61
- items: [],
62
- isLoading: false
57
+ items: null,
58
+ isLoading: false,
59
+ error: null
63
60
  });
64
61
  return;
65
62
  }
66
63
 
67
- var updates = activePlugin.resolve({
68
- query: url
64
+ var updates = activePlugin.resolve(state);
65
+ dispatch({
66
+ items: null,
67
+ isLoading: true,
68
+ error: null
69
69
  });
70
-
71
- if (pluginRef.current !== activePlugin) {
72
- dispatch({
73
- items: [],
74
- isLoading: true
75
- });
76
- } else {
77
- dispatch({
78
- isLoading: true
79
- });
80
- }
81
-
82
70
  var newQuery = ++queryVersion.current;
83
71
 
84
72
  var updateResults = /*#__PURE__*/function () {
@@ -156,6 +144,8 @@ function usePlugins(state, plugins) {
156
144
  _context.prev = 25;
157
145
  _context.t0 = _context["catch"](0);
158
146
  dispatch({
147
+ error: _context.t0,
148
+ items: null,
159
149
  isLoading: false
160
150
  });
161
151
 
@@ -173,7 +163,7 @@ function usePlugins(state, plugins) {
173
163
  }();
174
164
 
175
165
  updateResults();
176
- }, [activePlugin, queryVersion, url, isSelectedItem]);
166
+ }, [activePlugin, queryVersion, state, retries]);
177
167
  var tabs = (0, _react.useMemo)(function () {
178
168
  if (!plugins || plugins.length <= 1) {
179
169
  return [];
@@ -187,12 +177,19 @@ function usePlugins(state, plugins) {
187
177
  };
188
178
  });
189
179
  }, [plugins]);
180
+ var handleRetry = (0, _react.useCallback)(function () {
181
+ setRetries(function (prev) {
182
+ return ++prev;
183
+ });
184
+ }, []);
190
185
  return {
191
186
  tabs: tabs,
192
187
  items: items,
193
188
  isLoading: isLoading,
194
- isSelectedItem: isSelectedItem,
195
- isActivePlugin: !!activePlugin
189
+ isActivePlugin: !!activePlugin,
190
+ error: error,
191
+ retry: handleRetry,
192
+ errorFallback: activePlugin === null || activePlugin === void 0 ? void 0 : activePlugin.errorFallback
196
193
  };
197
194
  }
198
195
 
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useSearchQuery = useSearchQuery;
7
+
8
+ var _react = require("react");
9
+
10
+ var _url = require("../../ui/link-picker/url");
11
+
12
+ function useSearchQuery(state) {
13
+ var _queryState$current;
14
+
15
+ var queryState = (0, _react.useRef)(null);
16
+ /*
17
+ * When state contains a valid url AND is NOT selected from results
18
+ * queryState should be null to clear the items in plugin state
19
+ */
20
+
21
+ if ((0, _url.isSafeUrl)(state.url) && state.selectedIndex === -1) {
22
+ queryState.current = null;
23
+ }
24
+ /*
25
+ * When state contains a search term e.g. Not a valid url return a query
26
+ */
27
+
28
+
29
+ if (((_queryState$current = queryState.current) === null || _queryState$current === void 0 ? void 0 : _queryState$current.query) !== state.url && !(0, _url.isSafeUrl)(state.url)) {
30
+ queryState.current = {
31
+ query: state.url
32
+ };
33
+ }
34
+ /*
35
+ * When state contains a valid url and is selected from the list
36
+ * return current state to prevent triger a new plugin search
37
+ */
38
+
39
+
40
+ return queryState.current;
41
+ }
@@ -13,7 +13,7 @@ var _emptyState = _interopRequireDefault(require("@atlaskit/empty-state"));
13
13
 
14
14
  var _reactIntlNext = require("react-intl-next");
15
15
 
16
- var _genericErrorSvg = _interopRequireDefault(require("./generic-error-svg"));
16
+ var _genericErrorSvg = _interopRequireDefault(require("../../common/generic-error-svg"));
17
17
 
18
18
  var _styled = require("../link-picker/styled");
19
19
 
@@ -7,7 +7,7 @@ var _typeof = require("@babel/runtime/helpers/typeof");
7
7
  Object.defineProperty(exports, "__esModule", {
8
8
  value: true
9
9
  });
10
- exports.testIds = exports.default = exports.RECENT_SEARCH_LIST_SIZE = void 0;
10
+ exports.testIds = exports.default = exports.RECENT_SEARCH_LIST_SIZE = exports.CONTACT_SUPPORT_LINK = void 0;
11
11
 
12
12
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
13
13
 
@@ -31,6 +31,10 @@ var _tabs = _interopRequireWildcard(require("@atlaskit/tabs"));
31
31
 
32
32
  var _visuallyHidden = _interopRequireDefault(require("@atlaskit/visually-hidden"));
33
33
 
34
+ var _usePlugins2 = require("../../services/use-plugins");
35
+
36
+ var _useSearchQuery = require("../../services/use-search-query");
37
+
34
38
  var _messages = require("./messages");
35
39
 
36
40
  var _textInput = _interopRequireDefault(require("./text-input"));
@@ -43,13 +47,13 @@ var _styled = require("./styled");
43
47
 
44
48
  var _browser = require("./browser");
45
49
 
46
- var _transformTimeStamp = require("./transformTimeStamp");
47
-
48
50
  var _announcer = _interopRequireDefault(require("./announcer"));
49
51
 
50
52
  var _linkSearchNoResults = _interopRequireDefault(require("./link-search-no-results"));
51
53
 
52
- var _usePlugins2 = require("../../services/use-plugins");
54
+ var _transformTimeStamp = require("./transformTimeStamp");
55
+
56
+ var _linkSearchError = _interopRequireDefault(require("./link-search-error"));
53
57
 
54
58
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
55
59
 
@@ -61,6 +65,8 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
61
65
 
62
66
  var RECENT_SEARCH_LIST_SIZE = 5;
63
67
  exports.RECENT_SEARCH_LIST_SIZE = RECENT_SEARCH_LIST_SIZE;
68
+ var CONTACT_SUPPORT_LINK = 'https://support.atlassian.com/contact/';
69
+ exports.CONTACT_SUPPORT_LINK = CONTACT_SUPPORT_LINK;
64
70
  var testIds = {
65
71
  linkPicker: 'link-picker',
66
72
  urlInputField: 'link-url',
@@ -76,7 +82,8 @@ var testIds = {
76
82
  searchResultLoadingIndicator: 'link-picker.results-loading-indicator',
77
83
  urlError: 'link-error',
78
84
  tabList: 'link-picker-tabs',
79
- tabItem: 'link-picker-tab'
85
+ tabItem: 'link-picker-tab',
86
+ searchError: 'link-search-error'
80
87
  };
81
88
  exports.testIds = testIds;
82
89
  var initState = {
@@ -100,6 +107,8 @@ function reducer(state, payload) {
100
107
  }
101
108
 
102
109
  function LinkPicker(_ref) {
110
+ var _queryState$query, _items$length, _errorFallback;
111
+
103
112
  var onSubmit = _ref.onSubmit,
104
113
  onCancel = _ref.onCancel,
105
114
  onContentResize = _ref.onContentResize,
@@ -119,17 +128,23 @@ function LinkPicker(_ref) {
119
128
  selectedIndex = state.selectedIndex,
120
129
  url = state.url,
121
130
  displayText = state.displayText,
122
- invalidUrl = state.invalidUrl;
123
- var isEditing = !!initUrl;
131
+ invalidUrl = state.invalidUrl,
132
+ activeTab = state.activeTab;
124
133
  var intl = (0, _reactIntlNext.useIntl)();
134
+ var queryState = (0, _useSearchQuery.useSearchQuery)(state);
125
135
 
126
- var _usePlugins = (0, _usePlugins2.usePlugins)(state, plugins),
136
+ var _usePlugins = (0, _usePlugins2.usePlugins)(queryState, activeTab, plugins),
127
137
  items = _usePlugins.items,
128
138
  isLoading = _usePlugins.isLoading,
129
- isSelectedItem = _usePlugins.isSelectedItem,
130
139
  isActivePlugin = _usePlugins.isActivePlugin,
131
- tabs = _usePlugins.tabs;
140
+ tabs = _usePlugins.tabs,
141
+ error = _usePlugins.error,
142
+ retry = _usePlugins.retry,
143
+ errorFallback = _usePlugins.errorFallback;
132
144
 
145
+ var isEditing = !!initUrl;
146
+ var selectedItem = items === null || items === void 0 ? void 0 : items[selectedIndex];
147
+ var isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
133
148
  (0, _react.useLayoutEffect)(function () {
134
149
  if (onContentResize) {
135
150
  onContentResize();
@@ -183,15 +198,15 @@ function LinkPicker(_ref) {
183
198
  dispatch((0, _defineProperty2.default)({}, field, ''));
184
199
  }, []);
185
200
  var handleMouseEnterResultItem = (0, _react.useCallback)(function (objectId) {
186
- var index = findIndex(items, function (item) {
201
+ var index = items === null || items === void 0 ? void 0 : items.findIndex(function (item) {
187
202
  return item.objectId === objectId;
188
203
  });
189
204
  dispatch({
190
- activeIndex: index
205
+ activeIndex: index !== null && index !== void 0 ? index : -1
191
206
  });
192
207
  }, [items]);
193
208
  var handleMouseLeaveResultItem = (0, _react.useCallback)(function (objectId) {
194
- var index = findIndex(items, function (item) {
209
+ var index = items === null || items === void 0 ? void 0 : items.findIndex(function (item) {
195
210
  return item.objectId === objectId;
196
211
  }); // This is to avoid updating index that was set by other mouseenter event
197
212
 
@@ -214,7 +229,7 @@ function LinkPicker(_ref) {
214
229
  } : {}));
215
230
  }, [displayText, onSubmit, state.url]);
216
231
  var handleSelected = (0, _react.useCallback)(function (objectId) {
217
- var selectedItem = items.find(function (item) {
232
+ var selectedItem = items === null || items === void 0 ? void 0 : items.find(function (item) {
218
233
  return item.objectId === objectId;
219
234
  });
220
235
 
@@ -225,9 +240,7 @@ function LinkPicker(_ref) {
225
240
  }
226
241
  }, [handleInsert, items]);
227
242
  var handleSubmit = (0, _react.useCallback)(function () {
228
- var selectedItem = items[selectedIndex];
229
-
230
- if (isSelectedItem) {
243
+ if (isSelectedItem && selectedItem) {
231
244
  return handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
232
245
  }
233
246
 
@@ -240,18 +253,18 @@ function LinkPicker(_ref) {
240
253
  return dispatch({
241
254
  invalidUrl: true
242
255
  });
243
- }, [handleInsert, isSelectedItem, items, selectedIndex, url]);
256
+ }, [handleInsert, isSelectedItem, selectedItem, url]);
244
257
  var linkPlaceHolder = isActivePlugin ? _messages.messages.placeholder : _messages.messages.linkPlaceholder;
245
- var linkListTitle = url ? _messages.messages.titleResults : _messages.messages.titleRecentlyViewed;
258
+ var linkListTitle = (queryState === null || queryState === void 0 ? void 0 : (_queryState$query = queryState.query) === null || _queryState$query === void 0 ? void 0 : _queryState$query.length) !== 0 ? _messages.messages.titleResults : _messages.messages.titleRecentlyViewed;
246
259
  var insertButtonMsg = isEditing ? _messages.messages.saveButton : _messages.messages.insertButton;
247
- var noResults = isActivePlugin && !isLoading && !items.length && !(0, _url2.isSafeUrl)(url);
260
+ var noResults = (items === null || items === void 0 ? void 0 : items.length) === 0 && !error;
248
261
  var screenReaderDescriptionId = 'search-recent-links-field-description';
249
262
  var linkSearchListId = 'link-picker-search-list';
250
263
  var ariaActiveDescendant = selectedIndex > -1 ? "link-search-list-item-".concat(selectedIndex) : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
251
264
  // as the Aria design pattern for combobox does not work in this case
252
265
  // for details: https://a11y-internal.atlassian.net/browse/AK-740
253
266
 
254
- var screenReaderText = _browser.browser.safari && getScreenReaderText(items, selectedIndex, intl);
267
+ var screenReaderText = _browser.browser.safari && getScreenReaderText(items !== null && items !== void 0 ? items : [], selectedIndex, intl);
255
268
  var searchIcon = isActivePlugin && (0, _react2.jsx)("span", {
256
269
  css: _styled.searchIconStyles,
257
270
  "data-testid": testIds.searchIcon
@@ -303,7 +316,7 @@ function LinkPicker(_ref) {
303
316
  onSubmit: handleSubmit,
304
317
  onKeyDown: handleKeyDown,
305
318
  onChange: handleChangeText
306
- }), tabs.length > 0 && (0, _react2.jsx)("div", {
319
+ }), queryState && (0, _react2.jsx)(_react.Fragment, null, tabs.length > 0 && (0, _react2.jsx)("div", {
307
320
  css: _styled.tabsWrapperStyles
308
321
  }, (0, _react2.jsx)(_tabs.default, {
309
322
  id: testIds.tabList,
@@ -321,7 +334,7 @@ function LinkPicker(_ref) {
321
334
  key: tab.tabTitle,
322
335
  testId: testIds.tabItem
323
336
  }, tab.tabTitle);
324
- })))), !!items.length && (0, _react2.jsx)("div", {
337
+ })))), !!(items !== null && items !== void 0 && items.length) && (0, _react2.jsx)("div", {
325
338
  css: _styled.listTitleStyles,
326
339
  id: testIds.resultListTitle,
327
340
  "data-testid": testIds.resultListTitle
@@ -329,7 +342,7 @@ function LinkPicker(_ref) {
329
342
  id: "fabric.smartcard.linkpicker.suggested.results"
330
343
  }, url && (0, _react2.jsx)(_reactIntlNext.FormattedMessage, (0, _extends2.default)({}, _messages.messages.searchLinkResults, {
331
344
  values: {
332
- count: items.length
345
+ count: (_items$length = items === null || items === void 0 ? void 0 : items.length) !== null && _items$length !== void 0 ? _items$length : 0
333
346
  },
334
347
  "aria-live": "polite",
335
348
  "aria-atomic": "true"
@@ -338,18 +351,34 @@ function LinkPicker(_ref) {
338
351
  ariaControls: "fabric.smartcard.linkpicker.suggested.results",
339
352
  id: linkSearchListId,
340
353
  role: "listbox",
341
- items: items,
354
+ items: items !== null && items !== void 0 ? items : [],
342
355
  isLoading: isLoading,
343
356
  selectedIndex: selectedIndex,
344
357
  activeIndex: activeIndex,
345
358
  onSelect: handleSelected,
346
359
  onMouseEnter: handleMouseEnterResultItem,
347
360
  onMouseLeave: handleMouseLeaveResultItem
348
- }), noResults && (0, _react2.jsx)(_linkSearchNoResults.default, {
361
+ }), error && ((_errorFallback = errorFallback === null || errorFallback === void 0 ? void 0 : errorFallback(error, retry)) !== null && _errorFallback !== void 0 ? _errorFallback : (0, _react2.jsx)(_linkSearchError.default, {
362
+ testId: testIds.searchError,
363
+ header: intl.formatMessage(_messages.messages.searchErrorHeader),
364
+ description: (0, _react2.jsx)(_reactIntlNext.FormattedMessage, (0, _extends2.default)({}, _messages.messages.searchErrorDescription, {
365
+ values: {
366
+ a: function a(label) {
367
+ return (0, _react2.jsx)(_button.default, {
368
+ appearance: "link",
369
+ spacing: "none",
370
+ href: CONTACT_SUPPORT_LINK,
371
+ target: "_blank",
372
+ rel: "noopener noreferrer"
373
+ }, label);
374
+ }
375
+ }
376
+ }))
377
+ })), noResults && (0, _react2.jsx)(_linkSearchNoResults.default, {
349
378
  testId: testIds.emptyResultPage,
350
379
  header: intl.formatMessage(_messages.messages.noResults),
351
380
  description: intl.formatMessage(_messages.messages.noResultsDescription)
352
- }), (0, _react2.jsx)(_form.FormFooter, {
381
+ })), (0, _react2.jsx)(_form.FormFooter, {
353
382
  align: "end"
354
383
  }, (0, _react2.jsx)(_button.ButtonGroup, null, (0, _react2.jsx)(_button.default, {
355
384
  appearance: "default",
@@ -377,17 +406,4 @@ function getScreenReaderText(items, selectedIndex, intl) {
377
406
  var formattedDate = [date === null || date === void 0 ? void 0 : date.pageAction, date === null || date === void 0 ? void 0 : date.dateString, date === null || date === void 0 ? void 0 : date.timeSince].filter(Boolean).join(' ');
378
407
  return [name, container, formattedDate].filter(Boolean).join(', ');
379
408
  }
380
- }
381
-
382
- function findIndex(array, predicate) {
383
- var index = -1;
384
- array.some(function (item, i) {
385
- if (predicate(item)) {
386
- index = i;
387
- return true;
388
- }
389
-
390
- return false;
391
- });
392
- return index;
393
409
  }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.default = void 0;
9
+
10
+ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
11
+
12
+ var _react = _interopRequireDefault(require("react"));
13
+
14
+ var _emptyState = _interopRequireDefault(require("@atlaskit/empty-state"));
15
+
16
+ var _genericErrorSvg = _interopRequireDefault(require("../../../common/generic-error-svg"));
17
+
18
+ var LinkSearchError = function LinkSearchError(props) {
19
+ return /*#__PURE__*/_react.default.createElement(_emptyState.default, (0, _extends2.default)({}, props, {
20
+ renderImage: function renderImage() {
21
+ return /*#__PURE__*/_react.default.createElement(_genericErrorSvg.default, null);
22
+ }
23
+ }));
24
+ };
25
+
26
+ var _default = LinkSearchError;
27
+ exports.default = _default;
@@ -112,6 +112,16 @@ var messages = (0, _reactIntlNext.defineMessages)({
112
112
  id: 'fabric.linkPicker.button.save',
113
113
  defaultMessage: 'Save',
114
114
  description: 'Button to save edited link'
115
+ },
116
+ searchErrorHeader: {
117
+ id: 'fabric.linkPicker.search.error.heading',
118
+ defaultMessage: 'We’re having trouble loading data.',
119
+ description: 'Heading message shown when a search throws an error'
120
+ },
121
+ searchErrorDescription: {
122
+ id: 'fabric.linkPicker.search.error.description',
123
+ defaultMessage: 'Refresh the page, or contact <a>Atlassian Support</a> if this keeps happening.',
124
+ description: 'Describes possible actions when search throws an error'
115
125
  }
116
126
  });
117
127
  exports.messages = messages;
@@ -75,11 +75,6 @@ var linkifyMatch = function linkifyMatch(text) {
75
75
 
76
76
  var isSafeUrl = function isSafeUrl(url) {
77
77
  var urlTrimmed = url.trim();
78
-
79
- if (urlTrimmed.length === 0) {
80
- return true;
81
- }
82
-
83
78
  return whitelistedURLPatterns.some(function (p) {
84
79
  return p.test(urlTrimmed);
85
80
  });
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/link-picker",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "sideEffects": false
5
5
  }
@@ -1,6 +1,5 @@
1
- import { useEffect, useRef, useReducer, useMemo } from 'react';
1
+ import { useEffect, useReducer, useMemo, useRef, useState, useCallback } from 'react';
2
2
  import { RECENT_SEARCH_LIST_SIZE } from '../../ui/link-picker';
3
- import { isSafeUrl } from '../../ui/link-picker/url';
4
3
 
5
4
  function reducer(state, payload) {
6
5
  return { ...state,
@@ -8,56 +7,40 @@ function reducer(state, payload) {
8
7
  };
9
8
  }
10
9
 
11
- export function usePlugins(state, plugins) {
10
+ export function usePlugins(state, activeTab, plugins) {
11
+ const [retries, setRetries] = useState(0);
12
12
  const [pluginState, dispatch] = useReducer(reducer, {
13
- items: [],
13
+ items: null,
14
+ error: null,
14
15
  isLoading: false
15
16
  });
16
- const {
17
- url,
18
- selectedIndex
19
- } = state;
20
17
  const {
21
18
  items,
22
- isLoading
19
+ isLoading,
20
+ error
23
21
  } = pluginState;
24
- const activePlugin = !plugins || plugins.length === 0 ? null : plugins[state.activeTab];
25
22
  const queryVersion = useRef(0);
26
- const pluginRef = useRef(activePlugin);
27
- const selectedItem = items[selectedIndex];
28
- const isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
23
+ const activePlugin = plugins === null || plugins === void 0 ? void 0 : plugins[activeTab];
29
24
  useEffect(() => {
30
25
  if (!activePlugin) {
31
26
  return;
32
27
  }
33
28
 
34
- if (isSelectedItem) {
35
- return;
36
- }
37
-
38
- if (isSafeUrl(url) && url.length) {
29
+ if (!state) {
39
30
  dispatch({
40
- items: [],
41
- isLoading: false
31
+ items: null,
32
+ isLoading: false,
33
+ error: null
42
34
  });
43
35
  return;
44
36
  }
45
37
 
46
- const updates = activePlugin.resolve({
47
- query: url
38
+ const updates = activePlugin.resolve(state);
39
+ dispatch({
40
+ items: null,
41
+ isLoading: true,
42
+ error: null
48
43
  });
49
-
50
- if (pluginRef.current !== activePlugin) {
51
- dispatch({
52
- items: [],
53
- isLoading: true
54
- });
55
- } else {
56
- dispatch({
57
- isLoading: true
58
- });
59
- }
60
-
61
44
  const newQuery = ++queryVersion.current;
62
45
 
63
46
  const updateResults = async () => {
@@ -90,15 +73,17 @@ export function usePlugins(state, plugins) {
90
73
  return;
91
74
  }
92
75
  }
93
- } catch {
76
+ } catch (error) {
94
77
  dispatch({
78
+ error,
79
+ items: null,
95
80
  isLoading: false
96
81
  });
97
82
  }
98
83
  };
99
84
 
100
85
  updateResults();
101
- }, [activePlugin, queryVersion, url, isSelectedItem]);
86
+ }, [activePlugin, queryVersion, state, retries]);
102
87
  const tabs = useMemo(() => {
103
88
  if (!plugins || plugins.length <= 1) {
104
89
  return [];
@@ -108,12 +93,17 @@ export function usePlugins(state, plugins) {
108
93
  tabTitle: plugin.tabTitle
109
94
  }));
110
95
  }, [plugins]);
96
+ const handleRetry = useCallback(() => {
97
+ setRetries(prev => ++prev);
98
+ }, []);
111
99
  return {
112
100
  tabs,
113
101
  items,
114
102
  isLoading,
115
- isSelectedItem,
116
- isActivePlugin: !!activePlugin
103
+ isActivePlugin: !!activePlugin,
104
+ error,
105
+ retry: handleRetry,
106
+ errorFallback: activePlugin === null || activePlugin === void 0 ? void 0 : activePlugin.errorFallback
117
107
  };
118
108
  }
119
109
 
@@ -0,0 +1,32 @@
1
+ import { useRef } from 'react';
2
+ import { isSafeUrl } from '../../ui/link-picker/url';
3
+ export function useSearchQuery(state) {
4
+ var _queryState$current;
5
+
6
+ const queryState = useRef(null);
7
+ /*
8
+ * When state contains a valid url AND is NOT selected from results
9
+ * queryState should be null to clear the items in plugin state
10
+ */
11
+
12
+ if (isSafeUrl(state.url) && state.selectedIndex === -1) {
13
+ queryState.current = null;
14
+ }
15
+ /*
16
+ * When state contains a search term e.g. Not a valid url return a query
17
+ */
18
+
19
+
20
+ if (((_queryState$current = queryState.current) === null || _queryState$current === void 0 ? void 0 : _queryState$current.query) !== state.url && !isSafeUrl(state.url)) {
21
+ queryState.current = {
22
+ query: state.url
23
+ };
24
+ }
25
+ /*
26
+ * When state contains a valid url and is selected from the list
27
+ * return current state to prevent triger a new plugin search
28
+ */
29
+
30
+
31
+ return queryState.current;
32
+ }
@@ -2,7 +2,7 @@
2
2
  import { jsx } from '@emotion/react';
3
3
  import EmptyState from '@atlaskit/empty-state';
4
4
  import { defineMessages, useIntl } from 'react-intl-next';
5
- import GenericErrorSVG from './generic-error-svg';
5
+ import GenericErrorSVG from '../../common/generic-error-svg';
6
6
  import { rootContainerStyles } from '../link-picker/styled';
7
7
  const messages = defineMessages({
8
8
  heading: {