@atlaskit/editor-plugin-media-insert
Version:
Media Insert plugin for @atlaskit/editor-core
403 lines (401 loc) • 19.6 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.MediaInsertPicker = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _react = _interopRequireDefault(require("react"));
var _reactIntl = require("react-intl");
var _analytics = require("@atlaskit/editor-common/analytics");
var _getDomRefFromSelection = require("@atlaskit/editor-common/get-dom-ref-from-selection");
var _hooks = require("@atlaskit/editor-common/hooks");
var _rank = require("@atlaskit/editor-common/media-insert/rank");
var _messages = require("@atlaskit/editor-common/messages");
var _ui = require("@atlaskit/editor-common/ui");
var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
var _compiled = require("@atlaskit/primitives/compiled");
var _tabs = _interopRequireDefault(require("@atlaskit/tabs/tabs"));
var _tabList = _interopRequireDefault(require("@atlaskit/tabs/tab-list"));
var _useTab2 = _interopRequireDefault(require("@atlaskit/tabs/use-tab"));
var _useTabPanel = _interopRequireDefault(require("@atlaskit/tabs/use-tab-panel"));
var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
var _useFocus = require("./hooks/use-focus");
var _useUnholyAutofocus2 = require("./hooks/use-unholy-autofocus");
var _LocalMedia = require("./LocalMedia");
var _MediaFromURL = require("./MediaFromURL");
var _MediaInsertWrapper = require("./MediaInsertWrapper");
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; }
var PopupWithListeners = (0, _ui.withOuterListeners)(_ui.Popup);
var MEDIA_INSERT_PICKER_ANALYTICS_SOURCE = 'MediaInsertPicker';
var EMPTY_REGISTERED_TABS = [];
var getMediaInsertPickerTabSource = function getMediaInsertPickerTabSource(selectedTab) {
return "".concat(MEDIA_INSERT_PICKER_ANALYTICS_SOURCE, " - ").concat(selectedTab);
};
var getNextTabIndexForKey = function getNextTabIndexForKey(currentTabIndex, tabCount, key) {
if (key === 'Home') {
return 0;
}
if (key === 'End') {
return tabCount - 1;
}
if (key === 'ArrowRight') {
return currentTabIndex === tabCount - 1 ? 0 : currentTabIndex + 1;
}
if (key === 'ArrowLeft') {
return currentTabIndex === 0 ? tabCount - 1 : currentTabIndex - 1;
}
return undefined;
};
var TabWithAnalytics = function TabWithAnalytics(_ref) {
var children = _ref.children,
onSelectTabForAnalytics = _ref.onSelectTabForAnalytics,
selectedTabIndex = _ref.selectedTabIndex,
tabCount = _ref.tabCount;
var _useTab = (0, _useTab2.default)(),
onClick = _useTab.onClick,
id = _useTab.id,
ariaControls = _useTab['aria-controls'],
ariaPosinset = _useTab['aria-posinset'],
ariaSelected = _useTab['aria-selected'],
ariaSetsize = _useTab['aria-setsize'],
onKeyDown = _useTab.onKeyDown,
role = _useTab.role,
tabIndex = _useTab.tabIndex;
var handleClick = _react.default.useCallback(function () {
onSelectTabForAnalytics(selectedTabIndex);
onClick();
}, [onClick, onSelectTabForAnalytics, selectedTabIndex]);
var handleKeyDown = _react.default.useCallback(function (event) {
var nextTabIndex = getNextTabIndexForKey(selectedTabIndex, tabCount, event.key);
if (nextTabIndex !== undefined) {
onSelectTabForAnalytics(nextTabIndex);
}
onKeyDown(event);
}, [onKeyDown, onSelectTabForAnalytics, selectedTabIndex, tabCount]);
return /*#__PURE__*/_react.default.createElement(_compiled.Focusable, {
as: "div",
isInset: true,
onClick: handleClick,
id: id,
"aria-controls": ariaControls,
"aria-posinset": ariaPosinset,
"aria-selected": ariaSelected,
"aria-setsize": ariaSetsize,
onKeyDown: handleKeyDown,
role: role,
tabIndex: tabIndex
}, /*#__PURE__*/_react.default.createElement(_compiled.Text, {
weight: "medium",
color: "inherit",
maxLines: 1
}, children));
};
/**
* A custom TabPanel that is non-focusable.
*/
var CustomTabPanel = function CustomTabPanel(_ref2) {
var children = _ref2.children,
_ref2$disablePaddingB = _ref2.disablePaddingBlockEnd,
disablePaddingBlockEnd = _ref2$disablePaddingB === void 0 ? false : _ref2$disablePaddingB;
var tabPanelAttributes = (0, _useTabPanel.default)();
return /*#__PURE__*/_react.default.createElement(_compiled.Box, (0, _extends2.default)({
paddingBlockEnd: disablePaddingBlockEnd ? 'space.0' : 'space.150'
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
}, tabPanelAttributes, {
tabIndex: -1
}), children);
};
var MediaInsertPicker = exports.MediaInsertPicker = function MediaInsertPicker(_ref3) {
var _api$mediaInsert$acti, _api$mediaInsert, _api$mediaInsert$getI;
var api = _ref3.api,
editorView = _ref3.editorView,
dispatchAnalyticsEvent = _ref3.dispatchAnalyticsEvent,
popupsMountPoint = _ref3.popupsMountPoint,
popupsBoundariesElement = _ref3.popupsBoundariesElement,
popupsScrollableElement = _ref3.popupsScrollableElement,
closeMediaInsertPicker = _ref3.closeMediaInsertPicker,
insertMediaSingle = _ref3.insertMediaSingle,
insertExternalMediaSingle = _ref3.insertExternalMediaSingle,
insertFile = _ref3.insertFile,
_ref3$isOnlyExternalL = _ref3.isOnlyExternalLinks,
isOnlyExternalLinks = _ref3$isOnlyExternalL === void 0 ? false : _ref3$isOnlyExternalL,
customizedUrlValidation = _ref3.customizedUrlValidation,
customizedHelperMessage = _ref3.customizedHelperMessage;
// Tabs registered by other plugins via `api.mediaInsert.actions.registerInsertTab(...)`.
// Read once per render; the registry is mutated only at plugin setup time so this is stable
// for the lifetime of an editor instance.
var registeredTabs = (_api$mediaInsert$acti = api === null || api === void 0 || (_api$mediaInsert = api.mediaInsert) === null || _api$mediaInsert === void 0 || (_api$mediaInsert = _api$mediaInsert.actions) === null || _api$mediaInsert === void 0 || (_api$mediaInsert$getI = _api$mediaInsert.getInsertTabs) === null || _api$mediaInsert$getI === void 0 ? void 0 : _api$mediaInsert$getI.call(_api$mediaInsert)) !== null && _api$mediaInsert$acti !== void 0 ? _api$mediaInsert$acti : EMPTY_REGISTERED_TABS;
var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['media', 'mediaInsert'], function (states) {
var _states$mediaState, _states$mediaInsertSt, _states$mediaInsertSt2;
return {
mediaProvider: (_states$mediaState = states.mediaState) === null || _states$mediaState === void 0 ? void 0 : _states$mediaState.mediaProvider,
isOpen: (_states$mediaInsertSt = states.mediaInsertState) === null || _states$mediaInsertSt === void 0 ? void 0 : _states$mediaInsertSt.isOpen,
mountInfo: (_states$mediaInsertSt2 = states.mediaInsertState) === null || _states$mediaInsertSt2 === void 0 ? void 0 : _states$mediaInsertSt2.mountInfo
};
}),
mediaProvider = _useSharedPluginState.mediaProvider,
isOpen = _useSharedPluginState.isOpen,
mountInfo = _useSharedPluginState.mountInfo;
var targetRef;
var mountPoint;
if (mountInfo) {
targetRef = mountInfo.ref;
mountPoint = mountInfo.mountPoint;
} else {
var _api$analytics;
// If targetRef is undefined, target the selection in the editor
targetRef = (0, _getDomRefFromSelection.getDomRefFromSelection)(editorView, _analytics.ACTION_SUBJECT_ID.PICKER_MEDIA, api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions);
mountPoint = popupsMountPoint;
}
var focusButton = (0, _useFocus.useFocus)({
target: targetRef !== null && targetRef !== void 0 ? targetRef : editorView
});
var focusEditor = (0, _useFocus.useFocus)({
target: editorView
});
// returnFocusRef stores which function we call on unmount: focusButton | focusEditor
// Needs to be a ref so we can instantly toggle it before unmounting triggers the focus trap
var returnFocusRef = _react.default.useRef(focusEditor);
// When inserting media, close picker and focus editor.
var closeAndFocusEditor = _react.default.useCallback(function () {
returnFocusRef.current = focusEditor;
closeMediaInsertPicker();
}, [closeMediaInsertPicker, focusEditor]);
// When cancelling, close picker and return focus to button.
var closeAndReturnFocusToButton = _react.default.useCallback(function () {
returnFocusRef.current = focusButton;
closeMediaInsertPicker();
}, [closeMediaInsertPicker, focusButton]);
var intl = (0, _reactIntl.useIntl)();
var _useUnholyAutofocus = (0, _useUnholyAutofocus2.useUnholyAutofocus)(),
autofocusRef = _useUnholyAutofocus.autofocusRef,
onPositionCalculated = _useUnholyAutofocus.onPositionCalculated;
var orderedTabs = _react.default.useMemo(function () {
var tabs = [];
var tieBreaker = 0;
if (!isOnlyExternalLinks) {
tabs.push({
key: _analytics.MEDIA_INSERT_TAB.UPLOAD,
rank: _rank.MEDIA_INSERT_TAB_RANK[_analytics.MEDIA_INSERT_TAB.UPLOAD],
tieBreaker: tieBreaker++,
type: 'upload'
});
}
tabs.push({
key: _analytics.MEDIA_INSERT_TAB.LINK,
rank: _rank.MEDIA_INSERT_TAB_RANK[_analytics.MEDIA_INSERT_TAB.LINK],
tieBreaker: tieBreaker++,
type: 'link'
});
registeredTabs.forEach(function (registeredTab) {
var _registeredTab$rank;
tabs.push({
key: registeredTab.key,
rank: (_registeredTab$rank = registeredTab.rank) !== null && _registeredTab$rank !== void 0 ? _registeredTab$rank : _rank.DEFAULT_MEDIA_INSERT_TAB_RANK,
registeredTab: registeredTab,
tieBreaker: tieBreaker++,
type: 'registered'
});
});
return tabs.sort(function (a, b) {
return a.rank - b.rank || a.tieBreaker - b.tieBreaker;
});
}, [isOnlyExternalLinks, registeredTabs]);
var tabCount = orderedTabs.length;
var getTabAnalyticsMetadata = _react.default.useCallback(function (selectedTabIndex) {
var selectedTab = orderedTabs[selectedTabIndex];
if (selectedTab) {
return {
selectedTab: selectedTab.key,
selectedTabIndex: selectedTabIndex
};
}
return {
selectedTab: 'unknown',
selectedTabIndex: selectedTabIndex
};
}, [orderedTabs]);
var selectedTabAnalyticsMetadataRef = _react.default.useRef(getTabAnalyticsMetadata(0));
var tabsAnalyticsContext = _react.default.useMemo(function () {
return {
get source() {
return getMediaInsertPickerTabSource(selectedTabAnalyticsMetadataRef.current.selectedTab);
}
};
}, []);
var setSelectedTabAnalyticsMetadata = _react.default.useCallback(function (selectedTabIndex) {
selectedTabAnalyticsMetadataRef.current = getTabAnalyticsMetadata(selectedTabIndex);
}, [getTabAnalyticsMetadata]);
var hasDispatchedInitialTabViewedEventRef = _react.default.useRef(false);
// Atlaskit Tabs only calls `onChange` after the user changes tabs, so the
// initially opened tab needs its viewed analytics event dispatched from an
// effect. The ref keeps this to once per picker open without setting state.
_react.default.useEffect(function () {
if (!isOpen) {
hasDispatchedInitialTabViewedEventRef.current = false;
return;
}
if (!mediaProvider || !dispatchAnalyticsEvent || hasDispatchedInitialTabViewedEventRef.current) {
return;
}
var selectedTabMetadata = getTabAnalyticsMetadata(0);
selectedTabAnalyticsMetadataRef.current = selectedTabMetadata;
var payload = {
action: _analytics.ACTION.VIEWED,
actionSubject: _analytics.ACTION_SUBJECT.PICKER,
actionSubjectId: _analytics.ACTION_SUBJECT_ID.PICKER_MEDIA,
eventType: _analytics.EVENT_TYPE.UI,
attributes: {
selectedTab: selectedTabMetadata.selectedTab,
selectedTabIndex: selectedTabMetadata.selectedTabIndex
}
};
dispatchAnalyticsEvent(payload);
hasDispatchedInitialTabViewedEventRef.current = true;
}, [dispatchAnalyticsEvent, getTabAnalyticsMetadata, isOpen, mediaProvider]);
var handleTabChange = _react.default.useCallback(function (selectedTabIndex, analyticsEvent) {
var selectedTabMetadata = getTabAnalyticsMetadata(selectedTabIndex);
selectedTabAnalyticsMetadataRef.current = selectedTabMetadata;
analyticsEvent.update(function (payload) {
return _objectSpread(_objectSpread({}, payload), {}, {
attributes: _objectSpread(_objectSpread({}, payload.attributes), {}, {
selectedTab: selectedTabMetadata.selectedTab,
selectedTabIndex: selectedTabIndex
})
});
}).fire();
if (dispatchAnalyticsEvent) {
var payload = {
action: _analytics.ACTION.VIEWED,
actionSubject: _analytics.ACTION_SUBJECT.PICKER,
actionSubjectId: _analytics.ACTION_SUBJECT_ID.PICKER_MEDIA,
eventType: _analytics.EVENT_TYPE.UI,
attributes: {
selectedTab: selectedTabMetadata.selectedTab,
selectedTabIndex: selectedTabIndex
}
};
dispatchAnalyticsEvent(payload);
}
}, [dispatchAnalyticsEvent, getTabAnalyticsMetadata]);
if (!isOpen || !mediaProvider) {
return null;
}
var handleClose = function handleClose(exitMethod) {
return function (event) {
// Same as AIImageGenerationPopup: react-select can detach the option
// before `click` fires, so withOuterListeners treats it as outside.
if (exitMethod === _analytics.INPUT_METHOD.MOUSE && event.target instanceof Node && !event.target.isConnected) {
return;
}
event.preventDefault();
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: exitMethod
}
};
dispatchAnalyticsEvent(payload);
}
if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_fix_focus_MediaInsertPicker')) {
closeAndReturnFocusToButton();
} else {
closeMediaInsertPicker(); // Focuses editor on unmount
focusEditor();
}
};
};
var closePickerAndFocusEditor = function closePickerAndFocusEditor() {
if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_fix_focus_MediaInsertPicker')) {
closeAndFocusEditor();
} else {
closeMediaInsertPicker();
focusEditor();
}
};
var fileTabTitle = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('cc_page_experiences_editor_image_generation', 'isEnabled', true) ? intl.formatMessage(_messages.mediaInsertMessages.uploadTabTitle) : intl.formatMessage(_messages.mediaInsertMessages.fileTabTitle);
return /*#__PURE__*/_react.default.createElement(PopupWithListeners, {
ariaLabel: intl.formatMessage(_messages.mediaInsertMessages.mediaPickerPopupAriaLabel)
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
offset: [0, 12],
target: targetRef,
zIndex: _editorSharedStyles.akEditorFloatingDialogZIndex,
fitHeight: 390,
fitWidth: 340,
mountTo: mountPoint,
onUnmount: (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_fix_focus_MediaInsertPicker') ? function () {
return returnFocusRef.current();
} : undefined,
boundariesElement: popupsBoundariesElement,
handleClickOutside: handleClose(_analytics.INPUT_METHOD.MOUSE),
handleEscapeKeydown: handleClose(_analytics.INPUT_METHOD.KEYBOARD),
scrollableElement: popupsScrollableElement,
preventOverflow: true,
onPositionCalculated: onPositionCalculated,
focusTrap: true
}, /*#__PURE__*/_react.default.createElement(_ui.PlainOutsideClickTargetRefContext.Consumer, null, function (setOutsideClickTargetRef) {
return /*#__PURE__*/_react.default.createElement(_MediaInsertWrapper.MediaInsertWrapper, {
ref: setOutsideClickTargetRef
}, /*#__PURE__*/_react.default.createElement(_tabs.default, {
id: "media-insert-tab-navigation",
analyticsContext: tabsAnalyticsContext,
onChange: handleTabChange
}, /*#__PURE__*/_react.default.createElement(_compiled.Box, {
paddingBlockEnd: "space.150"
}, /*#__PURE__*/_react.default.createElement(_tabList.default, null, orderedTabs.map(function (tab, index) {
return /*#__PURE__*/_react.default.createElement(TabWithAnalytics, {
key: tab.key,
onSelectTabForAnalytics: setSelectedTabAnalyticsMetadata,
selectedTabIndex: index,
tabCount: tabCount
}, tab.type === 'registered' ? tab.registeredTab.label : tab.type === 'upload' ? fileTabTitle : intl.formatMessage(_messages.mediaInsertMessages.linkTabTitle));
}))), orderedTabs.map(function (tab) {
if (tab.type === 'registered') {
var TabComponent = tab.registeredTab.component;
return /*#__PURE__*/_react.default.createElement(CustomTabPanel, {
key: tab.key,
disablePaddingBlockEnd: true
}, /*#__PURE__*/_react.default.createElement(TabComponent, {
closeMediaInsertPicker: closePickerAndFocusEditor,
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
insertMediaSingle: insertMediaSingle,
mediaProvider: mediaProvider
}));
}
if (tab.type === 'upload') {
return /*#__PURE__*/_react.default.createElement(CustomTabPanel, {
key: tab.key
}, /*#__PURE__*/_react.default.createElement(_LocalMedia.LocalMedia, {
ref: autofocusRef,
mediaProvider: mediaProvider,
closeMediaInsertPicker: closePickerAndFocusEditor,
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
insertFile: insertFile
}));
}
return /*#__PURE__*/_react.default.createElement(CustomTabPanel, {
key: tab.key
}, /*#__PURE__*/_react.default.createElement(_MediaFromURL.MediaFromURL, {
mediaProvider: mediaProvider,
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
closeMediaInsertPicker: closePickerAndFocusEditor,
cancelMediaInsertPicker: closeAndReturnFocusToButton,
insertMediaSingle: insertMediaSingle,
insertExternalMediaSingle: insertExternalMediaSingle,
isOnlyExternalLinks: isOnlyExternalLinks,
customizedUrlValidation: customizedUrlValidation,
customizedHelperMessage: customizedHelperMessage
}));
})));
}));
};