UNPKG

@atlaskit/editor-plugin-card

Version:

Card plugin for @atlaskit/editor-core

699 lines (693 loc) • 28.3 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _extends from "@babel/runtime/helpers/extends"; import React from 'react'; import rafSchedule from 'raf-schd'; // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead import { v4 as uuid } from 'uuid'; import { SetAttrsStep } from '@atlaskit/adf-schema/steps/set-attrs'; import { isConfluenceSlideUrl } from '@atlaskit/editor-card-provider/url-checkers'; import RichMediaWrapper from '@atlaskit/editor-common/MediaSingle'; import { findOverflowScrollParent } from '@atlaskit/editor-common/Popup/utils'; import ReactNodeView from '@atlaskit/editor-common/react-node-view'; import UnsupportedBlock from '@atlaskit/editor-common/UnsupportedBlock'; import { useSharedPluginStateSelector } from '@atlaskit/editor-common/useSharedPluginStateSelector'; import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/useSharedPluginStateWithSelector'; import { floatingLayouts, isRichMediaInsideOfBlockNode } from '@atlaskit/editor-common/utils'; import { akEditorFullPageNarrowBreakout, DEFAULT_EMBED_CARD_HEIGHT, DEFAULT_EMBED_CARD_WIDTH } from '@atlaskit/editor-shared-styles/constants'; import { SmartLinkDraggable, SMART_LINK_DRAG_TYPES, SMART_LINK_APPEARANCE } from '@atlaskit/editor-smart-link-draggable'; import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled'; import { componentWithCondition } from '@atlaskit/platform-feature-flags-react/component-with-condition'; import { fg } from '@atlaskit/platform-feature-flags/fg'; import { Card as SmartCard } from '@atlaskit/smart-card/card/lazy'; import { EmbedResizeMessageListener } from '@atlaskit/smart-card/embed-resize-message-listener'; import { CardSSR } from '@atlaskit/smart-card/ssr'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/editor-experiment'; import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure'; import { registerCard, removeCard } from '../pm-plugins/actions'; import ResizableEmbedCard from '../ui/ResizableEmbedCard'; import { SmartCardSSRReactContextsProvider } from '../ui/SmartCardSSRReactContextsProvider'; import { BlockCardComponent } from './blockCard'; import { Card } from './genericCard'; import { isIntersectionObserverSupported } from './isIntersectionObserverSupported'; /** * Returns a forced aspect ratio for URLs that have a known canvas shape, * overriding the generic value returned by the link resolver. * Add new URL checks here as more content types get fixed aspect ratios. */ /** * Returns a forced aspect ratio for URLs that have a known canvas shape, * or `undefined` to use the default resolver-provided ratio. * * @internal Exported for testing only. */ export const getAspectRatioForUrl = url => { if (url && isConfluenceSlideUrl(url) && !fg('cc-mui-slides-opted-out') && expValEqualsNoExposure('cc-mui-slides-experiment', 'isEnabled', true)) { // Slides have a 16:9 canvas return 16 / 9; } return undefined; }; const selector = states => { var _states$widthState, _states$widthState2, _states$editorDisable; return { widthStateLineLength: ((_states$widthState = states.widthState) === null || _states$widthState === void 0 ? void 0 : _states$widthState.lineLength) || 0, widthStateWidth: ((_states$widthState2 = states.widthState) === null || _states$widthState2 === void 0 ? void 0 : _states$widthState2.width) || 0, editorDisabled: (_states$editorDisable = states.editorDisabledState) === null || _states$editorDisable === void 0 ? void 0 : _states$editorDisable.editorDisabled }; }; const CardInner = ({ pluginInjectionApi, getPosSafely, getLineLength, view, smartCard, updateSize, getPos, aspectRatio, allowResizing, hasPreview, heightAlone, cardProps, dispatchAnalyticsEvent }) => { const { widthStateLineLength, widthStateWidth, editorDisabled } = useSharedPluginStateWithSelector(pluginInjectionApi, ['width', 'editorDisabled'], selector); const pos = getPosSafely(); if (pos === undefined) { return null; } const lineLength = getLineLength(view, pos, widthStateLineLength); const containerWidth = isRichMediaInsideOfBlockNode(view, pos) ? lineLength : widthStateWidth; if (!allowResizing || !hasPreview) { // There are two ways `width` and `height` can be defined here: // 1) Either as `heightAlone` as height value and no width // 2) or as `1` for height and aspectRation (defined or a default one) as a width // See above for how aspectRation is calculated. const defaultAspectRatio = DEFAULT_EMBED_CARD_WIDTH / DEFAULT_EMBED_CARD_HEIGHT; let richMediaWrapperHeight = 1; let richMediaWrapperWidth = aspectRatio || defaultAspectRatio; if (heightAlone) { richMediaWrapperHeight = heightAlone; richMediaWrapperWidth = undefined; } return /*#__PURE__*/React.createElement(RichMediaWrapper // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading , _extends({}, cardProps, { height: richMediaWrapperHeight, width: richMediaWrapperWidth, nodeType: "embedCard", hasFallbackContainer: hasPreview, lineLength: lineLength, containerWidth: containerWidth }), smartCard); } const displayGrid = (visible, gridType, highlight) => { var _pluginInjectionApi$g, _pluginInjectionApi$g2; return pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$g = pluginInjectionApi.grid) === null || _pluginInjectionApi$g === void 0 ? void 0 : (_pluginInjectionApi$g2 = _pluginInjectionApi$g.actions) === null || _pluginInjectionApi$g2 === void 0 ? void 0 : _pluginInjectionApi$g2.displayGrid(view)({ visible, gridType, highlight: highlight }); }; return /*#__PURE__*/React.createElement(ResizableEmbedCard // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading , _extends({}, cardProps, { height: heightAlone, aspectRatio: aspectRatio, view: view, getPos: getPos, lineLength: lineLength, gridSize: 12, containerWidth: containerWidth, displayGrid: displayGrid, updateSize: updateSize, dispatchAnalyticsEvent: dispatchAnalyticsEvent, isResizeDisabled: editorDisabled }), smartCard); }; // eslint-disable-next-line @repo/internal/react/no-class-components export class EmbedCardComponent extends React.PureComponent { componentDidUpdate(prevProps) { if (isExperimentEnabled('platform_editor_preserve_smart_card_embeds')) { var _this$resolvedCardDat; if (((_this$resolvedCardDat = this.resolvedCardData) === null || _this$resolvedCardDat === void 0 ? void 0 : _this$resolvedCardDat.nodeUrl) !== this.props.node.attrs.url) { this.resolvedCardData = undefined; } if (isExperimentEnabled('platform_editor_reduce_forced_layout_2') && this.props.getPos !== prevProps.getPos && this.resolvedCardData) { const resolvedCardData = this.resolvedCardData; rafSchedule(() => this.registerResolvedCard(resolvedCardData))(); } } } constructor(props) { super(props); _defineProperty(this, "embedIframeRef", /*#__PURE__*/React.createRef()); _defineProperty(this, "getPosSafely", () => { const { getPos } = this.props; if (!getPos || typeof getPos === 'boolean') { return; } try { return getPos(); } catch { // Can blow up in rare cases, when node has been removed. } }); _defineProperty(this, "onResolve", data => { if (isExperimentEnabled('platform_editor_preserve_smart_card_embeds')) { this.resolvedCardData = { title: data.title, url: data.url, nodeUrl: this.props.node.attrs.url }; } this.resolveCard(data); }); _defineProperty(this, "resolveCard", data => { var _getAspectRatioForUrl; const { view } = this.props; const { title, url, aspectRatio: resolvedAspectRatio } = data; const { originalHeight, originalWidth } = this.props.node.attrs; const aspectRatio = (_getAspectRatioForUrl = getAspectRatioForUrl(url)) !== null && _getAspectRatioForUrl !== void 0 ? _getAspectRatioForUrl : resolvedAspectRatio; if (aspectRatio && !originalHeight && !originalWidth) { // Assumption here is if ADF already have both height and width set, // we will going to use that later on in this class as aspectRatio // Most likely we dealing with an embed that received aspectRatio via onResolve previously // and now this information already stored in ADF. this.setState({ initialAspectRatio: aspectRatio }); this.saveOriginalDimensionsAttributes(DEFAULT_EMBED_CARD_HEIGHT, DEFAULT_EMBED_CARD_HEIGHT * aspectRatio); } // don't dispatch immediately since we might be in the middle of // rendering a nodeview rafSchedule(() => { if (isExperimentEnabled('platform_editor_reduce_forced_layout_2')) { var _pluginInjectionApi$c; if (this.removeCardDispatched || this.getPosSafely() === undefined) { return; } const { pluginInjectionApi, id } = this.props; pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c = pluginInjectionApi.card.actions) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.registerCard({ id, title, url, getPos: this.getPosSafely }); } else { const pos = this.getPosSafely(); if (pos === undefined) { return; } return view.dispatch(registerCard({ title, url, pos, id: this.props.id })(view.state.tr)); } })(); try { var _this$props$cardConte, _this$props$cardConte2; const cardContext = (_this$props$cardConte = this.props.cardContext) !== null && _this$props$cardConte !== void 0 && _this$props$cardConte.value ? (_this$props$cardConte2 = this.props.cardContext) === null || _this$props$cardConte2 === void 0 ? void 0 : _this$props$cardConte2.value : undefined; const hasPreview = url && cardContext && cardContext.extractors.getPreview(url, 'web'); if (!hasPreview) { this.setState({ hasPreview: false }); } } catch {} }); _defineProperty(this, "registerResolvedCard", ({ title, url }) => { var _pluginInjectionApi$c2; if (this.removeCardDispatched || this.props.view.isDestroyed || this.getPosSafely() === undefined) { return; } const { pluginInjectionApi, id } = this.props; pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c2 = pluginInjectionApi.card.actions) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.registerCard({ id, title, url, getPos: this.getPosSafely }); }); _defineProperty(this, "updateSize", (pctWidth, layout) => { const { state, dispatch } = this.props.view; const pos = this.getPosSafely(); if (pos === undefined) { return; } const tr = state.tr.setNodeMarkup(pos, undefined, { ...this.props.node.attrs, width: pctWidth, layout }); tr.setMeta('scrollIntoView', false); dispatch(tr); return true; }); /** * Defers line-length measurement until the embed card DOM has fully rendered. * * When put embed in the expand, reload the page and open that expand, the embed was collapsed. * Because the `.rich-media-item` can temporarily report `offsetWidth = 1`, as proportional width styles have not been applied yet. * Measuring at that moment would cache the bogus width and break resize calculations. * Scheduling a measurement on the next animation frame ensures layout has * settled. We then force the node view to re-render so `getLineLength` * re-runs and captures the correct width. */ _defineProperty(this, "scheduleLineLengthRemeasureRaf", rafSchedule(view => { if (view) { this.forceUpdate(); } })); _defineProperty(this, "getLineLength", (view, pos, originalLineLength) => { if (typeof pos === 'number' && isRichMediaInsideOfBlockNode(view, pos)) { const $pos = view.state.doc.resolve(pos); const domNode = view.nodeDOM($pos.pos); if ($pos.nodeAfter && floatingLayouts.indexOf($pos.nodeAfter.attrs.layout) > -1 && domNode && domNode.parentElement) { return domNode.parentElement.offsetWidth; } if (domNode instanceof HTMLElement) { const measuredWidth = domNode.offsetWidth; if (measuredWidth <= 1) { this.scheduleLineLengthRemeasureRaf(view); return originalLineLength; } return measuredWidth; } } return originalLineLength; }); /** * Even though render is capable of listening and reacting to iframely wrapper iframe sent `resize` events * it's good idea to store latest actual height in ADF, so that when renderer (well, editor as well) is loading * we will show embed window of appropriate size and avoid unnecessary content jumping. */ _defineProperty(this, "saveOriginalDimensionsAttributes", (height, width) => { const { view } = this.props; // Please, do not copy or use this kind of code below // @ts-ignore const fakeTableResizePluginKey = { key: 'tableFlexiColumnResizing$', getState: state => { // eslint-disable-next-line return state['tableFlexiColumnResizing$']; } }; const fakeTableResizeState = fakeTableResizePluginKey.getState(view.state); // We are not updating ADF when this function fired while table is resizing. // Changing ADF in the middle of resize will break table resize plugin logic // (tables will be considered different at the end of the drag and cell size won't be stored) // But this is not a big problem, editor user will be seeing latest height anyway (via updated state) // And even if page to be saved with slightly outdated height, renderer is capable of reading latest height value // when embed loads, and so it won't be a problem. if (fakeTableResizeState !== null && fakeTableResizeState !== void 0 && fakeTableResizeState.dragging) { return; } rafSchedule(() => { const pos = this.getPosSafely(); if (pos === undefined) { return; } view.dispatch(view.state.tr.step(new SetAttrsStep(pos, { originalHeight: height, originalWidth: width })).setMeta('addToHistory', false)); })(); }); _defineProperty(this, "onHeightUpdate", height => { this.setState({ liveHeight: height }); this.saveOriginalDimensionsAttributes(height, undefined); }); _defineProperty(this, "onError", ({ err }) => { if (err) { throw err; } }); _defineProperty(this, "removeCardDispatched", false); this.scrollContainer = isExperimentEnabled('platform_editor_reduce_forced_layout_2') && isIntersectionObserverSupported() ? undefined : // eslint-disable-next-line @atlaskit/editor/no-as-casting findOverflowScrollParent(props.view.dom) || undefined; this.state = { hasPreview: true }; } componentWillUnmount() { this.removeCard(); } removeCard() { if (this.removeCardDispatched) { return; } this.removeCardDispatched = true; this.removeCardRegistration(); } removeCardRegistration() { if (isExperimentEnabled('platform_editor_preserve_smart_card_embeds') && this.props.view.isDestroyed) { return; } if (isExperimentEnabled('platform_editor_reduce_forced_layout_2')) { var _pluginInjectionApi$c3; const { pluginInjectionApi, id } = this.props; pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c3 = pluginInjectionApi.card.actions) === null || _pluginInjectionApi$c3 === void 0 ? void 0 : _pluginInjectionApi$c3.removeRegisteredCard(id); } else { const { tr } = this.props.view.state; removeCard({ id: this.props.id })(tr); this.props.view.dispatch(tr); } } render() { const { node, allowResizing, fullWidthMode, view, dispatchAnalyticsEvent, getPos, pluginInjectionApi, actionOptions, onClick, CompetitorPrompt, isPageSSRed } = this.props; const { url, width: pctWidth, layout, originalHeight, originalWidth } = node.attrs; const { hasPreview, liveHeight, initialAspectRatio } = this.state; // We don't want to use `originalHeight` when `originalWidth` also present, // since `heightAlone` is defined only when just height is available. const heightAlone = liveHeight !== null && liveHeight !== void 0 ? liveHeight : !originalWidth && originalHeight || undefined; const aspectRatio = !heightAlone && ( // No need getting aspectRatio if heightAlone defined already initialAspectRatio || // If we have initialAspectRatio (coming from iframely) we should go with that originalHeight && originalWidth && originalWidth / originalHeight) || // If ADF contains both width and height we get ratio from that undefined; const cardProps = { layout, pctWidth, fullWidthMode }; const smartCard = isPageSSRed ? /*#__PURE__*/React.createElement(CardSSR, { key: url, url: url, appearance: "embed", onClick: onClick, onResolve: this.onResolve, onError: this.onError, frameStyle: "show", inheritDimensions: true, platform: 'web', container: this.scrollContainer, embedIframeRef: this.embedIframeRef, actionOptions: actionOptions, CompetitorPrompt: CompetitorPrompt, hideIconLoadingSkeleton: true }) : /*#__PURE__*/React.createElement(SmartCard, { key: url, url: url, appearance: "embed", onClick: onClick, onResolve: this.onResolve, onError: this.onError, frameStyle: "show", inheritDimensions: true, platform: 'web', container: this.scrollContainer, embedIframeRef: this.embedIframeRef, actionOptions: actionOptions, CompetitorPrompt: CompetitorPrompt }); return /*#__PURE__*/React.createElement(SmartLinkDraggable, { url: url, appearance: SMART_LINK_APPEARANCE.EMBED, source: SMART_LINK_DRAG_TYPES.EDITOR }, /*#__PURE__*/React.createElement(EmbedResizeMessageListener, { embedIframeRef: this.embedIframeRef, onHeightUpdate: this.onHeightUpdate }, /*#__PURE__*/React.createElement(CardInner, { pluginInjectionApi: pluginInjectionApi, smartCard: smartCard, hasPreview: hasPreview, getPosSafely: this.getPosSafely, view: view, getLineLength: this.getLineLength, eventDispatcher: this.props.eventDispatcher, updateSize: this.updateSize, getPos: getPos, aspectRatio: aspectRatio, allowResizing: allowResizing, heightAlone: heightAlone, cardProps: cardProps, dispatchAnalyticsEvent: dispatchAnalyticsEvent }))); } } export const EmbedOrBlockCardComponent = props => { const width = useSharedPluginStateSelector(props.pluginInjectionApi, 'width.width'); const viewAsBlockCard = width && width <= akEditorFullPageNarrowBreakout; return viewAsBlockCard ? /*#__PURE__*/React.createElement(BlockCardComponent, { id: props.id, node: props.node, view: props.view, getPos: props.getPos, pluginInjectionApi: props.pluginInjectionApi, actionOptions: props.actionOptions, onClick: props.onClick, CompetitorPrompt: props.CompetitorPrompt, allowResizing: props.allowResizing, fullWidthMode: props.fullWidthMode, dispatchAnalyticsEvent: props.dispatchAnalyticsEvent, eventDispatcher: props.eventDispatcher, cardContext: props.cardContext, smartCard: props.smartCard, hasPreview: props.hasPreview, liveHeight: props.liveHeight, initialAspectRatio: props.initialAspectRatio, isPageSSRed: props.isPageSSRed, provider: props.provider }) : /*#__PURE__*/React.createElement(EmbedCardComponent, { id: props.id, node: props.node, view: props.view, getPos: props.getPos, pluginInjectionApi: props.pluginInjectionApi, actionOptions: props.actionOptions, onClick: props.onClick, CompetitorPrompt: props.CompetitorPrompt, allowResizing: props.allowResizing, fullWidthMode: props.fullWidthMode, dispatchAnalyticsEvent: props.dispatchAnalyticsEvent, eventDispatcher: props.eventDispatcher, cardContext: props.cardContext, smartCard: props.smartCard, hasPreview: props.hasPreview, liveHeight: props.liveHeight, initialAspectRatio: props.initialAspectRatio, isPageSSRed: props.isPageSSRed, provider: props.provider }); }; export const isPreviewPanelResponsivenessEnabled = () => editorExperiment('platform_editor_preview_panel_responsiveness', true, { exposure: true }); const WrappedEmbedCardWithCondition = componentWithCondition(() => editorExperiment('platform_editor_preview_panel_responsiveness', true, { exposure: true }), EmbedOrBlockCardComponent, EmbedCardComponent); const WrappedEmbedCard = Card(WrappedEmbedCardWithCondition, UnsupportedBlock); export class EmbedCard extends ReactNodeView { constructor(...args) { super(...args); // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead _defineProperty(this, "id", uuid()); _defineProperty(this, "updateContentEditable", (editorViewModeState, divElement) => { divElement.contentEditable = (editorViewModeState === null || editorViewModeState === void 0 ? void 0 : editorViewModeState.mode) === 'view' ? 'false' : 'true'; if (isExperimentEnabled('platform_editor_preserve_smart_card_embeds') && this.preservationSlot) { this.preservationSlot.contentEditable = divElement.contentEditable; } }); } viewShouldUpdate(nextNode) { if (this.node.attrs !== nextNode.attrs) { return true; } return super.viewShouldUpdate(nextNode); } createDomRef() { var _this$reactComponentP, _this$reactComponentP2, _this$reactComponentP3, _this$reactComponentP4; const domRef = document.createElement('div'); // It is a tradeoff for the bug mentioned that occurs in Chrome: https://product-fabric.atlassian.net/browse/ED-5379, https://github.com/ProseMirror/prosemirror/issues/884 this.unsubscribe = (_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$reactComponentP2.sharedState.onChange(({ nextSharedState }) => this.updateContentEditable(nextSharedState, domRef)); this.updateContentEditable((_this$reactComponentP3 = this.reactComponentProps.pluginInjectionApi) === null || _this$reactComponentP3 === void 0 ? void 0 : (_this$reactComponentP4 = _this$reactComponentP3.editorViewMode) === null || _this$reactComponentP4 === void 0 ? void 0 : _this$reactComponentP4.sharedState.currentState(), domRef); domRef.setAttribute('spellcheck', 'false'); return domRef; } setDomAttrs(node, element) { super.setDomAttrs(node, element); if (isExperimentEnabled('platform_editor_preserve_smart_card_embeds') && this.preservationSlot) { super.setDomAttrs(node, this.preservationSlot); if (element !== this.preservationSlot) { Object.keys(node.attrs || {}).forEach(attr => element.removeAttribute(attr)); } } } attachPreservationSlot(slot, getPos) { var _this$unsubscribe, _this$reactComponentP5, _this$reactComponentP6; this.getPos = getPos; this.preservationSlot = slot; slot.classList.add(`${this.node.type.name}View-content-wrap`); this.dom.classList.remove(`${this.node.type.name}View-content-wrap`); this.setDomAttrs(this.node, this.dom); (_this$unsubscribe = this.unsubscribe) === null || _this$unsubscribe === void 0 ? void 0 : _this$unsubscribe.call(this); const editorViewMode = (_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; this.unsubscribe = editorViewMode === null || editorViewMode === void 0 ? void 0 : editorViewMode.onChange(({ nextSharedState }) => this.updateContentEditable(nextSharedState, this.dom)); this.updateContentEditable(editorViewMode === null || editorViewMode === void 0 ? void 0 : editorViewMode.currentState(), this.dom); } canReuseWith(provider, eventDispatcher) { return this.reactComponentProps.provider === provider && this.eventDispatcher === eventDispatcher; } render() { const { eventDispatcher, allowResizing, fullWidthMode, dispatchAnalyticsEvent, pluginInjectionApi, onClickCallback, CompetitorPrompt, isPageSSRed, provider, intl, smartCardContext } = this.reactComponentProps; return /*#__PURE__*/React.createElement(SmartCardSSRReactContextsProvider, { intl: intl, smartCardContext: smartCardContext }, /*#__PURE__*/React.createElement(WrappedEmbedCard, { node: this.node, view: this.view, eventDispatcher: eventDispatcher, getPos: this.getPos, allowResizing: allowResizing, fullWidthMode: fullWidthMode, dispatchAnalyticsEvent: dispatchAnalyticsEvent, pluginInjectionApi: pluginInjectionApi, onClickCallback: onClickCallback, id: this.id, CompetitorPrompt: CompetitorPrompt, isPageSSRed: isPageSSRed, provider: provider })); } /** * Prevent ProseMirror from handling drag events on the smart-element-link, * allowing native drag to work so SmartLinkDraggable can intercept it. * @see {@link https://prosemirror.net/docs/ref/#view.NodeView.stopEvent} */ stopEvent(event) { if (event.type === 'dragstart') { const target = event.target; if (target instanceof HTMLElement && target.closest('[data-smart-element-link]')) { return true; } } return false; } destroy() { var _this$unsubscribe2; (_this$unsubscribe2 = this.unsubscribe) === null || _this$unsubscribe2 === void 0 ? void 0 : _this$unsubscribe2.call(this); if (isExperimentEnabled('platform_editor_preserve_smart_card_embeds')) { this.preservationSlot = undefined; } super.destroy(); } } export const embedCardNodeView = ({ allowResizing, fullWidthMode, pmPluginFactoryParams, pluginInjectionApi, actionOptions, onClickCallback, CompetitorPrompt, isPageSSRed, provider, intl, smartCardContext }) => (node, view, getPos) => { const { portalProviderAPI, eventDispatcher, dispatchAnalyticsEvent } = pmPluginFactoryParams; const reactComponentProps = { eventDispatcher, allowResizing, fullWidthMode, dispatchAnalyticsEvent, pluginInjectionApi, actionOptions, onClickCallback: onClickCallback, CompetitorPrompt, isPageSSRed, provider, intl, smartCardContext }; return new EmbedCard(node, view, getPos, portalProviderAPI, eventDispatcher, reactComponentProps).init(); };