@atlaskit/editor-plugin-media
Version:
Media plugin for @atlaskit/editor-core
209 lines (207 loc) • 9.26 kB
JavaScript
import React from 'react';
import memoizeOne from 'memoize-one';
import { wrappedLayouts } from '@atlaskit/editor-common/media-single';
import { mediaAndEmbedToolbarMessages } from '@atlaskit/editor-common/messages';
import { nonWrappedLayouts } from '@atlaskit/editor-common/utils';
import { findParentNodeOfType, findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode } from '@atlaskit/editor-prosemirror/utils';
import { akEditorFullWidthLayoutWidth } from '@atlaskit/editor-shared-styles';
import ImageInlineIcon from '@atlaskit/icon/core/image-inline';
import MaximizeIcon from '@atlaskit/icon/core/maximize';
import { getMediaClient } from '@atlaskit/media-client-react';
import { messages } from '@atlaskit/media-ui';
import { isVideo } from '../../pm-plugins/utils/media-single';
import { changeMediaInlineToMediaSingle, changeMediaSingleToMediaInline } from './commands';
export const isExternalMedia = attrs => {
return attrs.type === 'external';
};
const getSelectedMediaContainerNodeAttrs = mediaPluginState => {
var _mediaPluginState$sel;
const selectedNode = (_mediaPluginState$sel = mediaPluginState.selectedMediaContainerNode) === null || _mediaPluginState$sel === void 0 ? void 0 : _mediaPluginState$sel.call(mediaPluginState);
if (selectedNode && selectedNode.attrs) {
return selectedNode.attrs;
}
return null;
};
export const getSelectedNearestMediaContainerNodeAttrsFunction = selectedMediaContainerNode => {
const selectedNode = selectedMediaContainerNode === null || selectedMediaContainerNode === void 0 ? void 0 : selectedMediaContainerNode();
if (selectedNode) {
switch (selectedNode.type.name) {
case 'mediaSingle':
{
const childNode = selectedNode.firstChild;
return childNode === null || childNode === void 0 ? void 0 : childNode.attrs;
}
default:
return selectedNode.attrs;
}
}
return null;
};
export const getSelectedNearestMediaContainerNodeAttrs = mediaPluginState => {
return getSelectedNearestMediaContainerNodeAttrsFunction(mediaPluginState.selectedMediaContainerNode);
};
export const getIsDownloadDisabledByDataSecurityPolicy = mediaPluginState => {
var _mediaPluginState$med;
const enforceMediaDataSecurityPolicy = mediaPluginState === null || mediaPluginState === void 0 ? void 0 : (_mediaPluginState$med = mediaPluginState.mediaClientConfig) === null || _mediaPluginState$med === void 0 ? void 0 : _mediaPluginState$med.enforceDataSecurityPolicy;
return typeof enforceMediaDataSecurityPolicy === 'boolean' ? enforceMediaDataSecurityPolicy : false;
};
export const downloadMedia = async (mediaPluginState, isViewMode) => {
try {
const selectedNodeAttrs = isViewMode ? getSelectedNearestMediaContainerNodeAttrs(mediaPluginState) : getSelectedMediaContainerNodeAttrs(mediaPluginState);
if (selectedNodeAttrs && mediaPluginState.mediaClientConfig && !isExternalMedia(selectedNodeAttrs)) {
const {
id,
collection = ''
} = selectedNodeAttrs;
const mediaClient = getMediaClient(mediaPluginState.mediaClientConfig);
const fileState = await mediaClient.file.getCurrentState(id, {
collectionName: collection
});
const fileName = fileState.status === 'error' ? undefined : fileState.name;
mediaClient.file.downloadBinary(id, fileName, collection);
}
return true;
} catch (err) {
return false;
}
};
export const removeMediaGroupNode = state => {
const {
mediaGroup
} = state.schema.nodes;
const mediaGroupParent = findParentNodeOfType(mediaGroup)(state.selection);
let tr = state.tr;
// If it is the last media group in filmstrip, remove the entire filmstrip
if (mediaGroupParent && mediaGroupParent.node.childCount === 1) {
tr = removeParentNodeOfType(mediaGroup)(tr);
} else {
tr = removeSelectedNode(tr);
}
return tr;
};
export const getSelectedMediaSingle = state => {
const {
mediaSingle
} = state.schema.nodes;
return findSelectedNodeOfType(mediaSingle)(state.selection) || findParentNodeOfType(mediaSingle)(state.selection);
};
export const getPixelWidthOfElement = memoizeOne((editorView, pos, mediaWidth) => {
const domNode = editorView.nodeDOM(pos);
if (domNode instanceof HTMLElement) {
return domNode.offsetWidth;
}
return mediaWidth;
});
export const calcNewLayout = (width, layout, contentWidth, fullWidthMode = false, isNested = false) => {
const isWrappedLayout = wrappedLayouts.indexOf(layout) > -1;
//See flowchart for layout logic: https://hello.atlassian.net/wiki/spaces/TWPCP/whiteboard/2969594044
if (width >= akEditorFullWidthLayoutWidth) {
// If width is greater than or equal to full editor width
return 'full-width';
}
if (fullWidthMode) {
// If under editor full width mode
return isWrappedLayout ? layout : 'center';
}
if (width > contentWidth && !isNested) {
// If width is greater than content length and not nested
return 'wide';
}
return isNested || isWrappedLayout && width !== contentWidth ? layout : 'center';
};
let maxToolbarFitWidth = 0;
export const getMaxToolbarWidth = () => {
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
const toolbar = document.querySelector(`div[aria-label="Media floating controls"]`);
const toolbarWidth = toolbar === null || toolbar === void 0 ? void 0 : toolbar.getBoundingClientRect().width;
if (!toolbar) {
maxToolbarFitWidth = 0;
}
if (toolbarWidth && toolbarWidth > maxToolbarFitWidth) {
maxToolbarFitWidth = toolbarWidth;
}
return maxToolbarFitWidth;
};
export const getSelectedLayoutIcon = (layoutIcons, selectedNode) => {
const selectedLayout = selectedNode.attrs.layout;
return layoutIcons.find(icon => icon.value === (nonWrappedLayouts.includes(selectedLayout) ? 'center' : selectedLayout));
};
/**
* Check if 'original size' and 'inline' buttons can be shown in the toolbar for a given mediaSingle node.
* @param mediaSingleNode node to be checked
*/
export const canShowSwitchButtons = mediaSingleNode => {
if (mediaSingleNode) {
const mediaNode = mediaSingleNode.content.firstChild;
return mediaNode && !isVideo(mediaNode.attrs.__fileMimeType);
}
return false;
};
export const updateToFullHeightSeparator = items => {
const lastItem = items.at(-1);
if ((lastItem === null || lastItem === void 0 ? void 0 : lastItem.type) === 'separator') {
lastItem.fullHeight = true;
} else if (items.length) {
items.push({
type: 'separator',
fullHeight: true
});
}
};
export const getMediaSingleAndMediaInlineSwitcherDropdown = (nodeType, intl, pluginInjectionApi, hasCaption = false) => {
var _pluginInjectionApi$a, _pluginInjectionApi$a2;
const mediaInlineImageTitle = intl.formatMessage(mediaAndEmbedToolbarMessages.changeToMediaInlineImage);
const mediaSingleTitle = intl.formatMessage(mediaAndEmbedToolbarMessages.changeToMediaSingle);
const inlineSwitcherTitle = intl.formatMessage(hasCaption ? mediaAndEmbedToolbarMessages.changeToMediaInlineImageCaptionWarning : mediaAndEmbedToolbarMessages.changeToMediaInlineImage);
const InlineIcon = () => /*#__PURE__*/React.createElement(ImageInlineIcon, {
color: "currentColor",
spacing: "spacious",
label: ""
});
const BlockIcon = () => /*#__PURE__*/React.createElement(MaximizeIcon, {
color: "currentColor",
spacing: "spacious",
label: ""
});
const dropdownConfig = {
inline: {
handleInlineButtonClick: () => {
return true;
},
handleBlockButtonClick: changeMediaInlineToMediaSingle(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.width.sharedState.currentState()),
id: 'media-inline-to-block-toolbar-item',
icon: InlineIcon
},
block: {
handleInlineButtonClick: changeMediaSingleToMediaInline(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a2 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a2 === void 0 ? void 0 : _pluginInjectionApi$a2.actions),
handleBlockButtonClick: () => {
return true;
},
id: 'media-block-to-inline-toolbar-item',
icon: BlockIcon
}
};
const options = [{
id: 'editor.media.convert.mediainline',
title: mediaInlineImageTitle,
onClick: dropdownConfig[nodeType].handleInlineButtonClick,
selected: nodeType === 'inline',
icon: /*#__PURE__*/React.createElement(InlineIcon, null),
tooltip: hasCaption ? inlineSwitcherTitle : undefined
}, {
id: 'editor.media.convert.mediasingle',
title: mediaSingleTitle,
onClick: dropdownConfig[nodeType].handleBlockButtonClick,
selected: nodeType === 'block',
icon: /*#__PURE__*/React.createElement(BlockIcon, null)
}];
return {
id: dropdownConfig[nodeType].id,
testId: 'media-inline-to-block-dropdown',
title: intl.formatMessage(messages.sizeOptions),
type: 'dropdown',
options,
icon: dropdownConfig[nodeType].icon
};
};