@atlaskit/link-picker 0.0.2 → 0.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 +140 -0
- package/README.md +27 -5
- package/dist/cjs/browser.js +39 -0
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/messages.js +102 -0
- package/dist/cjs/styles.js +90 -0
- package/dist/cjs/transformTimeStamp.js +55 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/ui/LinkSearchList.js +112 -0
- package/dist/cjs/ui/LinkSearchListItem.js +101 -0
- package/dist/cjs/ui/PanelTextInput.js +119 -0
- package/dist/cjs/ui/announcer.js +64 -0
- package/dist/cjs/ui/link-picker.js +584 -0
- package/dist/cjs/url.js +104 -0
- package/dist/cjs/utils.js +40 -0
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/browser.js +34 -0
- package/dist/es2019/index.js +1 -1
- package/dist/es2019/messages.js +93 -0
- package/dist/es2019/styles.js +94 -0
- package/dist/es2019/transformTimeStamp.js +41 -0
- package/dist/es2019/types.js +1 -0
- package/dist/es2019/ui/LinkSearchList.js +65 -0
- package/dist/es2019/ui/LinkSearchListItem.js +88 -0
- package/dist/es2019/ui/PanelTextInput.js +93 -0
- package/dist/es2019/ui/announcer.js +38 -0
- package/dist/es2019/ui/link-picker.js +480 -0
- package/dist/es2019/url.js +88 -0
- package/dist/es2019/utils.js +24 -0
- package/dist/es2019/version.json +1 -1
- package/dist/esm/browser.js +34 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/messages.js +93 -0
- package/dist/esm/styles.js +46 -0
- package/dist/esm/transformTimeStamp.js +41 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/ui/LinkSearchList.js +87 -0
- package/dist/esm/ui/LinkSearchListItem.js +88 -0
- package/dist/esm/ui/PanelTextInput.js +93 -0
- package/dist/esm/ui/announcer.js +51 -0
- package/dist/esm/ui/link-picker.js +559 -0
- package/dist/esm/url.js +90 -0
- package/dist/esm/utils.js +24 -0
- package/dist/esm/version.json +1 -1
- package/dist/types/browser.d.ts +15 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/messages.d.ts +92 -0
- package/dist/types/styles.d.ts +18 -0
- package/dist/types/transformTimeStamp.d.ts +3 -0
- package/dist/types/types.d.ts +37 -0
- package/dist/types/ui/LinkSearchList.d.ts +20 -0
- package/dist/types/ui/LinkSearchListItem.d.ts +17 -0
- package/dist/types/ui/PanelTextInput.d.ts +17 -0
- package/dist/types/ui/announcer.d.ts +22 -0
- package/dist/types/ui/link-picker.d.ts +79 -0
- package/dist/types/url.d.ts +5 -0
- package/dist/types/utils.d.ts +4 -0
- package/example-helpers/flight-deck/_manifest.json +9 -0
- package/example-helpers/flight-deck/mockFDResponse.json +133 -0
- package/example-helpers/mock-data.ts +316 -0
- package/example-helpers/providers.ts +39 -0
- package/package.json +35 -6
- package/dist/cjs/link-picker.js +0 -12
- package/dist/es2019/link-picker.js +0 -3
- package/dist/esm/link-picker.js +0 -3
- package/dist/types/link-picker.d.ts +0 -1
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
|
|
3
|
+
/** @jsx jsx */
|
|
4
|
+
import React, { PureComponent } from 'react';
|
|
5
|
+
import { jsx } from '@emotion/core';
|
|
6
|
+
import { injectIntl } from 'react-intl-next';
|
|
7
|
+
import { messages } from '../messages';
|
|
8
|
+
import PanelTextInput from './PanelTextInput';
|
|
9
|
+
import LinkSearchList from './LinkSearchList';
|
|
10
|
+
import { normalizeUrl, isSafeUrl } from '../url';
|
|
11
|
+
import { recentListStyles, Container, SearchIconWrapper, ListTitleStyles } from '../styles';
|
|
12
|
+
import { browser } from '../browser';
|
|
13
|
+
import { transformTimeStamp } from '../transformTimeStamp';
|
|
14
|
+
import Announcer from './announcer';
|
|
15
|
+
import Button, { ButtonGroup } from '@atlaskit/button';
|
|
16
|
+
import { FormFooter } from '@atlaskit/form';
|
|
17
|
+
import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
|
|
18
|
+
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
19
|
+
export const RECENT_SEARCH_LIST_SIZE = 5;
|
|
20
|
+
export class LinkPicker extends PureComponent {
|
|
21
|
+
constructor(props) {
|
|
22
|
+
super(props);
|
|
23
|
+
|
|
24
|
+
_defineProperty(this, "wrapperRef", /*#__PURE__*/React.createRef());
|
|
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;
|
|
77
|
+
|
|
78
|
+
if (items.length && selectedIndex > -1) {
|
|
79
|
+
const {
|
|
80
|
+
name,
|
|
81
|
+
container,
|
|
82
|
+
lastUpdatedDate,
|
|
83
|
+
lastViewedDate
|
|
84
|
+
} = items[selectedIndex];
|
|
85
|
+
const date = transformTimeStamp(intl, lastViewedDate, lastUpdatedDate);
|
|
86
|
+
return `${name}, ${container}, ${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) || ''}`;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
_defineProperty(this, "isUrlPopulatedWithSelectedItem", () => {
|
|
91
|
+
/**
|
|
92
|
+
* When we use ArrowKey to navigate through result items,
|
|
93
|
+
* the URL field will be populated with the content of
|
|
94
|
+
* selected item.
|
|
95
|
+
* This function will check if the URL field is populated
|
|
96
|
+
* with selected item.
|
|
97
|
+
* It can be useful to detect whether we want to insert a
|
|
98
|
+
* smartlink or a hyperlink with customized title
|
|
99
|
+
*/
|
|
100
|
+
const {
|
|
101
|
+
items,
|
|
102
|
+
selectedIndex,
|
|
103
|
+
url
|
|
104
|
+
} = this.state;
|
|
105
|
+
const selectedItem = items[selectedIndex];
|
|
106
|
+
|
|
107
|
+
if (selectedItem && selectedItem.url === url) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return false;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
_defineProperty(this, "handleSelected", objectId => {
|
|
115
|
+
const {
|
|
116
|
+
items
|
|
117
|
+
} = this.state;
|
|
118
|
+
const selectedItem = items.find(item => item.objectId === objectId);
|
|
119
|
+
|
|
120
|
+
if (selectedItem) {
|
|
121
|
+
const {
|
|
122
|
+
url,
|
|
123
|
+
name
|
|
124
|
+
} = selectedItem;
|
|
125
|
+
this.handleInsert(url, name, 'typeAhead');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
_defineProperty(this, "handleInsert", (url, title, inputType) => {
|
|
130
|
+
const {
|
|
131
|
+
onSubmit
|
|
132
|
+
} = this.props;
|
|
133
|
+
const {
|
|
134
|
+
url: rawUrl,
|
|
135
|
+
displayText
|
|
136
|
+
} = this.state;
|
|
137
|
+
|
|
138
|
+
if (onSubmit) {
|
|
139
|
+
onSubmit({
|
|
140
|
+
url,
|
|
141
|
+
displayText: displayText || null,
|
|
142
|
+
title: title || null,
|
|
143
|
+
meta: {
|
|
144
|
+
inputMethod: inputType
|
|
145
|
+
},
|
|
146
|
+
...(inputType === 'manual' ? {
|
|
147
|
+
rawUrl
|
|
148
|
+
} : {})
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
_defineProperty(this, "handleMouseEnterResultItem", objectId => {
|
|
154
|
+
const {
|
|
155
|
+
items
|
|
156
|
+
} = this.state;
|
|
157
|
+
const index = findIndex(items, item => item.objectId === objectId);
|
|
158
|
+
this.setState({
|
|
159
|
+
activeIndex: index
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
_defineProperty(this, "handleMouseLeaveResultItem", objectId => {
|
|
164
|
+
const {
|
|
165
|
+
items,
|
|
166
|
+
activeIndex
|
|
167
|
+
} = this.state;
|
|
168
|
+
const index = findIndex(items, item => item.objectId === objectId); // This is to avoid updating index that was set by other mouseenter event
|
|
169
|
+
|
|
170
|
+
if (activeIndex === index) {
|
|
171
|
+
this.setState({
|
|
172
|
+
activeIndex: -1
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
_defineProperty(this, "handleSubmit", () => {
|
|
178
|
+
const {
|
|
179
|
+
url,
|
|
180
|
+
selectedIndex,
|
|
181
|
+
items
|
|
182
|
+
} = this.state;
|
|
183
|
+
const selectedItem = items[selectedIndex];
|
|
184
|
+
|
|
185
|
+
if (this.isUrlPopulatedWithSelectedItem()) {
|
|
186
|
+
this.handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
|
|
187
|
+
} else if (url.length > 0) {
|
|
188
|
+
const normalized = normalizeUrl(url);
|
|
189
|
+
|
|
190
|
+
if (normalized) {
|
|
191
|
+
this.handleInsert(normalized, null, 'manual');
|
|
192
|
+
} else {
|
|
193
|
+
const {
|
|
194
|
+
intl
|
|
195
|
+
} = this.props;
|
|
196
|
+
const linkInvalid = intl.formatMessage(messages.linkInvalid);
|
|
197
|
+
this.setState({
|
|
198
|
+
invalidUrl: linkInvalid
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
_defineProperty(this, "handleKeyDown", event => {
|
|
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", () => {
|
|
246
|
+
const {
|
|
247
|
+
onCancel
|
|
248
|
+
} = this.props;
|
|
249
|
+
|
|
250
|
+
if (onCancel) {
|
|
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
|
+
url: normalizeUrl(props.url) || '',
|
|
266
|
+
displayText: props.displayText || '',
|
|
267
|
+
items: [],
|
|
268
|
+
invalidUrl: null
|
|
269
|
+
};
|
|
270
|
+
/* Cache functions */
|
|
271
|
+
|
|
272
|
+
this.handleClearUrl = this.createClearHandler('url');
|
|
273
|
+
this.handleClearDisplayText = this.createClearHandler('displayText');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
componentDidMount() {
|
|
277
|
+
this.loadInitialLinkSearchResult();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
loadInitialLinkSearchResult() {
|
|
281
|
+
const {
|
|
282
|
+
url
|
|
283
|
+
} = this.state;
|
|
284
|
+
|
|
285
|
+
if (!url) {
|
|
286
|
+
this.updateResults();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
getActivePlugin() {
|
|
291
|
+
if (!this.props.plugins) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return this.props.plugins[0];
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Interface between link picker and plugins to update results
|
|
299
|
+
*/
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
async updateResults() {
|
|
303
|
+
const queryVersion = ++this.queryVersion;
|
|
304
|
+
const activePlugin = this.getActivePlugin();
|
|
305
|
+
|
|
306
|
+
if (!activePlugin) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const updates = activePlugin.resolve({
|
|
311
|
+
query: this.state.url
|
|
312
|
+
});
|
|
313
|
+
this.setState({
|
|
314
|
+
isLoading: true,
|
|
315
|
+
items: []
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
while (queryVersion === this.queryVersion) {
|
|
320
|
+
const {
|
|
321
|
+
done,
|
|
322
|
+
value
|
|
323
|
+
} = await updates.next();
|
|
324
|
+
|
|
325
|
+
if (queryVersion !== this.queryVersion) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this.setState({
|
|
330
|
+
items: limit(value),
|
|
331
|
+
isLoading: !done
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
if (done) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
this.setState({
|
|
340
|
+
isLoading: false
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
render() {
|
|
346
|
+
const {
|
|
347
|
+
items,
|
|
348
|
+
isLoading,
|
|
349
|
+
activeIndex,
|
|
350
|
+
selectedIndex,
|
|
351
|
+
url,
|
|
352
|
+
displayText
|
|
353
|
+
} = this.state;
|
|
354
|
+
const {
|
|
355
|
+
intl: {
|
|
356
|
+
formatMessage
|
|
357
|
+
}
|
|
358
|
+
} = this.props;
|
|
359
|
+
const activePlugin = this.getActivePlugin();
|
|
360
|
+
const linkPlaceHolder = formatMessage(activePlugin ? messages.placeholder : messages.linkPlaceholder);
|
|
361
|
+
const linkLabel = formatMessage(messages.linkLabel);
|
|
362
|
+
const linkTextLabel = formatMessage(messages.linkTextLabel);
|
|
363
|
+
const linkTextPlaceHolder = formatMessage(messages.linkTextPlaceholder);
|
|
364
|
+
const screenReaderDescriptionId = 'search-recent-links-field-description';
|
|
365
|
+
const linkSearchListId = 'link-picker-search-list';
|
|
366
|
+
const ariaActiveDescendant = selectedIndex > -1 ? `link-search-list-item-${selectedIndex}` : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
|
|
367
|
+
// as the Aria design pattern for combobox does not work in this case
|
|
368
|
+
// for details: https://a11y-internal.atlassian.net/browse/AK-740
|
|
369
|
+
|
|
370
|
+
const screenReaderText = browser.safari && this.getScreenReaderText();
|
|
371
|
+
const searchIcon = activePlugin && jsx(SearchIconWrapper, {
|
|
372
|
+
"data-testid": "link-picker-search-icon"
|
|
373
|
+
}, jsx(EditorSearchIcon, {
|
|
374
|
+
size: "medium",
|
|
375
|
+
label: ''
|
|
376
|
+
}));
|
|
377
|
+
return jsx("div", {
|
|
378
|
+
"data-testid": "link-picker",
|
|
379
|
+
className: "recent-list",
|
|
380
|
+
css: recentListStyles
|
|
381
|
+
}, jsx(Container, {
|
|
382
|
+
innerRef: this.wrapperRef
|
|
383
|
+
}, screenReaderText && jsx(Announcer, {
|
|
384
|
+
ariaLive: "assertive",
|
|
385
|
+
text: screenReaderText,
|
|
386
|
+
ariaRelevant: "additions",
|
|
387
|
+
delay: 250
|
|
388
|
+
}), jsx(VisuallyHidden, null, jsx("span", {
|
|
389
|
+
"aria-hidden": "true",
|
|
390
|
+
id: screenReaderDescriptionId
|
|
391
|
+
}, formatMessage(messages.searchLinkAriaDescription))), jsx(PanelTextInput, {
|
|
392
|
+
name: "link",
|
|
393
|
+
testId: "link-url",
|
|
394
|
+
role: "combobox",
|
|
395
|
+
label: linkLabel,
|
|
396
|
+
placeholder: linkPlaceHolder,
|
|
397
|
+
value: url,
|
|
398
|
+
autoFocus: true,
|
|
399
|
+
autoComplete: "off",
|
|
400
|
+
elemBeforeInput: searchIcon,
|
|
401
|
+
onSubmit: this.handleSubmit,
|
|
402
|
+
onChange: this.updateInput,
|
|
403
|
+
onKeyDown: this.handleKeyDown,
|
|
404
|
+
onClear: this.handleClearUrl,
|
|
405
|
+
error: this.state.invalidUrl,
|
|
406
|
+
clearLabel: formatMessage(messages.clearLink),
|
|
407
|
+
"aria-controls": linkSearchListId,
|
|
408
|
+
"aria-autocomplete": "list",
|
|
409
|
+
"aria-activedescendant": ariaActiveDescendant,
|
|
410
|
+
"aria-describedby": screenReaderDescriptionId,
|
|
411
|
+
"aria-expanded": true
|
|
412
|
+
}), jsx(PanelTextInput, {
|
|
413
|
+
name: "link-text",
|
|
414
|
+
testId: "link-text",
|
|
415
|
+
autoComplete: "off",
|
|
416
|
+
label: linkTextLabel,
|
|
417
|
+
placeholder: linkTextPlaceHolder,
|
|
418
|
+
"aria-label": formatMessage(messages.linkAriaLabel),
|
|
419
|
+
value: displayText,
|
|
420
|
+
onSubmit: this.handleSubmit,
|
|
421
|
+
onKeyDown: this.handleKeyDown,
|
|
422
|
+
onChange: this.updateTextInput,
|
|
423
|
+
onClear: this.handleClearDisplayText,
|
|
424
|
+
clearLabel: formatMessage(messages.clearText)
|
|
425
|
+
}), !!items.length && jsx("div", {
|
|
426
|
+
css: ListTitleStyles,
|
|
427
|
+
id: "link-picker-list-title",
|
|
428
|
+
"data-testid": "link-picker-list-title"
|
|
429
|
+
}, formatMessage(this.state.url ? messages.titleResults : messages.titleRecentlyViewed)), jsx(VisuallyHidden, null, jsx("span", {
|
|
430
|
+
"aria-live": "polite",
|
|
431
|
+
"aria-atomic": "true",
|
|
432
|
+
id: "fabric.smartcard.linkpicker.suggested.results"
|
|
433
|
+
}, url && formatMessage(messages.searchLinkResults, {
|
|
434
|
+
count: items.length
|
|
435
|
+
}))), jsx(LinkSearchList, {
|
|
436
|
+
ariaLabelledBy: "link-picker-list-title",
|
|
437
|
+
ariaControls: "fabric.smartcard.linkpicker.suggested.results",
|
|
438
|
+
id: linkSearchListId,
|
|
439
|
+
role: "listbox",
|
|
440
|
+
items: items,
|
|
441
|
+
isLoading: isLoading,
|
|
442
|
+
selectedIndex: selectedIndex,
|
|
443
|
+
activeIndex: activeIndex,
|
|
444
|
+
onSelect: this.handleSelected,
|
|
445
|
+
onMouseEnter: this.handleMouseEnterResultItem,
|
|
446
|
+
onMouseLeave: this.handleMouseLeaveResultItem
|
|
447
|
+
}), jsx(FormFooter, {
|
|
448
|
+
align: "end"
|
|
449
|
+
}, jsx(ButtonGroup, null, jsx(Button, {
|
|
450
|
+
appearance: "default",
|
|
451
|
+
onClick: this.handleCancel,
|
|
452
|
+
testId: "link-picker-cancel-button"
|
|
453
|
+
}, formatMessage(messages.cancelButton)), jsx(Button, {
|
|
454
|
+
type: "submit",
|
|
455
|
+
appearance: "primary",
|
|
456
|
+
onClick: this.handleSubmit,
|
|
457
|
+
testId: "link-picker-insert-button"
|
|
458
|
+
}, formatMessage(messages.insertButton))))));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function findIndex(array, predicate) {
|
|
464
|
+
let index = -1;
|
|
465
|
+
array.some((item, i) => {
|
|
466
|
+
if (predicate(item)) {
|
|
467
|
+
index = i;
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return false;
|
|
472
|
+
});
|
|
473
|
+
return index;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function limit(items) {
|
|
477
|
+
return items.slice(0, RECENT_SEARCH_LIST_SIZE);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export const LinkPickerWithIntl = injectIntl(LinkPicker);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import LinkifyIt from 'linkify-it';
|
|
2
|
+
const linkify = LinkifyIt();
|
|
3
|
+
linkify.add('sourcetree:', 'http:');
|
|
4
|
+
const tlds = 'biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф'.split('|');
|
|
5
|
+
const tlds2Char = 'a[cdefgilmnoqrtuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrtuvwxyz]|n[acefgilopruz]|om|p[aefghkmnrtw]|qa|r[eosuw]|s[abcdegijklmnrtuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]';
|
|
6
|
+
tlds.push(tlds2Char);
|
|
7
|
+
linkify.tlds(tlds, false);
|
|
8
|
+
const whitelistedURLPatterns = [/^https?:\/\/[^\s]*$/im, /^ftps?:\/\//im, /^\//im, /^mailto:/im, /^skype:/im, /^callto:[^\s]*$/im, /^facetime:[^\s]*$/im, /^git:/im, /^irc6?:/im, /^news:/im, /^nntp:/im, /^feed:/im, /^cvs:/im, /^svn:/im, /^mvn:/im, /^ssh:/im, /^scp:\/\//im, /^sftp:\/\//im, /^itms:/im, /^notes:/im, /^hipchat:\/\//im, /^sourcetree:/im, /^urn:/im, /^tel:/im, /^xmpp:/im, /^telnet:/im, /^vnc:/im, /^rdp:/im, /^whatsapp:/im, /^slack:/im, /^sips?:/im, /^magnet:/im, /^foo:/im, /^#/im];
|
|
9
|
+
|
|
10
|
+
const normaliseLinkHref = url => {
|
|
11
|
+
const match = getLinkMatch(url);
|
|
12
|
+
return match && match.url || null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getLinkMatch = str => {
|
|
16
|
+
if (!str) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let match = linkifyMatch(str);
|
|
21
|
+
|
|
22
|
+
if (!match.length) {
|
|
23
|
+
match = linkify.match(str);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return match && match.length > 0 && match[0].index === 0 && match[0].lastIndex === str.trim().length ? match[0] : null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const LINK_REGEXP = /(https?|ftp):\/\/[^\s]+/;
|
|
30
|
+
|
|
31
|
+
const linkifyMatch = text => {
|
|
32
|
+
const matches = [];
|
|
33
|
+
|
|
34
|
+
if (!LINK_REGEXP.test(text)) {
|
|
35
|
+
return matches;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let startpos = 0;
|
|
39
|
+
let substr;
|
|
40
|
+
|
|
41
|
+
while (substr = text.substr(startpos)) {
|
|
42
|
+
const link = (substr.match(LINK_REGEXP) || [''])[0];
|
|
43
|
+
|
|
44
|
+
if (link) {
|
|
45
|
+
const index = substr.search(LINK_REGEXP);
|
|
46
|
+
const start = index >= 0 ? index + startpos : index;
|
|
47
|
+
const end = start + link.length;
|
|
48
|
+
matches.push({
|
|
49
|
+
index: start,
|
|
50
|
+
lastIndex: end,
|
|
51
|
+
raw: link,
|
|
52
|
+
url: link,
|
|
53
|
+
text: link,
|
|
54
|
+
schema: ''
|
|
55
|
+
});
|
|
56
|
+
startpos += end;
|
|
57
|
+
} else {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return matches;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const isSafeUrl = url => {
|
|
66
|
+
const urlTrimmed = url.trim();
|
|
67
|
+
|
|
68
|
+
if (urlTrimmed.length === 0) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return whitelistedURLPatterns.some(p => p.test(urlTrimmed));
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Adds protocol to url if needed.
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
export function normalizeUrl(url) {
|
|
79
|
+
if (!url) {
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isSafeUrl(url)) {
|
|
84
|
+
return url.trim();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return normaliseLinkHref(url);
|
|
88
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { browser } from './browser';
|
|
2
|
+
export const relativeFontSizeToBase16 = px => {
|
|
3
|
+
if (typeof px === 'string') {
|
|
4
|
+
px = parseInt(px);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (isNaN(px)) {
|
|
8
|
+
throw new Error(`Invalid font size: '${px}'`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return `${px / 16}rem`;
|
|
12
|
+
};
|
|
13
|
+
const KeyZCode = 90;
|
|
14
|
+
const KeyYCode = 89;
|
|
15
|
+
export const isUndoEvent = e => {
|
|
16
|
+
return e.keyCode === KeyZCode && ( // cmd + z for mac
|
|
17
|
+
browser.mac && e.metaKey && !e.shiftKey || // ctrl + z for non-mac
|
|
18
|
+
!browser.mac && e.ctrlKey);
|
|
19
|
+
};
|
|
20
|
+
export const isRedoEvent = e => {
|
|
21
|
+
return (// ctrl + y for non-mac
|
|
22
|
+
!browser.mac && e.ctrlKey && e.keyCode === KeyYCode || browser.mac && e.metaKey && e.shiftKey && e.keyCode === KeyZCode || e.ctrlKey && e.shiftKey && e.keyCode === KeyZCode
|
|
23
|
+
);
|
|
24
|
+
};
|
package/dist/es2019/version.json
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
var result = {
|
|
2
|
+
mac: false,
|
|
3
|
+
ie: false,
|
|
4
|
+
ie_version: 0,
|
|
5
|
+
gecko: false,
|
|
6
|
+
chrome: false,
|
|
7
|
+
chrome_version: 0,
|
|
8
|
+
android: false,
|
|
9
|
+
ios: false,
|
|
10
|
+
webkit: false,
|
|
11
|
+
safari: false,
|
|
12
|
+
supportsIntersectionObserver: false,
|
|
13
|
+
supportsResizeObserver: false
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
if (typeof navigator !== 'undefined') {
|
|
17
|
+
var ieEdge = /Edge\/(\d+)/.exec(navigator.userAgent);
|
|
18
|
+
var ieUpTo10 = /MSIE \d/.test(navigator.userAgent);
|
|
19
|
+
var ie11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
|
|
20
|
+
result.mac = /Mac/.test(navigator.platform);
|
|
21
|
+
var ie = result.ie = !!(ieUpTo10 || ie11up || ieEdge);
|
|
22
|
+
result.ie_version = ieUpTo10 ? document.documentMode || 6 : ie11up ? +ie11up[1] : ieEdge ? +ieEdge[1] : null;
|
|
23
|
+
result.gecko = !ie && /gecko\/\d/i.test(navigator.userAgent);
|
|
24
|
+
result.chrome = !ie && /Chrome\//.test(navigator.userAgent);
|
|
25
|
+
result.chrome_version = parseInt((navigator.userAgent.match(/Chrome\/(\d+)/) || [])[1], 10);
|
|
26
|
+
result.android = /Android \d/.test(navigator.userAgent);
|
|
27
|
+
result.ios = !ie && /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
|
|
28
|
+
result.webkit = !ie && !!document.documentElement && 'WebkitAppearance' in document.documentElement.style;
|
|
29
|
+
result.safari = Boolean(navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && navigator.userAgent && navigator.userAgent.indexOf('CriOS') === -1 && navigator.userAgent.indexOf('FxiOS') === -1);
|
|
30
|
+
result.supportsIntersectionObserver = typeof window !== 'undefined' && 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype;
|
|
31
|
+
result.supportsResizeObserver = typeof window !== 'undefined' && 'ResizeObserver' in window && 'ResizeObserverEntry' in window;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { result as browser };
|
package/dist/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { LinkPicker } from './link-picker';
|
|
1
|
+
export { LinkPickerWithIntl as LinkPicker } from './ui/link-picker';
|