@atlaskit/editor-plugin-media-insert 2.3.2 → 2.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 CHANGED
@@ -1,5 +1,30 @@
1
1
  # @atlaskit/editor-plugin-media-insert
2
2
 
3
+ ## 2.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#138801](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/138801)
8
+ [`f742cd24b83a1`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/f742cd24b83a1) -
9
+ [ux] [ED-24877] Improve keyboard UX for "from link" media uploads
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+
15
+ ## 2.4.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [#136903](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/136903)
20
+ [`2a9928406d4d3`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/2a9928406d4d3) -
21
+ [ux] [ED-24756] **@atlaskit/editor-plugin-media-insert**: Added a hook to correctly focus on the
22
+ Upload button when user opens media insert (preventing scrolling to the top of the editor)
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies
27
+
3
28
  ## 2.3.2
4
29
 
5
30
  ### Patch Changes
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+
3
+ var _typeof = require("@babel/runtime/helpers/typeof");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.useUnholyAutofocus = void 0;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
10
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
11
+ /**
12
+ * Autofocuses the first interactive element in the first tab panel
13
+ * when the media picker is opened.
14
+ *
15
+ * This is to mitigate the issue where the PopupWithListeners component
16
+ * renders initially at the top of the editor and then repositioned.
17
+ *
18
+ * We want to autofocus after the repositioning to ensure we don't scroll
19
+ * to the top of the editor when the media picker is opened.
20
+ */
21
+ var useUnholyAutofocus = exports.useUnholyAutofocus = function useUnholyAutofocus() {
22
+ var autofocusRef = _react.default.useRef(null);
23
+ var positionRef = _react.default.useRef(null);
24
+ var onPositionCalculated = _react.default.useCallback(function (position) {
25
+ if (positionRef.current === null) {
26
+ // Initial position is _always incorrect, so the first time this is set
27
+ // we're going to ignore it.
28
+ positionRef.current = position;
29
+ } else if (positionRef.current !== position) {
30
+ var _autofocusRef$current;
31
+ // If it isn't the first position and it has changed, we're likely in
32
+ // the actual position we want. We'll call focus and update the position.
33
+ (_autofocusRef$current = autofocusRef.current) === null || _autofocusRef$current === void 0 || _autofocusRef$current.focus();
34
+ positionRef.current = position;
35
+ }
36
+
37
+ // Important to return this as the popup uses the returned position
38
+ return position;
39
+ }, [autofocusRef]);
40
+
41
+ /**
42
+ * If we don't clear the ref, then reopening the media picker will
43
+ * not correctly focus the button.
44
+ *
45
+ * WARNING: If the component re-renders the ref will be cleared and
46
+ * the button will be focused again.
47
+ *
48
+ * This is a trade-off, we prefer that the button is correctly focused
49
+ * if the media picker is re-opened, rather than re-focusing the button
50
+ * if the component re-renders/ the browser window is resized.
51
+ *
52
+ * This is a temporary solution until we can find a better way to do it.
53
+ */
54
+ (0, _react.useEffect)(function () {
55
+ return function () {
56
+ positionRef.current = null;
57
+ };
58
+ });
59
+ return {
60
+ autofocusRef: autofocusRef,
61
+ onPositionCalculated: onPositionCalculated
62
+ };
63
+ };
@@ -47,7 +47,7 @@ var uploadReducer = function uploadReducer(state, action) {
47
47
  var isImagePreview = function isImagePreview(preview) {
48
48
  return 'dimensions' in preview;
49
49
  };
50
- var LocalMedia = exports.LocalMedia = function LocalMedia(_ref) {
50
+ var LocalMedia = exports.LocalMedia = /*#__PURE__*/_react.default.forwardRef(function (_ref, ref) {
51
51
  var mediaProvider = _ref.mediaProvider,
52
52
  insertMediaSingle = _ref.insertMediaSingle,
53
53
  dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent,
@@ -106,6 +106,7 @@ var LocalMedia = exports.LocalMedia = function LocalMedia(_ref) {
106
106
  appearance: "error"
107
107
  }, uploadState.error === 'upload_fail' ? strings.networkError : strings.genericError), /*#__PURE__*/_react.default.createElement(_new.default, {
108
108
  iconBefore: _upload.default,
109
+ ref: ref,
109
110
  shouldFitContainer: true,
110
111
  isDisabled: !uploadMediaClientConfig || !uploadParams,
111
112
  onClick: function onClick() {
@@ -113,8 +114,7 @@ var LocalMedia = exports.LocalMedia = function LocalMedia(_ref) {
113
114
  dispatch({
114
115
  type: 'open'
115
116
  });
116
- },
117
- autoFocus: true
117
+ }
118
118
  }, strings.upload), uploadMediaClientConfig && uploadParams && /*#__PURE__*/_react.default.createElement(_mediaPicker.Browser, {
119
119
  isOpen: uploadState.isOpen,
120
120
  config: {
@@ -142,4 +142,4 @@ var LocalMedia = exports.LocalMedia = function LocalMedia(_ref) {
142
142
  });
143
143
  }
144
144
  }));
145
- };
145
+ });
@@ -260,14 +260,25 @@ function MediaFromURL(_ref) {
260
260
  as: "form",
261
261
  onSubmit: function onSubmit(e) {
262
262
  e.preventDefault();
263
- uploadExternalMedia(inputUrl);
263
+ // This can be triggered from an enter key event on the input even when
264
+ // the button is disabled, so we explicitly do nothing when in loading
265
+ // state.
266
+ if (previewState.isLoading) {
267
+ return;
268
+ }
269
+ if (previewState.previewInfo) {
270
+ return onInsert();
271
+ }
272
+ if (previewState.warning) {
273
+ return onExternalInsert(inputUrl);
274
+ }
275
+ return uploadExternalMedia(inputUrl);
264
276
  },
265
277
  xcss: FormStyles
266
278
  }, /*#__PURE__*/_react.default.createElement(_primitives.Stack, {
267
279
  space: "space.150",
268
280
  grow: "fill"
269
281
  }, /*#__PURE__*/_react.default.createElement(_textfield.default, {
270
- autoFocus: true,
271
282
  value: inputUrl,
272
283
  placeholder: strings.pasteLinkToUpload,
273
284
  onChange: onURLChange,
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
4
5
  Object.defineProperty(exports, "__esModule", {
5
6
  value: true
6
7
  });
@@ -13,9 +14,15 @@ var _messages = require("@atlaskit/editor-common/messages");
13
14
  var _ui = require("@atlaskit/editor-common/ui");
14
15
  var _utils = require("@atlaskit/editor-prosemirror/utils");
15
16
  var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
17
+ var _primitives = require("@atlaskit/primitives");
18
+ var _tabs = _interopRequireWildcard(require("@atlaskit/tabs"));
16
19
  var _useFocusEditor = require("../hooks/use-focus-editor");
17
- var _MediaInsertContent = require("./MediaInsertContent");
20
+ var _useUnholyAutofocus2 = require("../hooks/use-unholy-autofocus");
21
+ var _LocalMedia = require("./LocalMedia");
22
+ var _MediaFromURL = require("./MediaFromURL");
18
23
  var _MediaInsertWrapper = require("./MediaInsertWrapper");
24
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
25
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
19
26
  var PopupWithListeners = (0, _ui.withOuterListeners)(_ui.Popup);
20
27
  var getDomRefFromSelection = function getDomRefFromSelection(view, dispatchAnalyticsEvent) {
21
28
  try {
@@ -58,6 +65,9 @@ var MediaInsertPicker = exports.MediaInsertPicker = function MediaInsertPicker(_
58
65
  var focusEditor = (0, _useFocusEditor.useFocusEditor)({
59
66
  editorView: editorView
60
67
  });
68
+ var _useUnholyAutofocus = (0, _useUnholyAutofocus2.useUnholyAutofocus)(),
69
+ autofocusRef = _useUnholyAutofocus.autofocusRef,
70
+ onPositionCalculated = _useUnholyAutofocus.onPositionCalculated;
61
71
  if (!isOpen || !mediaProvider) {
62
72
  return null;
63
73
  }
@@ -93,8 +103,22 @@ var MediaInsertPicker = exports.MediaInsertPicker = function MediaInsertPicker(_
93
103
  handleEscapeKeydown: handleClose(_analytics.INPUT_METHOD.KEYBOARD),
94
104
  scrollableElement: popupsScrollableElement,
95
105
  preventOverflow: true,
106
+ onPositionCalculated: onPositionCalculated,
96
107
  focusTrap: true
97
- }, /*#__PURE__*/_react.default.createElement(_MediaInsertWrapper.MediaInsertWrapper, null, /*#__PURE__*/_react.default.createElement(_MediaInsertContent.MediaInsertContent, {
108
+ }, /*#__PURE__*/_react.default.createElement(_MediaInsertWrapper.MediaInsertWrapper, null, /*#__PURE__*/_react.default.createElement(_tabs.default, {
109
+ id: "media-insert-tab-navigation"
110
+ }, /*#__PURE__*/_react.default.createElement(_primitives.Box, {
111
+ paddingBlockEnd: "space.150"
112
+ }, /*#__PURE__*/_react.default.createElement(_tabs.TabList, null, /*#__PURE__*/_react.default.createElement(_tabs.Tab, null, intl.formatMessage(_messages.mediaInsertMessages.fileTabTitle)), /*#__PURE__*/_react.default.createElement(_tabs.Tab, null, intl.formatMessage(_messages.mediaInsertMessages.linkTabTitle)))), /*#__PURE__*/_react.default.createElement(_tabs.TabPanel, null, /*#__PURE__*/_react.default.createElement(_LocalMedia.LocalMedia, {
113
+ ref: autofocusRef,
114
+ mediaProvider: mediaProvider,
115
+ insertMediaSingle: insertMediaSingle,
116
+ closeMediaInsertPicker: function closeMediaInsertPicker() {
117
+ _closeMediaInsertPicker();
118
+ focusEditor();
119
+ },
120
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent
121
+ })), /*#__PURE__*/_react.default.createElement(_tabs.TabPanel, null, /*#__PURE__*/_react.default.createElement(_MediaFromURL.MediaFromURL, {
98
122
  mediaProvider: mediaProvider,
99
123
  dispatchAnalyticsEvent: dispatchAnalyticsEvent,
100
124
  closeMediaInsertPicker: function closeMediaInsertPicker() {
@@ -103,5 +127,5 @@ var MediaInsertPicker = exports.MediaInsertPicker = function MediaInsertPicker(_
103
127
  },
104
128
  insertMediaSingle: insertMediaSingle,
105
129
  insertExternalMediaSingle: insertExternalMediaSingle
106
- })));
130
+ })))));
107
131
  };
@@ -0,0 +1,54 @@
1
+ import React, { useEffect } from 'react';
2
+ /**
3
+ * Autofocuses the first interactive element in the first tab panel
4
+ * when the media picker is opened.
5
+ *
6
+ * This is to mitigate the issue where the PopupWithListeners component
7
+ * renders initially at the top of the editor and then repositioned.
8
+ *
9
+ * We want to autofocus after the repositioning to ensure we don't scroll
10
+ * to the top of the editor when the media picker is opened.
11
+ */
12
+ export const useUnholyAutofocus = () => {
13
+ const autofocusRef = React.useRef(null);
14
+ const positionRef = React.useRef(null);
15
+ const onPositionCalculated = React.useCallback(position => {
16
+ if (positionRef.current === null) {
17
+ // Initial position is _always incorrect, so the first time this is set
18
+ // we're going to ignore it.
19
+ positionRef.current = position;
20
+ } else if (positionRef.current !== position) {
21
+ var _autofocusRef$current;
22
+ // If it isn't the first position and it has changed, we're likely in
23
+ // the actual position we want. We'll call focus and update the position.
24
+ (_autofocusRef$current = autofocusRef.current) === null || _autofocusRef$current === void 0 ? void 0 : _autofocusRef$current.focus();
25
+ positionRef.current = position;
26
+ }
27
+
28
+ // Important to return this as the popup uses the returned position
29
+ return position;
30
+ }, [autofocusRef]);
31
+
32
+ /**
33
+ * If we don't clear the ref, then reopening the media picker will
34
+ * not correctly focus the button.
35
+ *
36
+ * WARNING: If the component re-renders the ref will be cleared and
37
+ * the button will be focused again.
38
+ *
39
+ * This is a trade-off, we prefer that the button is correctly focused
40
+ * if the media picker is re-opened, rather than re-focusing the button
41
+ * if the component re-renders/ the browser window is resized.
42
+ *
43
+ * This is a temporary solution until we can find a better way to do it.
44
+ */
45
+ useEffect(() => {
46
+ return () => {
47
+ positionRef.current = null;
48
+ };
49
+ });
50
+ return {
51
+ autofocusRef,
52
+ onPositionCalculated
53
+ };
54
+ };
@@ -39,12 +39,12 @@ const uploadReducer = (state, action) => {
39
39
  const isImagePreview = preview => {
40
40
  return 'dimensions' in preview;
41
41
  };
42
- export const LocalMedia = ({
42
+ export const LocalMedia = /*#__PURE__*/React.forwardRef(({
43
43
  mediaProvider,
44
44
  insertMediaSingle,
45
45
  dispatchAnalyticsEvent,
46
46
  closeMediaInsertPicker
47
- }) => {
47
+ }, ref) => {
48
48
  const intl = useIntl();
49
49
  const strings = {
50
50
  upload: intl.formatMessage(mediaInsertMessages.upload),
@@ -100,6 +100,7 @@ export const LocalMedia = ({
100
100
  appearance: "error"
101
101
  }, uploadState.error === 'upload_fail' ? strings.networkError : strings.genericError), /*#__PURE__*/React.createElement(Button, {
102
102
  iconBefore: UploadIcon,
103
+ ref: ref,
103
104
  shouldFitContainer: true,
104
105
  isDisabled: !uploadMediaClientConfig || !uploadParams,
105
106
  onClick: () => {
@@ -107,8 +108,7 @@ export const LocalMedia = ({
107
108
  dispatch({
108
109
  type: 'open'
109
110
  });
110
- },
111
- autoFocus: true
111
+ }
112
112
  }, strings.upload), uploadMediaClientConfig && uploadParams && /*#__PURE__*/React.createElement(Browser, {
113
113
  isOpen: uploadState.isOpen,
114
114
  config: {
@@ -136,4 +136,4 @@ export const LocalMedia = ({
136
136
  });
137
137
  }
138
138
  }));
139
- };
139
+ });
@@ -228,14 +228,25 @@ export function MediaFromURL({
228
228
  as: "form",
229
229
  onSubmit: e => {
230
230
  e.preventDefault();
231
- uploadExternalMedia(inputUrl);
231
+ // This can be triggered from an enter key event on the input even when
232
+ // the button is disabled, so we explicitly do nothing when in loading
233
+ // state.
234
+ if (previewState.isLoading) {
235
+ return;
236
+ }
237
+ if (previewState.previewInfo) {
238
+ return onInsert();
239
+ }
240
+ if (previewState.warning) {
241
+ return onExternalInsert(inputUrl);
242
+ }
243
+ return uploadExternalMedia(inputUrl);
232
244
  },
233
245
  xcss: FormStyles
234
246
  }, /*#__PURE__*/React.createElement(Stack, {
235
247
  space: "space.150",
236
248
  grow: "fill"
237
249
  }, /*#__PURE__*/React.createElement(TextField, {
238
- autoFocus: true,
239
250
  value: inputUrl,
240
251
  placeholder: strings.pasteLinkToUpload,
241
252
  onChange: onURLChange,
@@ -6,8 +6,12 @@ import { mediaInsertMessages } from '@atlaskit/editor-common/messages';
6
6
  import { Popup, withOuterListeners } from '@atlaskit/editor-common/ui';
7
7
  import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
8
8
  import { akEditorFloatingDialogZIndex } from '@atlaskit/editor-shared-styles';
9
+ import { Box } from '@atlaskit/primitives';
10
+ import Tabs, { Tab, TabList, TabPanel } from '@atlaskit/tabs';
9
11
  import { useFocusEditor } from '../hooks/use-focus-editor';
10
- import { MediaInsertContent } from './MediaInsertContent';
12
+ import { useUnholyAutofocus } from '../hooks/use-unholy-autofocus';
13
+ import { LocalMedia } from './LocalMedia';
14
+ import { MediaFromURL } from './MediaFromURL';
11
15
  import { MediaInsertWrapper } from './MediaInsertWrapper';
12
16
  const PopupWithListeners = withOuterListeners(Popup);
13
17
  const getDomRefFromSelection = (view, dispatchAnalyticsEvent) => {
@@ -52,6 +56,10 @@ export const MediaInsertPicker = ({
52
56
  const focusEditor = useFocusEditor({
53
57
  editorView
54
58
  });
59
+ const {
60
+ autofocusRef,
61
+ onPositionCalculated
62
+ } = useUnholyAutofocus();
55
63
  if (!isOpen || !mediaProvider) {
56
64
  return null;
57
65
  }
@@ -85,8 +93,22 @@ export const MediaInsertPicker = ({
85
93
  handleEscapeKeydown: handleClose(INPUT_METHOD.KEYBOARD),
86
94
  scrollableElement: popupsScrollableElement,
87
95
  preventOverflow: true,
96
+ onPositionCalculated: onPositionCalculated,
88
97
  focusTrap: true
89
- }, /*#__PURE__*/React.createElement(MediaInsertWrapper, null, /*#__PURE__*/React.createElement(MediaInsertContent, {
98
+ }, /*#__PURE__*/React.createElement(MediaInsertWrapper, null, /*#__PURE__*/React.createElement(Tabs, {
99
+ id: "media-insert-tab-navigation"
100
+ }, /*#__PURE__*/React.createElement(Box, {
101
+ paddingBlockEnd: "space.150"
102
+ }, /*#__PURE__*/React.createElement(TabList, null, /*#__PURE__*/React.createElement(Tab, null, intl.formatMessage(mediaInsertMessages.fileTabTitle)), /*#__PURE__*/React.createElement(Tab, null, intl.formatMessage(mediaInsertMessages.linkTabTitle)))), /*#__PURE__*/React.createElement(TabPanel, null, /*#__PURE__*/React.createElement(LocalMedia, {
103
+ ref: autofocusRef,
104
+ mediaProvider: mediaProvider,
105
+ insertMediaSingle: insertMediaSingle,
106
+ closeMediaInsertPicker: () => {
107
+ closeMediaInsertPicker();
108
+ focusEditor();
109
+ },
110
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent
111
+ })), /*#__PURE__*/React.createElement(TabPanel, null, /*#__PURE__*/React.createElement(MediaFromURL, {
90
112
  mediaProvider: mediaProvider,
91
113
  dispatchAnalyticsEvent: dispatchAnalyticsEvent,
92
114
  closeMediaInsertPicker: () => {
@@ -95,5 +117,5 @@ export const MediaInsertPicker = ({
95
117
  },
96
118
  insertMediaSingle: insertMediaSingle,
97
119
  insertExternalMediaSingle: insertExternalMediaSingle
98
- })));
120
+ })))));
99
121
  };
@@ -0,0 +1,54 @@
1
+ import React, { useEffect } from 'react';
2
+ /**
3
+ * Autofocuses the first interactive element in the first tab panel
4
+ * when the media picker is opened.
5
+ *
6
+ * This is to mitigate the issue where the PopupWithListeners component
7
+ * renders initially at the top of the editor and then repositioned.
8
+ *
9
+ * We want to autofocus after the repositioning to ensure we don't scroll
10
+ * to the top of the editor when the media picker is opened.
11
+ */
12
+ export var useUnholyAutofocus = function useUnholyAutofocus() {
13
+ var autofocusRef = React.useRef(null);
14
+ var positionRef = React.useRef(null);
15
+ var onPositionCalculated = React.useCallback(function (position) {
16
+ if (positionRef.current === null) {
17
+ // Initial position is _always incorrect, so the first time this is set
18
+ // we're going to ignore it.
19
+ positionRef.current = position;
20
+ } else if (positionRef.current !== position) {
21
+ var _autofocusRef$current;
22
+ // If it isn't the first position and it has changed, we're likely in
23
+ // the actual position we want. We'll call focus and update the position.
24
+ (_autofocusRef$current = autofocusRef.current) === null || _autofocusRef$current === void 0 || _autofocusRef$current.focus();
25
+ positionRef.current = position;
26
+ }
27
+
28
+ // Important to return this as the popup uses the returned position
29
+ return position;
30
+ }, [autofocusRef]);
31
+
32
+ /**
33
+ * If we don't clear the ref, then reopening the media picker will
34
+ * not correctly focus the button.
35
+ *
36
+ * WARNING: If the component re-renders the ref will be cleared and
37
+ * the button will be focused again.
38
+ *
39
+ * This is a trade-off, we prefer that the button is correctly focused
40
+ * if the media picker is re-opened, rather than re-focusing the button
41
+ * if the component re-renders/ the browser window is resized.
42
+ *
43
+ * This is a temporary solution until we can find a better way to do it.
44
+ */
45
+ useEffect(function () {
46
+ return function () {
47
+ positionRef.current = null;
48
+ };
49
+ });
50
+ return {
51
+ autofocusRef: autofocusRef,
52
+ onPositionCalculated: onPositionCalculated
53
+ };
54
+ };
@@ -40,7 +40,7 @@ var uploadReducer = function uploadReducer(state, action) {
40
40
  var isImagePreview = function isImagePreview(preview) {
41
41
  return 'dimensions' in preview;
42
42
  };
43
- export var LocalMedia = function LocalMedia(_ref) {
43
+ export var LocalMedia = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
44
44
  var mediaProvider = _ref.mediaProvider,
45
45
  insertMediaSingle = _ref.insertMediaSingle,
46
46
  dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent,
@@ -99,6 +99,7 @@ export var LocalMedia = function LocalMedia(_ref) {
99
99
  appearance: "error"
100
100
  }, uploadState.error === 'upload_fail' ? strings.networkError : strings.genericError), /*#__PURE__*/React.createElement(Button, {
101
101
  iconBefore: UploadIcon,
102
+ ref: ref,
102
103
  shouldFitContainer: true,
103
104
  isDisabled: !uploadMediaClientConfig || !uploadParams,
104
105
  onClick: function onClick() {
@@ -106,8 +107,7 @@ export var LocalMedia = function LocalMedia(_ref) {
106
107
  dispatch({
107
108
  type: 'open'
108
109
  });
109
- },
110
- autoFocus: true
110
+ }
111
111
  }, strings.upload), uploadMediaClientConfig && uploadParams && /*#__PURE__*/React.createElement(Browser, {
112
112
  isOpen: uploadState.isOpen,
113
113
  config: {
@@ -135,4 +135,4 @@ export var LocalMedia = function LocalMedia(_ref) {
135
135
  });
136
136
  }
137
137
  }));
138
- };
138
+ });
@@ -253,14 +253,25 @@ export function MediaFromURL(_ref) {
253
253
  as: "form",
254
254
  onSubmit: function onSubmit(e) {
255
255
  e.preventDefault();
256
- uploadExternalMedia(inputUrl);
256
+ // This can be triggered from an enter key event on the input even when
257
+ // the button is disabled, so we explicitly do nothing when in loading
258
+ // state.
259
+ if (previewState.isLoading) {
260
+ return;
261
+ }
262
+ if (previewState.previewInfo) {
263
+ return onInsert();
264
+ }
265
+ if (previewState.warning) {
266
+ return onExternalInsert(inputUrl);
267
+ }
268
+ return uploadExternalMedia(inputUrl);
257
269
  },
258
270
  xcss: FormStyles
259
271
  }, /*#__PURE__*/React.createElement(Stack, {
260
272
  space: "space.150",
261
273
  grow: "fill"
262
274
  }, /*#__PURE__*/React.createElement(TextField, {
263
- autoFocus: true,
264
275
  value: inputUrl,
265
276
  placeholder: strings.pasteLinkToUpload,
266
277
  onChange: onURLChange,
@@ -6,8 +6,12 @@ import { mediaInsertMessages } from '@atlaskit/editor-common/messages';
6
6
  import { Popup, withOuterListeners } from '@atlaskit/editor-common/ui';
7
7
  import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
8
8
  import { akEditorFloatingDialogZIndex } from '@atlaskit/editor-shared-styles';
9
+ import { Box } from '@atlaskit/primitives';
10
+ import Tabs, { Tab, TabList, TabPanel } from '@atlaskit/tabs';
9
11
  import { useFocusEditor } from '../hooks/use-focus-editor';
10
- import { MediaInsertContent } from './MediaInsertContent';
12
+ import { useUnholyAutofocus } from '../hooks/use-unholy-autofocus';
13
+ import { LocalMedia } from './LocalMedia';
14
+ import { MediaFromURL } from './MediaFromURL';
11
15
  import { MediaInsertWrapper } from './MediaInsertWrapper';
12
16
  var PopupWithListeners = withOuterListeners(Popup);
13
17
  var getDomRefFromSelection = function getDomRefFromSelection(view, dispatchAnalyticsEvent) {
@@ -51,6 +55,9 @@ export var MediaInsertPicker = function MediaInsertPicker(_ref) {
51
55
  var focusEditor = useFocusEditor({
52
56
  editorView: editorView
53
57
  });
58
+ var _useUnholyAutofocus = useUnholyAutofocus(),
59
+ autofocusRef = _useUnholyAutofocus.autofocusRef,
60
+ onPositionCalculated = _useUnholyAutofocus.onPositionCalculated;
54
61
  if (!isOpen || !mediaProvider) {
55
62
  return null;
56
63
  }
@@ -86,8 +93,22 @@ export var MediaInsertPicker = function MediaInsertPicker(_ref) {
86
93
  handleEscapeKeydown: handleClose(INPUT_METHOD.KEYBOARD),
87
94
  scrollableElement: popupsScrollableElement,
88
95
  preventOverflow: true,
96
+ onPositionCalculated: onPositionCalculated,
89
97
  focusTrap: true
90
- }, /*#__PURE__*/React.createElement(MediaInsertWrapper, null, /*#__PURE__*/React.createElement(MediaInsertContent, {
98
+ }, /*#__PURE__*/React.createElement(MediaInsertWrapper, null, /*#__PURE__*/React.createElement(Tabs, {
99
+ id: "media-insert-tab-navigation"
100
+ }, /*#__PURE__*/React.createElement(Box, {
101
+ paddingBlockEnd: "space.150"
102
+ }, /*#__PURE__*/React.createElement(TabList, null, /*#__PURE__*/React.createElement(Tab, null, intl.formatMessage(mediaInsertMessages.fileTabTitle)), /*#__PURE__*/React.createElement(Tab, null, intl.formatMessage(mediaInsertMessages.linkTabTitle)))), /*#__PURE__*/React.createElement(TabPanel, null, /*#__PURE__*/React.createElement(LocalMedia, {
103
+ ref: autofocusRef,
104
+ mediaProvider: mediaProvider,
105
+ insertMediaSingle: insertMediaSingle,
106
+ closeMediaInsertPicker: function closeMediaInsertPicker() {
107
+ _closeMediaInsertPicker();
108
+ focusEditor();
109
+ },
110
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent
111
+ })), /*#__PURE__*/React.createElement(TabPanel, null, /*#__PURE__*/React.createElement(MediaFromURL, {
91
112
  mediaProvider: mediaProvider,
92
113
  dispatchAnalyticsEvent: dispatchAnalyticsEvent,
93
114
  closeMediaInsertPicker: function closeMediaInsertPicker() {
@@ -96,5 +117,5 @@ export var MediaInsertPicker = function MediaInsertPicker(_ref) {
96
117
  },
97
118
  insertMediaSingle: insertMediaSingle,
98
119
  insertExternalMediaSingle: insertExternalMediaSingle
99
- })));
120
+ })))));
100
121
  };
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import type { Position as PopupPosition } from '@atlaskit/editor-common/src/ui/Popup/utils';
3
+ /**
4
+ * Autofocuses the first interactive element in the first tab panel
5
+ * when the media picker is opened.
6
+ *
7
+ * This is to mitigate the issue where the PopupWithListeners component
8
+ * renders initially at the top of the editor and then repositioned.
9
+ *
10
+ * We want to autofocus after the repositioning to ensure we don't scroll
11
+ * to the top of the editor when the media picker is opened.
12
+ */
13
+ export declare const useUnholyAutofocus: () => {
14
+ autofocusRef: React.RefObject<HTMLButtonElement>;
15
+ onPositionCalculated: (position: PopupPosition) => PopupPosition;
16
+ };
@@ -1,4 +1,4 @@
1
- /// <reference types="react" />
1
+ import React from 'react';
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 InsertMediaSingle } from '../types';
@@ -8,5 +8,5 @@ type Props = {
8
8
  closeMediaInsertPicker: () => void;
9
9
  dispatchAnalyticsEvent?: DispatchAnalyticsEvent;
10
10
  };
11
- export declare const LocalMedia: ({ mediaProvider, insertMediaSingle, dispatchAnalyticsEvent, closeMediaInsertPicker, }: Props) => JSX.Element;
11
+ export declare const LocalMedia: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLButtonElement>>;
12
12
  export {};
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import type { Position as PopupPosition } from '@atlaskit/editor-common/src/ui/Popup/utils';
3
+ /**
4
+ * Autofocuses the first interactive element in the first tab panel
5
+ * when the media picker is opened.
6
+ *
7
+ * This is to mitigate the issue where the PopupWithListeners component
8
+ * renders initially at the top of the editor and then repositioned.
9
+ *
10
+ * We want to autofocus after the repositioning to ensure we don't scroll
11
+ * to the top of the editor when the media picker is opened.
12
+ */
13
+ export declare const useUnholyAutofocus: () => {
14
+ autofocusRef: React.RefObject<HTMLButtonElement>;
15
+ onPositionCalculated: (position: PopupPosition) => PopupPosition;
16
+ };
@@ -1,4 +1,4 @@
1
- /// <reference types="react" />
1
+ import React from 'react';
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 InsertMediaSingle } from '../types';
@@ -8,5 +8,5 @@ type Props = {
8
8
  closeMediaInsertPicker: () => void;
9
9
  dispatchAnalyticsEvent?: DispatchAnalyticsEvent;
10
10
  };
11
- export declare const LocalMedia: ({ mediaProvider, insertMediaSingle, dispatchAnalyticsEvent, closeMediaInsertPicker, }: Props) => JSX.Element;
11
+ export declare const LocalMedia: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLButtonElement>>;
12
12
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-media-insert",
3
- "version": "2.3.2",
3
+ "version": "2.5.0",
4
4
  "description": "Media Insert plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -24,9 +24,9 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@atlaskit/button": "^20.1.0",
27
- "@atlaskit/editor-common": "^88.11.0",
27
+ "@atlaskit/editor-common": "^88.13.0",
28
28
  "@atlaskit/editor-plugin-analytics": "^1.8.0",
29
- "@atlaskit/editor-plugin-media": "^1.29.0",
29
+ "@atlaskit/editor-plugin-media": "^1.31.0",
30
30
  "@atlaskit/editor-prosemirror": "5.0.1",
31
31
  "@atlaskit/editor-shared-styles": "^2.13.0",
32
32
  "@atlaskit/icon": "^22.16.0",
@@ -1,41 +0,0 @@
1
- "use strict";
2
-
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
- var _typeof = require("@babel/runtime/helpers/typeof");
5
- Object.defineProperty(exports, "__esModule", {
6
- value: true
7
- });
8
- exports.MediaInsertContent = void 0;
9
- var _react = _interopRequireDefault(require("react"));
10
- var _reactIntlNext = require("react-intl-next");
11
- var _messages = require("@atlaskit/editor-common/messages");
12
- var _primitives = require("@atlaskit/primitives");
13
- var _tabs = _interopRequireWildcard(require("@atlaskit/tabs"));
14
- var _LocalMedia = require("./LocalMedia");
15
- var _MediaFromURL = require("./MediaFromURL");
16
- function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
17
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
18
- var MediaInsertContent = exports.MediaInsertContent = function MediaInsertContent(_ref) {
19
- var mediaProvider = _ref.mediaProvider,
20
- dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent,
21
- closeMediaInsertPicker = _ref.closeMediaInsertPicker,
22
- insertMediaSingle = _ref.insertMediaSingle,
23
- insertExternalMediaSingle = _ref.insertExternalMediaSingle;
24
- var intl = (0, _reactIntlNext.useIntl)();
25
- return /*#__PURE__*/_react.default.createElement(_tabs.default, {
26
- id: "media-insert-tab-navigation"
27
- }, /*#__PURE__*/_react.default.createElement(_primitives.Box, {
28
- paddingBlockEnd: "space.150"
29
- }, /*#__PURE__*/_react.default.createElement(_tabs.TabList, null, /*#__PURE__*/_react.default.createElement(_tabs.Tab, null, intl.formatMessage(_messages.mediaInsertMessages.fileTabTitle)), /*#__PURE__*/_react.default.createElement(_tabs.Tab, null, intl.formatMessage(_messages.mediaInsertMessages.linkTabTitle)))), /*#__PURE__*/_react.default.createElement(_tabs.TabPanel, null, /*#__PURE__*/_react.default.createElement(_LocalMedia.LocalMedia, {
30
- mediaProvider: mediaProvider,
31
- dispatchAnalyticsEvent: dispatchAnalyticsEvent,
32
- closeMediaInsertPicker: closeMediaInsertPicker,
33
- insertMediaSingle: insertMediaSingle
34
- })), /*#__PURE__*/_react.default.createElement(_tabs.TabPanel, null, /*#__PURE__*/_react.default.createElement(_MediaFromURL.MediaFromURL, {
35
- mediaProvider: mediaProvider,
36
- dispatchAnalyticsEvent: dispatchAnalyticsEvent,
37
- closeMediaInsertPicker: closeMediaInsertPicker,
38
- insertMediaSingle: insertMediaSingle,
39
- insertExternalMediaSingle: insertExternalMediaSingle
40
- })));
41
- };
@@ -1,32 +0,0 @@
1
- import React from 'react';
2
- import { useIntl } from 'react-intl-next';
3
- import { mediaInsertMessages } from '@atlaskit/editor-common/messages';
4
- import { Box } from '@atlaskit/primitives';
5
- import Tabs, { Tab, TabList, TabPanel } from '@atlaskit/tabs';
6
- import { LocalMedia } from './LocalMedia';
7
- import { MediaFromURL } from './MediaFromURL';
8
- export const MediaInsertContent = ({
9
- mediaProvider,
10
- dispatchAnalyticsEvent,
11
- closeMediaInsertPicker,
12
- insertMediaSingle,
13
- insertExternalMediaSingle
14
- }) => {
15
- const intl = useIntl();
16
- return /*#__PURE__*/React.createElement(Tabs, {
17
- id: "media-insert-tab-navigation"
18
- }, /*#__PURE__*/React.createElement(Box, {
19
- paddingBlockEnd: "space.150"
20
- }, /*#__PURE__*/React.createElement(TabList, null, /*#__PURE__*/React.createElement(Tab, null, intl.formatMessage(mediaInsertMessages.fileTabTitle)), /*#__PURE__*/React.createElement(Tab, null, intl.formatMessage(mediaInsertMessages.linkTabTitle)))), /*#__PURE__*/React.createElement(TabPanel, null, /*#__PURE__*/React.createElement(LocalMedia, {
21
- mediaProvider: mediaProvider,
22
- dispatchAnalyticsEvent: dispatchAnalyticsEvent,
23
- closeMediaInsertPicker: closeMediaInsertPicker,
24
- insertMediaSingle: insertMediaSingle
25
- })), /*#__PURE__*/React.createElement(TabPanel, null, /*#__PURE__*/React.createElement(MediaFromURL, {
26
- mediaProvider: mediaProvider,
27
- dispatchAnalyticsEvent: dispatchAnalyticsEvent,
28
- closeMediaInsertPicker: closeMediaInsertPicker,
29
- insertMediaSingle: insertMediaSingle,
30
- insertExternalMediaSingle: insertExternalMediaSingle
31
- })));
32
- };
@@ -1,31 +0,0 @@
1
- import React from 'react';
2
- import { useIntl } from 'react-intl-next';
3
- import { mediaInsertMessages } from '@atlaskit/editor-common/messages';
4
- import { Box } from '@atlaskit/primitives';
5
- import Tabs, { Tab, TabList, TabPanel } from '@atlaskit/tabs';
6
- import { LocalMedia } from './LocalMedia';
7
- import { MediaFromURL } from './MediaFromURL';
8
- export var MediaInsertContent = function MediaInsertContent(_ref) {
9
- var mediaProvider = _ref.mediaProvider,
10
- dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent,
11
- closeMediaInsertPicker = _ref.closeMediaInsertPicker,
12
- insertMediaSingle = _ref.insertMediaSingle,
13
- insertExternalMediaSingle = _ref.insertExternalMediaSingle;
14
- var intl = useIntl();
15
- return /*#__PURE__*/React.createElement(Tabs, {
16
- id: "media-insert-tab-navigation"
17
- }, /*#__PURE__*/React.createElement(Box, {
18
- paddingBlockEnd: "space.150"
19
- }, /*#__PURE__*/React.createElement(TabList, null, /*#__PURE__*/React.createElement(Tab, null, intl.formatMessage(mediaInsertMessages.fileTabTitle)), /*#__PURE__*/React.createElement(Tab, null, intl.formatMessage(mediaInsertMessages.linkTabTitle)))), /*#__PURE__*/React.createElement(TabPanel, null, /*#__PURE__*/React.createElement(LocalMedia, {
20
- mediaProvider: mediaProvider,
21
- dispatchAnalyticsEvent: dispatchAnalyticsEvent,
22
- closeMediaInsertPicker: closeMediaInsertPicker,
23
- insertMediaSingle: insertMediaSingle
24
- })), /*#__PURE__*/React.createElement(TabPanel, null, /*#__PURE__*/React.createElement(MediaFromURL, {
25
- mediaProvider: mediaProvider,
26
- dispatchAnalyticsEvent: dispatchAnalyticsEvent,
27
- closeMediaInsertPicker: closeMediaInsertPicker,
28
- insertMediaSingle: insertMediaSingle,
29
- insertExternalMediaSingle: insertExternalMediaSingle
30
- })));
31
- };
@@ -1,13 +0,0 @@
1
- /// <reference types="react" />
2
- import type { DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
3
- import type { MediaProvider } from '@atlaskit/editor-common/provider-factory';
4
- import { type InsertExternalMediaSingle, type InsertMediaSingle } from '../types';
5
- type Props = {
6
- mediaProvider: MediaProvider;
7
- dispatchAnalyticsEvent?: DispatchAnalyticsEvent;
8
- closeMediaInsertPicker: () => void;
9
- insertMediaSingle: InsertMediaSingle;
10
- insertExternalMediaSingle: InsertExternalMediaSingle;
11
- };
12
- export declare const MediaInsertContent: ({ mediaProvider, dispatchAnalyticsEvent, closeMediaInsertPicker, insertMediaSingle, insertExternalMediaSingle, }: Props) => JSX.Element;
13
- export {};
@@ -1,13 +0,0 @@
1
- /// <reference types="react" />
2
- import type { DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
3
- import type { MediaProvider } from '@atlaskit/editor-common/provider-factory';
4
- import { type InsertExternalMediaSingle, type InsertMediaSingle } from '../types';
5
- type Props = {
6
- mediaProvider: MediaProvider;
7
- dispatchAnalyticsEvent?: DispatchAnalyticsEvent;
8
- closeMediaInsertPicker: () => void;
9
- insertMediaSingle: InsertMediaSingle;
10
- insertExternalMediaSingle: InsertExternalMediaSingle;
11
- };
12
- export declare const MediaInsertContent: ({ mediaProvider, dispatchAnalyticsEvent, closeMediaInsertPicker, insertMediaSingle, insertExternalMediaSingle, }: Props) => JSX.Element;
13
- export {};