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