@atlaskit/link-picker 1.4.0 → 1.5.1
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 +308 -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 +288 -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 +298 -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 +24 -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 +4 -3
|
@@ -1,504 +1,331 @@
|
|
|
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
|
+
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
|
+
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
|
|
100
93
|
});
|
|
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;
|
|
94
|
+
}, []);
|
|
95
|
+
const handleChangeText = useCallback(displayText => {
|
|
96
|
+
dispatch({
|
|
97
|
+
displayText
|
|
124
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;
|
|
125
106
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
} = this.state;
|
|
130
|
-
const selectedItem = items.find(item => item.objectId === objectId);
|
|
107
|
+
if (!items || !items.length) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
131
110
|
|
|
132
|
-
|
|
133
|
-
const {
|
|
134
|
-
url,
|
|
135
|
-
name
|
|
136
|
-
} = selectedItem;
|
|
137
|
-
this.handleInsert(url, name, 'typeAhead');
|
|
138
|
-
}
|
|
139
|
-
});
|
|
111
|
+
let updatedIndex = activeIndex;
|
|
140
112
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
displayText
|
|
148
|
-
} = this.state;
|
|
113
|
+
switch (keyCode) {
|
|
114
|
+
case KEY_CODE_ARROW_DOWN:
|
|
115
|
+
// down
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
updatedIndex = (activeIndex + 1) % items.length;
|
|
118
|
+
break;
|
|
149
119
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
inputMethod: inputType
|
|
157
|
-
},
|
|
158
|
-
...(inputType === 'manual' ? {
|
|
159
|
-
rawUrl
|
|
160
|
-
} : {})
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
});
|
|
120
|
+
case KEY_CODE_ARROW_UP:
|
|
121
|
+
// up
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
updatedIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
164
126
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
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
|
|
172
133
|
});
|
|
134
|
+
}
|
|
135
|
+
}, [activeIndex, items]);
|
|
136
|
+
const handleClear = useCallback(field => {
|
|
137
|
+
dispatch({
|
|
138
|
+
[field]: ''
|
|
173
139
|
});
|
|
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
|
-
}
|
|
140
|
+
}, []);
|
|
141
|
+
const handleMouseEnterResultItem = useCallback(objectId => {
|
|
142
|
+
const index = findIndex(items, item => item.objectId === objectId);
|
|
143
|
+
dispatch({
|
|
144
|
+
activeIndex: index
|
|
187
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
|
|
188
149
|
|
|
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)
|
|
150
|
+
if (activeIndex === index) {
|
|
151
|
+
dispatch({
|
|
152
|
+
activeIndex: -1
|
|
212
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
|
+
} : {})
|
|
213
167
|
});
|
|
168
|
+
}, [displayText, onSubmit, state.url]);
|
|
169
|
+
const handleSelected = useCallback(objectId => {
|
|
170
|
+
const selectedItem = items.find(item => item.objectId === objectId);
|
|
214
171
|
|
|
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", () => {
|
|
172
|
+
if (selectedItem) {
|
|
257
173
|
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();
|
|
174
|
+
url,
|
|
175
|
+
name
|
|
176
|
+
} = selectedItem;
|
|
177
|
+
handleInsert(url, name, 'typeAhead');
|
|
300
178
|
}
|
|
301
|
-
}
|
|
179
|
+
}, [handleInsert, items]);
|
|
180
|
+
const handleSubmit = useCallback(() => {
|
|
181
|
+
const selectedItem = items[selectedIndex];
|
|
302
182
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return null;
|
|
183
|
+
if (isSelectedItem) {
|
|
184
|
+
return handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
|
|
306
185
|
}
|
|
307
186
|
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Interface between link picker and plugins to update results
|
|
312
|
-
*/
|
|
313
|
-
|
|
187
|
+
const normalized = normalizeUrl(url);
|
|
314
188
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const activePlugin = this.getActivePlugin();
|
|
318
|
-
|
|
319
|
-
if (!activePlugin) {
|
|
320
|
-
return;
|
|
189
|
+
if (normalized) {
|
|
190
|
+
return handleInsert(normalized, null, 'manual');
|
|
321
191
|
}
|
|
322
192
|
|
|
323
|
-
|
|
324
|
-
|
|
193
|
+
return dispatch({
|
|
194
|
+
invalidUrl: true
|
|
325
195
|
});
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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("div", {
|
|
260
|
+
css: tabsWrapperStyles
|
|
261
|
+
}, jsx(Tabs, {
|
|
262
|
+
id: testIds.tabList,
|
|
263
|
+
testId: testIds.tabList,
|
|
264
|
+
onChange: activeTab => dispatch({
|
|
265
|
+
selectedIndex: -1,
|
|
266
|
+
activeIndex: -1,
|
|
267
|
+
invalidUrl: false,
|
|
268
|
+
activeTab
|
|
269
|
+
})
|
|
270
|
+
}, jsx(TabList, null, tabs.map(tab => jsx(Tab, {
|
|
271
|
+
key: tab.tabTitle,
|
|
272
|
+
testId: testIds.tabItem
|
|
273
|
+
}, tab.tabTitle))))), !!items.length && jsx("div", {
|
|
274
|
+
css: listTitleStyles,
|
|
275
|
+
id: testIds.resultListTitle,
|
|
276
|
+
"data-testid": testIds.resultListTitle
|
|
277
|
+
}, jsx(FormattedMessage, linkListTitle)), jsx(VisuallyHidden, {
|
|
278
|
+
id: "fabric.smartcard.linkpicker.suggested.results"
|
|
279
|
+
}, url && jsx(FormattedMessage, _extends({}, messages.searchLinkResults, {
|
|
280
|
+
values: {
|
|
281
|
+
count: items.length
|
|
282
|
+
},
|
|
283
|
+
"aria-live": "polite",
|
|
284
|
+
"aria-atomic": "true"
|
|
285
|
+
}))), jsx(LinkSearchList, {
|
|
286
|
+
ariaLabelledBy: testIds.resultListTitle,
|
|
287
|
+
ariaControls: "fabric.smartcard.linkpicker.suggested.results",
|
|
288
|
+
id: linkSearchListId,
|
|
289
|
+
role: "listbox",
|
|
290
|
+
items: items,
|
|
291
|
+
isLoading: isLoading,
|
|
292
|
+
selectedIndex: selectedIndex,
|
|
293
|
+
activeIndex: activeIndex,
|
|
294
|
+
onSelect: handleSelected,
|
|
295
|
+
onMouseEnter: handleMouseEnterResultItem,
|
|
296
|
+
onMouseLeave: handleMouseLeaveResultItem
|
|
297
|
+
}), noResults && jsx(LinkSearchNoResults, {
|
|
298
|
+
testId: testIds.emptyResultPage,
|
|
299
|
+
header: intl.formatMessage(messages.noResults),
|
|
300
|
+
description: intl.formatMessage(messages.noResultsDescription)
|
|
301
|
+
}), jsx(FormFooter, {
|
|
302
|
+
align: "end"
|
|
303
|
+
}, jsx(ButtonGroup, null, jsx(Button, {
|
|
304
|
+
appearance: "default",
|
|
305
|
+
onClick: onCancel,
|
|
306
|
+
testId: testIds.cancelButton
|
|
307
|
+
}, intl.formatMessage(messages.cancelButton)), jsx(Button, {
|
|
308
|
+
type: "submit",
|
|
309
|
+
appearance: "primary",
|
|
310
|
+
onClick: handleSubmit,
|
|
311
|
+
testId: testIds.insertButton
|
|
312
|
+
}, intl.formatMessage(insertButtonMsg)))));
|
|
313
|
+
}
|
|
350
314
|
|
|
351
|
-
|
|
352
|
-
items: limit(value.data),
|
|
353
|
-
isLoading: !done
|
|
354
|
-
}));
|
|
315
|
+
export default LinkPicker;
|
|
355
316
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
} catch {
|
|
361
|
-
this.setState({
|
|
362
|
-
isLoading: false
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
render() {
|
|
317
|
+
function getScreenReaderText(items, selectedIndex, intl) {
|
|
318
|
+
if (items.length && selectedIndex > -1) {
|
|
368
319
|
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
|
-
}, tabs.length > 0 && jsx(Tabs, {
|
|
407
|
-
id: "link-picker-tabs",
|
|
408
|
-
testId: "link-picker-tabs",
|
|
409
|
-
onChange: activePlugin => this.setState({
|
|
410
|
-
activePlugin
|
|
411
|
-
}, () => {
|
|
412
|
-
this.updateResults();
|
|
413
|
-
})
|
|
414
|
-
}, jsx(TabList, null, tabs.map(tab => {
|
|
415
|
-
const label = tab.tabTitle;
|
|
416
|
-
return jsx(Tab, {
|
|
417
|
-
key: label,
|
|
418
|
-
testId: "link-picker-tab"
|
|
419
|
-
}, label);
|
|
420
|
-
}))), screenReaderText && jsx(Announcer, {
|
|
421
|
-
ariaLive: "assertive",
|
|
422
|
-
text: screenReaderText,
|
|
423
|
-
ariaRelevant: "additions",
|
|
424
|
-
delay: 250
|
|
425
|
-
}), jsx(VisuallyHidden, null, jsx("span", {
|
|
426
|
-
"aria-hidden": "true",
|
|
427
|
-
id: screenReaderDescriptionId
|
|
428
|
-
}, formatMessage(messages.searchLinkAriaDescription))), jsx(PanelTextInput, {
|
|
429
|
-
name: "link",
|
|
430
|
-
testId: "link-url",
|
|
431
|
-
role: "combobox",
|
|
432
|
-
label: linkLabel,
|
|
433
|
-
placeholder: linkPlaceHolder,
|
|
434
|
-
value: url,
|
|
435
|
-
autoFocus: true,
|
|
436
|
-
autoComplete: "off",
|
|
437
|
-
elemBeforeInput: searchIcon,
|
|
438
|
-
onSubmit: this.handleSubmit,
|
|
439
|
-
onChange: this.updateInput,
|
|
440
|
-
onKeyDown: this.handleKeyDown,
|
|
441
|
-
onClear: this.handleClearUrl,
|
|
442
|
-
error: this.state.invalidUrl,
|
|
443
|
-
clearLabel: formatMessage(messages.clearLink),
|
|
444
|
-
"aria-controls": linkSearchListId,
|
|
445
|
-
"aria-autocomplete": "list",
|
|
446
|
-
"aria-activedescendant": ariaActiveDescendant,
|
|
447
|
-
"aria-describedby": screenReaderDescriptionId,
|
|
448
|
-
"aria-expanded": true
|
|
449
|
-
}), jsx(PanelTextInput, {
|
|
450
|
-
name: "link-text",
|
|
451
|
-
testId: "link-text",
|
|
452
|
-
autoComplete: "off",
|
|
453
|
-
label: linkTextLabel,
|
|
454
|
-
placeholder: linkTextPlaceHolder,
|
|
455
|
-
"aria-label": formatMessage(messages.linkAriaLabel),
|
|
456
|
-
value: displayText,
|
|
457
|
-
onSubmit: this.handleSubmit,
|
|
458
|
-
onKeyDown: this.handleKeyDown,
|
|
459
|
-
onChange: this.updateTextInput,
|
|
460
|
-
onClear: this.handleClearDisplayText,
|
|
461
|
-
clearLabel: formatMessage(messages.clearText)
|
|
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)))));
|
|
320
|
+
name,
|
|
321
|
+
container,
|
|
322
|
+
lastUpdatedDate,
|
|
323
|
+
lastViewedDate
|
|
324
|
+
} = items[selectedIndex];
|
|
325
|
+
const date = transformTimeStamp(intl, lastViewedDate, lastUpdatedDate);
|
|
326
|
+
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(' ');
|
|
327
|
+
return [name, container, formattedDate].filter(Boolean).join(', ');
|
|
500
328
|
}
|
|
501
|
-
|
|
502
329
|
}
|
|
503
330
|
|
|
504
331
|
function findIndex(array, predicate) {
|
|
@@ -512,10 +339,4 @@ function findIndex(array, predicate) {
|
|
|
512
339
|
return false;
|
|
513
340
|
});
|
|
514
341
|
return index;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
function limit(items) {
|
|
518
|
-
return items.slice(0, RECENT_SEARCH_LIST_SIZE);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
export default injectIntl(LinkPicker);
|
|
342
|
+
}
|