UNPKG

@atlaskit/editor-plugin-media-insert

Version:

Media Insert plugin for @atlaskit/editor-core

413 lines (412 loc) 20.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.MediaFromURL = MediaFromURL; exports.isValidUrl = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireWildcard(require("react")); var _reactIntl = require("react-intl"); var _adfSchema = require("@atlaskit/adf-schema"); var _buttonGroup = _interopRequireDefault(require("@atlaskit/button/button-group")); var _new = _interopRequireDefault(require("@atlaskit/button/new")); var _analytics = require("@atlaskit/editor-common/analytics"); var _messages = require("@atlaskit/editor-common/messages"); var _form = _interopRequireWildcard(require("@atlaskit/form")); var _growDiagonal = _interopRequireDefault(require("@atlaskit/icon/core/grow-diagonal")); var _mediaClientReact = require("@atlaskit/media-client-react"); var _platformFeatureFlags = require("@atlaskit/platform-feature-flags"); var _primitives = require("@atlaskit/primitives"); var _sectionMessage = _interopRequireDefault(require("@atlaskit/section-message")); var _textfield = _interopRequireDefault(require("@atlaskit/textfield")); var _MediaCard = require("./MediaCard"); var _useAnalyticsEvents2 = require("./useAnalyticsEvents"); var _excluded = ["value", "onChange"]; function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } 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) { (0, _defineProperty2.default)(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; } // eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives -- to be migrated to @atlaskit/primitives/compiled – go/akcss var PreviewBoxStyles = (0, _primitives.xcss)({ borderWidth: 'border.width', borderStyle: 'dashed', borderColor: 'color.border', borderRadius: 'radius.small', height: '200px' }); var PreviewImageStyles = (0, _primitives.xcss)({ height: '200px' }); var FormStyles = (0, _primitives.xcss)({ flexGrow: 1 }); var INITIAL_PREVIEW_STATE = Object.freeze({ isLoading: false, error: null, warning: null, previewInfo: null }); var isValidInput = function isValidInput(value, customizedUrlValidation) { if (customizedUrlValidation) { return customizedUrlValidation(value); } return isValidUrl(value); }; var MAX_URL_LENGTH = 2048; var isValidUrl = exports.isValidUrl = function isValidUrl(value) { try { // Check for spaces and length first to avoid the expensive URL parsing // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp if (/\s/.test(value) || value.length > MAX_URL_LENGTH) { return false; } new URL(value); } catch (_unused) { return false; } return (0, _adfSchema.isSafeUrl)(value); }; var previewStateReducer = function previewStateReducer(state, action) { switch (action.type) { case 'loading': return _objectSpread(_objectSpread({}, INITIAL_PREVIEW_STATE), {}, { isLoading: true }); case 'error': return _objectSpread(_objectSpread({}, INITIAL_PREVIEW_STATE), {}, { error: action.error }); case 'warning': return _objectSpread(_objectSpread({}, INITIAL_PREVIEW_STATE), {}, { warning: action.warning }); case 'success': return _objectSpread(_objectSpread({}, INITIAL_PREVIEW_STATE), {}, { previewInfo: action.payload }); case 'reset': return INITIAL_PREVIEW_STATE; default: return state; } }; function MediaFromURL(_ref) { var mediaProvider = _ref.mediaProvider, dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent, closeMediaInsertPicker = _ref.closeMediaInsertPicker, insertMediaSingle = _ref.insertMediaSingle, insertExternalMediaSingle = _ref.insertExternalMediaSingle, isOnlyExternalLinks = _ref.isOnlyExternalLinks, customizedUrlValidation = _ref.customizedUrlValidation, customizedHelperMessage = _ref.customizedHelperMessage; var intl = (0, _reactIntl.useIntl)(); var strings = { loadPreview: intl.formatMessage(_messages.mediaInsertMessages.loadPreview), insert: intl.formatMessage(_messages.mediaInsertMessages.insert), pasteLinkToUpload: intl.formatMessage(_messages.mediaInsertMessages.pasteLinkToUpload), cancel: intl.formatMessage(_messages.mediaInsertMessages.cancel), errorMessage: intl.formatMessage(_messages.mediaInsertMessages.fromUrlErrorMessage), warning: intl.formatMessage(_messages.mediaInsertMessages.fromUrlWarning), invalidUrl: intl.formatMessage(_messages.mediaInsertMessages.invalidUrlErrorMessage) }; var _React$useReducer = _react.default.useReducer(previewStateReducer, INITIAL_PREVIEW_STATE), _React$useReducer2 = (0, _slicedToArray2.default)(_React$useReducer, 2), previewState = _React$useReducer2[0], dispatch = _React$useReducer2[1]; var _React$useState = _react.default.useState(''), _React$useState2 = (0, _slicedToArray2.default)(_React$useState, 2), input = _React$useState2[0], setInput = _React$useState2[1]; var pasteFlag = _react.default.useRef(false); var _useAnalyticsEvents = (0, _useAnalyticsEvents2.useAnalyticsEvents)(dispatchAnalyticsEvent), onUploadButtonClickedAnalytics = _useAnalyticsEvents.onUploadButtonClickedAnalytics, onUploadCommencedAnalytics = _useAnalyticsEvents.onUploadCommencedAnalytics, onUploadSuccessAnalytics = _useAnalyticsEvents.onUploadSuccessAnalytics, onUploadFailureAnalytics = _useAnalyticsEvents.onUploadFailureAnalytics; var uploadExternalMedia = _react.default.useCallback( /*#__PURE__*/function () { var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(url) { var uploadMediaClientConfig, uploadParams, mediaClient, collection, _yield$mediaClient$fi, uploadableFileUpfrontIds, dimensions, mimeType, message; return _regenerator.default.wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: onUploadButtonClickedAnalytics(); dispatch({ type: 'loading' }); uploadMediaClientConfig = mediaProvider.uploadMediaClientConfig, uploadParams = mediaProvider.uploadParams; if (uploadMediaClientConfig) { _context.next = 5; break; } return _context.abrupt("return"); case 5: mediaClient = (0, _mediaClientReact.getMediaClient)(uploadMediaClientConfig); collection = uploadParams === null || uploadParams === void 0 ? void 0 : uploadParams.collection; onUploadCommencedAnalytics('url'); _context.prev = 8; _context.next = 11; return mediaClient.file.uploadExternal(url, collection); case 11: _yield$mediaClient$fi = _context.sent; uploadableFileUpfrontIds = _yield$mediaClient$fi.uploadableFileUpfrontIds; dimensions = _yield$mediaClient$fi.dimensions; mimeType = _yield$mediaClient$fi.mimeType; onUploadSuccessAnalytics('url'); dispatch({ type: 'success', payload: { id: uploadableFileUpfrontIds.id, collection: collection, dimensions: dimensions, occurrenceKey: uploadableFileUpfrontIds.occurrenceKey, fileMimeType: mimeType } // eslint-disable-next-line no-unused-vars }); _context.next = 22; break; case 19: _context.prev = 19; _context.t0 = _context["catch"](8); // eslint-disable-line no-unused-vars if (typeof _context.t0 === 'string' && _context.t0 === 'Could not download remote file') { // TODO: ED-26962 - Make sure this gets good unit test coverage with the actual media plugin. // This hard coded error message could be changed at any // point and we need a unit test to break to stop people changing it. onUploadFailureAnalytics(_context.t0, 'url'); dispatch({ type: 'warning', warning: _context.t0, url: url }); } else if (_context.t0 instanceof Error) { message = 'Image preview fetch failed'; onUploadFailureAnalytics(message, 'url'); dispatch({ type: 'error', error: message }); } else { onUploadFailureAnalytics('Unknown error', 'url'); dispatch({ type: 'error', error: 'Unknown error' }); } case 22: case "end": return _context.stop(); } }, _callee, null, [[8, 19]]); })); return function (_x) { return _ref2.apply(this, arguments); }; }(), [onUploadButtonClickedAnalytics, mediaProvider, onUploadCommencedAnalytics, onUploadSuccessAnalytics, onUploadFailureAnalytics]); var onURLChange = _react.default.useCallback(function (e) { var url = e.currentTarget.value; dispatch({ type: 'reset' }); setInput(url); if (!isValidInput(url, customizedUrlValidation)) { return; } if (pasteFlag.current) { pasteFlag.current = false; if (!isOnlyExternalLinks) { uploadExternalMedia(url); } } }, [uploadExternalMedia, isOnlyExternalLinks, customizedUrlValidation]); var _onPaste = _react.default.useCallback(function (e, inputUrl) { // Note: this is a little weird, but the paste event will always be // fired before the change event when pasting. We don't really want to // duplicate logic by handling pastes separately to changes, so we're // just noting paste occurred to then be handled in the onURLChange fn // above. The one exception to this is where paste inputs exactly what was // already in the input, in which case we want to ignore it. if (e.clipboardData.getData('text') !== inputUrl) { pasteFlag.current = true; } }, []); var onInsert = _react.default.useCallback(function () { if (previewState.previewInfo) { insertMediaSingle({ mediaState: previewState.previewInfo, inputMethod: _analytics.INPUT_METHOD.MEDIA_PICKER }); } closeMediaInsertPicker(); }, [closeMediaInsertPicker, insertMediaSingle, previewState.previewInfo]); var onExternalInsert = _react.default.useCallback(function (url) { if (previewState.warning || isOnlyExternalLinks) { insertExternalMediaSingle({ url: url, alt: '', inputMethod: _analytics.INPUT_METHOD.MEDIA_PICKER }); } closeMediaInsertPicker(); }, [closeMediaInsertPicker, insertExternalMediaSingle, previewState.warning, isOnlyExternalLinks]); var onInputKeyPress = _react.default.useCallback(function (event) { if (event && event.key === 'Esc') { if (dispatchAnalyticsEvent) { var payload = { action: _analytics.ACTION.CLOSED, actionSubject: _analytics.ACTION_SUBJECT.PICKER, actionSubjectId: _analytics.ACTION_SUBJECT_ID.PICKER_MEDIA, eventType: _analytics.EVENT_TYPE.UI, attributes: { exitMethod: _analytics.INPUT_METHOD.KEYBOARD } }; dispatchAnalyticsEvent(payload); } closeMediaInsertPicker(); } }, [dispatchAnalyticsEvent, closeMediaInsertPicker]); var onCancel = _react.default.useCallback(function () { if (dispatchAnalyticsEvent) { var payload = { action: _analytics.ACTION.CANCELLED, actionSubject: _analytics.ACTION_SUBJECT.PICKER, actionSubjectId: _analytics.ACTION_SUBJECT_ID.PICKER_MEDIA, eventType: _analytics.EVENT_TYPE.UI }; dispatchAnalyticsEvent(payload); } closeMediaInsertPicker(); }, [closeMediaInsertPicker, dispatchAnalyticsEvent]); return /*#__PURE__*/_react.default.createElement(_form.default, { // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) onSubmit: function onSubmit(_ref3, form) { var inputUrl = _ref3.inputUrl; // This can be triggered from an enter key event on the input even when // the button is disabled, so we explicitly do nothing when in loading // state. if (previewState.isLoading || form.getState().invalid) { return; } if (previewState.previewInfo) { return onInsert(); } if (previewState.warning || isOnlyExternalLinks) { return onExternalInsert(inputUrl); } return uploadExternalMedia(inputUrl); } }, function (_ref4) { var formProps = _ref4.formProps; return ( /*#__PURE__*/ // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading _react.default.createElement(_primitives.Box, { xcss: FormStyles }, /*#__PURE__*/_react.default.createElement(_primitives.Stack, { space: "space.150", grow: "fill" }, /*#__PURE__*/_react.default.createElement(_form.Field, { isRequired: true, name: "inputUrl" // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , validate: function validate(value) { return value && isValidInput(value, customizedUrlValidation) ? undefined : strings.invalidUrl; } }, function (_ref5) { var _ref5$fieldProps = _ref5.fieldProps, value = _ref5$fieldProps.value, _onChange = _ref5$fieldProps.onChange, rest = (0, _objectWithoutProperties2.default)(_ref5$fieldProps, _excluded), error = _ref5.error, meta = _ref5.meta; return /*#__PURE__*/_react.default.createElement(_primitives.Stack, { space: "space.150", grow: "fill" }, /*#__PURE__*/_react.default.createElement(_primitives.Box, null, /*#__PURE__*/_react.default.createElement(_textfield.default // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading , (0, _extends2.default)({}, rest, { value: value, "aria-label": (0, _platformFeatureFlags.fg)('platform_editor_nov_a11y_fixes') ? strings.pasteLinkToUpload : undefined, placeholder: strings.pasteLinkToUpload, maxLength: MAX_URL_LENGTH, onKeyPress: onInputKeyPress // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onPaste: function onPaste(event) { return _onPaste(event, value); } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onChange: function onChange(value) { onURLChange(value); _onChange(value); } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onKeyDown: function onKeyDown(e) { if (e.key === 'Enter') { e.preventDefault(); formProps.onSubmit(); } } })), customizedHelperMessage && /*#__PURE__*/_react.default.createElement(_react.Fragment, null, /*#__PURE__*/_react.default.createElement(_form.HelperMessage, null, customizedHelperMessage)), /*#__PURE__*/_react.default.createElement(_form.MessageWrapper, null, error && /*#__PURE__*/_react.default.createElement(_form.ErrorMessage, null, /*#__PURE__*/_react.default.createElement(_primitives.Box, { as: "span" }, error)))), !previewState.previewInfo && !previewState.error && !previewState.warning && !isOnlyExternalLinks && /*#__PURE__*/_react.default.createElement(_primitives.Flex, { xcss: PreviewBoxStyles, alignItems: "center", justifyContent: "center" }, /*#__PURE__*/_react.default.createElement(_new.default, { type: "button" // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onClick: function onClick() { return formProps.onSubmit(); }, isLoading: previewState.isLoading, isDisabled: !!error || !meta.dirty // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , iconBefore: function iconBefore() { return /*#__PURE__*/_react.default.createElement(_growDiagonal.default, { label: "" }); } }, strings.loadPreview))); }), previewState.previewInfo && /*#__PURE__*/_react.default.createElement(_primitives.Inline, { alignInline: "center", alignBlock: "center", xcss: PreviewImageStyles, space: "space.200" }, /*#__PURE__*/_react.default.createElement(_MediaCard.MediaCard, { attrs: previewState.previewInfo, mediaProvider: mediaProvider })), /*#__PURE__*/_react.default.createElement(_form.MessageWrapper, null, previewState.error && /*#__PURE__*/_react.default.createElement(_sectionMessage.default, { appearance: "error" }, strings.errorMessage), previewState.warning && /*#__PURE__*/_react.default.createElement(_sectionMessage.default, { appearance: "warning" }, strings.warning)), /*#__PURE__*/_react.default.createElement(_form.FormFooter, null, /*#__PURE__*/_react.default.createElement(_buttonGroup.default, null, /*#__PURE__*/_react.default.createElement(_new.default, { appearance: "subtle", onClick: onCancel }, strings.cancel), /*#__PURE__*/_react.default.createElement(_new.default, { type: "button", appearance: "primary", isDisabled: isOnlyExternalLinks ? !input || !isValidInput(input, customizedUrlValidation) : !previewState.previewInfo && !previewState.warning // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onClick: function onClick() { return formProps.onSubmit(); } }, strings.insert))))) ); }); }