@atlaskit/editor-plugin-media
Version:
Media plugin for @atlaskit/editor-core
271 lines (268 loc) • 9.96 kB
JavaScript
/**
* @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();
};