@atlaskit/editor-plugin-media
Version:
Media plugin for @atlaskit/editor-core
965 lines (959 loc) • 48 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import React from 'react';
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
import { alignmentIcons, buildLayoutButtons, buildLayoutDropdown, layoutToMessages, wrappingIcons } from '@atlaskit/editor-common/card';
import { withAnalytics } from '@atlaskit/editor-common/editor-analytics';
import { mediaInlineImagesEnabled } from '@atlaskit/editor-common/media-inline';
import commonMessages, { cardMessages, mediaAndEmbedToolbarMessages } from '@atlaskit/editor-common/messages';
import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check';
import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
import { contains, findParentNodeOfType, hasParentNodeOfType, removeSelectedNode } from '@atlaskit/editor-prosemirror/utils';
import { akEditorSelectedNodeClassName } from '@atlaskit/editor-shared-styles';
import ImageCropIcon from '@atlaskit/icon-lab/core/image-crop';
import CopyIcon from '@atlaskit/icon/core/copy';
import DeleteIcon from '@atlaskit/icon/core/delete';
import DownloadIcon from '@atlaskit/icon/core/download';
import GrowDiagonalIcon from '@atlaskit/icon/core/grow-diagonal';
import ImageFullscreenIcon from '@atlaskit/icon/core/image-fullscreen';
import ImageInlineIcon from '@atlaskit/icon/core/image-inline';
import MaximizeIcon from '@atlaskit/icon/core/maximize';
import SmartLinkCardIcon from '@atlaskit/icon/core/smart-link-card';
import { mediaFilmstripItemDOMSelector } from '@atlaskit/media-filmstrip';
import { messages } from '@atlaskit/media-ui';
import { fg } from '@atlaskit/platform-feature-flags';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { MediaSingleNodeSelector } from '../../nodeviews/styles';
import { getPluginState as getMediaAltTextPluginState } from '../../pm-plugins/alt-text';
import { showLinkingToolbar } from '../../pm-plugins/commands/linking';
import { getMediaLinkingState } from '../../pm-plugins/linking';
import { getPluginState as getMediaPixelResizingPluginState } from '../../pm-plugins/pixel-resizing';
import { FullWidthDisplay, PixelEntry } from '../../pm-plugins/pixel-resizing/ui';
import { stateKey } from '../../pm-plugins/plugin-key';
import { currentMediaOrInlineNodeBorderMark } from '../../pm-plugins/utils/current-media-node';
import { isVideo } from '../../pm-plugins/utils/media-single';
import ImageBorderItem from '../../ui/ImageBorder';
import { altTextButton, getAltTextDropdownOption, getAltTextToolbar } from './alt-text';
import { changeMediaCardToInline, changeMediaSingleToMediaInline, setBorderMark, toggleBorderMark } from './commands';
import { commentButton } from './comments';
import { shouldShowImageBorder } from './imageBorder';
import { LayoutGroup } from './layout-group';
import { getLinkingDropdownOptions, getLinkingToolbar, getOpenLinkToolbarButtonOption, shouldShowMediaLinkToolbar } from './linking';
import { LinkToolbarAppearance } from './linking-toolbar-appearance';
import { generateMediaInlineFloatingToolbar } from './mediaInline';
import { getPixelResizingToolbar, getResizeDropdownOption } from './pixel-resizing';
import { canShowSwitchButtons, downloadMedia, getIsDownloadDisabledByDataSecurityPolicy, getMaxToolbarWidth, getMediaSingleAndMediaInlineSwitcherDropdown, getSelectedLayoutIcon, getSelectedMediaSingle, getSelectedNearestMediaContainerNodeAttrs, removeMediaGroupNode, updateToFullHeightSeparator } from './utils';
const mediaTypeMessages = {
image: messages.file_image_is_selected,
video: messages.file_video_is_selected,
audio: messages.file_audio_is_selected,
doc: messages.file_doc_is_selected,
archive: messages.file_archive_is_selected,
unknown: messages.file_unknown_is_selected
};
const getMediaActionSubject = nodeType => {
switch (nodeType.name) {
case 'mediaSingle':
return ACTION_SUBJECT.MEDIA_SINGLE;
case 'mediaInline':
return ACTION_SUBJECT.MEDIA_INLINE;
case 'mediaGroup':
return ACTION_SUBJECT.MEDIA_GROUP;
default:
return ACTION_SUBJECT.MEDIA;
}
};
const removeWithAnalytics = (editorAnalyticsApi, nodeType) => {
if (!nodeType) {
return remove;
}
const mediaType = getMediaActionSubject(nodeType);
return withAnalytics(editorAnalyticsApi, {
action: ACTION.DELETED,
actionSubject: mediaType,
attributes: {
inputMethod: INPUT_METHOD.FLOATING_TB
},
eventType: EVENT_TYPE.TRACK
})(remove);
};
const remove = (state, dispatch) => {
if (dispatch) {
dispatch(removeSelectedNode(state.tr));
}
return true;
};
const handleRemoveMediaGroupWithAnalytics = editorAnalyticsApi => {
return withAnalytics(editorAnalyticsApi, {
action: ACTION.DELETED,
actionSubject: ACTION_SUBJECT.MEDIA_GROUP,
attributes: {
inputMethod: INPUT_METHOD.FLOATING_TB
},
eventType: EVENT_TYPE.TRACK
})(handleRemoveMediaGroup);
};
const handleRemoveMediaGroup = (state, dispatch) => {
const tr = removeMediaGroupNode(state);
if (dispatch) {
dispatch(tr);
}
return true;
};
export const handleShowMediaViewer = ({
api,
mediaPluginState
}) => {
const selectedNodeAttrs = getSelectedNearestMediaContainerNodeAttrs(mediaPluginState);
if (!selectedNodeAttrs) {
return false;
}
api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : api.media.commands.showMediaViewer(selectedNodeAttrs));
};
export const handleShowImageEditor = ({
api,
mediaPluginState
}) => {
var _api$mediaEditing;
const selectedNodeAttrs = getSelectedNearestMediaContainerNodeAttrs(mediaPluginState);
if (!selectedNodeAttrs) {
return false;
}
api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : (_api$mediaEditing = api.mediaEditing) === null || _api$mediaEditing === void 0 ? void 0 : _api$mediaEditing.commands.showImageEditor(selectedNodeAttrs));
};
const generateMediaCardFloatingToolbar = (state, intl, mediaPluginState, hoverDecoration, pluginInjectionApi, editorAnalyticsAPI, forceFocusSelector, isViewOnly) => {
var _pluginInjectionApi$c, _pluginInjectionApi$c2, _pluginInjectionApi$c3;
const disableDownloadButton = getIsDownloadDisabledByDataSecurityPolicy(mediaPluginState);
const areAnyNewToolbarFlagsEnabled = areToolbarFlagsEnabled(Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar));
const preview = {
id: 'editor.media.viewer',
testId: 'file-preview-toolbar-button',
type: 'button',
icon: areAnyNewToolbarFlagsEnabled ? GrowDiagonalIcon : MaximizeIcon,
title: intl.formatMessage(messages.preview),
onClick: () => {
var _handleShowMediaViewe;
return (_handleShowMediaViewe = handleShowMediaViewer({
mediaPluginState,
api: pluginInjectionApi
})) !== null && _handleShowMediaViewe !== void 0 ? _handleShowMediaViewe : false;
},
disabled: isOfflineMode(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c = pluginInjectionApi.connectivity) === null || _pluginInjectionApi$c === void 0 ? void 0 : (_pluginInjectionApi$c2 = _pluginInjectionApi$c.sharedState) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : (_pluginInjectionApi$c3 = _pluginInjectionApi$c2.currentState()) === null || _pluginInjectionApi$c3 === void 0 ? void 0 : _pluginInjectionApi$c3.mode),
supportsViewMode: true
};
const download = {
id: 'editor.media.card.download',
type: 'button',
icon: DownloadIcon,
onClick: () => {
downloadMedia(mediaPluginState);
return true;
},
title: intl.formatMessage(messages.download),
disabled: disableDownloadButton,
...(areAnyNewToolbarFlagsEnabled && {
supportsViewMode: true
})
};
if (isViewOnly && !areAnyNewToolbarFlagsEnabled) {
return [];
}
const {
mediaGroup
} = state.schema.nodes;
const items = [];
if (!areAnyNewToolbarFlagsEnabled) {
items.push({
id: 'editor.media.view.switcher.inline',
type: 'button',
icon: ImageInlineIcon,
selected: false,
focusEditoronEnter: true,
disabled: false,
onClick: changeMediaCardToInline(editorAnalyticsAPI, forceFocusSelector),
title: intl.formatMessage(cardMessages.inlineTitle),
testId: 'inline-appearance',
className: 'inline-appearance' // a11y. uses to force focus on item
}, {
id: 'editor.media.view.switcher.thumbnail',
type: 'button',
icon: SmartLinkCardIcon,
selected: true,
disabled: false,
focusEditoronEnter: true,
onClick: () => true,
title: intl.formatMessage(cardMessages.blockTitle),
testId: 'thumbnail-appearance',
className: 'thumbnail-appearance' // a11y. uses to force focus on item
}, {
type: 'separator'
}, preview, {
type: 'separator'
}, download, {
type: 'separator'
}, {
type: 'copy-button',
supportsViewMode: true,
items: [{
state,
formatMessage: intl.formatMessage,
nodeType: mediaGroup
}]
}, {
type: 'separator'
}, {
id: 'editor.media.delete',
type: 'button',
appearance: 'danger',
focusEditoronEnter: true,
icon: DeleteIcon,
onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(mediaGroup, true),
onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(mediaGroup, false),
onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(mediaGroup, true),
onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(mediaGroup, false),
title: intl.formatMessage(commonMessages.remove),
onClick: handleRemoveMediaGroupWithAnalytics(editorAnalyticsAPI),
testId: 'media-toolbar-remove-button'
});
} else {
const options = [{
id: 'editor.media.view.switcher.inline',
title: intl.formatMessage(cardMessages.inlineTitle),
onClick: changeMediaCardToInline(editorAnalyticsAPI, forceFocusSelector),
icon: /*#__PURE__*/React.createElement(ImageInlineIcon, {
label: "",
spacing: "spacious"
})
}, {
id: 'editor.media.view.switcher.thumbnail',
title: intl.formatMessage(cardMessages.blockTitle),
selected: true,
onClick: () => true,
icon: /*#__PURE__*/React.createElement(SmartLinkCardIcon, {
label: "",
spacing: "spacious"
})
}];
const switcherDropdown = {
title: intl.formatMessage(messages.fileDisplayOptions),
id: 'media-group-inline-switcher-toolbar-item',
testId: 'media-group-inline-switcher-dropdown',
type: 'dropdown',
options,
icon: () => /*#__PURE__*/React.createElement(SmartLinkCardIcon, {
label: "",
spacing: "spacious"
})
};
items.push(switcherDropdown, {
type: 'separator',
fullHeight: true
}, download, {
type: 'separator',
supportsViewMode: true
}, preview, {
type: 'separator',
fullHeight: true
});
}
return items;
};
const generateMediaSingleFloatingToolbar = (state, intl, options, pluginState, mediaLinkingState, pluginInjectionApi) => {
var _pluginInjectionApi$d, _pluginInjectionApi$d2;
const {
mediaSingle
} = state.schema.nodes;
const {
allowResizing,
allowLinking,
allowAdvancedToolBarOptions,
allowCommentsOnMedia,
allowResizingInTables,
allowAltTextOnImages,
allowMediaInline,
allowMediaInlineImages,
allowImageEditing,
allowImagePreview,
isViewOnly,
allowPixelResizing,
onCommentButtonMount,
createCommentExperience
} = options;
let toolbarButtons = [];
const {
hoverDecoration
} = (_pluginInjectionApi$d = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$d2 = pluginInjectionApi.decorations) === null || _pluginInjectionApi$d2 === void 0 ? void 0 : _pluginInjectionApi$d2.actions) !== null && _pluginInjectionApi$d !== void 0 ? _pluginInjectionApi$d : {};
const areAnyNewToolbarFlagsEnabled = areToolbarFlagsEnabled(Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar));
const disableDownloadButton = getIsDownloadDisabledByDataSecurityPolicy(pluginState);
if (shouldShowImageBorder(state)) {
toolbarButtons.push({
type: 'custom',
fallback: [],
render: editorView => {
if (!editorView) {
return null;
}
const {
dispatch,
state
} = editorView;
const borderMark = currentMediaOrInlineNodeBorderMark(state);
return /*#__PURE__*/React.createElement(ImageBorderItem
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
, {
toggleBorder: () => {
var _pluginInjectionApi$a;
toggleBorderMark(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions)(state, dispatch);
}
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
setBorder: attrs => {
var _pluginInjectionApi$a2;
setBorderMark(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a2 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a2 === void 0 ? void 0 : _pluginInjectionApi$a2.actions)(attrs)(state, dispatch);
},
borderMark: borderMark,
intl: intl
});
}
});
if (!areAnyNewToolbarFlagsEnabled) {
toolbarButtons.push({
type: 'separator'
});
}
}
if (allowAdvancedToolBarOptions) {
var _pluginInjectionApi$a3;
const widthPlugin = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.width;
let isChangingLayoutDisabled = false;
const selectedNode = getSelectedMediaSingle(state);
if (allowPixelResizing) {
var _widthPlugin$sharedSt;
const contentWidth = widthPlugin === null || widthPlugin === void 0 ? void 0 : (_widthPlugin$sharedSt = widthPlugin.sharedState.currentState()) === null || _widthPlugin$sharedSt === void 0 ? void 0 : _widthPlugin$sharedSt.lineLength;
const selectedNodeMaxWidth = pluginState.currentMaxWidth || contentWidth;
if (selectedNode && selectedNodeMaxWidth && selectedNode.node.attrs.width >= selectedNodeMaxWidth) {
isChangingLayoutDisabled = true;
}
}
const layoutButtons = buildLayoutButtons(state, intl, state.schema.nodes.mediaSingle, widthPlugin, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a3 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a3 === void 0 ? void 0 : _pluginInjectionApi$a3.actions, allowResizing, allowResizingInTables, true, true, isChangingLayoutDisabled, allowPixelResizing);
const addLayoutDropdownToToolbar = () => {
if (areAnyNewToolbarFlagsEnabled) {
var _pluginInjectionApi$a4;
const layoutDropdown = buildLayoutDropdown(state, intl, state.schema.nodes.mediaSingle, widthPlugin, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a4 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a4 === void 0 ? void 0 : _pluginInjectionApi$a4.actions, allowResizing, allowResizingInTables, true, true, isChangingLayoutDisabled, allowPixelResizing);
toolbarButtons = [...toolbarButtons, ...layoutDropdown];
} else {
const selectedLayoutIcon = getSelectedLayoutIcon([...alignmentIcons, ...wrappingIcons],
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
selectedNode.node);
if (selectedLayoutIcon && layoutButtons.length) {
const options = {
render: props => {
return /*#__PURE__*/React.createElement(LayoutGroup, _extends({
layoutButtons: layoutButtons
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
}, props, {
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
}));
},
width: 188,
height: 32
};
const trigger = {
id: 'media-single-layout',
testId: 'media-single-layout-dropdown-trigger',
type: 'dropdown',
options: options,
title: intl.formatMessage(layoutToMessages[selectedLayoutIcon.value]),
icon: selectedLayoutIcon.icon
};
toolbarButtons = [...toolbarButtons, trigger, ...(areAnyNewToolbarFlagsEnabled ? [] : [{
type: 'separator'
}])];
}
}
};
if (fg('platform_editor_remove_media_inline_feature_flag')) {
if (allowMediaInlineImages && selectedNode) {
addLayoutDropdownToToolbar();
} else {
toolbarButtons = [...toolbarButtons, ...layoutButtons];
if (layoutButtons.length) {
toolbarButtons.push({
type: 'separator'
});
}
}
} else {
if (mediaInlineImagesEnabled(allowMediaInline, allowMediaInlineImages) && selectedNode) {
addLayoutDropdownToToolbar();
} else {
toolbarButtons = [...toolbarButtons, ...layoutButtons];
if (layoutButtons.length && !areAnyNewToolbarFlagsEnabled) {
toolbarButtons.push({
type: 'separator'
});
}
}
}
// floating and inline switcher
if (pluginState.allowInlineImages && selectedNode && canShowSwitchButtons(selectedNode.node)) {
var _selectedNode$node$fi;
// mediaInlne doesn't suppprt external images, so hiding buttons to prevent conversion from mediaSingle to mediaInline
if (((_selectedNode$node$fi = selectedNode.node.firstChild) === null || _selectedNode$node$fi === void 0 ? void 0 : _selectedNode$node$fi.attrs.type) !== 'external') {
const hasCaption = contains(selectedNode.node, state.schema.nodes.caption);
const inlineSwitcherTitle = intl.formatMessage(hasCaption ? mediaAndEmbedToolbarMessages.changeToMediaInlineImageCaptionWarning : mediaAndEmbedToolbarMessages.changeToMediaInlineImage);
const floatingSwitcherTitle = intl.formatMessage(mediaAndEmbedToolbarMessages.changeToMediaSingle);
if (!areAnyNewToolbarFlagsEnabled) {
var _pluginInjectionApi$a5;
toolbarButtons.push({
type: 'button',
id: 'editor.media.image.view.switcher.inline',
title: inlineSwitcherTitle,
icon: () => /*#__PURE__*/React.createElement(ImageInlineIcon, {
color: "currentColor",
spacing: "spacious",
label: inlineSwitcherTitle
}),
onClick: changeMediaSingleToMediaInline(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a5 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a5 === void 0 ? void 0 : _pluginInjectionApi$a5.actions),
testId: 'image-inline-appearance',
selected: false
}, {
type: 'button',
id: 'editor.media.image.view.switcher.floating',
title: floatingSwitcherTitle,
icon: () => /*#__PURE__*/React.createElement(ImageFullscreenIcon, {
color: "currentColor",
spacing: "spacious",
label: floatingSwitcherTitle
}),
onClick: () => {
return true;
},
testId: 'image-floating-appearance',
selected: true
}, {
type: 'separator'
});
} else {
const switchFromBlockToInline = getMediaSingleAndMediaInlineSwitcherDropdown('block', intl, pluginInjectionApi, hasCaption);
toolbarButtons.push(switchFromBlockToInline, {
type: 'separator',
fullHeight: true
});
}
}
}
// A separator is needed regardless switcher is enabled or not
if (Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar)) {
toolbarButtons.push({
type: 'separator',
fullHeight: true
});
}
// Pixel Entry Toolbar Support
const {
selection
} = state;
const isWithinTable = hasParentNodeOfType([state.schema.nodes.table])(selection);
if (allowResizing && (!isWithinTable || allowResizingInTables === true) && allowPixelResizing) {
const selectedMediaSingleNode = getSelectedMediaSingle(state);
const sizeInput = {
type: 'custom',
fallback: [],
render: editorView => {
if (!editorView || !selectedMediaSingleNode) {
return null;
}
return /*#__PURE__*/React.createElement(PixelEntry, {
editorView: editorView,
intl: intl,
selectedMediaSingleNode: selectedMediaSingleNode,
pluginInjectionApi: pluginInjectionApi,
pluginState: pluginState,
hoverDecoration: hoverDecoration,
isEditorFullWidthEnabled: options.fullWidthEnabled
});
}
};
if (pluginState.isResizing) {
// If the image is resizing
// then return pixel entry component or full width label as the only toolbar item
if (!selectedMediaSingleNode) {
return [];
}
const {
layout
} = selectedMediaSingleNode.node.attrs;
if (layout === 'full-width') {
const fullWidthLabel = {
type: 'custom',
fallback: [],
render: () => {
return /*#__PURE__*/React.createElement(FullWidthDisplay, {
intl: intl
});
}
};
return [fullWidthLabel];
}
return [sizeInput];
}
if (!areAnyNewToolbarFlagsEnabled) {
toolbarButtons.push(sizeInput);
toolbarButtons.push({
type: 'separator'
});
}
}
if (!areAnyNewToolbarFlagsEnabled) {
if (allowCommentsOnMedia) {
toolbarButtons.push(commentButton(intl, state, pluginInjectionApi, onCommentButtonMount, createCommentExperience), {
type: 'separator',
supportsViewMode: true
});
}
if (allowLinking && shouldShowMediaLinkToolbar(state)) {
toolbarButtons.push({
type: 'custom',
fallback: [],
render: (editorView, idx) => {
if (editorView !== null && editorView !== void 0 && editorView.state) {
const editLink = () => {
if (editorView) {
const {
state,
dispatch
} = editorView;
showLinkingToolbar(state, dispatch);
}
};
const openLink = () => {
if (editorView) {
var _pluginInjectionApi$a6;
const {
state: {
tr
},
dispatch
} = editorView;
pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a6 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a6 === void 0 ? void 0 : _pluginInjectionApi$a6.actions.attachAnalyticsEvent({
eventType: EVENT_TYPE.TRACK,
action: ACTION.VISITED,
actionSubject: ACTION_SUBJECT.MEDIA,
actionSubjectId: ACTION_SUBJECT_ID.LINK
})(tr);
dispatch(tr);
return true;
}
};
return /*#__PURE__*/React.createElement(LinkToolbarAppearance, {
key: idx,
editorState: editorView.state,
intl: intl,
mediaLinkingState: mediaLinkingState,
onAddLink: editLink,
onEditLink: editLink,
onOpenLink: openLink,
isViewOnly: isViewOnly,
areAnyNewToolbarFlagsEnabled: false
});
}
return null;
},
supportsViewMode: true
});
}
// Image Editing Support
if (!!(pluginInjectionApi !== null && pluginInjectionApi !== void 0 && pluginInjectionApi.mediaEditing) && allowImageEditing && expValEquals('platform_editor_add_image_editing', 'isEnabled', true)) {
var _mediaNode$attrs, _mediaNode$attrs2, _mediaNode$attrs3;
const selectedMediaSingleNode = getSelectedMediaSingle(state);
const mediaNode = selectedMediaSingleNode === null || selectedMediaSingleNode === void 0 ? void 0 : selectedMediaSingleNode.node.content.firstChild;
// Disable image editing for external media, as we cannot save changes to external images per CORS policy
const isExternal = (mediaNode === null || mediaNode === void 0 ? void 0 : (_mediaNode$attrs = mediaNode.attrs) === null || _mediaNode$attrs === void 0 ? void 0 : _mediaNode$attrs.type) === 'external';
// Disable image editing for gifs as CropperJS does not support gif editing
const isGif = (mediaNode === null || mediaNode === void 0 ? void 0 : (_mediaNode$attrs2 = mediaNode.attrs) === null || _mediaNode$attrs2 === void 0 ? void 0 : _mediaNode$attrs2.__fileMimeType) === 'image/gif';
if (!isVideo(mediaNode === null || mediaNode === void 0 ? void 0 : (_mediaNode$attrs3 = mediaNode.attrs) === null || _mediaNode$attrs3 === void 0 ? void 0 : _mediaNode$attrs3.__fileMimeType) && !isExternal && !isGif) {
toolbarButtons.push({
id: 'editor.media.edit',
testId: 'image-edit-toolbar-button',
type: 'button',
icon: ImageCropIcon,
title: intl.formatMessage(commonMessages.imageEdit),
onClick: () => {
var _handleShowImageEdito;
return (_handleShowImageEdito = handleShowImageEditor({
api: pluginInjectionApi,
mediaPluginState: pluginState
})) !== null && _handleShowImageEdito !== void 0 ? _handleShowImageEdito : false;
},
// Making the button more accessible
supportsViewMode: false,
isRadioButton: true,
selected: false
});
}
}
// Preview Support
if (allowImagePreview) {
var _mediaNode$attrs4;
const selectedMediaSingleNode = getSelectedMediaSingle(state);
const mediaNode = selectedMediaSingleNode === null || selectedMediaSingleNode === void 0 ? void 0 : selectedMediaSingleNode.node.content.firstChild;
if (!isVideo(mediaNode === null || mediaNode === void 0 ? void 0 : (_mediaNode$attrs4 = mediaNode.attrs) === null || _mediaNode$attrs4 === void 0 ? void 0 : _mediaNode$attrs4.__fileMimeType)) {
var _pluginInjectionApi$c4, _pluginInjectionApi$c5, _pluginInjectionApi$c6;
toolbarButtons.push({
id: 'editor.media.viewer',
testId: 'file-preview-toolbar-button',
type: 'button',
icon: MaximizeIcon,
title: intl.formatMessage(messages.preview),
onClick: () => {
var _handleShowMediaViewe2;
return (_handleShowMediaViewe2 = handleShowMediaViewer({
api: pluginInjectionApi,
mediaPluginState: pluginState
})) !== null && _handleShowMediaViewe2 !== void 0 ? _handleShowMediaViewe2 : false;
},
disabled: isOfflineMode(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c4 = pluginInjectionApi.connectivity) === null || _pluginInjectionApi$c4 === void 0 ? void 0 : (_pluginInjectionApi$c5 = _pluginInjectionApi$c4.sharedState) === null || _pluginInjectionApi$c5 === void 0 ? void 0 : (_pluginInjectionApi$c6 = _pluginInjectionApi$c5.currentState()) === null || _pluginInjectionApi$c6 === void 0 ? void 0 : _pluginInjectionApi$c6.mode),
supportsViewMode: true
}, {
type: 'separator',
supportsViewMode: true
});
}
}
}
}
if (isViewOnly) {
toolbarButtons.push({
id: 'editor.media.image.download',
type: 'button',
icon: DownloadIcon,
onClick: () => {
downloadMedia(pluginState, isViewOnly);
return true;
},
disabled: disableDownloadButton,
title: intl.formatMessage(messages.download),
supportsViewMode: true
}, {
type: 'separator',
supportsViewMode: true
});
}
if (!areAnyNewToolbarFlagsEnabled) {
var _pluginInjectionApi$a8;
if (allowAltTextOnImages) {
var _pluginInjectionApi$a7;
toolbarButtons.push(altTextButton(intl, state, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a7 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a7 === void 0 ? void 0 : _pluginInjectionApi$a7.actions), {
type: 'separator'
});
}
const removeButton = {
id: 'editor.media.delete',
type: 'button',
appearance: 'danger',
focusEditoronEnter: true,
icon: DeleteIcon,
onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(mediaSingle, true),
onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(mediaSingle, false),
onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(mediaSingle, true),
onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(mediaSingle, false),
title: intl.formatMessage(commonMessages.remove),
onClick: removeWithAnalytics(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a8 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a8 === void 0 ? void 0 : _pluginInjectionApi$a8.actions),
testId: 'media-toolbar-remove-button',
supportsViewMode: false
};
const items = [...toolbarButtons, {
type: 'copy-button',
items: [{
state,
formatMessage: intl.formatMessage,
nodeType: mediaSingle
}],
supportsViewMode: true
}];
items.push({
type: 'separator',
supportsViewMode: false
});
items.push(removeButton);
return items;
} else {
// Preview Support
if (allowAdvancedToolBarOptions && allowImagePreview) {
var _mediaNode$attrs5;
const selectedMediaSingleNode = getSelectedMediaSingle(state);
const mediaNode = selectedMediaSingleNode === null || selectedMediaSingleNode === void 0 ? void 0 : selectedMediaSingleNode.node.content.firstChild;
if (!isVideo(mediaNode === null || mediaNode === void 0 ? void 0 : (_mediaNode$attrs5 = mediaNode.attrs) === null || _mediaNode$attrs5 === void 0 ? void 0 : _mediaNode$attrs5.__fileMimeType)) {
var _pluginInjectionApi$c7, _pluginInjectionApi$c8, _pluginInjectionApi$c9;
toolbarButtons.push({
id: 'editor.media.viewer',
testId: 'file-preview-toolbar-button',
type: 'button',
icon: GrowDiagonalIcon,
title: intl.formatMessage(messages.preview),
onClick: () => {
var _handleShowMediaViewe3;
return (_handleShowMediaViewe3 = handleShowMediaViewer({
api: pluginInjectionApi,
mediaPluginState: pluginState
})) !== null && _handleShowMediaViewe3 !== void 0 ? _handleShowMediaViewe3 : false;
},
disabled: isOfflineMode(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c7 = pluginInjectionApi.connectivity) === null || _pluginInjectionApi$c7 === void 0 ? void 0 : (_pluginInjectionApi$c8 = _pluginInjectionApi$c7.sharedState) === null || _pluginInjectionApi$c8 === void 0 ? void 0 : (_pluginInjectionApi$c9 = _pluginInjectionApi$c8.currentState()) === null || _pluginInjectionApi$c9 === void 0 ? void 0 : _pluginInjectionApi$c9.mode),
supportsViewMode: true
}, {
type: 'separator',
supportsViewMode: true
});
}
}
if (allowAdvancedToolBarOptions && allowImageEditing && expValEquals('platform_editor_add_image_editing', 'isEnabled', true)) {
var _mediaNode$attrs6, _mediaNode$attrs7, _mediaNode$attrs8;
const selectedMediaSingleNode = getSelectedMediaSingle(state);
const mediaNode = selectedMediaSingleNode === null || selectedMediaSingleNode === void 0 ? void 0 : selectedMediaSingleNode.node.content.firstChild;
// Disable image editing for external media, as we cannot save changes to external images per CORS policy
const isExternal = (mediaNode === null || mediaNode === void 0 ? void 0 : (_mediaNode$attrs6 = mediaNode.attrs) === null || _mediaNode$attrs6 === void 0 ? void 0 : _mediaNode$attrs6.type) === 'external';
// Disable image editing for gifs as CropperJS does not support gif editing
const isGif = (mediaNode === null || mediaNode === void 0 ? void 0 : (_mediaNode$attrs7 = mediaNode.attrs) === null || _mediaNode$attrs7 === void 0 ? void 0 : _mediaNode$attrs7.__fileMimeType) === 'image/gif';
if (!isVideo(mediaNode === null || mediaNode === void 0 ? void 0 : (_mediaNode$attrs8 = mediaNode.attrs) === null || _mediaNode$attrs8 === void 0 ? void 0 : _mediaNode$attrs8.__fileMimeType) && !isExternal && !isGif) {
toolbarButtons.push({
id: 'editor.media.edit',
testId: 'image-edit-toolbar-button',
type: 'button',
icon: ImageCropIcon,
title: intl.formatMessage(commonMessages.imageEdit),
onClick: () => {
var _handleShowImageEdito2;
return (_handleShowImageEdito2 = handleShowImageEditor({
api: pluginInjectionApi,
mediaPluginState: pluginState
})) !== null && _handleShowImageEdito2 !== void 0 ? _handleShowImageEdito2 : false;
},
supportsViewMode: false
}, {
type: 'separator',
supportsViewMode: false
});
}
}
// open link
if (allowLinking && shouldShowMediaLinkToolbar(state) && mediaLinkingState && mediaLinkingState.editable) {
toolbarButtons.push(getOpenLinkToolbarButtonOption(intl, mediaLinkingState, pluginInjectionApi), {
type: 'separator',
supportsViewMode: true
});
}
isViewOnly && toolbarButtons.push({
type: 'copy-button',
items: [{
state,
formatMessage: intl.formatMessage,
nodeType: mediaSingle
}],
supportsViewMode: true
}, {
type: 'separator',
supportsViewMode: true
});
if (allowAdvancedToolBarOptions && allowCommentsOnMedia) {
updateToFullHeightSeparator(toolbarButtons);
toolbarButtons.push(commentButton(intl, state, pluginInjectionApi, onCommentButtonMount, createCommentExperience));
}
return toolbarButtons;
}
};
const getMediaTypeMessage = selectedNodeTypeSingle => {
const mediaType = Object.keys(mediaTypeMessages).find(key => selectedNodeTypeSingle === null || selectedNodeTypeSingle === void 0 ? void 0 : selectedNodeTypeSingle.includes(key));
return mediaType ? mediaTypeMessages[mediaType] : messages.file_unknown_is_selected;
};
export const overflowDropdwonBtnTriggerTestId = 'media-overflow-dropdown-trigger';
const isMediaSelection = (selection, nodeType) => {
if (selection instanceof NodeSelection) {
return nodeType.includes(selection.node.type);
}
return false;
};
export const floatingToolbar = (state, intl, options = {}, pluginInjectionApi) => {
var _pluginInjectionApi$d3, _pluginInjectionApi$d4;
const {
media,
mediaInline,
mediaSingle,
mediaGroup
} = state.schema.nodes;
const {
altTextValidator,
allowLinking,
allowAltTextOnImages,
providerFactory,
allowMediaInlineImages,
allowResizing,
isViewOnly,
allowResizingInTables,
allowAdvancedToolBarOptions,
allowPixelResizing
} = options;
let {
allowMediaInline
} = options;
allowMediaInline = fg('platform_editor_remove_media_inline_feature_flag') ? allowMediaInlineImages : allowMediaInline;
const mediaPluginState = stateKey.getState(state);
const mediaLinkingState = getMediaLinkingState(state);
const {
hoverDecoration
} = (_pluginInjectionApi$d3 = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$d4 = pluginInjectionApi.decorations) === null || _pluginInjectionApi$d4 === void 0 ? void 0 : _pluginInjectionApi$d4.actions) !== null && _pluginInjectionApi$d3 !== void 0 ? _pluginInjectionApi$d3 : {};
if (!mediaPluginState) {
return;
}
const nodeType = allowMediaInline ? [mediaInline, mediaSingle, media] : [mediaSingle];
const isSelectedNodeMediaSingle = state.selection instanceof NodeSelection && state.selection.node.type === mediaSingle;
if (!isMediaSelection(state.selection, nodeType)) {
return;
}
const baseToolbar = {
title: 'Media floating controls',
nodeType,
getDomRef: () => {
var _mediaPluginState$ele;
const element = isSelectedNodeMediaSingle ? ((_mediaPluginState$ele = mediaPluginState.element) === null || _mediaPluginState$ele === void 0 ? void 0 : _mediaPluginState$ele.querySelector(`.${MediaSingleNodeSelector}`)) || mediaPluginState.element : mediaPluginState.element;
return element;
}
};
if (allowLinking && mediaLinkingState && mediaLinkingState.visible && shouldShowMediaLinkToolbar(state)) {
const linkingToolbar = getLinkingToolbar(baseToolbar, mediaLinkingState, state, intl, pluginInjectionApi, providerFactory);
if (linkingToolbar) {
return linkingToolbar;
}
}
// testId is required to show focus on trigger button on ESC key press
// see hideOnEsc in platform/packages/editor/editor-plugin-floating-toolbar/src/ui/Dropdown.tsx
const overflowButtonSelector = areToolbarFlagsEnabled(Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar)) ? `[data-testid="${overflowDropdwonBtnTriggerTestId}"]` : undefined;
if (allowAltTextOnImages) {
const mediaAltTextPluginState = getMediaAltTextPluginState(state);
if (mediaAltTextPluginState.isAltTextEditorOpen) {
var _pluginInjectionApi$f, _pluginInjectionApi$f2;
return getAltTextToolbar(baseToolbar, {
altTextValidator,
forceFocusSelector: pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$f = pluginInjectionApi.floatingToolbar) === null || _pluginInjectionApi$f === void 0 ? void 0 : (_pluginInjectionApi$f2 = _pluginInjectionApi$f.actions) === null || _pluginInjectionApi$f2 === void 0 ? void 0 : _pluginInjectionApi$f2.forceFocusSelector,
triggerButtonSelector: overflowButtonSelector,
areAnyNewToolbarFlagsEnabled: areToolbarFlagsEnabled(Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar))
});
}
}
const {
selection
} = state;
const isWithinTable = hasParentNodeOfType([state.schema.nodes.table])(selection);
if (allowAdvancedToolBarOptions && allowResizing && (!isWithinTable || allowResizingInTables === true) && allowPixelResizing && areToolbarFlagsEnabled(Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar))) {
const mediaPixelResizingPluginState = getMediaPixelResizingPluginState(state);
if (mediaPixelResizingPluginState !== null && mediaPixelResizingPluginState !== void 0 && mediaPixelResizingPluginState.isPixelEditorOpen) {
return getPixelResizingToolbar(baseToolbar, {
intl,
pluginInjectionApi,
pluginState: mediaPluginState,
hoverDecoration,
isEditorFullWidthEnabled: options.fullWidthEnabled,
triggerButtonSelector: overflowButtonSelector
});
}
}
let items = [];
const parentMediaGroupNode = findParentNodeOfType(mediaGroup)(state.selection);
let selectedNodeType;
if (state.selection instanceof NodeSelection) {
selectedNodeType = state.selection.node.type;
}
if (allowMediaInline && (parentMediaGroupNode === null || parentMediaGroupNode === void 0 ? void 0 : parentMediaGroupNode.node.type) === mediaGroup) {
var _pluginInjectionApi$a9, _pluginInjectionApi$f3, _pluginInjectionApi$f4;
const mediaOffset = state.selection.$from.parentOffset + 1;
baseToolbar.getDomRef = () => {
var _mediaPluginState$ele2;
const selector = mediaFilmstripItemDOMSelector(mediaOffset);
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
return (_mediaPluginState$ele2 = mediaPluginState.element) === null || _mediaPluginState$ele2 === void 0 ? void 0 : _mediaPluginState$ele2.querySelector(selector);
};
items = generateMediaCardFloatingToolbar(state, intl, mediaPluginState, hoverDecoration, pluginInjectionApi, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a9 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a9 === void 0 ? void 0 : _pluginInjectionApi$a9.actions, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$f3 = pluginInjectionApi.floatingToolbar) === null || _pluginInjectionApi$f3 === void 0 ? void 0 : (_pluginInjectionApi$f4 = _pluginInjectionApi$f3.actions) === null || _pluginInjectionApi$f4 === void 0 ? void 0 : _pluginInjectionApi$f4.forceFocusSelector, isViewOnly);
} else if (allowMediaInline && selectedNodeType && selectedNodeType === mediaInline) {
items = generateMediaInlineFloatingToolbar(state, intl, mediaPluginState, hoverDecoration, pluginInjectionApi, options);
} else {
baseToolbar.getDomRef = () => {
var _mediaPluginState$ele3;
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
const element = (_mediaPluginState$ele3 = mediaPluginState.element) === null || _mediaPluginState$ele3 === void 0 ? void 0 : _mediaPluginState$ele3.querySelector(`.${MediaSingleNodeSelector}`);
return element || mediaPluginState.element;
};
items = generateMediaSingleFloatingToolbar(state, intl, options, mediaPluginState, mediaLinkingState, pluginInjectionApi);
}
if (!mediaPluginState.isResizing && areToolbarFlagsEnabled(Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar))) {
var _pluginInjectionApi$a0, _pluginInjectionApi$a1, _pluginInjectionApi$a10;
updateToFullHeightSeparator(items);
const customOptions = [...getLinkingDropdownOptions(state, intl, mediaLinkingState, allowMediaInline && selectedNodeType && selectedNodeType === mediaInline, allowLinking, isViewOnly), ...getAltTextDropdownOption(state, intl.formatMessage, allowAltTextOnImages, selectedNodeType, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a0 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a0 === void 0 ? void 0 : _pluginInjectionApi$a0.actions), ...getResizeDropdownOption(options, state, intl.formatMessage, selectedNodeType)];
if (customOptions.length) {
customOptions.push({
type: 'separator'
});
}
const hoverDecorationProps = (nodeType, className) => ({
onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true, className),
onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false, className),
onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true, className),
onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false, className)
});
items.push({
type: 'overflow-dropdown',
id: 'media',
testId: overflowDropdwonBtnTriggerTestId,
options: [...customOptions, {
title: intl === null || intl === void 0 ? void 0 : intl.formatMessage(commonMessages.copyToClipboard),
onClick: () => {
var _pluginInjectionApi$c0, _pluginInjectionApi$f5;
pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c0 = pluginInjectionApi.core) === null || _pluginInjectionApi$c0 === void 0 ? void 0 : _pluginInjectionApi$c0.actions.execute( // @ts-ignore
pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$f5 = pluginInjectionApi.floatingToolbar) === null || _pluginInjectionApi$f5 === void 0 ? void 0 : _pluginInjectionApi$f5.commands.copyNode(nodeType, INPUT_METHOD.FLOATING_TB));
return true;
},
icon: /*#__PURE__*/React.createElement(CopyIcon, {
label: ""
}),
...hoverDecorationProps(nodeType, akEditorSelectedNodeClassName)
}, {
title: intl === null || intl === void 0 ? void 0 : intl.formatMessage(commonMessages.delete),
onClick: (parentMediaGroupNode === null || parentMediaGroupNode === void 0 ? void 0 : parentMediaGroupNode.node.type) === mediaGroup ? handleRemoveMediaGroupWithAnalytics(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a1 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a1 === void 0 ? void 0 : _pluginInjectionApi$a1.actions) : removeWithAnalytics(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a10 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a10 === void 0 ? void 0 : _pluginInjectionApi$a10.actions, selectedNodeType),
icon: /*#__PURE__*/React.createElement(DeleteIcon, {
label: ""
}),
...hoverDecorationProps(nodeType)
}]
});
}
// Ignored via go/ees005
// eslint-disable-next-line no-var
var assistiveMessage = '';
const selectedMediaSingleNode = getSelectedMediaSingle(state);
if (selectedMediaSingleNode) {
const selectedMediaNodeView = selectedMediaSingleNode === null || selectedMediaSingleNode === void 0 ? void 0 : selectedMediaSingleNode.node.content;
if (selectedMediaNodeView) {
const selectedMediaNode = selectedMediaNodeView.firstChild;
const selectedNodeTypeSingle = selectedMediaNode === null || selectedMediaNode === void 0 ? void 0 : selectedMediaNode.attrs.__fileMimeType;
const selectedMediaAlt = selectedMediaNode === null || selectedMediaNode === void 0 ? void 0 : selectedMediaNode.attrs.alt;
assistiveMessage = intl === null || intl === void 0 ? void 0 : intl.formatMessage(getMediaTypeMessage(selectedNodeTypeSingle), {
name: selectedMediaAlt
});
}
}
const toolbarConfig = {
...baseToolbar,
items,
scrollable: true,
mediaAssistiveMessage: assistiveMessage
};
if (allowResizing && allowPixelResizing) {
return {
...toolbarConfig,
width: mediaPluginState.isResizing ? undefined : getMaxToolbarWidth()
};
}
return toolbarConfig;
};