@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.
- package/CHANGELOG.md +6 -0
- package/dist/cjs/{ui/error-boundary-fallback → common}/generic-error-svg/index.js +0 -0
- package/dist/cjs/{ui/error-boundary-fallback → common}/generic-error-svg/styled.js +0 -0
- package/dist/cjs/services/use-plugins/index.js +32 -35
- package/dist/cjs/services/use-search-query/index.js +41 -0
- package/dist/cjs/ui/error-boundary-fallback/index.js +1 -1
- package/dist/cjs/ui/link-picker/index.js +56 -40
- package/dist/cjs/ui/link-picker/link-search-error/index.js +27 -0
- package/dist/cjs/ui/link-picker/messages.js +10 -0
- package/dist/cjs/ui/link-picker/url.js +0 -5
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/{ui/error-boundary-fallback → common}/generic-error-svg/index.js +0 -0
- package/dist/es2019/{ui/error-boundary-fallback → common}/generic-error-svg/styled.js +0 -0
- package/dist/es2019/services/use-plugins/index.js +28 -38
- package/dist/es2019/services/use-search-query/index.js +32 -0
- package/dist/es2019/ui/error-boundary-fallback/index.js +1 -1
- package/dist/es2019/ui/link-picker/index.js +50 -39
- package/dist/es2019/ui/link-picker/link-search-error/index.js +10 -0
- package/dist/es2019/ui/link-picker/messages.js +10 -0
- package/dist/es2019/ui/link-picker/url.js +0 -5
- package/dist/es2019/version.json +1 -1
- package/dist/esm/{ui/error-boundary-fallback → common}/generic-error-svg/index.js +0 -0
- package/dist/esm/{ui/error-boundary-fallback → common}/generic-error-svg/styled.js +0 -0
- package/dist/esm/services/use-plugins/index.js +33 -35
- package/dist/esm/services/use-search-query/index.js +32 -0
- package/dist/esm/ui/error-boundary-fallback/index.js +1 -1
- package/dist/esm/ui/link-picker/index.js +52 -39
- package/dist/esm/ui/link-picker/link-search-error/index.js +14 -0
- package/dist/esm/ui/link-picker/messages.js +10 -0
- package/dist/esm/ui/link-picker/url.js +0 -5
- package/dist/esm/version.json +1 -1
- package/dist/types/{ui/error-boundary-fallback → common}/generic-error-svg/index.d.ts +0 -0
- package/dist/types/{ui/error-boundary-fallback → common}/generic-error-svg/styled.d.ts +0 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/services/use-plugins/index.d.ts +6 -5
- package/dist/types/services/use-search-query/index.d.ts +3 -0
- package/dist/types/ui/link-picker/index.d.ts +2 -0
- package/dist/types/ui/link-picker/link-search-error/index.d.ts +4 -0
- package/dist/types/ui/link-picker/messages.d.ts +10 -0
- package/dist/types/ui/types.d.ts +5 -2
- package/package.json +4 -4
- package/report.api.md +11 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/extends";
|
|
2
2
|
|
|
3
3
|
/** @jsx jsx */
|
|
4
|
-
import { useCallback, useLayoutEffect, useReducer } from 'react';
|
|
4
|
+
import { Fragment, useCallback, useLayoutEffect, useReducer } from 'react';
|
|
5
5
|
import { jsx } from '@emotion/react';
|
|
6
6
|
import { useIntl, FormattedMessage } from 'react-intl-next';
|
|
7
7
|
import Button, { ButtonGroup } from '@atlaskit/button';
|
|
@@ -9,17 +9,20 @@ import { FormFooter } from '@atlaskit/form';
|
|
|
9
9
|
import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
|
|
10
10
|
import Tabs, { Tab, TabList } from '@atlaskit/tabs';
|
|
11
11
|
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
12
|
+
import { usePlugins } from '../../services/use-plugins';
|
|
13
|
+
import { useSearchQuery } from '../../services/use-search-query';
|
|
12
14
|
import { messages } from './messages';
|
|
13
15
|
import PanelTextInput from './text-input';
|
|
14
16
|
import LinkSearchList from './link-search-list';
|
|
15
17
|
import { normalizeUrl, isSafeUrl } from './url';
|
|
16
18
|
import { rootContainerStyles, searchIconStyles, listTitleStyles, tabsWrapperStyles } from './styled';
|
|
17
19
|
import { browser } from './browser';
|
|
18
|
-
import { transformTimeStamp } from './transformTimeStamp';
|
|
19
20
|
import Announcer from './announcer';
|
|
20
21
|
import LinkSearchNoResults from './link-search-no-results';
|
|
21
|
-
import {
|
|
22
|
+
import { transformTimeStamp } from './transformTimeStamp';
|
|
23
|
+
import LinkSearchError from './link-search-error';
|
|
22
24
|
export const RECENT_SEARCH_LIST_SIZE = 5;
|
|
25
|
+
export const CONTACT_SUPPORT_LINK = 'https://support.atlassian.com/contact/';
|
|
23
26
|
export const testIds = {
|
|
24
27
|
linkPicker: 'link-picker',
|
|
25
28
|
urlInputField: 'link-url',
|
|
@@ -35,7 +38,8 @@ export const testIds = {
|
|
|
35
38
|
searchResultLoadingIndicator: 'link-picker.results-loading-indicator',
|
|
36
39
|
urlError: 'link-error',
|
|
37
40
|
tabList: 'link-picker-tabs',
|
|
38
|
-
tabItem: 'link-picker-tab'
|
|
41
|
+
tabItem: 'link-picker-tab',
|
|
42
|
+
searchError: 'link-search-error'
|
|
39
43
|
};
|
|
40
44
|
const initState = {
|
|
41
45
|
url: '',
|
|
@@ -68,6 +72,8 @@ function LinkPicker({
|
|
|
68
72
|
url: initUrl,
|
|
69
73
|
displayText: initDisplayText
|
|
70
74
|
}) {
|
|
75
|
+
var _queryState$query, _items$length, _errorFallback;
|
|
76
|
+
|
|
71
77
|
const [state, dispatch] = useReducer(reducer, { ...initState,
|
|
72
78
|
url: normalizeUrl(initUrl) || '',
|
|
73
79
|
displayText: initDisplayText || ''
|
|
@@ -77,17 +83,23 @@ function LinkPicker({
|
|
|
77
83
|
selectedIndex,
|
|
78
84
|
url,
|
|
79
85
|
displayText,
|
|
80
|
-
invalidUrl
|
|
86
|
+
invalidUrl,
|
|
87
|
+
activeTab
|
|
81
88
|
} = state;
|
|
82
|
-
const isEditing = !!initUrl;
|
|
83
89
|
const intl = useIntl();
|
|
90
|
+
const queryState = useSearchQuery(state);
|
|
84
91
|
const {
|
|
85
92
|
items,
|
|
86
93
|
isLoading,
|
|
87
|
-
isSelectedItem,
|
|
88
94
|
isActivePlugin,
|
|
89
|
-
tabs
|
|
90
|
-
|
|
95
|
+
tabs,
|
|
96
|
+
error,
|
|
97
|
+
retry,
|
|
98
|
+
errorFallback
|
|
99
|
+
} = usePlugins(queryState, activeTab, plugins);
|
|
100
|
+
const isEditing = !!initUrl;
|
|
101
|
+
const selectedItem = items === null || items === void 0 ? void 0 : items[selectedIndex];
|
|
102
|
+
const isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
|
|
91
103
|
useLayoutEffect(() => {
|
|
92
104
|
if (onContentResize) {
|
|
93
105
|
onContentResize();
|
|
@@ -145,13 +157,13 @@ function LinkPicker({
|
|
|
145
157
|
});
|
|
146
158
|
}, []);
|
|
147
159
|
const handleMouseEnterResultItem = useCallback(objectId => {
|
|
148
|
-
const index = findIndex(
|
|
160
|
+
const index = items === null || items === void 0 ? void 0 : items.findIndex(item => item.objectId === objectId);
|
|
149
161
|
dispatch({
|
|
150
|
-
activeIndex: index
|
|
162
|
+
activeIndex: index !== null && index !== void 0 ? index : -1
|
|
151
163
|
});
|
|
152
164
|
}, [items]);
|
|
153
165
|
const handleMouseLeaveResultItem = useCallback(objectId => {
|
|
154
|
-
const index = findIndex(
|
|
166
|
+
const index = items === null || items === void 0 ? void 0 : items.findIndex(item => item.objectId === objectId); // This is to avoid updating index that was set by other mouseenter event
|
|
155
167
|
|
|
156
168
|
if (activeIndex === index) {
|
|
157
169
|
dispatch({
|
|
@@ -173,7 +185,7 @@ function LinkPicker({
|
|
|
173
185
|
});
|
|
174
186
|
}, [displayText, onSubmit, state.url]);
|
|
175
187
|
const handleSelected = useCallback(objectId => {
|
|
176
|
-
const selectedItem = items.find(item => item.objectId === objectId);
|
|
188
|
+
const selectedItem = items === null || items === void 0 ? void 0 : items.find(item => item.objectId === objectId);
|
|
177
189
|
|
|
178
190
|
if (selectedItem) {
|
|
179
191
|
const {
|
|
@@ -184,9 +196,7 @@ function LinkPicker({
|
|
|
184
196
|
}
|
|
185
197
|
}, [handleInsert, items]);
|
|
186
198
|
const handleSubmit = useCallback(() => {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (isSelectedItem) {
|
|
199
|
+
if (isSelectedItem && selectedItem) {
|
|
190
200
|
return handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
|
|
191
201
|
}
|
|
192
202
|
|
|
@@ -199,18 +209,18 @@ function LinkPicker({
|
|
|
199
209
|
return dispatch({
|
|
200
210
|
invalidUrl: true
|
|
201
211
|
});
|
|
202
|
-
}, [handleInsert, isSelectedItem,
|
|
212
|
+
}, [handleInsert, isSelectedItem, selectedItem, url]);
|
|
203
213
|
const linkPlaceHolder = isActivePlugin ? messages.placeholder : messages.linkPlaceholder;
|
|
204
|
-
const linkListTitle =
|
|
214
|
+
const linkListTitle = (queryState === null || queryState === void 0 ? void 0 : (_queryState$query = queryState.query) === null || _queryState$query === void 0 ? void 0 : _queryState$query.length) !== 0 ? messages.titleResults : messages.titleRecentlyViewed;
|
|
205
215
|
const insertButtonMsg = isEditing ? messages.saveButton : messages.insertButton;
|
|
206
|
-
const noResults =
|
|
216
|
+
const noResults = (items === null || items === void 0 ? void 0 : items.length) === 0 && !error;
|
|
207
217
|
const screenReaderDescriptionId = 'search-recent-links-field-description';
|
|
208
218
|
const linkSearchListId = 'link-picker-search-list';
|
|
209
219
|
const ariaActiveDescendant = selectedIndex > -1 ? `link-search-list-item-${selectedIndex}` : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
|
|
210
220
|
// as the Aria design pattern for combobox does not work in this case
|
|
211
221
|
// for details: https://a11y-internal.atlassian.net/browse/AK-740
|
|
212
222
|
|
|
213
|
-
const screenReaderText = browser.safari && getScreenReaderText(items, selectedIndex, intl);
|
|
223
|
+
const screenReaderText = browser.safari && getScreenReaderText(items !== null && items !== void 0 ? items : [], selectedIndex, intl);
|
|
214
224
|
const searchIcon = isActivePlugin && jsx("span", {
|
|
215
225
|
css: searchIconStyles,
|
|
216
226
|
"data-testid": testIds.searchIcon
|
|
@@ -262,7 +272,7 @@ function LinkPicker({
|
|
|
262
272
|
onSubmit: handleSubmit,
|
|
263
273
|
onKeyDown: handleKeyDown,
|
|
264
274
|
onChange: handleChangeText
|
|
265
|
-
}), tabs.length > 0 && jsx("div", {
|
|
275
|
+
}), queryState && jsx(Fragment, null, tabs.length > 0 && jsx("div", {
|
|
266
276
|
css: tabsWrapperStyles
|
|
267
277
|
}, jsx(Tabs, {
|
|
268
278
|
id: testIds.tabList,
|
|
@@ -276,7 +286,7 @@ function LinkPicker({
|
|
|
276
286
|
}, jsx(TabList, null, tabs.map(tab => jsx(Tab, {
|
|
277
287
|
key: tab.tabTitle,
|
|
278
288
|
testId: testIds.tabItem
|
|
279
|
-
}, tab.tabTitle))))), !!items.length && jsx("div", {
|
|
289
|
+
}, tab.tabTitle))))), !!(items !== null && items !== void 0 && items.length) && jsx("div", {
|
|
280
290
|
css: listTitleStyles,
|
|
281
291
|
id: testIds.resultListTitle,
|
|
282
292
|
"data-testid": testIds.resultListTitle
|
|
@@ -284,7 +294,7 @@ function LinkPicker({
|
|
|
284
294
|
id: "fabric.smartcard.linkpicker.suggested.results"
|
|
285
295
|
}, url && jsx(FormattedMessage, _extends({}, messages.searchLinkResults, {
|
|
286
296
|
values: {
|
|
287
|
-
count: items.length
|
|
297
|
+
count: (_items$length = items === null || items === void 0 ? void 0 : items.length) !== null && _items$length !== void 0 ? _items$length : 0
|
|
288
298
|
},
|
|
289
299
|
"aria-live": "polite",
|
|
290
300
|
"aria-atomic": "true"
|
|
@@ -293,18 +303,32 @@ function LinkPicker({
|
|
|
293
303
|
ariaControls: "fabric.smartcard.linkpicker.suggested.results",
|
|
294
304
|
id: linkSearchListId,
|
|
295
305
|
role: "listbox",
|
|
296
|
-
items: items,
|
|
306
|
+
items: items !== null && items !== void 0 ? items : [],
|
|
297
307
|
isLoading: isLoading,
|
|
298
308
|
selectedIndex: selectedIndex,
|
|
299
309
|
activeIndex: activeIndex,
|
|
300
310
|
onSelect: handleSelected,
|
|
301
311
|
onMouseEnter: handleMouseEnterResultItem,
|
|
302
312
|
onMouseLeave: handleMouseLeaveResultItem
|
|
303
|
-
}),
|
|
313
|
+
}), error && ((_errorFallback = errorFallback === null || errorFallback === void 0 ? void 0 : errorFallback(error, retry)) !== null && _errorFallback !== void 0 ? _errorFallback : jsx(LinkSearchError, {
|
|
314
|
+
testId: testIds.searchError,
|
|
315
|
+
header: intl.formatMessage(messages.searchErrorHeader),
|
|
316
|
+
description: jsx(FormattedMessage, _extends({}, messages.searchErrorDescription, {
|
|
317
|
+
values: {
|
|
318
|
+
a: label => jsx(Button, {
|
|
319
|
+
appearance: "link",
|
|
320
|
+
spacing: "none",
|
|
321
|
+
href: CONTACT_SUPPORT_LINK,
|
|
322
|
+
target: "_blank",
|
|
323
|
+
rel: "noopener noreferrer"
|
|
324
|
+
}, label)
|
|
325
|
+
}
|
|
326
|
+
}))
|
|
327
|
+
})), noResults && jsx(LinkSearchNoResults, {
|
|
304
328
|
testId: testIds.emptyResultPage,
|
|
305
329
|
header: intl.formatMessage(messages.noResults),
|
|
306
330
|
description: intl.formatMessage(messages.noResultsDescription)
|
|
307
|
-
}), jsx(FormFooter, {
|
|
331
|
+
})), jsx(FormFooter, {
|
|
308
332
|
align: "end"
|
|
309
333
|
}, jsx(ButtonGroup, null, jsx(Button, {
|
|
310
334
|
appearance: "default",
|
|
@@ -332,17 +356,4 @@ function getScreenReaderText(items, selectedIndex, intl) {
|
|
|
332
356
|
const 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(' ');
|
|
333
357
|
return [name, container, formattedDate].filter(Boolean).join(', ');
|
|
334
358
|
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function findIndex(array, predicate) {
|
|
338
|
-
let index = -1;
|
|
339
|
-
array.some((item, i) => {
|
|
340
|
-
if (predicate(item)) {
|
|
341
|
-
index = i;
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return false;
|
|
346
|
-
});
|
|
347
|
-
return index;
|
|
348
359
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import EmptyState from '@atlaskit/empty-state';
|
|
4
|
+
import GenericErrorSVG from '../../../common/generic-error-svg';
|
|
5
|
+
|
|
6
|
+
const LinkSearchError = props => /*#__PURE__*/React.createElement(EmptyState, _extends({}, props, {
|
|
7
|
+
renderImage: () => /*#__PURE__*/React.createElement(GenericErrorSVG, null)
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
export default LinkSearchError;
|
|
@@ -104,5 +104,15 @@ export const messages = defineMessages({
|
|
|
104
104
|
id: 'fabric.linkPicker.button.save',
|
|
105
105
|
defaultMessage: 'Save',
|
|
106
106
|
description: 'Button to save edited link'
|
|
107
|
+
},
|
|
108
|
+
searchErrorHeader: {
|
|
109
|
+
id: 'fabric.linkPicker.search.error.heading',
|
|
110
|
+
defaultMessage: 'We’re having trouble loading data.',
|
|
111
|
+
description: 'Heading message shown when a search throws an error'
|
|
112
|
+
},
|
|
113
|
+
searchErrorDescription: {
|
|
114
|
+
id: 'fabric.linkPicker.search.error.description',
|
|
115
|
+
defaultMessage: 'Refresh the page, or contact <a>Atlassian Support</a> if this keeps happening.',
|
|
116
|
+
description: 'Describes possible actions when search throws an error'
|
|
107
117
|
}
|
|
108
118
|
});
|
package/dist/es2019/version.json
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -7,64 +7,53 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
|
|
|
7
7
|
|
|
8
8
|
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; }
|
|
9
9
|
|
|
10
|
-
import { useEffect, useRef,
|
|
10
|
+
import { useEffect, useReducer, useMemo, useRef, useState, useCallback } from 'react';
|
|
11
11
|
import { RECENT_SEARCH_LIST_SIZE } from '../../ui/link-picker';
|
|
12
|
-
import { isSafeUrl } from '../../ui/link-picker/url';
|
|
13
12
|
|
|
14
13
|
function reducer(state, payload) {
|
|
15
14
|
return _objectSpread(_objectSpread({}, state), payload);
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
export function usePlugins(state, plugins) {
|
|
17
|
+
export function usePlugins(state, activeTab, plugins) {
|
|
18
|
+
var _useState = useState(0),
|
|
19
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
20
|
+
retries = _useState2[0],
|
|
21
|
+
setRetries = _useState2[1];
|
|
22
|
+
|
|
19
23
|
var _useReducer = useReducer(reducer, {
|
|
20
|
-
items:
|
|
24
|
+
items: null,
|
|
25
|
+
error: null,
|
|
21
26
|
isLoading: false
|
|
22
27
|
}),
|
|
23
28
|
_useReducer2 = _slicedToArray(_useReducer, 2),
|
|
24
29
|
pluginState = _useReducer2[0],
|
|
25
30
|
dispatch = _useReducer2[1];
|
|
26
31
|
|
|
27
|
-
var url = state.url,
|
|
28
|
-
selectedIndex = state.selectedIndex;
|
|
29
32
|
var items = pluginState.items,
|
|
30
|
-
isLoading = pluginState.isLoading
|
|
31
|
-
|
|
33
|
+
isLoading = pluginState.isLoading,
|
|
34
|
+
error = pluginState.error;
|
|
32
35
|
var queryVersion = useRef(0);
|
|
33
|
-
var
|
|
34
|
-
var selectedItem = items[selectedIndex];
|
|
35
|
-
var isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
|
|
36
|
+
var activePlugin = plugins === null || plugins === void 0 ? void 0 : plugins[activeTab];
|
|
36
37
|
useEffect(function () {
|
|
37
38
|
if (!activePlugin) {
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
if (
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (isSafeUrl(url) && url.length) {
|
|
42
|
+
if (!state) {
|
|
46
43
|
dispatch({
|
|
47
|
-
items:
|
|
48
|
-
isLoading: false
|
|
44
|
+
items: null,
|
|
45
|
+
isLoading: false,
|
|
46
|
+
error: null
|
|
49
47
|
});
|
|
50
48
|
return;
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
var updates = activePlugin.resolve(
|
|
54
|
-
|
|
51
|
+
var updates = activePlugin.resolve(state);
|
|
52
|
+
dispatch({
|
|
53
|
+
items: null,
|
|
54
|
+
isLoading: true,
|
|
55
|
+
error: null
|
|
55
56
|
});
|
|
56
|
-
|
|
57
|
-
if (pluginRef.current !== activePlugin) {
|
|
58
|
-
dispatch({
|
|
59
|
-
items: [],
|
|
60
|
-
isLoading: true
|
|
61
|
-
});
|
|
62
|
-
} else {
|
|
63
|
-
dispatch({
|
|
64
|
-
isLoading: true
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
57
|
var newQuery = ++queryVersion.current;
|
|
69
58
|
|
|
70
59
|
var updateResults = /*#__PURE__*/function () {
|
|
@@ -142,6 +131,8 @@ export function usePlugins(state, plugins) {
|
|
|
142
131
|
_context.prev = 25;
|
|
143
132
|
_context.t0 = _context["catch"](0);
|
|
144
133
|
dispatch({
|
|
134
|
+
error: _context.t0,
|
|
135
|
+
items: null,
|
|
145
136
|
isLoading: false
|
|
146
137
|
});
|
|
147
138
|
|
|
@@ -159,7 +150,7 @@ export function usePlugins(state, plugins) {
|
|
|
159
150
|
}();
|
|
160
151
|
|
|
161
152
|
updateResults();
|
|
162
|
-
}, [activePlugin, queryVersion,
|
|
153
|
+
}, [activePlugin, queryVersion, state, retries]);
|
|
163
154
|
var tabs = useMemo(function () {
|
|
164
155
|
if (!plugins || plugins.length <= 1) {
|
|
165
156
|
return [];
|
|
@@ -173,12 +164,19 @@ export function usePlugins(state, plugins) {
|
|
|
173
164
|
};
|
|
174
165
|
});
|
|
175
166
|
}, [plugins]);
|
|
167
|
+
var handleRetry = useCallback(function () {
|
|
168
|
+
setRetries(function (prev) {
|
|
169
|
+
return ++prev;
|
|
170
|
+
});
|
|
171
|
+
}, []);
|
|
176
172
|
return {
|
|
177
173
|
tabs: tabs,
|
|
178
174
|
items: items,
|
|
179
175
|
isLoading: isLoading,
|
|
180
|
-
|
|
181
|
-
|
|
176
|
+
isActivePlugin: !!activePlugin,
|
|
177
|
+
error: error,
|
|
178
|
+
retry: handleRetry,
|
|
179
|
+
errorFallback: activePlugin === null || activePlugin === void 0 ? void 0 : activePlugin.errorFallback
|
|
182
180
|
};
|
|
183
181
|
}
|
|
184
182
|
|
|
@@ -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
|
+
var 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 '
|
|
5
|
+
import GenericErrorSVG from '../../common/generic-error-svg';
|
|
6
6
|
import { rootContainerStyles } from '../link-picker/styled';
|
|
7
7
|
var messages = defineMessages({
|
|
8
8
|
heading: {
|
|
@@ -7,7 +7,7 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
|
|
|
7
7
|
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; }
|
|
8
8
|
|
|
9
9
|
/** @jsx jsx */
|
|
10
|
-
import { useCallback, useLayoutEffect, useReducer } from 'react';
|
|
10
|
+
import { Fragment, useCallback, useLayoutEffect, useReducer } from 'react';
|
|
11
11
|
import { jsx } from '@emotion/react';
|
|
12
12
|
import { useIntl, FormattedMessage } from 'react-intl-next';
|
|
13
13
|
import Button, { ButtonGroup } from '@atlaskit/button';
|
|
@@ -15,17 +15,20 @@ import { FormFooter } from '@atlaskit/form';
|
|
|
15
15
|
import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
|
|
16
16
|
import Tabs, { Tab, TabList } from '@atlaskit/tabs';
|
|
17
17
|
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
18
|
+
import { usePlugins } from '../../services/use-plugins';
|
|
19
|
+
import { useSearchQuery } from '../../services/use-search-query';
|
|
18
20
|
import { messages } from './messages';
|
|
19
21
|
import PanelTextInput from './text-input';
|
|
20
22
|
import LinkSearchList from './link-search-list';
|
|
21
23
|
import { normalizeUrl, isSafeUrl } from './url';
|
|
22
24
|
import { rootContainerStyles, searchIconStyles, listTitleStyles, tabsWrapperStyles } from './styled';
|
|
23
25
|
import { browser } from './browser';
|
|
24
|
-
import { transformTimeStamp } from './transformTimeStamp';
|
|
25
26
|
import Announcer from './announcer';
|
|
26
27
|
import LinkSearchNoResults from './link-search-no-results';
|
|
27
|
-
import {
|
|
28
|
+
import { transformTimeStamp } from './transformTimeStamp';
|
|
29
|
+
import LinkSearchError from './link-search-error';
|
|
28
30
|
export var RECENT_SEARCH_LIST_SIZE = 5;
|
|
31
|
+
export var CONTACT_SUPPORT_LINK = 'https://support.atlassian.com/contact/';
|
|
29
32
|
export var testIds = {
|
|
30
33
|
linkPicker: 'link-picker',
|
|
31
34
|
urlInputField: 'link-url',
|
|
@@ -41,7 +44,8 @@ export var testIds = {
|
|
|
41
44
|
searchResultLoadingIndicator: 'link-picker.results-loading-indicator',
|
|
42
45
|
urlError: 'link-error',
|
|
43
46
|
tabList: 'link-picker-tabs',
|
|
44
|
-
tabItem: 'link-picker-tab'
|
|
47
|
+
tabItem: 'link-picker-tab',
|
|
48
|
+
searchError: 'link-search-error'
|
|
45
49
|
};
|
|
46
50
|
var initState = {
|
|
47
51
|
url: '',
|
|
@@ -64,6 +68,8 @@ function reducer(state, payload) {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
function LinkPicker(_ref) {
|
|
71
|
+
var _queryState$query, _items$length, _errorFallback;
|
|
72
|
+
|
|
67
73
|
var onSubmit = _ref.onSubmit,
|
|
68
74
|
onCancel = _ref.onCancel,
|
|
69
75
|
onContentResize = _ref.onContentResize,
|
|
@@ -83,17 +89,23 @@ function LinkPicker(_ref) {
|
|
|
83
89
|
selectedIndex = state.selectedIndex,
|
|
84
90
|
url = state.url,
|
|
85
91
|
displayText = state.displayText,
|
|
86
|
-
invalidUrl = state.invalidUrl
|
|
87
|
-
|
|
92
|
+
invalidUrl = state.invalidUrl,
|
|
93
|
+
activeTab = state.activeTab;
|
|
88
94
|
var intl = useIntl();
|
|
95
|
+
var queryState = useSearchQuery(state);
|
|
89
96
|
|
|
90
|
-
var _usePlugins = usePlugins(
|
|
97
|
+
var _usePlugins = usePlugins(queryState, activeTab, plugins),
|
|
91
98
|
items = _usePlugins.items,
|
|
92
99
|
isLoading = _usePlugins.isLoading,
|
|
93
|
-
isSelectedItem = _usePlugins.isSelectedItem,
|
|
94
100
|
isActivePlugin = _usePlugins.isActivePlugin,
|
|
95
|
-
tabs = _usePlugins.tabs
|
|
101
|
+
tabs = _usePlugins.tabs,
|
|
102
|
+
error = _usePlugins.error,
|
|
103
|
+
retry = _usePlugins.retry,
|
|
104
|
+
errorFallback = _usePlugins.errorFallback;
|
|
96
105
|
|
|
106
|
+
var isEditing = !!initUrl;
|
|
107
|
+
var selectedItem = items === null || items === void 0 ? void 0 : items[selectedIndex];
|
|
108
|
+
var isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
|
|
97
109
|
useLayoutEffect(function () {
|
|
98
110
|
if (onContentResize) {
|
|
99
111
|
onContentResize();
|
|
@@ -147,15 +159,15 @@ function LinkPicker(_ref) {
|
|
|
147
159
|
dispatch(_defineProperty({}, field, ''));
|
|
148
160
|
}, []);
|
|
149
161
|
var handleMouseEnterResultItem = useCallback(function (objectId) {
|
|
150
|
-
var index = findIndex(
|
|
162
|
+
var index = items === null || items === void 0 ? void 0 : items.findIndex(function (item) {
|
|
151
163
|
return item.objectId === objectId;
|
|
152
164
|
});
|
|
153
165
|
dispatch({
|
|
154
|
-
activeIndex: index
|
|
166
|
+
activeIndex: index !== null && index !== void 0 ? index : -1
|
|
155
167
|
});
|
|
156
168
|
}, [items]);
|
|
157
169
|
var handleMouseLeaveResultItem = useCallback(function (objectId) {
|
|
158
|
-
var index = findIndex(
|
|
170
|
+
var index = items === null || items === void 0 ? void 0 : items.findIndex(function (item) {
|
|
159
171
|
return item.objectId === objectId;
|
|
160
172
|
}); // This is to avoid updating index that was set by other mouseenter event
|
|
161
173
|
|
|
@@ -178,7 +190,7 @@ function LinkPicker(_ref) {
|
|
|
178
190
|
} : {}));
|
|
179
191
|
}, [displayText, onSubmit, state.url]);
|
|
180
192
|
var handleSelected = useCallback(function (objectId) {
|
|
181
|
-
var selectedItem = items.find(function (item) {
|
|
193
|
+
var selectedItem = items === null || items === void 0 ? void 0 : items.find(function (item) {
|
|
182
194
|
return item.objectId === objectId;
|
|
183
195
|
});
|
|
184
196
|
|
|
@@ -189,9 +201,7 @@ function LinkPicker(_ref) {
|
|
|
189
201
|
}
|
|
190
202
|
}, [handleInsert, items]);
|
|
191
203
|
var handleSubmit = useCallback(function () {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (isSelectedItem) {
|
|
204
|
+
if (isSelectedItem && selectedItem) {
|
|
195
205
|
return handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
|
|
196
206
|
}
|
|
197
207
|
|
|
@@ -204,18 +214,18 @@ function LinkPicker(_ref) {
|
|
|
204
214
|
return dispatch({
|
|
205
215
|
invalidUrl: true
|
|
206
216
|
});
|
|
207
|
-
}, [handleInsert, isSelectedItem,
|
|
217
|
+
}, [handleInsert, isSelectedItem, selectedItem, url]);
|
|
208
218
|
var linkPlaceHolder = isActivePlugin ? messages.placeholder : messages.linkPlaceholder;
|
|
209
|
-
var linkListTitle =
|
|
219
|
+
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.titleResults : messages.titleRecentlyViewed;
|
|
210
220
|
var insertButtonMsg = isEditing ? messages.saveButton : messages.insertButton;
|
|
211
|
-
var noResults =
|
|
221
|
+
var noResults = (items === null || items === void 0 ? void 0 : items.length) === 0 && !error;
|
|
212
222
|
var screenReaderDescriptionId = 'search-recent-links-field-description';
|
|
213
223
|
var linkSearchListId = 'link-picker-search-list';
|
|
214
224
|
var ariaActiveDescendant = selectedIndex > -1 ? "link-search-list-item-".concat(selectedIndex) : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
|
|
215
225
|
// as the Aria design pattern for combobox does not work in this case
|
|
216
226
|
// for details: https://a11y-internal.atlassian.net/browse/AK-740
|
|
217
227
|
|
|
218
|
-
var screenReaderText = browser.safari && getScreenReaderText(items, selectedIndex, intl);
|
|
228
|
+
var screenReaderText = browser.safari && getScreenReaderText(items !== null && items !== void 0 ? items : [], selectedIndex, intl);
|
|
219
229
|
var searchIcon = isActivePlugin && jsx("span", {
|
|
220
230
|
css: searchIconStyles,
|
|
221
231
|
"data-testid": testIds.searchIcon
|
|
@@ -267,7 +277,7 @@ function LinkPicker(_ref) {
|
|
|
267
277
|
onSubmit: handleSubmit,
|
|
268
278
|
onKeyDown: handleKeyDown,
|
|
269
279
|
onChange: handleChangeText
|
|
270
|
-
}), tabs.length > 0 && jsx("div", {
|
|
280
|
+
}), queryState && jsx(Fragment, null, tabs.length > 0 && jsx("div", {
|
|
271
281
|
css: tabsWrapperStyles
|
|
272
282
|
}, jsx(Tabs, {
|
|
273
283
|
id: testIds.tabList,
|
|
@@ -285,7 +295,7 @@ function LinkPicker(_ref) {
|
|
|
285
295
|
key: tab.tabTitle,
|
|
286
296
|
testId: testIds.tabItem
|
|
287
297
|
}, tab.tabTitle);
|
|
288
|
-
})))), !!items.length && jsx("div", {
|
|
298
|
+
})))), !!(items !== null && items !== void 0 && items.length) && jsx("div", {
|
|
289
299
|
css: listTitleStyles,
|
|
290
300
|
id: testIds.resultListTitle,
|
|
291
301
|
"data-testid": testIds.resultListTitle
|
|
@@ -293,7 +303,7 @@ function LinkPicker(_ref) {
|
|
|
293
303
|
id: "fabric.smartcard.linkpicker.suggested.results"
|
|
294
304
|
}, url && jsx(FormattedMessage, _extends({}, messages.searchLinkResults, {
|
|
295
305
|
values: {
|
|
296
|
-
count: items.length
|
|
306
|
+
count: (_items$length = items === null || items === void 0 ? void 0 : items.length) !== null && _items$length !== void 0 ? _items$length : 0
|
|
297
307
|
},
|
|
298
308
|
"aria-live": "polite",
|
|
299
309
|
"aria-atomic": "true"
|
|
@@ -302,18 +312,34 @@ function LinkPicker(_ref) {
|
|
|
302
312
|
ariaControls: "fabric.smartcard.linkpicker.suggested.results",
|
|
303
313
|
id: linkSearchListId,
|
|
304
314
|
role: "listbox",
|
|
305
|
-
items: items,
|
|
315
|
+
items: items !== null && items !== void 0 ? items : [],
|
|
306
316
|
isLoading: isLoading,
|
|
307
317
|
selectedIndex: selectedIndex,
|
|
308
318
|
activeIndex: activeIndex,
|
|
309
319
|
onSelect: handleSelected,
|
|
310
320
|
onMouseEnter: handleMouseEnterResultItem,
|
|
311
321
|
onMouseLeave: handleMouseLeaveResultItem
|
|
312
|
-
}),
|
|
322
|
+
}), error && ((_errorFallback = errorFallback === null || errorFallback === void 0 ? void 0 : errorFallback(error, retry)) !== null && _errorFallback !== void 0 ? _errorFallback : jsx(LinkSearchError, {
|
|
323
|
+
testId: testIds.searchError,
|
|
324
|
+
header: intl.formatMessage(messages.searchErrorHeader),
|
|
325
|
+
description: jsx(FormattedMessage, _extends({}, messages.searchErrorDescription, {
|
|
326
|
+
values: {
|
|
327
|
+
a: function a(label) {
|
|
328
|
+
return jsx(Button, {
|
|
329
|
+
appearance: "link",
|
|
330
|
+
spacing: "none",
|
|
331
|
+
href: CONTACT_SUPPORT_LINK,
|
|
332
|
+
target: "_blank",
|
|
333
|
+
rel: "noopener noreferrer"
|
|
334
|
+
}, label);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}))
|
|
338
|
+
})), noResults && jsx(LinkSearchNoResults, {
|
|
313
339
|
testId: testIds.emptyResultPage,
|
|
314
340
|
header: intl.formatMessage(messages.noResults),
|
|
315
341
|
description: intl.formatMessage(messages.noResultsDescription)
|
|
316
|
-
}), jsx(FormFooter, {
|
|
342
|
+
})), jsx(FormFooter, {
|
|
317
343
|
align: "end"
|
|
318
344
|
}, jsx(ButtonGroup, null, jsx(Button, {
|
|
319
345
|
appearance: "default",
|
|
@@ -340,17 +366,4 @@ function getScreenReaderText(items, selectedIndex, intl) {
|
|
|
340
366
|
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(' ');
|
|
341
367
|
return [name, container, formattedDate].filter(Boolean).join(', ');
|
|
342
368
|
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function findIndex(array, predicate) {
|
|
346
|
-
var index = -1;
|
|
347
|
-
array.some(function (item, i) {
|
|
348
|
-
if (predicate(item)) {
|
|
349
|
-
index = i;
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return false;
|
|
354
|
-
});
|
|
355
|
-
return index;
|
|
356
369
|
}
|