UNPKG

@atlaskit/editor-plugin-media

Version:

Media plugin for @atlaskit/editor-core

277 lines (272 loc) 12.4 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; /** * @jsxRuntime classic * @jsx jsx * @jsxFrag */ import { useCallback, useMemo } 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 { WithProviders } from '@atlaskit/editor-common/provider-factory'; import ReactNodeView from '@atlaskit/editor-common/react-node-view'; import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports import { isNodeSelectedOrInRange } from '@atlaskit/editor-common/utils'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { MEDIA_CONTENT_WRAP_CLASS_NAME } from '../pm-plugins/main'; import { MediaSingleNodeNext } from './mediaSingleNext'; const selector = states => { var _states$mediaState, _states$mediaState2, _states$annotationSta, _states$annotationSta2, _states$widthState, _states$widthState2, _states$editorDisable, _states$editorViewMod; return { mediaProviderPromise: (_states$mediaState = states.mediaState) === null || _states$mediaState === void 0 ? void 0 : _states$mediaState.mediaProvider, addPendingTask: (_states$mediaState2 = states.mediaState) === null || _states$mediaState2 === void 0 ? void 0 : _states$mediaState2.addPendingTask, isDrafting: (_states$annotationSta = states.annotationState) === null || _states$annotationSta === void 0 ? void 0 : _states$annotationSta.isDrafting, targetNodeId: (_states$annotationSta2 = states.annotationState) === null || _states$annotationSta2 === void 0 ? void 0 : _states$annotationSta2.targetNodeId, width: (_states$widthState = states.widthState) === null || _states$widthState === void 0 ? void 0 : _states$widthState.width, lineLength: (_states$widthState2 = states.widthState) === null || _states$widthState2 === void 0 ? void 0 : _states$widthState2.lineLength, editorDisabled: (_states$editorDisable = states.editorDisabledState) === null || _states$editorDisable === void 0 ? void 0 : _states$editorDisable.editorDisabled, viewMode: (_states$editorViewMod = states.editorViewModeState) === null || _states$editorViewMod === void 0 ? void 0 : _states$editorViewMod.mode }; }; const MediaSingleNodeWrapper = ({ pluginInjectionApi, contextIdentifierProvider, node, getPos, mediaOptions, view, fullWidthMode, selected, eventDispatcher, dispatchAnalyticsEvent, forwardRef, editorAppearance }) => { const { mediaProviderPromise, addPendingTask, isDrafting, targetNodeId, width, lineLength, editorDisabled, viewMode } = useSharedPluginStateWithSelector(pluginInjectionApi, ['width', 'media', 'annotation', 'editorDisabled', 'editorViewMode'], selector); const interactionState = useSharedPluginStateSelector(pluginInjectionApi, 'interaction.interactionState'); const mediaProvider = useMemo(() => mediaProviderPromise ? Promise.resolve(mediaProviderPromise) : undefined, [mediaProviderPromise]); const isSelectedAndInteracted = useCallback(() => Boolean(selected() && interactionState !== 'hasNotHadInteraction'), [interactionState, selected]); return jsx(MediaSingleNodeNext, { width: width || 0, lineLength: lineLength || 0, node: node, getPos: getPos, mediaProvider: mediaProvider, contextIdentifierProvider: contextIdentifierProvider, mediaOptions: mediaOptions, view: view, fullWidthMode: fullWidthMode, selected: isSelectedAndInteracted, eventDispatcher: eventDispatcher, addPendingTask: addPendingTask, isDrafting: isDrafting, targetNodeId: targetNodeId, dispatchAnalyticsEvent: dispatchAnalyticsEvent, forwardRef: forwardRef, pluginInjectionApi: pluginInjectionApi, editorDisabled: editorDisabled, editorViewMode: viewMode === 'view', editorAppearance: editorAppearance }); }; class MediaSingleNodeView extends ReactNodeView { constructor(...args) { super(...args); _defineProperty(this, "lastOffsetLeft", 0); _defineProperty(this, "forceViewUpdate", false); _defineProperty(this, "selectionType", null); _defineProperty(this, "hasResized", false); _defineProperty(this, "checkAndUpdateSelectionType", () => { const getPos = this.getPos; const { selection } = this.view.state; /** * 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 = getPos ? getPos() : undefined; } catch { pos = undefined; } const isNodeSelected = isNodeSelectedOrInRange(selection.$anchor.pos, selection.$head.pos, pos, this.node.nodeSize); this.selectionType = isNodeSelected; return isNodeSelected; }); _defineProperty(this, "isNodeSelected", () => { this.checkAndUpdateSelectionType(); return this.selectionType !== null; }); } createDomRef() { var _this$reactComponentP, _this$reactComponentP2, _this$reactComponentP3, _this$reactComponentP4; const domRef = document.createElement('div'); // control the domRef contentEditable attribute based on the editor view mode this.unsubscribeToViewModeChange = this.subscribeToViewModeChange(domRef); const initialViewMode = (_this$reactComponentP = this.reactComponentProps.pluginInjectionApi) === null || _this$reactComponentP === void 0 ? void 0 : (_this$reactComponentP2 = _this$reactComponentP.editorViewMode) === null || _this$reactComponentP2 === void 0 ? void 0 : (_this$reactComponentP3 = _this$reactComponentP2.sharedState.currentState()) === null || _this$reactComponentP3 === void 0 ? void 0 : _this$reactComponentP3.mode; this.updateDomRefContentEditable(domRef, initialViewMode); if ((_this$reactComponentP4 = this.reactComponentProps.mediaOptions) !== null && _this$reactComponentP4 !== void 0 && _this$reactComponentP4.allowPixelResizing) { domRef.classList.add('media-extended-resize-experience'); } domRef.setAttribute('data-media-vc-wrapper', 'true'); return domRef; } getContentDOM() { const dom = document.createElement('div'); dom.classList.add(MEDIA_CONTENT_WRAP_CLASS_NAME); return { dom }; } viewShouldUpdate(nextNode) { if (this.forceViewUpdate) { this.forceViewUpdate = false; return true; } if (this.node.attrs !== nextNode.attrs) { return true; } if (this.selectionType !== this.checkAndUpdateSelectionType()) { return true; } if (this.node.childCount !== nextNode.childCount) { return true; } return super.viewShouldUpdate(nextNode); } subscribeToViewModeChange(domRef) { var _this$reactComponentP5, _this$reactComponentP6; return (_this$reactComponentP5 = this.reactComponentProps.pluginInjectionApi) === null || _this$reactComponentP5 === void 0 ? void 0 : (_this$reactComponentP6 = _this$reactComponentP5.editorViewMode) === null || _this$reactComponentP6 === void 0 ? void 0 : _this$reactComponentP6.sharedState.onChange(viewModeState => { var _viewModeState$nextSh; this.updateDomRefContentEditable(domRef, (_viewModeState$nextSh = viewModeState.nextSharedState) === null || _viewModeState$nextSh === void 0 ? void 0 : _viewModeState$nextSh.mode); }); } updateDomRefContentEditable(domRef, editorViewMode) { var _this$reactComponentP7; // if the editor is in view mode, we should not allow editing if (editorViewMode === 'view') { domRef.contentEditable = 'false'; return; } // if the editor is in edit mode, we should allow editing if the media options allow it if ((_this$reactComponentP7 = this.reactComponentProps.mediaOptions) !== null && _this$reactComponentP7 !== void 0 && _this$reactComponentP7.allowMediaSingleEditable) { // workaround Chrome bug in https://product-fabric.atlassian.net/browse/ED-5379 // see also: https://github.com/ProseMirror/prosemirror/issues/884 domRef.contentEditable = 'true'; } } getNodeMediaId(node) { if (node.firstChild) { return node.firstChild.attrs.id; } return undefined; } stopEvent(event) { if (this.isNodeSelected() && event instanceof KeyboardEvent && (event === null || event === void 0 ? void 0 : event.target) instanceof HTMLElement) { const targetType = event.target.type; if (event.key === 'Enter' && targetType === 'button') { return true; } } return false; } update(node, decorations, _innerDecorations, isValidUpdate) { if (!isValidUpdate) { isValidUpdate = (currentNode, newNode) => this.getNodeMediaId(currentNode) === this.getNodeMediaId(newNode); } // Detect mediaSingle width attribute changes and signal child media node to update if (!this.hasResized && this.node.attrs.width !== node.attrs.width && expValEquals('platform_editor_media_vc_fixes', 'isEnabled', true)) { const target = this.dom.querySelector('div[data-prosemirror-node-name="media"]'); target === null || target === void 0 ? void 0 : target.dispatchEvent(new CustomEvent('resized')); } return super.update(node, decorations, _innerDecorations, isValidUpdate); } render(props, forwardRef) { const { eventDispatcher, fullWidthMode, providerFactory, mediaOptions, dispatchAnalyticsEvent, pluginInjectionApi, editorAppearance } = this.reactComponentProps; // getPos is a boolean for marks, since this is a node we know it must be a function 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: ({ contextIdentifierProvider }) => { return jsx(MediaSingleNodeWrapper, { pluginInjectionApi: pluginInjectionApi, contextIdentifierProvider: contextIdentifierProvider, node: this.node, getPos: getPos, mediaOptions: mediaOptions, view: this.view, fullWidthMode: fullWidthMode, selected: this.isNodeSelected, eventDispatcher: eventDispatcher // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion , dispatchAnalyticsEvent: dispatchAnalyticsEvent // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion , forwardRef: forwardRef, editorAppearance: editorAppearance }); } }); } ignoreMutation() { // DOM has changed; recalculate if we need to re-render if (this.dom) { // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting const offsetLeft = this.dom.offsetLeft; if (offsetLeft !== this.lastOffsetLeft) { this.lastOffsetLeft = offsetLeft; this.forceViewUpdate = true; this.update(this.node, [], undefined, () => true); } } return true; } destroy() { var _this$unsubscribeToVi; (_this$unsubscribeToVi = this.unsubscribeToViewModeChange) === null || _this$unsubscribeToVi === void 0 ? void 0 : _this$unsubscribeToVi.call(this); } } export const ReactMediaSingleNode = (portalProviderAPI, eventDispatcher, providerFactory, pluginInjectionApi, dispatchAnalyticsEvent, mediaOptions = {}) => (node, view, getPos) => { return new MediaSingleNodeView(node, view, getPos, portalProviderAPI, eventDispatcher, { eventDispatcher, fullWidthMode: mediaOptions.fullWidthEnabled, providerFactory, mediaOptions, dispatchAnalyticsEvent, isCopyPasteEnabled: mediaOptions.isCopyPasteEnabled, pluginInjectionApi, editorAppearance: mediaOptions.editorAppearance }).init(); };