@atlaskit/link-picker 1.5.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 +18 -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 +62 -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 +56 -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 +58 -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 +5 -1
  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 +6 -5
  42. package/report.api.md +21 -13
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
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
+
9
+ ## 1.6.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [`e3f36b94557`](https://bitbucket.org/atlassian/atlassian-frontend/commits/e3f36b94557) - Inlines svg assets directly into the UI
14
+
15
+ ## 1.6.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [`c9c0a119587`](https://bitbucket.org/atlassian/atlassian-frontend/commits/c9c0a119587) - [ux] EDM-3881: add onContentRezise prop to allow callbacks after contents changed in items, plugin, tabs, etc.
20
+
3
21
  ## 1.5.1
4
22
 
5
23
  ### 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,8 +107,11 @@ 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,
114
+ onContentResize = _ref.onContentResize,
105
115
  plugins = _ref.plugins,
106
116
  initUrl = _ref.url,
107
117
  initDisplayText = _ref.displayText;
@@ -118,17 +128,28 @@ function LinkPicker(_ref) {
118
128
  selectedIndex = state.selectedIndex,
119
129
  url = state.url,
120
130
  displayText = state.displayText,
121
- invalidUrl = state.invalidUrl;
122
- var isEditing = !!initUrl;
131
+ invalidUrl = state.invalidUrl,
132
+ activeTab = state.activeTab;
123
133
  var intl = (0, _reactIntlNext.useIntl)();
134
+ var queryState = (0, _useSearchQuery.useSearchQuery)(state);
124
135
 
125
- var _usePlugins = (0, _usePlugins2.usePlugins)(state, plugins),
136
+ var _usePlugins = (0, _usePlugins2.usePlugins)(queryState, activeTab, plugins),
126
137
  items = _usePlugins.items,
127
138
  isLoading = _usePlugins.isLoading,
128
- isSelectedItem = _usePlugins.isSelectedItem,
129
139
  isActivePlugin = _usePlugins.isActivePlugin,
130
- tabs = _usePlugins.tabs;
140
+ tabs = _usePlugins.tabs,
141
+ error = _usePlugins.error,
142
+ retry = _usePlugins.retry,
143
+ errorFallback = _usePlugins.errorFallback;
131
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;
148
+ (0, _react.useLayoutEffect)(function () {
149
+ if (onContentResize) {
150
+ onContentResize();
151
+ }
152
+ }, [onContentResize, items, isLoading, isActivePlugin, tabs]);
132
153
  var handleChangeUrl = (0, _react.useCallback)(function (url) {
133
154
  dispatch({
134
155
  url: url
@@ -177,15 +198,15 @@ function LinkPicker(_ref) {
177
198
  dispatch((0, _defineProperty2.default)({}, field, ''));
178
199
  }, []);
179
200
  var handleMouseEnterResultItem = (0, _react.useCallback)(function (objectId) {
180
- var index = findIndex(items, function (item) {
201
+ var index = items === null || items === void 0 ? void 0 : items.findIndex(function (item) {
181
202
  return item.objectId === objectId;
182
203
  });
183
204
  dispatch({
184
- activeIndex: index
205
+ activeIndex: index !== null && index !== void 0 ? index : -1
185
206
  });
186
207
  }, [items]);
187
208
  var handleMouseLeaveResultItem = (0, _react.useCallback)(function (objectId) {
188
- var index = findIndex(items, function (item) {
209
+ var index = items === null || items === void 0 ? void 0 : items.findIndex(function (item) {
189
210
  return item.objectId === objectId;
190
211
  }); // This is to avoid updating index that was set by other mouseenter event
191
212
 
@@ -208,7 +229,7 @@ function LinkPicker(_ref) {
208
229
  } : {}));
209
230
  }, [displayText, onSubmit, state.url]);
210
231
  var handleSelected = (0, _react.useCallback)(function (objectId) {
211
- var selectedItem = items.find(function (item) {
232
+ var selectedItem = items === null || items === void 0 ? void 0 : items.find(function (item) {
212
233
  return item.objectId === objectId;
213
234
  });
214
235
 
@@ -219,9 +240,7 @@ function LinkPicker(_ref) {
219
240
  }
220
241
  }, [handleInsert, items]);
221
242
  var handleSubmit = (0, _react.useCallback)(function () {
222
- var selectedItem = items[selectedIndex];
223
-
224
- if (isSelectedItem) {
243
+ if (isSelectedItem && selectedItem) {
225
244
  return handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
226
245
  }
227
246
 
@@ -234,18 +253,18 @@ function LinkPicker(_ref) {
234
253
  return dispatch({
235
254
  invalidUrl: true
236
255
  });
237
- }, [handleInsert, isSelectedItem, items, selectedIndex, url]);
256
+ }, [handleInsert, isSelectedItem, selectedItem, url]);
238
257
  var linkPlaceHolder = isActivePlugin ? _messages.messages.placeholder : _messages.messages.linkPlaceholder;
239
- 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;
240
259
  var insertButtonMsg = isEditing ? _messages.messages.saveButton : _messages.messages.insertButton;
241
- var noResults = isActivePlugin && !isLoading && !items.length && !(0, _url2.isSafeUrl)(url);
260
+ var noResults = (items === null || items === void 0 ? void 0 : items.length) === 0 && !error;
242
261
  var screenReaderDescriptionId = 'search-recent-links-field-description';
243
262
  var linkSearchListId = 'link-picker-search-list';
244
263
  var ariaActiveDescendant = selectedIndex > -1 ? "link-search-list-item-".concat(selectedIndex) : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
245
264
  // as the Aria design pattern for combobox does not work in this case
246
265
  // for details: https://a11y-internal.atlassian.net/browse/AK-740
247
266
 
248
- var screenReaderText = _browser.browser.safari && getScreenReaderText(items, selectedIndex, intl);
267
+ var screenReaderText = _browser.browser.safari && getScreenReaderText(items !== null && items !== void 0 ? items : [], selectedIndex, intl);
249
268
  var searchIcon = isActivePlugin && (0, _react2.jsx)("span", {
250
269
  css: _styled.searchIconStyles,
251
270
  "data-testid": testIds.searchIcon
@@ -297,7 +316,7 @@ function LinkPicker(_ref) {
297
316
  onSubmit: handleSubmit,
298
317
  onKeyDown: handleKeyDown,
299
318
  onChange: handleChangeText
300
- }), tabs.length > 0 && (0, _react2.jsx)("div", {
319
+ }), queryState && (0, _react2.jsx)(_react.Fragment, null, tabs.length > 0 && (0, _react2.jsx)("div", {
301
320
  css: _styled.tabsWrapperStyles
302
321
  }, (0, _react2.jsx)(_tabs.default, {
303
322
  id: testIds.tabList,
@@ -315,7 +334,7 @@ function LinkPicker(_ref) {
315
334
  key: tab.tabTitle,
316
335
  testId: testIds.tabItem
317
336
  }, tab.tabTitle);
318
- })))), !!items.length && (0, _react2.jsx)("div", {
337
+ })))), !!(items !== null && items !== void 0 && items.length) && (0, _react2.jsx)("div", {
319
338
  css: _styled.listTitleStyles,
320
339
  id: testIds.resultListTitle,
321
340
  "data-testid": testIds.resultListTitle
@@ -323,7 +342,7 @@ function LinkPicker(_ref) {
323
342
  id: "fabric.smartcard.linkpicker.suggested.results"
324
343
  }, url && (0, _react2.jsx)(_reactIntlNext.FormattedMessage, (0, _extends2.default)({}, _messages.messages.searchLinkResults, {
325
344
  values: {
326
- count: items.length
345
+ count: (_items$length = items === null || items === void 0 ? void 0 : items.length) !== null && _items$length !== void 0 ? _items$length : 0
327
346
  },
328
347
  "aria-live": "polite",
329
348
  "aria-atomic": "true"
@@ -332,18 +351,34 @@ function LinkPicker(_ref) {
332
351
  ariaControls: "fabric.smartcard.linkpicker.suggested.results",
333
352
  id: linkSearchListId,
334
353
  role: "listbox",
335
- items: items,
354
+ items: items !== null && items !== void 0 ? items : [],
336
355
  isLoading: isLoading,
337
356
  selectedIndex: selectedIndex,
338
357
  activeIndex: activeIndex,
339
358
  onSelect: handleSelected,
340
359
  onMouseEnter: handleMouseEnterResultItem,
341
360
  onMouseLeave: handleMouseLeaveResultItem
342
- }), 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, {
343
378
  testId: testIds.emptyResultPage,
344
379
  header: intl.formatMessage(_messages.messages.noResults),
345
380
  description: intl.formatMessage(_messages.messages.noResultsDescription)
346
- }), (0, _react2.jsx)(_form.FormFooter, {
381
+ })), (0, _react2.jsx)(_form.FormFooter, {
347
382
  align: "end"
348
383
  }, (0, _react2.jsx)(_button.ButtonGroup, null, (0, _react2.jsx)(_button.default, {
349
384
  appearance: "default",
@@ -371,17 +406,4 @@ function getScreenReaderText(items, selectedIndex, intl) {
371
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(' ');
372
407
  return [name, container, formattedDate].filter(Boolean).join(', ');
373
408
  }
374
- }
375
-
376
- function findIndex(array, predicate) {
377
- var index = -1;
378
- array.some(function (item, i) {
379
- if (predicate(item)) {
380
- index = i;
381
- return true;
382
- }
383
-
384
- return false;
385
- });
386
- return index;
387
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.5.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
+ }