UNPKG

@atlaskit/editor-plugin-media

Version:

Media plugin for @atlaskit/editor-core

314 lines (312 loc) 11.7 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import React from 'react'; import { injectIntl } from 'react-intl-next'; import { useSharedPluginState } from '@atlaskit/editor-common/hooks'; import { WithProviders } from '@atlaskit/editor-common/provider-factory'; import ReactNodeView from '@atlaskit/editor-common/react-node-view'; import { isNodeSelectedOrInRange, SelectedState, setNodeSelection } from '@atlaskit/editor-common/utils'; import EditorCloseIcon from '@atlaskit/icon/glyph/editor/close'; import { getMediaFeatureFlag } from '@atlaskit/media-common'; import { Filmstrip } from '@atlaskit/media-filmstrip'; import { stateKey as mediaStateKey } from '../pm-plugins/plugin-key'; import { MediaNodeUpdater } from './mediaNodeUpdater'; import { messages } from './messages'; const isMediaGroupSelectedFromProps = props => { /** * ED-19831 * There is a getPos issue coming from this code. We need to apply this workaround for now and apply a patch * directly to confluence since this bug is now in production. */ let pos; try { pos = props.getPos ? props.getPos() : undefined; } catch (e) { pos = undefined; } if (typeof pos !== 'number') { return false; } return isNodeSelectedOrInRange(props.anchorPos, props.headPos, pos, props.node.nodeSize); }; const hasSelectionChanged = (oldProps, newProps) => { if (isMediaGroupSelectedFromProps(oldProps) !== isMediaGroupSelectedFromProps(newProps)) { return true; } if (isMediaGroupSelectedFromProps(newProps) === SelectedState.selectedInside) { return oldProps.anchorPos !== newProps.anchorPos; } return false; }; // eslint-disable-next-line @repo/internal/react/no-class-components class MediaGroup extends React.Component { constructor(_props) { super(_props); _defineProperty(this, "state", { viewMediaClientConfig: undefined }); _defineProperty(this, "updateNodeAttrs", (props, node, getPos) => { const { view, mediaProvider, contextIdentifierProvider } = props; const mediaNodeUpdater = new MediaNodeUpdater({ view, mediaProvider, contextIdentifierProvider, node, isMediaSingle: false }); mediaNodeUpdater.updateNodeAttrs(getPos); }); _defineProperty(this, "setMediaItems", (props, updatedAttrs = false) => { var _this$mediaPluginStat; const { node } = props; const oldMediaNodes = this.mediaNodes; this.mediaNodes = []; node.forEach((item, childOffset) => { const getPos = () => { const pos = props.getPos(); if (typeof pos !== 'number') { // That may seems weird, but the previous type wasn't match with the real ProseMirror code. And a lot of Media API was built expecting a number // Because the original code would return NaN on runtime // We are just make it explict now. // We may run a deep investagation on Media code to figure out a better fix. But, for now, we want to keep the current behavior. // TODO: ED-13910 prosemirror-bump leftovers return NaN; } return pos + childOffset + 1; }; this.mediaNodes.push(item); if (updatedAttrs) { this.updateNodeAttrs(props, item, getPos); } }); (_this$mediaPluginStat = this.mediaPluginState) === null || _this$mediaPluginStat === void 0 ? void 0 : _this$mediaPluginStat.handleMediaGroupUpdate(oldMediaNodes, this.mediaNodes); }); _defineProperty(this, "getIdentifier", item => { if (item.attrs.type === 'external') { return { mediaItemType: 'external-image', dataURI: item.attrs.url }; } return { id: item.attrs.id, mediaItemType: 'file', collectionName: item.attrs.collection }; }); _defineProperty(this, "isNodeSelected", nodePos => { const selected = isMediaGroupSelectedFromProps(this.props); if (selected === SelectedState.selectedInRange) { return true; } if (selected === SelectedState.selectedInside && this.props.anchorPos === nodePos) { return true; } return false; }); _defineProperty(this, "renderChildNodes", () => { const { viewMediaClientConfig } = this.state; const { getPos, allowLazyLoading, disabled, mediaOptions } = this.props; const items = this.mediaNodes.map((item, idx) => { // We declared this to get a fresh position every time const getNodePos = () => { const pos = getPos(); if (typeof pos !== 'number') { // That may seems weird, but the previous type wasn't match with the real ProseMirror code. And a lot of Media API was built expecting a number // Because the original code would return NaN on runtime // We are just make it explict now. // We may run a deep investagation on Media code to figure out a better fix. But, for now, we want to keep the current behavior. // TODO: ED-13910 prosemirror-bump leftovers return NaN; } return pos + idx + 1; }; // Media Inline creates a floating toolbar with the same options, excludes these options if enabled const mediaInlineOptions = (allowMediaInline = false) => { if (!allowMediaInline) { return { shouldEnableDownloadButton: mediaOptions.enableDownloadButton, actions: [{ handler: disabled || !this.mediaPluginState ? () => {} : this.mediaPluginState.handleMediaNodeRemoval.bind(null, undefined, getNodePos), icon: /*#__PURE__*/React.createElement(EditorCloseIcon, { label: this.props.intl.formatMessage(messages.mediaGroupDeleteLabel) }) }] }; } }; return { identifier: this.getIdentifier(item), isLazy: allowLazyLoading, selected: this.isNodeSelected(getNodePos()), onClick: () => { setNodeSelection(this.props.view, getNodePos()); }, ...mediaInlineOptions(getMediaFeatureFlag('mediaInline', mediaOptions.featureFlags)) }; }); return /*#__PURE__*/React.createElement(Filmstrip, { items: items, mediaClientConfig: viewMediaClientConfig, featureFlags: mediaOptions.featureFlags }); }); this.mediaNodes = []; this.mediaPluginState = mediaStateKey.getState(_props.view.state); this.setMediaItems(_props); this.state = { viewMediaClientConfig: undefined }; } componentDidMount() { this.updateMediaClientConfig(); this.mediaNodes.forEach(async node => { if (node.attrs.type === 'external') { return; } const { view, mediaProvider, contextIdentifierProvider } = this.props; const mediaNodeUpdater = new MediaNodeUpdater({ view, mediaProvider, contextIdentifierProvider, node, isMediaSingle: false }); const getPos = () => { const pos = this.props.getPos(); if (typeof pos !== 'number') { // That may seems weird, but the previous type wasn't match with the real ProseMirror code. And a lot of Media API was built expecting a number // Because the original code would return NaN on runtime // We are just make it explict now. // We may run a deep investagation on Media code to figure out a better fix. But, for now, we want to keep the current behavior. // TODO: ED-13910 prosemirror-bump leftovers return NaN; } return pos + 1; }; const contextId = mediaNodeUpdater.getNodeContextId(); if (!contextId) { await mediaNodeUpdater.updateNodeContextId(getPos); } const hasDifferentContextId = await mediaNodeUpdater.hasDifferentContextId(); if (hasDifferentContextId) { await mediaNodeUpdater.copyNodeFromPos(getPos, { traceId: node.attrs.__mediaTraceId }); } }); } componentWillUnmount() { var _this$mediaPluginStat2; (_this$mediaPluginStat2 = this.mediaPluginState) === null || _this$mediaPluginStat2 === void 0 ? void 0 : _this$mediaPluginStat2.handleMediaGroupUpdate(this.mediaNodes, []); } UNSAFE_componentWillReceiveProps(props) { this.updateMediaClientConfig(); this.setMediaItems(props, props.isCopyPasteEnabled || props.isCopyPasteEnabled === undefined); } shouldComponentUpdate(nextProps) { var _this$mediaPluginStat3; if (hasSelectionChanged(this.props, nextProps) || this.props.node !== nextProps.node || this.state.viewMediaClientConfig !== ((_this$mediaPluginStat3 = this.mediaPluginState) === null || _this$mediaPluginStat3 === void 0 ? void 0 : _this$mediaPluginStat3.mediaClientConfig)) { return true; } return false; } updateMediaClientConfig() { const { viewMediaClientConfig } = this.state; const { mediaClientConfig } = this.mediaPluginState || {}; if (!viewMediaClientConfig && mediaClientConfig) { this.setState({ viewMediaClientConfig: mediaClientConfig }); } } render() { return this.renderChildNodes(); } } _defineProperty(MediaGroup, "displayName", 'MediaGroup'); const IntlMediaGroup = injectIntl(MediaGroup); export default IntlMediaGroup; function MediaGroupNodeViewInternal({ renderFn, pluginInjectionApi }) { const { editorDisabledState: editorDisabledPlugin } = useSharedPluginState(pluginInjectionApi, ['editorDisabled']); return renderFn({ editorDisabledPlugin }); } class MediaGroupNodeView extends ReactNodeView { render(props, forwardRef) { const { providerFactory, mediaOptions, pluginInjectionApi } = props; const getPos = this.getPos; return /*#__PURE__*/React.createElement(WithProviders, { providers: ['mediaProvider', 'contextIdentifierProvider'], providerFactory: providerFactory, renderNode: ({ mediaProvider, contextIdentifierProvider }) => { const renderFn = ({ editorDisabledPlugin }) => { if (!mediaProvider) { return null; } return /*#__PURE__*/React.createElement(IntlMediaGroup, { node: this.node, getPos: getPos, view: this.view, forwardRef: forwardRef, disabled: (editorDisabledPlugin || {}).editorDisabled, allowLazyLoading: mediaOptions.allowLazyLoading, mediaProvider: mediaProvider, contextIdentifierProvider: contextIdentifierProvider, isCopyPasteEnabled: mediaOptions.isCopyPasteEnabled, anchorPos: this.view.state.selection.$anchor.pos, headPos: this.view.state.selection.$head.pos, mediaOptions: mediaOptions }); }; return /*#__PURE__*/React.createElement(MediaGroupNodeViewInternal, { renderFn: renderFn, pluginInjectionApi: pluginInjectionApi }); } }); } } export const ReactMediaGroupNode = (portalProviderAPI, eventDispatcher, providerFactory, mediaOptions = {}, pluginInjectionApi) => (node, view, getPos) => { const hasIntlContext = true; return new MediaGroupNodeView(node, view, getPos, portalProviderAPI, eventDispatcher, { providerFactory, mediaOptions, pluginInjectionApi }, undefined, undefined, undefined, hasIntlContext).init(); };