@atlaskit/editor-plugin-media-insert 2.7.2 → 2.8.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 +14 -0
- package/README.md +1 -1
- package/dist/cjs/ui/MediaFromURL.js +58 -6
- package/dist/es2019/ui/MediaFromURL.js +51 -6
- package/dist/esm/ui/MediaFromURL.js +57 -6
- package/dist/types/ui/MediaFromURL.d.ts +1 -0
- package/dist/types-ts4.5/ui/MediaFromURL.d.ts +1 -0
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-media-insert
|
|
2
2
|
|
|
3
|
+
## 2.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#142802](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/142802)
|
|
8
|
+
[`09c0d0a18b491`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/09c0d0a18b491) -
|
|
9
|
+
[ux] [ED-24935] [ED24921] Add URL validation and enforce max length for input field
|
|
10
|
+
|
|
11
|
+
## 2.7.3
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
|
|
3
17
|
## 2.7.2
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -27,4 +27,4 @@ Please see [Atlaskit - Editor plugin media-insert](https://atlaskit.atlassian.co
|
|
|
27
27
|
For internal Atlassian, visit the slack channel [#help-editor](https://atlassian.slack.com/archives/CFG3PSQ9E) for support or visit [go/editor-help](https://go/editor-help) to submit a bug.
|
|
28
28
|
## License
|
|
29
29
|
---
|
|
30
|
-
Please see [Atlassian Frontend - License](https://
|
|
30
|
+
Please see [Atlassian Frontend - License](https://hello.atlassian.net/wiki/spaces/AF/pages/2589099144/Documentation#Platform-License) for more licensing information.
|
|
@@ -5,16 +5,20 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports.MediaFromURL = MediaFromURL;
|
|
8
|
+
exports.isValidUrl = void 0;
|
|
8
9
|
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
10
|
+
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
9
11
|
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
10
12
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
11
13
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
12
14
|
var _react = _interopRequireDefault(require("react"));
|
|
13
15
|
var _reactIntlNext = require("react-intl-next");
|
|
16
|
+
var _adfSchema = require("@atlaskit/adf-schema");
|
|
14
17
|
var _buttonGroup = _interopRequireDefault(require("@atlaskit/button/button-group"));
|
|
15
18
|
var _new = _interopRequireDefault(require("@atlaskit/button/new"));
|
|
16
19
|
var _analytics = require("@atlaskit/editor-common/analytics");
|
|
17
20
|
var _messages = require("@atlaskit/editor-common/messages");
|
|
21
|
+
var _form = require("@atlaskit/form");
|
|
18
22
|
var _filePreview = _interopRequireDefault(require("@atlaskit/icon/glyph/editor/file-preview"));
|
|
19
23
|
var _mediaClientReact = require("@atlaskit/media-client-react");
|
|
20
24
|
var _primitives = require("@atlaskit/primitives");
|
|
@@ -46,6 +50,19 @@ var INITIAL_PREVIEW_STATE = Object.freeze({
|
|
|
46
50
|
warning: null,
|
|
47
51
|
previewInfo: null
|
|
48
52
|
});
|
|
53
|
+
var MAX_URL_LENGTH = 2048;
|
|
54
|
+
var isValidUrl = exports.isValidUrl = function isValidUrl(value) {
|
|
55
|
+
try {
|
|
56
|
+
// Check for spaces and length first to avoid the expensive URL parsing
|
|
57
|
+
if (/\s/.test(value) || value.length > MAX_URL_LENGTH) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
new URL(value);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return (0, _adfSchema.isSafeUrl)(value);
|
|
65
|
+
};
|
|
49
66
|
var previewStateReducer = function previewStateReducer(state, action) {
|
|
50
67
|
switch (action.type) {
|
|
51
68
|
case 'loading':
|
|
@@ -83,16 +100,25 @@ function MediaFromURL(_ref) {
|
|
|
83
100
|
pasteLinkToUpload: intl.formatMessage(_messages.mediaInsertMessages.pasteLinkToUpload),
|
|
84
101
|
cancel: intl.formatMessage(_messages.mediaInsertMessages.cancel),
|
|
85
102
|
errorMessage: intl.formatMessage(_messages.mediaInsertMessages.fromUrlErrorMessage),
|
|
86
|
-
warning: intl.formatMessage(_messages.mediaInsertMessages.fromUrlWarning)
|
|
103
|
+
warning: intl.formatMessage(_messages.mediaInsertMessages.fromUrlWarning),
|
|
104
|
+
invalidUrl: intl.formatMessage(_messages.mediaInsertMessages.invalidUrlErrorMessage)
|
|
87
105
|
};
|
|
88
106
|
var _React$useState = _react.default.useState(''),
|
|
89
107
|
_React$useState2 = (0, _slicedToArray2.default)(_React$useState, 2),
|
|
90
108
|
inputUrl = _React$useState2[0],
|
|
91
109
|
setUrl = _React$useState2[1];
|
|
110
|
+
var _React$useState3 = _react.default.useState(),
|
|
111
|
+
_React$useState4 = (0, _slicedToArray2.default)(_React$useState3, 2),
|
|
112
|
+
hasUrlError = _React$useState4[0],
|
|
113
|
+
setHasUrlError = _React$useState4[1];
|
|
92
114
|
var _React$useReducer = _react.default.useReducer(previewStateReducer, INITIAL_PREVIEW_STATE),
|
|
93
115
|
_React$useReducer2 = (0, _slicedToArray2.default)(_React$useReducer, 2),
|
|
94
116
|
previewState = _React$useReducer2[0],
|
|
95
117
|
dispatch = _React$useReducer2[1];
|
|
118
|
+
var _React$useState5 = _react.default.useState(false),
|
|
119
|
+
_React$useState6 = (0, _slicedToArray2.default)(_React$useState5, 2),
|
|
120
|
+
isInputFocused = _React$useState6[0],
|
|
121
|
+
setInputFocused = _React$useState6[1];
|
|
96
122
|
var pasteFlag = _react.default.useRef(false);
|
|
97
123
|
var _useAnalyticsEvents = (0, _useAnalyticsEvents2.useAnalyticsEvents)(dispatchAnalyticsEvent),
|
|
98
124
|
onUploadButtonClickedAnalytics = _useAnalyticsEvents.onUploadButtonClickedAnalytics,
|
|
@@ -178,12 +204,32 @@ function MediaFromURL(_ref) {
|
|
|
178
204
|
return _ref2.apply(this, arguments);
|
|
179
205
|
};
|
|
180
206
|
}(), [onUploadButtonClickedAnalytics, mediaProvider, onUploadCommencedAnalytics, onUploadSuccessAnalytics, onUploadFailureAnalytics, inputUrl]);
|
|
207
|
+
var errorAttributes = {};
|
|
208
|
+
if (!isInputFocused) {
|
|
209
|
+
errorAttributes['aria-relevant'] = 'all';
|
|
210
|
+
errorAttributes['aria-atomic'] = 'false';
|
|
211
|
+
}
|
|
212
|
+
var onBlur = _react.default.useCallback(function () {
|
|
213
|
+
if (!isValidUrl(inputUrl)) {
|
|
214
|
+
setHasUrlError(true);
|
|
215
|
+
}
|
|
216
|
+
setInputFocused(false);
|
|
217
|
+
}, [inputUrl]);
|
|
218
|
+
var onFocus = _react.default.useCallback(function () {
|
|
219
|
+
setInputFocused(true);
|
|
220
|
+
}, []);
|
|
181
221
|
var onURLChange = _react.default.useCallback(function (e) {
|
|
182
222
|
var url = e.target.value;
|
|
183
223
|
setUrl(url);
|
|
184
224
|
dispatch({
|
|
185
225
|
type: 'reset'
|
|
186
226
|
});
|
|
227
|
+
if (!isValidUrl(url)) {
|
|
228
|
+
setHasUrlError(true);
|
|
229
|
+
return;
|
|
230
|
+
} else {
|
|
231
|
+
setHasUrlError(false);
|
|
232
|
+
}
|
|
187
233
|
if (pasteFlag.current === true) {
|
|
188
234
|
pasteFlag.current = false;
|
|
189
235
|
uploadExternalMedia(url);
|
|
@@ -193,7 +239,7 @@ function MediaFromURL(_ref) {
|
|
|
193
239
|
// Note: this is a little weird, but the paste event will always be
|
|
194
240
|
// fired before the change event when pasting. We don't really want to
|
|
195
241
|
// duplicate logic by handling pastes separately to changes, so we're
|
|
196
|
-
// just noting paste
|
|
242
|
+
// just noting paste occurred to then be handled in the onURLChange fn
|
|
197
243
|
// above. The one exception to this is where paste inputs exactly what was
|
|
198
244
|
// already in the input, in which case we want to ignore it.
|
|
199
245
|
if (e.clipboardData.getData('text') !== inputUrl) {
|
|
@@ -263,7 +309,7 @@ function MediaFromURL(_ref) {
|
|
|
263
309
|
// This can be triggered from an enter key event on the input even when
|
|
264
310
|
// the button is disabled, so we explicitly do nothing when in loading
|
|
265
311
|
// state.
|
|
266
|
-
if (previewState.isLoading) {
|
|
312
|
+
if (previewState.isLoading || hasUrlError) {
|
|
267
313
|
return;
|
|
268
314
|
}
|
|
269
315
|
if (previewState.previewInfo) {
|
|
@@ -281,10 +327,16 @@ function MediaFromURL(_ref) {
|
|
|
281
327
|
}, /*#__PURE__*/_react.default.createElement(_textfield.default, {
|
|
282
328
|
value: inputUrl,
|
|
283
329
|
placeholder: strings.pasteLinkToUpload,
|
|
330
|
+
isInvalid: hasUrlError,
|
|
331
|
+
maxLength: MAX_URL_LENGTH,
|
|
284
332
|
onChange: onURLChange,
|
|
285
333
|
onKeyPress: onInputKeyPress,
|
|
286
|
-
onPaste: onPaste
|
|
287
|
-
|
|
334
|
+
onPaste: onPaste,
|
|
335
|
+
onBlur: onBlur,
|
|
336
|
+
onFocus: onFocus
|
|
337
|
+
}), hasUrlError && /*#__PURE__*/_react.default.createElement(_form.ErrorMessage, null, /*#__PURE__*/_react.default.createElement(_primitives.Box, (0, _extends2.default)({
|
|
338
|
+
as: "span"
|
|
339
|
+
}, errorAttributes), strings.invalidUrl)), previewState.previewInfo && /*#__PURE__*/_react.default.createElement(_primitives.Inline, {
|
|
288
340
|
alignInline: "center",
|
|
289
341
|
alignBlock: "center",
|
|
290
342
|
xcss: PreviewImageStyles,
|
|
@@ -303,7 +355,7 @@ function MediaFromURL(_ref) {
|
|
|
303
355
|
}, /*#__PURE__*/_react.default.createElement(_new.default, {
|
|
304
356
|
type: "submit",
|
|
305
357
|
isLoading: previewState.isLoading,
|
|
306
|
-
isDisabled: inputUrl.length === 0,
|
|
358
|
+
isDisabled: inputUrl.length === 0 || hasUrlError,
|
|
307
359
|
iconBefore: _filePreview.default
|
|
308
360
|
}, strings.loadPreview)), /*#__PURE__*/_react.default.createElement(_primitives.Box, {
|
|
309
361
|
xcss: ButtonGroupStyles
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
1
2
|
import React from 'react';
|
|
2
3
|
import { useIntl } from 'react-intl-next';
|
|
4
|
+
import { isSafeUrl } from '@atlaskit/adf-schema';
|
|
3
5
|
import ButtonGroup from '@atlaskit/button/button-group';
|
|
4
6
|
import Button from '@atlaskit/button/new';
|
|
5
7
|
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
6
8
|
import { mediaInsertMessages } from '@atlaskit/editor-common/messages';
|
|
9
|
+
import { ErrorMessage } from '@atlaskit/form';
|
|
7
10
|
import EditorFilePreviewIcon from '@atlaskit/icon/glyph/editor/file-preview';
|
|
8
11
|
import { getMediaClient } from '@atlaskit/media-client-react';
|
|
9
12
|
import { Box, Flex, Inline, Stack, xcss } from '@atlaskit/primitives';
|
|
@@ -33,6 +36,19 @@ const INITIAL_PREVIEW_STATE = Object.freeze({
|
|
|
33
36
|
warning: null,
|
|
34
37
|
previewInfo: null
|
|
35
38
|
});
|
|
39
|
+
const MAX_URL_LENGTH = 2048;
|
|
40
|
+
export const isValidUrl = value => {
|
|
41
|
+
try {
|
|
42
|
+
// Check for spaces and length first to avoid the expensive URL parsing
|
|
43
|
+
if (/\s/.test(value) || value.length > MAX_URL_LENGTH) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
new URL(value);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return isSafeUrl(value);
|
|
51
|
+
};
|
|
36
52
|
const previewStateReducer = (state, action) => {
|
|
37
53
|
switch (action.type) {
|
|
38
54
|
case 'loading':
|
|
@@ -75,10 +91,13 @@ export function MediaFromURL({
|
|
|
75
91
|
pasteLinkToUpload: intl.formatMessage(mediaInsertMessages.pasteLinkToUpload),
|
|
76
92
|
cancel: intl.formatMessage(mediaInsertMessages.cancel),
|
|
77
93
|
errorMessage: intl.formatMessage(mediaInsertMessages.fromUrlErrorMessage),
|
|
78
|
-
warning: intl.formatMessage(mediaInsertMessages.fromUrlWarning)
|
|
94
|
+
warning: intl.formatMessage(mediaInsertMessages.fromUrlWarning),
|
|
95
|
+
invalidUrl: intl.formatMessage(mediaInsertMessages.invalidUrlErrorMessage)
|
|
79
96
|
};
|
|
80
97
|
const [inputUrl, setUrl] = React.useState('');
|
|
98
|
+
const [hasUrlError, setHasUrlError] = React.useState();
|
|
81
99
|
const [previewState, dispatch] = React.useReducer(previewStateReducer, INITIAL_PREVIEW_STATE);
|
|
100
|
+
const [isInputFocused, setInputFocused] = React.useState(false);
|
|
82
101
|
const pasteFlag = React.useRef(false);
|
|
83
102
|
const {
|
|
84
103
|
onUploadButtonClickedAnalytics,
|
|
@@ -146,12 +165,32 @@ export function MediaFromURL({
|
|
|
146
165
|
}
|
|
147
166
|
}
|
|
148
167
|
}, [onUploadButtonClickedAnalytics, mediaProvider, onUploadCommencedAnalytics, onUploadSuccessAnalytics, onUploadFailureAnalytics, inputUrl]);
|
|
168
|
+
const errorAttributes = {};
|
|
169
|
+
if (!isInputFocused) {
|
|
170
|
+
errorAttributes['aria-relevant'] = 'all';
|
|
171
|
+
errorAttributes['aria-atomic'] = 'false';
|
|
172
|
+
}
|
|
173
|
+
const onBlur = React.useCallback(() => {
|
|
174
|
+
if (!isValidUrl(inputUrl)) {
|
|
175
|
+
setHasUrlError(true);
|
|
176
|
+
}
|
|
177
|
+
setInputFocused(false);
|
|
178
|
+
}, [inputUrl]);
|
|
179
|
+
const onFocus = React.useCallback(() => {
|
|
180
|
+
setInputFocused(true);
|
|
181
|
+
}, []);
|
|
149
182
|
const onURLChange = React.useCallback(e => {
|
|
150
183
|
const url = e.target.value;
|
|
151
184
|
setUrl(url);
|
|
152
185
|
dispatch({
|
|
153
186
|
type: 'reset'
|
|
154
187
|
});
|
|
188
|
+
if (!isValidUrl(url)) {
|
|
189
|
+
setHasUrlError(true);
|
|
190
|
+
return;
|
|
191
|
+
} else {
|
|
192
|
+
setHasUrlError(false);
|
|
193
|
+
}
|
|
155
194
|
if (pasteFlag.current === true) {
|
|
156
195
|
pasteFlag.current = false;
|
|
157
196
|
uploadExternalMedia(url);
|
|
@@ -161,7 +200,7 @@ export function MediaFromURL({
|
|
|
161
200
|
// Note: this is a little weird, but the paste event will always be
|
|
162
201
|
// fired before the change event when pasting. We don't really want to
|
|
163
202
|
// duplicate logic by handling pastes separately to changes, so we're
|
|
164
|
-
// just noting paste
|
|
203
|
+
// just noting paste occurred to then be handled in the onURLChange fn
|
|
165
204
|
// above. The one exception to this is where paste inputs exactly what was
|
|
166
205
|
// already in the input, in which case we want to ignore it.
|
|
167
206
|
if (e.clipboardData.getData('text') !== inputUrl) {
|
|
@@ -231,7 +270,7 @@ export function MediaFromURL({
|
|
|
231
270
|
// This can be triggered from an enter key event on the input even when
|
|
232
271
|
// the button is disabled, so we explicitly do nothing when in loading
|
|
233
272
|
// state.
|
|
234
|
-
if (previewState.isLoading) {
|
|
273
|
+
if (previewState.isLoading || hasUrlError) {
|
|
235
274
|
return;
|
|
236
275
|
}
|
|
237
276
|
if (previewState.previewInfo) {
|
|
@@ -249,10 +288,16 @@ export function MediaFromURL({
|
|
|
249
288
|
}, /*#__PURE__*/React.createElement(TextField, {
|
|
250
289
|
value: inputUrl,
|
|
251
290
|
placeholder: strings.pasteLinkToUpload,
|
|
291
|
+
isInvalid: hasUrlError,
|
|
292
|
+
maxLength: MAX_URL_LENGTH,
|
|
252
293
|
onChange: onURLChange,
|
|
253
294
|
onKeyPress: onInputKeyPress,
|
|
254
|
-
onPaste: onPaste
|
|
255
|
-
|
|
295
|
+
onPaste: onPaste,
|
|
296
|
+
onBlur: onBlur,
|
|
297
|
+
onFocus: onFocus
|
|
298
|
+
}), hasUrlError && /*#__PURE__*/React.createElement(ErrorMessage, null, /*#__PURE__*/React.createElement(Box, _extends({
|
|
299
|
+
as: "span"
|
|
300
|
+
}, errorAttributes), strings.invalidUrl)), previewState.previewInfo && /*#__PURE__*/React.createElement(Inline, {
|
|
256
301
|
alignInline: "center",
|
|
257
302
|
alignBlock: "center",
|
|
258
303
|
xcss: PreviewImageStyles,
|
|
@@ -271,7 +316,7 @@ export function MediaFromURL({
|
|
|
271
316
|
}, /*#__PURE__*/React.createElement(Button, {
|
|
272
317
|
type: "submit",
|
|
273
318
|
isLoading: previewState.isLoading,
|
|
274
|
-
isDisabled: inputUrl.length === 0,
|
|
319
|
+
isDisabled: inputUrl.length === 0 || hasUrlError,
|
|
275
320
|
iconBefore: EditorFilePreviewIcon
|
|
276
321
|
}, strings.loadPreview)), /*#__PURE__*/React.createElement(Box, {
|
|
277
322
|
xcss: ButtonGroupStyles
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
1
2
|
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
3
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
3
4
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
@@ -6,10 +7,12 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
|
|
|
6
7
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
7
8
|
import React from 'react';
|
|
8
9
|
import { useIntl } from 'react-intl-next';
|
|
10
|
+
import { isSafeUrl } from '@atlaskit/adf-schema';
|
|
9
11
|
import ButtonGroup from '@atlaskit/button/button-group';
|
|
10
12
|
import Button from '@atlaskit/button/new';
|
|
11
13
|
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
12
14
|
import { mediaInsertMessages } from '@atlaskit/editor-common/messages';
|
|
15
|
+
import { ErrorMessage } from '@atlaskit/form';
|
|
13
16
|
import EditorFilePreviewIcon from '@atlaskit/icon/glyph/editor/file-preview';
|
|
14
17
|
import { getMediaClient } from '@atlaskit/media-client-react';
|
|
15
18
|
import { Box, Flex, Inline, Stack, xcss } from '@atlaskit/primitives';
|
|
@@ -39,6 +42,19 @@ var INITIAL_PREVIEW_STATE = Object.freeze({
|
|
|
39
42
|
warning: null,
|
|
40
43
|
previewInfo: null
|
|
41
44
|
});
|
|
45
|
+
var MAX_URL_LENGTH = 2048;
|
|
46
|
+
export var isValidUrl = function isValidUrl(value) {
|
|
47
|
+
try {
|
|
48
|
+
// Check for spaces and length first to avoid the expensive URL parsing
|
|
49
|
+
if (/\s/.test(value) || value.length > MAX_URL_LENGTH) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
new URL(value);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return isSafeUrl(value);
|
|
57
|
+
};
|
|
42
58
|
var previewStateReducer = function previewStateReducer(state, action) {
|
|
43
59
|
switch (action.type) {
|
|
44
60
|
case 'loading':
|
|
@@ -76,16 +92,25 @@ export function MediaFromURL(_ref) {
|
|
|
76
92
|
pasteLinkToUpload: intl.formatMessage(mediaInsertMessages.pasteLinkToUpload),
|
|
77
93
|
cancel: intl.formatMessage(mediaInsertMessages.cancel),
|
|
78
94
|
errorMessage: intl.formatMessage(mediaInsertMessages.fromUrlErrorMessage),
|
|
79
|
-
warning: intl.formatMessage(mediaInsertMessages.fromUrlWarning)
|
|
95
|
+
warning: intl.formatMessage(mediaInsertMessages.fromUrlWarning),
|
|
96
|
+
invalidUrl: intl.formatMessage(mediaInsertMessages.invalidUrlErrorMessage)
|
|
80
97
|
};
|
|
81
98
|
var _React$useState = React.useState(''),
|
|
82
99
|
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
83
100
|
inputUrl = _React$useState2[0],
|
|
84
101
|
setUrl = _React$useState2[1];
|
|
102
|
+
var _React$useState3 = React.useState(),
|
|
103
|
+
_React$useState4 = _slicedToArray(_React$useState3, 2),
|
|
104
|
+
hasUrlError = _React$useState4[0],
|
|
105
|
+
setHasUrlError = _React$useState4[1];
|
|
85
106
|
var _React$useReducer = React.useReducer(previewStateReducer, INITIAL_PREVIEW_STATE),
|
|
86
107
|
_React$useReducer2 = _slicedToArray(_React$useReducer, 2),
|
|
87
108
|
previewState = _React$useReducer2[0],
|
|
88
109
|
dispatch = _React$useReducer2[1];
|
|
110
|
+
var _React$useState5 = React.useState(false),
|
|
111
|
+
_React$useState6 = _slicedToArray(_React$useState5, 2),
|
|
112
|
+
isInputFocused = _React$useState6[0],
|
|
113
|
+
setInputFocused = _React$useState6[1];
|
|
89
114
|
var pasteFlag = React.useRef(false);
|
|
90
115
|
var _useAnalyticsEvents = useAnalyticsEvents(dispatchAnalyticsEvent),
|
|
91
116
|
onUploadButtonClickedAnalytics = _useAnalyticsEvents.onUploadButtonClickedAnalytics,
|
|
@@ -171,12 +196,32 @@ export function MediaFromURL(_ref) {
|
|
|
171
196
|
return _ref2.apply(this, arguments);
|
|
172
197
|
};
|
|
173
198
|
}(), [onUploadButtonClickedAnalytics, mediaProvider, onUploadCommencedAnalytics, onUploadSuccessAnalytics, onUploadFailureAnalytics, inputUrl]);
|
|
199
|
+
var errorAttributes = {};
|
|
200
|
+
if (!isInputFocused) {
|
|
201
|
+
errorAttributes['aria-relevant'] = 'all';
|
|
202
|
+
errorAttributes['aria-atomic'] = 'false';
|
|
203
|
+
}
|
|
204
|
+
var onBlur = React.useCallback(function () {
|
|
205
|
+
if (!isValidUrl(inputUrl)) {
|
|
206
|
+
setHasUrlError(true);
|
|
207
|
+
}
|
|
208
|
+
setInputFocused(false);
|
|
209
|
+
}, [inputUrl]);
|
|
210
|
+
var onFocus = React.useCallback(function () {
|
|
211
|
+
setInputFocused(true);
|
|
212
|
+
}, []);
|
|
174
213
|
var onURLChange = React.useCallback(function (e) {
|
|
175
214
|
var url = e.target.value;
|
|
176
215
|
setUrl(url);
|
|
177
216
|
dispatch({
|
|
178
217
|
type: 'reset'
|
|
179
218
|
});
|
|
219
|
+
if (!isValidUrl(url)) {
|
|
220
|
+
setHasUrlError(true);
|
|
221
|
+
return;
|
|
222
|
+
} else {
|
|
223
|
+
setHasUrlError(false);
|
|
224
|
+
}
|
|
180
225
|
if (pasteFlag.current === true) {
|
|
181
226
|
pasteFlag.current = false;
|
|
182
227
|
uploadExternalMedia(url);
|
|
@@ -186,7 +231,7 @@ export function MediaFromURL(_ref) {
|
|
|
186
231
|
// Note: this is a little weird, but the paste event will always be
|
|
187
232
|
// fired before the change event when pasting. We don't really want to
|
|
188
233
|
// duplicate logic by handling pastes separately to changes, so we're
|
|
189
|
-
// just noting paste
|
|
234
|
+
// just noting paste occurred to then be handled in the onURLChange fn
|
|
190
235
|
// above. The one exception to this is where paste inputs exactly what was
|
|
191
236
|
// already in the input, in which case we want to ignore it.
|
|
192
237
|
if (e.clipboardData.getData('text') !== inputUrl) {
|
|
@@ -256,7 +301,7 @@ export function MediaFromURL(_ref) {
|
|
|
256
301
|
// This can be triggered from an enter key event on the input even when
|
|
257
302
|
// the button is disabled, so we explicitly do nothing when in loading
|
|
258
303
|
// state.
|
|
259
|
-
if (previewState.isLoading) {
|
|
304
|
+
if (previewState.isLoading || hasUrlError) {
|
|
260
305
|
return;
|
|
261
306
|
}
|
|
262
307
|
if (previewState.previewInfo) {
|
|
@@ -274,10 +319,16 @@ export function MediaFromURL(_ref) {
|
|
|
274
319
|
}, /*#__PURE__*/React.createElement(TextField, {
|
|
275
320
|
value: inputUrl,
|
|
276
321
|
placeholder: strings.pasteLinkToUpload,
|
|
322
|
+
isInvalid: hasUrlError,
|
|
323
|
+
maxLength: MAX_URL_LENGTH,
|
|
277
324
|
onChange: onURLChange,
|
|
278
325
|
onKeyPress: onInputKeyPress,
|
|
279
|
-
onPaste: onPaste
|
|
280
|
-
|
|
326
|
+
onPaste: onPaste,
|
|
327
|
+
onBlur: onBlur,
|
|
328
|
+
onFocus: onFocus
|
|
329
|
+
}), hasUrlError && /*#__PURE__*/React.createElement(ErrorMessage, null, /*#__PURE__*/React.createElement(Box, _extends({
|
|
330
|
+
as: "span"
|
|
331
|
+
}, errorAttributes), strings.invalidUrl)), previewState.previewInfo && /*#__PURE__*/React.createElement(Inline, {
|
|
281
332
|
alignInline: "center",
|
|
282
333
|
alignBlock: "center",
|
|
283
334
|
xcss: PreviewImageStyles,
|
|
@@ -296,7 +347,7 @@ export function MediaFromURL(_ref) {
|
|
|
296
347
|
}, /*#__PURE__*/React.createElement(Button, {
|
|
297
348
|
type: "submit",
|
|
298
349
|
isLoading: previewState.isLoading,
|
|
299
|
-
isDisabled: inputUrl.length === 0,
|
|
350
|
+
isDisabled: inputUrl.length === 0 || hasUrlError,
|
|
300
351
|
iconBefore: EditorFilePreviewIcon
|
|
301
352
|
}, strings.loadPreview)), /*#__PURE__*/React.createElement(Box, {
|
|
302
353
|
xcss: ButtonGroupStyles
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { type DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
|
|
3
3
|
import type { MediaProvider } from '@atlaskit/editor-common/provider-factory';
|
|
4
4
|
import { type InsertExternalMediaSingle, type InsertMediaSingle } from '../types';
|
|
5
|
+
export declare const isValidUrl: (value: string) => boolean;
|
|
5
6
|
type Props = {
|
|
6
7
|
mediaProvider: MediaProvider;
|
|
7
8
|
dispatchAnalyticsEvent?: DispatchAnalyticsEvent;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { type DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
|
|
3
3
|
import type { MediaProvider } from '@atlaskit/editor-common/provider-factory';
|
|
4
4
|
import { type InsertExternalMediaSingle, type InsertMediaSingle } from '../types';
|
|
5
|
+
export declare const isValidUrl: (value: string) => boolean;
|
|
5
6
|
type Props = {
|
|
6
7
|
mediaProvider: MediaProvider;
|
|
7
8
|
dispatchAnalyticsEvent?: DispatchAnalyticsEvent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-media-insert",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "Media Insert plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -23,17 +23,19 @@
|
|
|
23
23
|
".": "./src/index.ts"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"@atlaskit/adf-schema": "^40.9.0",
|
|
26
27
|
"@atlaskit/button": "^20.1.0",
|
|
27
|
-
"@atlaskit/editor-common": "^
|
|
28
|
+
"@atlaskit/editor-common": "^90.0.0",
|
|
28
29
|
"@atlaskit/editor-plugin-analytics": "^1.8.0",
|
|
29
30
|
"@atlaskit/editor-plugin-media": "^1.31.0",
|
|
30
31
|
"@atlaskit/editor-prosemirror": "6.0.0",
|
|
31
32
|
"@atlaskit/editor-shared-styles": "^2.13.0",
|
|
33
|
+
"@atlaskit/form": "^10.5.3",
|
|
32
34
|
"@atlaskit/icon": "^22.18.0",
|
|
33
|
-
"@atlaskit/media-card": "^78.
|
|
35
|
+
"@atlaskit/media-card": "^78.5.0",
|
|
34
36
|
"@atlaskit/media-client": "^28.0.0",
|
|
35
37
|
"@atlaskit/media-client-react": "^2.2.0",
|
|
36
|
-
"@atlaskit/media-picker": "^66.
|
|
38
|
+
"@atlaskit/media-picker": "^66.7.0",
|
|
37
39
|
"@atlaskit/platform-feature-flags": "^0.3.0",
|
|
38
40
|
"@atlaskit/primitives": "^12.1.0",
|
|
39
41
|
"@atlaskit/section-message": "^6.6.0",
|