@atlaskit/editor-plugin-media-insert
Version:
Media Insert plugin for @atlaskit/editor-core
413 lines (412 loc) • 20.2 kB
JavaScript
"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)))))
);
});
}