UNPKG

@atlaskit/editor-plugin-media

Version:

Media plugin for @atlaskit/editor-core

271 lines (268 loc) 9.96 kB
/** * @jsxRuntime classic * @jsx jsx */ import { useEffect, useMemo, useState } from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports import { jsx } from '@emotion/react'; import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'; import { MediaInlineImageCard } from '@atlaskit/editor-common/media-inline'; import { WithProviders } from '@atlaskit/editor-common/provider-factory'; import { SelectionBasedNodeView } from '@atlaskit/editor-common/selection-based-node-view'; import { MediaInlineCard } from '@atlaskit/media-card'; import { getMediaClient } from '@atlaskit/media-client-react'; import { MediaInlineCardLoadingView } from '@atlaskit/media-ui'; import { isImage } from '../pm-plugins/utils/is-type'; import { MediaViewerContainer } from '../ui/MediaViewer/MediaViewerContainer'; import { MediaNodeUpdater } from './mediaNodeUpdater'; const createMediaNodeUpdater = props => { const node = props.node; return new MediaNodeUpdater({ ...props, isMediaSingle: true, node: node ? node : props.node, dispatchAnalyticsEvent: props.dispatchAnalyticsEvent, contextIdentifierProvider: props.contextIdentifierProvider }); }; /** * Handles updating the media inline node attributes * but also handling copy-paste for cross-editor of the same instance * using the contextid * */ const updateMediaNodeAttributes = async (props, mediaNodeUpdater) => { const { addPendingTask } = props; const node = props.node; if (!node) { return; } const contextId = mediaNodeUpdater.getNodeContextId(); if (!contextId) { await mediaNodeUpdater.updateContextId(); } const shouldNodeBeDeepCopied = await mediaNodeUpdater.shouldNodeBeDeepCopied(); if (shouldNodeBeDeepCopied) { // Copy paste flow (different pages) try { const copyNode = mediaNodeUpdater.copyNode({ traceId: node.attrs.__mediaTraceId }); addPendingTask(copyNode); await copyNode; } catch { return; } } await mediaNodeUpdater.updateMediaSingleFileAttrs(); }; export const handleNewNode = props => { const { node, handleMediaNodeMount, getPos } = props; handleMediaNodeMount(node, () => getPos()); }; export const MediaInline = props => { var _props$node, _props$node$marks; const [viewMediaClientConfig, setViewMediaClientConfig] = useState(); const [isNodeScopeUnsync, setIsNodeScopeUnsync] = useState(true); useEffect(() => { const mediaNodeUpdater = createMediaNodeUpdater(props); mediaNodeUpdater.shouldNodeBeDeepCopied().then(setIsNodeScopeUnsync); handleNewNode(props); updateMediaNodeAttributes(props, mediaNodeUpdater); updateViewMediaClientConfig(props); return () => { const { handleMediaNodeUnmount } = props; handleMediaNodeUnmount(props.node); }; }, [props]); const updateViewMediaClientConfig = async props => { const mediaProvider = await props.mediaProvider; if (mediaProvider) { const viewMediaClientConfig = mediaProvider.viewMediaClientConfig; setViewMediaClientConfig(viewMediaClientConfig); } }; const { id, collection, type, alt, width, height } = props.node.attrs; const identifier = { id, mediaItemType: 'file', // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion collectionName: collection }; /* * Show the loading view if * 1. The media provider is not ready * 2. Context Id is not synced * to prevent calling the media API (in mounting of `MediaInlineCard`) * before the prerequisites meet */ if (!viewMediaClientConfig || isNodeScopeUnsync) { return jsx(MediaInlineCardLoadingView, { message: "", isSelected: false }); } const { allowInlineImages } = props; const borderMark = (_props$node = props.node) === null || _props$node === void 0 ? void 0 : (_props$node$marks = _props$node.marks) === null || _props$node$marks === void 0 ? void 0 : _props$node$marks.find(mark => mark.type.name === 'border'); if (allowInlineImages && isImage(type)) { return jsx(MediaInlineImageCard, { mediaClient: getMediaClient(viewMediaClientConfig), identifier: identifier, isSelected: props.isSelected, alt: alt, width: width, height: height // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , border: { borderSize: borderMark === null || borderMark === void 0 ? void 0 : borderMark.attrs.size, borderColor: borderMark === null || borderMark === void 0 ? void 0 : borderMark.attrs.color }, isViewOnly: props.editorViewMode }); } return jsx(MediaViewerContainer, { mediaNode: props.node, selectedMediaContainerNode: props.selectedMediaContainerNode, mediaClientConfig: props.mediaClientConfig, isEditorViewMode: props.editorViewMode, isSelected: props.isSelected, isInline: true }, jsx(MediaInlineCard, { isSelected: props.isSelected, identifier: identifier, mediaClientConfig: viewMediaClientConfig, fallbackMediaNameFetcher: props.fallbackMediaNameFetcher })); }; const selector = states => { var _states$editorViewMod, _states$mediaState, _states$mediaState2, _states$mediaState3, _states$mediaState4, _states$mediaState5, _states$mediaState6, _states$mediaState7; return { viewMode: (_states$editorViewMod = states.editorViewModeState) === null || _states$editorViewMod === void 0 ? void 0 : _states$editorViewMod.mode, mediaProvider: (_states$mediaState = states.mediaState) === null || _states$mediaState === void 0 ? void 0 : _states$mediaState.mediaProvider, handleMediaNodeMount: (_states$mediaState2 = states.mediaState) === null || _states$mediaState2 === void 0 ? void 0 : _states$mediaState2.handleMediaNodeMount, handleMediaNodeUnmount: (_states$mediaState3 = states.mediaState) === null || _states$mediaState3 === void 0 ? void 0 : _states$mediaState3.handleMediaNodeUnmount, allowInlineImages: (_states$mediaState4 = states.mediaState) === null || _states$mediaState4 === void 0 ? void 0 : _states$mediaState4.allowInlineImages, addPendingTask: (_states$mediaState5 = states.mediaState) === null || _states$mediaState5 === void 0 ? void 0 : _states$mediaState5.addPendingTask, selectedMediaContainerNode: (_states$mediaState6 = states.mediaState) === null || _states$mediaState6 === void 0 ? void 0 : _states$mediaState6.selectedMediaContainerNode, mediaClientConfig: (_states$mediaState7 = states.mediaState) === null || _states$mediaState7 === void 0 ? void 0 : _states$mediaState7.mediaClientConfig }; }; const MediaInlineSharedState = ({ identifier, node, isSelected, getPos, contextIdentifierProvider, api, view, fallbackMediaNameFetcher }) => { const { mediaProvider, allowInlineImages, handleMediaNodeMount, handleMediaNodeUnmount, addPendingTask, selectedMediaContainerNode, mediaClientConfig, viewMode } = useSharedPluginStateWithSelector(api, ['editorViewMode', 'media'], selector); const newMediaProvider = useMemo(() => mediaProvider ? Promise.resolve(mediaProvider) : undefined, [mediaProvider]); if (!handleMediaNodeMount || !handleMediaNodeUnmount || !addPendingTask || !selectedMediaContainerNode || !mediaClientConfig || !newMediaProvider) { return null; } return jsx(MediaInline, { identifier: identifier, mediaProvider: newMediaProvider, handleMediaNodeMount: handleMediaNodeMount, handleMediaNodeUnmount: handleMediaNodeUnmount, allowInlineImages: allowInlineImages, addPendingTask: addPendingTask, selectedMediaContainerNode: selectedMediaContainerNode, mediaClientConfig: mediaClientConfig, node: node, isSelected: isSelected, view: view, getPos: getPos, contextIdentifierProvider: contextIdentifierProvider, editorViewMode: viewMode === 'view', fallbackMediaNameFetcher: fallbackMediaNameFetcher }); }; export class MediaInlineNodeView extends SelectionBasedNodeView { createDomRef() { const domRef = document.createElement('span'); domRef.contentEditable = 'false'; return domRef; } ignoreMutation() { return true; } viewShouldUpdate(nextNode) { if (this.node.attrs !== nextNode.attrs) { return true; } return super.viewShouldUpdate(nextNode); } render(props) { const { providerFactory, api, fallbackMediaNameFetcher } = props; const { view } = this; const getPos = this.getPos; return jsx(WithProviders // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , { providers: ['contextIdentifierProvider'], providerFactory: providerFactory // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , renderNode: ({ mediaProvider: _mediaProvider, contextIdentifierProvider }) => { return jsx(MediaInlineSharedState, { identifier: this.node.attrs.id, node: this.node, isSelected: this.nodeInsideSelection(), view: view, getPos: getPos, contextIdentifierProvider: contextIdentifierProvider, api: api, fallbackMediaNameFetcher: fallbackMediaNameFetcher }); } }); } } export const ReactMediaInlineNode = (portalProviderAPI, eventDispatcher, providerFactory, api, dispatchAnalyticsEvent, fallbackMediaNameFetcher) => (node, view, getPos) => { return new MediaInlineNodeView(node, view, getPos, portalProviderAPI, eventDispatcher, { providerFactory, dispatchAnalyticsEvent, api, fallbackMediaNameFetcher }).init(); };