@atlaskit/link-picker 1.4.1 → 1.6.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 +19 -0
- package/dist/cjs/services/use-plugins/index.js +201 -0
- package/dist/cjs/ui/link-picker/index.js +314 -599
- package/dist/cjs/ui/link-picker/link-search-list/index.js +54 -82
- package/dist/cjs/ui/link-picker/list-item/index.js +5 -3
- package/dist/cjs/ui/link-picker/styled.js +5 -3
- package/dist/cjs/ui/link-picker/text-input/index.js +6 -4
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/services/use-plugins/index.js +122 -0
- package/dist/es2019/ui/link-picker/index.js +294 -467
- package/dist/es2019/ui/link-picker/link-search-list/index.js +49 -50
- package/dist/es2019/ui/link-picker/list-item/index.js +4 -3
- package/dist/es2019/ui/link-picker/styled.js +4 -0
- package/dist/es2019/ui/link-picker/text-input/index.js +5 -4
- package/dist/es2019/version.json +1 -1
- package/dist/esm/services/use-plugins/index.js +187 -0
- package/dist/esm/ui/link-picker/index.js +304 -601
- package/dist/esm/ui/link-picker/link-search-list/index.js +51 -79
- package/dist/esm/ui/link-picker/list-item/index.js +4 -3
- package/dist/esm/ui/link-picker/styled.js +3 -2
- package/dist/esm/ui/link-picker/text-input/index.js +5 -4
- package/dist/esm/version.json +1 -1
- package/dist/types/services/use-plugins/index.d.ts +12 -0
- package/dist/types/ui/link-picker/index.d.ts +26 -13
- package/dist/types/ui/link-picker/link-search-list/index.d.ts +3 -5
- package/dist/types/ui/link-picker/styled.d.ts +1 -0
- package/dist/types/ui/link-picker/text-input/index.d.ts +1 -1
- package/package.json +5 -3
|
@@ -1,504 +1,337 @@
|
|
|
1
|
-
import
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
2
|
|
|
3
3
|
/** @jsx jsx */
|
|
4
|
-
import {
|
|
4
|
+
import { useCallback, useLayoutEffect, useReducer } from 'react';
|
|
5
5
|
import { jsx } from '@emotion/react';
|
|
6
|
-
import {
|
|
6
|
+
import { useIntl, FormattedMessage } from 'react-intl-next';
|
|
7
|
+
import Button, { ButtonGroup } from '@atlaskit/button';
|
|
8
|
+
import { FormFooter } from '@atlaskit/form';
|
|
9
|
+
import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
|
|
7
10
|
import Tabs, { Tab, TabList } from '@atlaskit/tabs';
|
|
11
|
+
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
8
12
|
import { messages } from './messages';
|
|
9
13
|
import PanelTextInput from './text-input';
|
|
10
14
|
import LinkSearchList from './link-search-list';
|
|
11
15
|
import { normalizeUrl, isSafeUrl } from './url';
|
|
12
|
-
import { rootContainerStyles, searchIconStyles, listTitleStyles } from './styled';
|
|
16
|
+
import { rootContainerStyles, searchIconStyles, listTitleStyles, tabsWrapperStyles } from './styled';
|
|
13
17
|
import { browser } from './browser';
|
|
14
18
|
import { transformTimeStamp } from './transformTimeStamp';
|
|
15
|
-
import Announcer from '
|
|
16
|
-
import Button, { ButtonGroup } from '@atlaskit/button';
|
|
17
|
-
import { FormFooter } from '@atlaskit/form';
|
|
18
|
-
import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
|
|
19
|
-
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
19
|
+
import Announcer from './announcer';
|
|
20
20
|
import LinkSearchNoResults from './link-search-no-results';
|
|
21
|
+
import { usePlugins } from '../../services/use-plugins';
|
|
21
22
|
export const RECENT_SEARCH_LIST_SIZE = 5;
|
|
23
|
+
export const testIds = {
|
|
24
|
+
linkPicker: 'link-picker',
|
|
25
|
+
urlInputField: 'link-url',
|
|
26
|
+
textInputField: 'link-text',
|
|
27
|
+
searchIcon: 'link-picker-search-icon',
|
|
28
|
+
insertButton: 'link-picker-insert-button',
|
|
29
|
+
cancelButton: 'link-picker-cancel-button',
|
|
30
|
+
clearUrlButton: 'clear-text',
|
|
31
|
+
resultListTitle: 'link-picker-list-title',
|
|
32
|
+
emptyResultPage: 'link-search-no-results',
|
|
33
|
+
searchResultList: 'link-search-list',
|
|
34
|
+
searchResultItem: 'link-search-list-item',
|
|
35
|
+
searchResultLoadingIndicator: 'link-picker.results-loading-indicator',
|
|
36
|
+
urlError: 'link-error',
|
|
37
|
+
tabList: 'link-picker-tabs',
|
|
38
|
+
tabItem: 'link-picker-tab'
|
|
39
|
+
};
|
|
40
|
+
const initState = {
|
|
41
|
+
url: '',
|
|
42
|
+
displayText: '',
|
|
43
|
+
activeIndex: -1,
|
|
44
|
+
selectedIndex: -1,
|
|
45
|
+
invalidUrl: false,
|
|
46
|
+
activeTab: 0
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function reducer(state, payload) {
|
|
50
|
+
if (payload.url && state.url !== payload.url) {
|
|
51
|
+
return { ...state,
|
|
52
|
+
invalidUrl: false,
|
|
53
|
+
selectedIndex: isSafeUrl(payload.url) && payload.url.length ? -1 : state.selectedIndex,
|
|
54
|
+
...payload
|
|
55
|
+
};
|
|
56
|
+
}
|
|
22
57
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
_defineProperty(this, "queryVersion", 0);
|
|
28
|
-
|
|
29
|
-
_defineProperty(this, "updateInput", async input => {
|
|
30
|
-
// If input is a valid URL, do not search or display link suggestions
|
|
31
|
-
if (input.length && isSafeUrl(input)) {
|
|
32
|
-
return this.setState({
|
|
33
|
-
url: input,
|
|
34
|
-
selectedIndex: -1,
|
|
35
|
-
items: [],
|
|
36
|
-
isLoading: false,
|
|
37
|
-
invalidUrl: null
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return this.setState({
|
|
42
|
-
url: input,
|
|
43
|
-
// Unset selection on any input change
|
|
44
|
-
selectedIndex: -1,
|
|
45
|
-
invalidUrl: null
|
|
46
|
-
}, () => {
|
|
47
|
-
this.updateResults();
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
_defineProperty(this, "createClearHandler", field => {
|
|
52
|
-
return () => {
|
|
53
|
-
switch (field) {
|
|
54
|
-
case 'url':
|
|
55
|
-
{
|
|
56
|
-
this.updateInput('');
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
case 'displayText':
|
|
61
|
-
{
|
|
62
|
-
this.setState({
|
|
63
|
-
[field]: ''
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
_defineProperty(this, "getScreenReaderText", () => {
|
|
71
|
-
const {
|
|
72
|
-
intl
|
|
73
|
-
} = this.props;
|
|
74
|
-
const {
|
|
75
|
-
items,
|
|
76
|
-
selectedIndex
|
|
77
|
-
} = this.state;
|
|
78
|
-
|
|
79
|
-
if (items.length && selectedIndex > -1) {
|
|
80
|
-
const {
|
|
81
|
-
name,
|
|
82
|
-
container,
|
|
83
|
-
lastUpdatedDate,
|
|
84
|
-
lastViewedDate
|
|
85
|
-
} = items[selectedIndex];
|
|
86
|
-
const date = transformTimeStamp(intl, lastViewedDate, lastUpdatedDate);
|
|
87
|
-
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(' ');
|
|
88
|
-
return [name, container, formattedDate].filter(Boolean).join(', ');
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
_defineProperty(this, "getTabs", () => {
|
|
93
|
-
if (this.props.plugins && this.props.plugins.length > 1) {
|
|
94
|
-
return this.props.plugins.filter(plugin => !!plugin.tabTitle).map(plugin => ({
|
|
95
|
-
tabTitle: plugin.tabTitle
|
|
96
|
-
}));
|
|
97
|
-
}
|
|
58
|
+
return { ...state,
|
|
59
|
+
...payload
|
|
60
|
+
};
|
|
61
|
+
}
|
|
98
62
|
|
|
99
|
-
|
|
63
|
+
function LinkPicker({
|
|
64
|
+
onSubmit,
|
|
65
|
+
onCancel,
|
|
66
|
+
onContentResize,
|
|
67
|
+
plugins,
|
|
68
|
+
url: initUrl,
|
|
69
|
+
displayText: initDisplayText
|
|
70
|
+
}) {
|
|
71
|
+
const [state, dispatch] = useReducer(reducer, { ...initState,
|
|
72
|
+
url: normalizeUrl(initUrl) || '',
|
|
73
|
+
displayText: initDisplayText || ''
|
|
74
|
+
});
|
|
75
|
+
const {
|
|
76
|
+
activeIndex,
|
|
77
|
+
selectedIndex,
|
|
78
|
+
url,
|
|
79
|
+
displayText,
|
|
80
|
+
invalidUrl
|
|
81
|
+
} = state;
|
|
82
|
+
const isEditing = !!initUrl;
|
|
83
|
+
const intl = useIntl();
|
|
84
|
+
const {
|
|
85
|
+
items,
|
|
86
|
+
isLoading,
|
|
87
|
+
isSelectedItem,
|
|
88
|
+
isActivePlugin,
|
|
89
|
+
tabs
|
|
90
|
+
} = usePlugins(state, plugins);
|
|
91
|
+
useLayoutEffect(() => {
|
|
92
|
+
if (onContentResize) {
|
|
93
|
+
onContentResize();
|
|
94
|
+
}
|
|
95
|
+
}, [onContentResize, items, isLoading, isActivePlugin, tabs]);
|
|
96
|
+
const handleChangeUrl = useCallback(url => {
|
|
97
|
+
dispatch({
|
|
98
|
+
url
|
|
100
99
|
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* the URL field will be populated with the content of
|
|
106
|
-
* selected item.
|
|
107
|
-
* This function will check if the URL field is populated
|
|
108
|
-
* with selected item.
|
|
109
|
-
* It can be useful to detect whether we want to insert a
|
|
110
|
-
* smartlink or a hyperlink with customized title
|
|
111
|
-
*/
|
|
112
|
-
const {
|
|
113
|
-
items,
|
|
114
|
-
selectedIndex,
|
|
115
|
-
url
|
|
116
|
-
} = this.state;
|
|
117
|
-
const selectedItem = items[selectedIndex];
|
|
118
|
-
|
|
119
|
-
if (selectedItem && selectedItem.url === url) {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return false;
|
|
100
|
+
}, []);
|
|
101
|
+
const handleChangeText = useCallback(displayText => {
|
|
102
|
+
dispatch({
|
|
103
|
+
displayText
|
|
124
104
|
});
|
|
105
|
+
}, []);
|
|
106
|
+
const handleKeyDown = useCallback(event => {
|
|
107
|
+
const {
|
|
108
|
+
keyCode
|
|
109
|
+
} = event;
|
|
110
|
+
const KEY_CODE_ARROW_DOWN = 40;
|
|
111
|
+
const KEY_CODE_ARROW_UP = 38;
|
|
125
112
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
} = this.state;
|
|
130
|
-
const selectedItem = items.find(item => item.objectId === objectId);
|
|
113
|
+
if (!items || !items.length) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
131
116
|
|
|
132
|
-
|
|
133
|
-
const {
|
|
134
|
-
url,
|
|
135
|
-
name
|
|
136
|
-
} = selectedItem;
|
|
137
|
-
this.handleInsert(url, name, 'typeAhead');
|
|
138
|
-
}
|
|
139
|
-
});
|
|
117
|
+
let updatedIndex = activeIndex;
|
|
140
118
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
displayText
|
|
148
|
-
} = this.state;
|
|
119
|
+
switch (keyCode) {
|
|
120
|
+
case KEY_CODE_ARROW_DOWN:
|
|
121
|
+
// down
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
updatedIndex = (activeIndex + 1) % items.length;
|
|
124
|
+
break;
|
|
149
125
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
inputMethod: inputType
|
|
157
|
-
},
|
|
158
|
-
...(inputType === 'manual' ? {
|
|
159
|
-
rawUrl
|
|
160
|
-
} : {})
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
});
|
|
126
|
+
case KEY_CODE_ARROW_UP:
|
|
127
|
+
// up
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
updatedIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
164
132
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
activeIndex: index
|
|
133
|
+
if ([KEY_CODE_ARROW_DOWN, KEY_CODE_ARROW_UP].includes(keyCode) && items[updatedIndex]) {
|
|
134
|
+
dispatch({
|
|
135
|
+
activeIndex: updatedIndex,
|
|
136
|
+
selectedIndex: updatedIndex,
|
|
137
|
+
url: items[updatedIndex].url,
|
|
138
|
+
invalidUrl: false
|
|
172
139
|
});
|
|
140
|
+
}
|
|
141
|
+
}, [activeIndex, items]);
|
|
142
|
+
const handleClear = useCallback(field => {
|
|
143
|
+
dispatch({
|
|
144
|
+
[field]: ''
|
|
173
145
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} = this.state;
|
|
180
|
-
const index = findIndex(items, item => item.objectId === objectId); // This is to avoid updating index that was set by other mouseenter event
|
|
181
|
-
|
|
182
|
-
if (activeIndex === index) {
|
|
183
|
-
this.setState({
|
|
184
|
-
activeIndex: -1
|
|
185
|
-
});
|
|
186
|
-
}
|
|
146
|
+
}, []);
|
|
147
|
+
const handleMouseEnterResultItem = useCallback(objectId => {
|
|
148
|
+
const index = findIndex(items, item => item.objectId === objectId);
|
|
149
|
+
dispatch({
|
|
150
|
+
activeIndex: index
|
|
187
151
|
});
|
|
152
|
+
}, [items]);
|
|
153
|
+
const handleMouseLeaveResultItem = useCallback(objectId => {
|
|
154
|
+
const index = findIndex(items, item => item.objectId === objectId); // This is to avoid updating index that was set by other mouseenter event
|
|
188
155
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
selectedIndex,
|
|
193
|
-
items
|
|
194
|
-
} = this.state;
|
|
195
|
-
const {
|
|
196
|
-
intl
|
|
197
|
-
} = this.props;
|
|
198
|
-
const selectedItem = items[selectedIndex];
|
|
199
|
-
|
|
200
|
-
if (this.isUrlPopulatedWithSelectedItem()) {
|
|
201
|
-
return this.handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const normalized = normalizeUrl(url);
|
|
205
|
-
|
|
206
|
-
if (normalized) {
|
|
207
|
-
return this.handleInsert(normalized, null, 'manual');
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return this.setState({
|
|
211
|
-
invalidUrl: intl.formatMessage(messages.linkInvalid)
|
|
156
|
+
if (activeIndex === index) {
|
|
157
|
+
dispatch({
|
|
158
|
+
activeIndex: -1
|
|
212
159
|
});
|
|
160
|
+
}
|
|
161
|
+
}, [activeIndex, items]);
|
|
162
|
+
const handleInsert = useCallback((url, title, inputType) => {
|
|
163
|
+
onSubmit({
|
|
164
|
+
url,
|
|
165
|
+
displayText: displayText || null,
|
|
166
|
+
title: title || null,
|
|
167
|
+
meta: {
|
|
168
|
+
inputMethod: inputType
|
|
169
|
+
},
|
|
170
|
+
...(inputType === 'manual' ? {
|
|
171
|
+
rawUrl: state.url
|
|
172
|
+
} : {})
|
|
213
173
|
});
|
|
174
|
+
}, [displayText, onSubmit, state.url]);
|
|
175
|
+
const handleSelected = useCallback(objectId => {
|
|
176
|
+
const selectedItem = items.find(item => item.objectId === objectId);
|
|
214
177
|
|
|
215
|
-
|
|
216
|
-
const {
|
|
217
|
-
items,
|
|
218
|
-
activeIndex
|
|
219
|
-
} = this.state;
|
|
220
|
-
const {
|
|
221
|
-
keyCode
|
|
222
|
-
} = event;
|
|
223
|
-
const KEY_CODE_ARROW_DOWN = 40;
|
|
224
|
-
const KEY_CODE_ARROW_UP = 38;
|
|
225
|
-
|
|
226
|
-
if (!items || !items.length) {
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
let updatedIndex = activeIndex;
|
|
231
|
-
|
|
232
|
-
switch (keyCode) {
|
|
233
|
-
case KEY_CODE_ARROW_DOWN:
|
|
234
|
-
// down
|
|
235
|
-
event.preventDefault();
|
|
236
|
-
updatedIndex = (activeIndex + 1) % items.length;
|
|
237
|
-
break;
|
|
238
|
-
|
|
239
|
-
case KEY_CODE_ARROW_UP:
|
|
240
|
-
// up
|
|
241
|
-
event.preventDefault();
|
|
242
|
-
updatedIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if ([KEY_CODE_ARROW_DOWN, KEY_CODE_ARROW_UP].includes(keyCode) && items[updatedIndex]) {
|
|
247
|
-
this.setState({
|
|
248
|
-
activeIndex: updatedIndex,
|
|
249
|
-
selectedIndex: updatedIndex,
|
|
250
|
-
url: items[updatedIndex].url,
|
|
251
|
-
invalidUrl: null
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
_defineProperty(this, "handleCancel", () => {
|
|
178
|
+
if (selectedItem) {
|
|
257
179
|
const {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
onCancel();
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
_defineProperty(this, "updateTextInput", displayText => {
|
|
267
|
-
this.setState({
|
|
268
|
-
displayText: displayText
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
this.state = {
|
|
273
|
-
selectedIndex: -1,
|
|
274
|
-
activeIndex: -1,
|
|
275
|
-
isLoading: false,
|
|
276
|
-
isEditing: !!props.url,
|
|
277
|
-
url: normalizeUrl(props.url) || '',
|
|
278
|
-
displayText: props.displayText || '',
|
|
279
|
-
items: [],
|
|
280
|
-
invalidUrl: null,
|
|
281
|
-
activePlugin: 0
|
|
282
|
-
};
|
|
283
|
-
/* Cache functions */
|
|
284
|
-
|
|
285
|
-
this.handleClearUrl = this.createClearHandler('url');
|
|
286
|
-
this.handleClearDisplayText = this.createClearHandler('displayText');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
componentDidMount() {
|
|
290
|
-
this.loadInitialLinkSearchResult();
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
loadInitialLinkSearchResult() {
|
|
294
|
-
const {
|
|
295
|
-
url
|
|
296
|
-
} = this.state;
|
|
297
|
-
|
|
298
|
-
if (!url) {
|
|
299
|
-
this.updateResults();
|
|
180
|
+
url,
|
|
181
|
+
name
|
|
182
|
+
} = selectedItem;
|
|
183
|
+
handleInsert(url, name, 'typeAhead');
|
|
300
184
|
}
|
|
301
|
-
}
|
|
185
|
+
}, [handleInsert, items]);
|
|
186
|
+
const handleSubmit = useCallback(() => {
|
|
187
|
+
const selectedItem = items[selectedIndex];
|
|
302
188
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return null;
|
|
189
|
+
if (isSelectedItem) {
|
|
190
|
+
return handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
|
|
306
191
|
}
|
|
307
192
|
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Interface between link picker and plugins to update results
|
|
312
|
-
*/
|
|
313
|
-
|
|
193
|
+
const normalized = normalizeUrl(url);
|
|
314
194
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const activePlugin = this.getActivePlugin();
|
|
318
|
-
|
|
319
|
-
if (!activePlugin) {
|
|
320
|
-
return;
|
|
195
|
+
if (normalized) {
|
|
196
|
+
return handleInsert(normalized, null, 'manual');
|
|
321
197
|
}
|
|
322
198
|
|
|
323
|
-
|
|
324
|
-
|
|
199
|
+
return dispatch({
|
|
200
|
+
invalidUrl: true
|
|
325
201
|
});
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
202
|
+
}, [handleInsert, isSelectedItem, items, selectedIndex, url]);
|
|
203
|
+
const linkPlaceHolder = isActivePlugin ? messages.placeholder : messages.linkPlaceholder;
|
|
204
|
+
const linkListTitle = url ? messages.titleResults : messages.titleRecentlyViewed;
|
|
205
|
+
const insertButtonMsg = isEditing ? messages.saveButton : messages.insertButton;
|
|
206
|
+
const noResults = isActivePlugin && !isLoading && !items.length && !isSafeUrl(url);
|
|
207
|
+
const screenReaderDescriptionId = 'search-recent-links-field-description';
|
|
208
|
+
const linkSearchListId = 'link-picker-search-list';
|
|
209
|
+
const ariaActiveDescendant = selectedIndex > -1 ? `link-search-list-item-${selectedIndex}` : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
|
|
210
|
+
// as the Aria design pattern for combobox does not work in this case
|
|
211
|
+
// for details: https://a11y-internal.atlassian.net/browse/AK-740
|
|
212
|
+
|
|
213
|
+
const screenReaderText = browser.safari && getScreenReaderText(items, selectedIndex, intl);
|
|
214
|
+
const searchIcon = isActivePlugin && jsx("span", {
|
|
215
|
+
css: searchIconStyles,
|
|
216
|
+
"data-testid": testIds.searchIcon
|
|
217
|
+
}, jsx(EditorSearchIcon, {
|
|
218
|
+
size: "medium",
|
|
219
|
+
label: ''
|
|
220
|
+
}));
|
|
221
|
+
return jsx("div", {
|
|
222
|
+
"data-testid": testIds.linkPicker,
|
|
223
|
+
css: rootContainerStyles
|
|
224
|
+
}, screenReaderText && jsx(Announcer, {
|
|
225
|
+
ariaLive: "assertive",
|
|
226
|
+
text: screenReaderText,
|
|
227
|
+
ariaRelevant: "additions",
|
|
228
|
+
delay: 250
|
|
229
|
+
}), jsx(VisuallyHidden, {
|
|
230
|
+
id: screenReaderDescriptionId
|
|
231
|
+
}, jsx(FormattedMessage, messages.searchLinkAriaDescription)), jsx(PanelTextInput, {
|
|
232
|
+
role: "combobox",
|
|
233
|
+
autoComplete: "off",
|
|
234
|
+
name: "url",
|
|
235
|
+
testId: testIds.urlInputField,
|
|
236
|
+
label: intl.formatMessage(messages.linkLabel),
|
|
237
|
+
placeholder: intl.formatMessage(linkPlaceHolder),
|
|
238
|
+
value: url,
|
|
239
|
+
autoFocus: true,
|
|
240
|
+
elemBeforeInput: searchIcon,
|
|
241
|
+
clearLabel: intl.formatMessage(messages.clearLink),
|
|
242
|
+
"aria-expanded": true,
|
|
243
|
+
"aria-autocomplete": "list",
|
|
244
|
+
"aria-controls": linkSearchListId,
|
|
245
|
+
"aria-activedescendant": ariaActiveDescendant,
|
|
246
|
+
"aria-describedby": screenReaderDescriptionId,
|
|
247
|
+
error: invalidUrl ? intl.formatMessage(messages.linkInvalid) : null,
|
|
248
|
+
onClear: handleClear,
|
|
249
|
+
onSubmit: handleSubmit,
|
|
250
|
+
onKeyDown: handleKeyDown,
|
|
251
|
+
onChange: handleChangeUrl
|
|
252
|
+
}), jsx(PanelTextInput, {
|
|
253
|
+
autoComplete: "off",
|
|
254
|
+
name: "displayText",
|
|
255
|
+
testId: testIds.textInputField,
|
|
256
|
+
value: displayText,
|
|
257
|
+
label: intl.formatMessage(messages.linkTextLabel),
|
|
258
|
+
placeholder: intl.formatMessage(messages.linkTextPlaceholder),
|
|
259
|
+
clearLabel: intl.formatMessage(messages.clearText),
|
|
260
|
+
"aria-label": intl.formatMessage(messages.linkAriaLabel),
|
|
261
|
+
onClear: handleClear,
|
|
262
|
+
onSubmit: handleSubmit,
|
|
263
|
+
onKeyDown: handleKeyDown,
|
|
264
|
+
onChange: handleChangeText
|
|
265
|
+
}), tabs.length > 0 && jsx("div", {
|
|
266
|
+
css: tabsWrapperStyles
|
|
267
|
+
}, jsx(Tabs, {
|
|
268
|
+
id: testIds.tabList,
|
|
269
|
+
testId: testIds.tabList,
|
|
270
|
+
onChange: activeTab => dispatch({
|
|
271
|
+
selectedIndex: -1,
|
|
272
|
+
activeIndex: -1,
|
|
273
|
+
invalidUrl: false,
|
|
274
|
+
activeTab
|
|
275
|
+
})
|
|
276
|
+
}, jsx(TabList, null, tabs.map(tab => jsx(Tab, {
|
|
277
|
+
key: tab.tabTitle,
|
|
278
|
+
testId: testIds.tabItem
|
|
279
|
+
}, tab.tabTitle))))), !!items.length && jsx("div", {
|
|
280
|
+
css: listTitleStyles,
|
|
281
|
+
id: testIds.resultListTitle,
|
|
282
|
+
"data-testid": testIds.resultListTitle
|
|
283
|
+
}, jsx(FormattedMessage, linkListTitle)), jsx(VisuallyHidden, {
|
|
284
|
+
id: "fabric.smartcard.linkpicker.suggested.results"
|
|
285
|
+
}, url && jsx(FormattedMessage, _extends({}, messages.searchLinkResults, {
|
|
286
|
+
values: {
|
|
287
|
+
count: items.length
|
|
288
|
+
},
|
|
289
|
+
"aria-live": "polite",
|
|
290
|
+
"aria-atomic": "true"
|
|
291
|
+
}))), jsx(LinkSearchList, {
|
|
292
|
+
ariaLabelledBy: testIds.resultListTitle,
|
|
293
|
+
ariaControls: "fabric.smartcard.linkpicker.suggested.results",
|
|
294
|
+
id: linkSearchListId,
|
|
295
|
+
role: "listbox",
|
|
296
|
+
items: items,
|
|
297
|
+
isLoading: isLoading,
|
|
298
|
+
selectedIndex: selectedIndex,
|
|
299
|
+
activeIndex: activeIndex,
|
|
300
|
+
onSelect: handleSelected,
|
|
301
|
+
onMouseEnter: handleMouseEnterResultItem,
|
|
302
|
+
onMouseLeave: handleMouseLeaveResultItem
|
|
303
|
+
}), noResults && jsx(LinkSearchNoResults, {
|
|
304
|
+
testId: testIds.emptyResultPage,
|
|
305
|
+
header: intl.formatMessage(messages.noResults),
|
|
306
|
+
description: intl.formatMessage(messages.noResultsDescription)
|
|
307
|
+
}), jsx(FormFooter, {
|
|
308
|
+
align: "end"
|
|
309
|
+
}, jsx(ButtonGroup, null, jsx(Button, {
|
|
310
|
+
appearance: "default",
|
|
311
|
+
onClick: onCancel,
|
|
312
|
+
testId: testIds.cancelButton
|
|
313
|
+
}, intl.formatMessage(messages.cancelButton)), jsx(Button, {
|
|
314
|
+
type: "submit",
|
|
315
|
+
appearance: "primary",
|
|
316
|
+
onClick: handleSubmit,
|
|
317
|
+
testId: testIds.insertButton
|
|
318
|
+
}, intl.formatMessage(insertButtonMsg)))));
|
|
319
|
+
}
|
|
350
320
|
|
|
351
|
-
|
|
352
|
-
items: limit(value.data),
|
|
353
|
-
isLoading: !done
|
|
354
|
-
}));
|
|
321
|
+
export default LinkPicker;
|
|
355
322
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
} catch {
|
|
361
|
-
this.setState({
|
|
362
|
-
isLoading: false
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
render() {
|
|
323
|
+
function getScreenReaderText(items, selectedIndex, intl) {
|
|
324
|
+
if (items.length && selectedIndex > -1) {
|
|
368
325
|
const {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const {
|
|
378
|
-
intl: {
|
|
379
|
-
formatMessage
|
|
380
|
-
}
|
|
381
|
-
} = this.props;
|
|
382
|
-
const activePlugin = this.getActivePlugin();
|
|
383
|
-
const tabs = this.getTabs();
|
|
384
|
-
const linkPlaceHolder = formatMessage(activePlugin ? messages.placeholder : messages.linkPlaceholder);
|
|
385
|
-
const linkLabel = formatMessage(messages.linkLabel);
|
|
386
|
-
const linkTextLabel = formatMessage(messages.linkTextLabel);
|
|
387
|
-
const linkTextPlaceHolder = formatMessage(messages.linkTextPlaceholder);
|
|
388
|
-
const screenReaderDescriptionId = 'search-recent-links-field-description';
|
|
389
|
-
const linkSearchListId = 'link-picker-search-list';
|
|
390
|
-
const ariaActiveDescendant = selectedIndex > -1 ? `link-search-list-item-${selectedIndex}` : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
|
|
391
|
-
// as the Aria design pattern for combobox does not work in this case
|
|
392
|
-
// for details: https://a11y-internal.atlassian.net/browse/AK-740
|
|
393
|
-
|
|
394
|
-
const screenReaderText = browser.safari && this.getScreenReaderText();
|
|
395
|
-
const searchIcon = activePlugin && jsx("span", {
|
|
396
|
-
css: searchIconStyles,
|
|
397
|
-
"data-testid": "link-picker-search-icon"
|
|
398
|
-
}, jsx(EditorSearchIcon, {
|
|
399
|
-
size: "medium",
|
|
400
|
-
label: ''
|
|
401
|
-
}));
|
|
402
|
-
const noResults = activePlugin && !isLoading && !items.length && !isSafeUrl(url);
|
|
403
|
-
return jsx("div", {
|
|
404
|
-
"data-testid": "link-picker",
|
|
405
|
-
css: rootContainerStyles
|
|
406
|
-
}, screenReaderText && jsx(Announcer, {
|
|
407
|
-
ariaLive: "assertive",
|
|
408
|
-
text: screenReaderText,
|
|
409
|
-
ariaRelevant: "additions",
|
|
410
|
-
delay: 250
|
|
411
|
-
}), jsx(VisuallyHidden, null, jsx("span", {
|
|
412
|
-
"aria-hidden": "true",
|
|
413
|
-
id: screenReaderDescriptionId
|
|
414
|
-
}, formatMessage(messages.searchLinkAriaDescription))), jsx(PanelTextInput, {
|
|
415
|
-
name: "link",
|
|
416
|
-
testId: "link-url",
|
|
417
|
-
role: "combobox",
|
|
418
|
-
label: linkLabel,
|
|
419
|
-
placeholder: linkPlaceHolder,
|
|
420
|
-
value: url,
|
|
421
|
-
autoFocus: true,
|
|
422
|
-
autoComplete: "off",
|
|
423
|
-
elemBeforeInput: searchIcon,
|
|
424
|
-
onSubmit: this.handleSubmit,
|
|
425
|
-
onChange: this.updateInput,
|
|
426
|
-
onKeyDown: this.handleKeyDown,
|
|
427
|
-
onClear: this.handleClearUrl,
|
|
428
|
-
error: this.state.invalidUrl,
|
|
429
|
-
clearLabel: formatMessage(messages.clearLink),
|
|
430
|
-
"aria-controls": linkSearchListId,
|
|
431
|
-
"aria-autocomplete": "list",
|
|
432
|
-
"aria-activedescendant": ariaActiveDescendant,
|
|
433
|
-
"aria-describedby": screenReaderDescriptionId,
|
|
434
|
-
"aria-expanded": true
|
|
435
|
-
}), jsx(PanelTextInput, {
|
|
436
|
-
name: "link-text",
|
|
437
|
-
testId: "link-text",
|
|
438
|
-
autoComplete: "off",
|
|
439
|
-
label: linkTextLabel,
|
|
440
|
-
placeholder: linkTextPlaceHolder,
|
|
441
|
-
"aria-label": formatMessage(messages.linkAriaLabel),
|
|
442
|
-
value: displayText,
|
|
443
|
-
onSubmit: this.handleSubmit,
|
|
444
|
-
onKeyDown: this.handleKeyDown,
|
|
445
|
-
onChange: this.updateTextInput,
|
|
446
|
-
onClear: this.handleClearDisplayText,
|
|
447
|
-
clearLabel: formatMessage(messages.clearText)
|
|
448
|
-
}), tabs.length > 0 && jsx(Tabs, {
|
|
449
|
-
id: "link-picker-tabs",
|
|
450
|
-
testId: "link-picker-tabs",
|
|
451
|
-
onChange: activePlugin => this.setState({
|
|
452
|
-
activePlugin
|
|
453
|
-
}, () => {
|
|
454
|
-
this.updateResults();
|
|
455
|
-
})
|
|
456
|
-
}, jsx(TabList, null, tabs.map(tab => {
|
|
457
|
-
const label = tab.tabTitle;
|
|
458
|
-
return jsx(Tab, {
|
|
459
|
-
key: label,
|
|
460
|
-
testId: "link-picker-tab"
|
|
461
|
-
}, label);
|
|
462
|
-
}))), !!items.length && jsx("div", {
|
|
463
|
-
css: listTitleStyles,
|
|
464
|
-
id: "link-picker-list-title",
|
|
465
|
-
"data-testid": "link-picker-list-title"
|
|
466
|
-
}, formatMessage(this.state.url ? messages.titleResults : messages.titleRecentlyViewed)), jsx(VisuallyHidden, null, jsx("span", {
|
|
467
|
-
"aria-live": "polite",
|
|
468
|
-
"aria-atomic": "true",
|
|
469
|
-
id: "fabric.smartcard.linkpicker.suggested.results"
|
|
470
|
-
}, url && formatMessage(messages.searchLinkResults, {
|
|
471
|
-
count: items.length
|
|
472
|
-
}))), jsx(LinkSearchList, {
|
|
473
|
-
ariaLabelledBy: "link-picker-list-title",
|
|
474
|
-
ariaControls: "fabric.smartcard.linkpicker.suggested.results",
|
|
475
|
-
id: linkSearchListId,
|
|
476
|
-
role: "listbox",
|
|
477
|
-
items: items,
|
|
478
|
-
isLoading: isLoading,
|
|
479
|
-
selectedIndex: selectedIndex,
|
|
480
|
-
activeIndex: activeIndex,
|
|
481
|
-
onSelect: this.handleSelected,
|
|
482
|
-
onMouseEnter: this.handleMouseEnterResultItem,
|
|
483
|
-
onMouseLeave: this.handleMouseLeaveResultItem
|
|
484
|
-
}), noResults && jsx(LinkSearchNoResults, {
|
|
485
|
-
testId: "link-search-no-results",
|
|
486
|
-
header: formatMessage(messages.noResults),
|
|
487
|
-
description: formatMessage(messages.noResultsDescription)
|
|
488
|
-
}), jsx(FormFooter, {
|
|
489
|
-
align: "end"
|
|
490
|
-
}, jsx(ButtonGroup, null, jsx(Button, {
|
|
491
|
-
appearance: "default",
|
|
492
|
-
onClick: this.handleCancel,
|
|
493
|
-
testId: "link-picker-cancel-button"
|
|
494
|
-
}, formatMessage(messages.cancelButton)), jsx(Button, {
|
|
495
|
-
type: "submit",
|
|
496
|
-
appearance: "primary",
|
|
497
|
-
onClick: this.handleSubmit,
|
|
498
|
-
testId: "link-picker-insert-button"
|
|
499
|
-
}, formatMessage(isEditing ? messages.saveButton : messages.insertButton)))));
|
|
326
|
+
name,
|
|
327
|
+
container,
|
|
328
|
+
lastUpdatedDate,
|
|
329
|
+
lastViewedDate
|
|
330
|
+
} = items[selectedIndex];
|
|
331
|
+
const date = transformTimeStamp(intl, lastViewedDate, lastUpdatedDate);
|
|
332
|
+
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
|
+
return [name, container, formattedDate].filter(Boolean).join(', ');
|
|
500
334
|
}
|
|
501
|
-
|
|
502
335
|
}
|
|
503
336
|
|
|
504
337
|
function findIndex(array, predicate) {
|
|
@@ -512,10 +345,4 @@ function findIndex(array, predicate) {
|
|
|
512
345
|
return false;
|
|
513
346
|
});
|
|
514
347
|
return index;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
function limit(items) {
|
|
518
|
-
return items.slice(0, RECENT_SEARCH_LIST_SIZE);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
export default injectIntl(LinkPicker);
|
|
348
|
+
}
|