@atlaskit/editor-plugin-card
Version:
Card plugin for @atlaskit/editor-core
244 lines (242 loc) • 11.2 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';
import rafSchedule from 'raf-schd';
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
import uuid from 'uuid/v4';
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
import { UnsupportedInline, findOverflowScrollParent } from '@atlaskit/editor-common/ui';
import { SmartLinkDraggable, SMART_LINK_DRAG_TYPES, SMART_LINK_APPEARANCE } from '@atlaskit/editor-smart-link-draggable';
import { fg } from '@atlaskit/platform-feature-flags';
import { Card as SmartCard } from '@atlaskit/smart-card';
import { CardSSR } from '@atlaskit/smart-card/ssr';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { registerCard, removeCard } from '../pm-plugins/actions';
import { getAwarenessProps } from '../pm-plugins/utils';
import { visitCardLinkAnalytics } from '../ui/toolbar';
import { Card } from './genericCard';
import { InlineCardWithAwareness } from './inlineCardWithAwareness';
export var InlineCard = /*#__PURE__*/memo(function (_ref) {
var _cardContext$value;
var node = _ref.node,
cardContext = _ref.cardContext,
actionOptions = _ref.actionOptions,
useAlternativePreloader = _ref.useAlternativePreloader,
view = _ref.view,
getPos = _ref.getPos,
propsOnClick = _ref.onClick,
onRes = _ref.onResolve,
isHovered = _ref.isHovered,
showHoverPreview = _ref.showHoverPreview,
hoverPreviewOptions = _ref.hoverPreviewOptions,
isPageSSRed = _ref.isPageSSRed,
pluginInjectionApi = _ref.pluginInjectionApi,
disablePreviewPanel = _ref.disablePreviewPanel;
var _node$attrs = node.attrs,
url = _node$attrs.url,
data = _node$attrs.data;
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
var refId = useRef(uuid());
var removeCardDispatched = useRef(false);
var _ref2 = (cardContext === null || cardContext === void 0 || (_cardContext$value = cardContext.value) === null || _cardContext$value === void 0 ? void 0 : _cardContext$value.store) || {},
getSmartlinkState = _ref2.getState;
var cardState = getSmartlinkState === null || getSmartlinkState === void 0 ? void 0 : getSmartlinkState()[url || ''];
useEffect(function () {
var id = refId.current;
removeCardDispatched.current = false;
return function () {
if (expValEquals('platform_editor_inline_card_dispatch_guard', 'isEnabled', true) && removeCardDispatched.current) {
return;
}
removeCardDispatched.current = true;
var tr = view.state.tr;
removeCard({
id: id
})(tr);
view.dispatch(tr);
};
}, [getPos, view]);
var scrollContainer = useMemo(
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
function () {
return findOverflowScrollParent(view.dom) || undefined;
}, [view.dom]);
var onResolve = useCallback(function (data) {
if (!getPos || typeof getPos === 'boolean') {
return;
}
var title = data.title,
url = data.url;
// don't dispatch immediately since we might be in the middle of
// rendering a nodeview
rafSchedule(function () {
// prosemirror-bump-fix
var pos = getPos();
if (typeof pos !== 'number') {
return;
}
var tr = view.state.tr;
registerCard({
title: title,
url: url,
pos: pos,
id: refId.current
})(tr);
onRes === null || onRes === void 0 || onRes(tr, title);
view.dispatch(tr);
})();
}, [getPos, view, onRes]);
var onError = useCallback(function (data) {
var url = data.url,
err = data.err;
if (err) {
throw err;
}
onResolve({
url: url
});
}, [onResolve]);
var handleOnClick = useCallback(function (event) {
if (event.metaKey || event.ctrlKey) {
var _pluginInjectionApi$a;
var _ref3 = (_pluginInjectionApi$a = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.analytics) !== null && _pluginInjectionApi$a !== void 0 ? _pluginInjectionApi$a : {},
editorAnalyticsApi = _ref3.actions;
visitCardLinkAnalytics(editorAnalyticsApi, INPUT_METHOD.META_CLICK)(view.state, view.dispatch);
window.open(url, '_blank');
} else {
// only trigger the provided onClick callback if the meta key or ctrl key is not pressed
propsOnClick === null || propsOnClick === void 0 || propsOnClick(event);
}
}, [propsOnClick, url, view, pluginInjectionApi]);
var onClick = editorExperiment('platform_editor_controls', 'variant1') ? handleOnClick : propsOnClick;
var card = useMemo(function () {
if ((isPageSSRed || cardState && expValEquals('platform_editor_smartlink_local_cache', 'isEnabled', true)) && url) {
return /*#__PURE__*/React.createElement(CardSSR, {
key: url,
url: url,
appearance: "inline",
onClick: onClick,
container: scrollContainer,
onResolve: onResolve,
onError: onError,
inlinePreloaderStyle: useAlternativePreloader ? 'on-right-without-skeleton' : undefined,
actionOptions: actionOptions,
isHovered: isHovered,
showHoverPreview: showHoverPreview,
hoverPreviewOptions: hoverPreviewOptions,
disablePreviewPanel: disablePreviewPanel,
hideIconLoadingSkeleton: true
});
}
return /*#__PURE__*/React.createElement(SmartCard, {
key: url,
url: url !== null && url !== void 0 ? url : data.url,
appearance: "inline",
onClick: onClick,
container: scrollContainer,
onResolve: onResolve,
onError: onError,
inlinePreloaderStyle: useAlternativePreloader ? 'on-right-without-skeleton' : undefined,
actionOptions: actionOptions,
isHovered: isHovered,
showHoverPreview: showHoverPreview,
hoverPreviewOptions: hoverPreviewOptions,
disablePreviewPanel: disablePreviewPanel
});
}, [url, data, onClick, scrollContainer, onResolve, onError, useAlternativePreloader, actionOptions, isHovered, showHoverPreview, hoverPreviewOptions, isPageSSRed, disablePreviewPanel, cardState]);
// [WS-2307]: we only render card wrapped into a Provider when the value is ready,
// otherwise if we got data, we can render the card directly since it doesn't need the Provider
return cardContext && cardContext.value ? /*#__PURE__*/React.createElement(cardContext.Provider, {
value: cardContext.value
}, card) : data ? card : null;
});
var WrappedInlineCardWithAwareness = Card(InlineCardWithAwareness, UnsupportedInline);
var selectorWithCard = function selectorWithCard(states) {
var _states$editorViewMod, _states$cardState;
return {
mode: (_states$editorViewMod = states.editorViewModeState) === null || _states$editorViewMod === void 0 ? void 0 : _states$editorViewMod.mode,
resolvedInlineSmartLinks: (_states$cardState = states.cardState) === null || _states$cardState === void 0 ? void 0 : _states$cardState.resolvedInlineSmartLinks
};
};
var selectorWithoutCard = function selectorWithoutCard(states) {
var _states$editorViewMod2;
return {
mode: (_states$editorViewMod2 = states.editorViewModeState) === null || _states$editorViewMod2 === void 0 ? void 0 : _states$editorViewMod2.mode,
resolvedInlineSmartLinks: undefined
};
};
/**
* Inline card node view component that renders a Smart Link inline card within the editor.
*
* @param props
* @example
*/
export function InlineCardNodeView(props) {
var _resolvedInlineSmartL;
var useAlternativePreloader = props.useAlternativePreloader,
node = props.node,
view = props.view,
getPos = props.getPos,
actionOptions = props.actionOptions,
allowEmbeds = props.allowEmbeds,
allowBlockCards = props.allowBlockCards,
enableInlineUpgradeFeatures = props.enableInlineUpgradeFeatures,
pluginInjectionApi = props.pluginInjectionApi,
onClickCallback = props.onClickCallback,
isPageSSRed = props.isPageSSRed,
provider = props.provider,
CompetitorPrompt = props.CompetitorPrompt;
var _useSharedPluginState = useSharedPluginStateWithSelector(pluginInjectionApi, fg('cc_dnd_smart_link_changeboard_po_template_gate') ? ['editorViewMode', 'card'] : ['editorViewMode'], fg('cc_dnd_smart_link_changeboard_po_template_gate') ? selectorWithCard : selectorWithoutCard),
mode = _useSharedPluginState.mode,
resolvedInlineSmartLinks = _useSharedPluginState.resolvedInlineSmartLinks;
var url = node.attrs.url;
var CompetitorPromptComponent = CompetitorPrompt && url ? /*#__PURE__*/React.createElement(CompetitorPrompt, {
sourceUrl: url,
linkType: "inline"
}) : null;
useEffect(function () {
if (expValEquals('platform_editor_smartlink_local_cache', 'isEnabled', true)) {
// Refresh cache in the background
provider === null || provider === void 0 || provider.then(function (providerInstance) {
var _refreshCache, _ref4;
(_refreshCache = (_ref4 = providerInstance).refreshCache) === null || _refreshCache === void 0 || _refreshCache.call(_ref4, props.node);
});
}
}, [provider, props.node]);
var linkPosition = useMemo(function () {
if (!getPos || typeof getPos === 'boolean') {
return undefined;
}
var pos = getPos();
return typeof pos === 'number' ? pos : undefined;
}, [getPos]);
var isChangeboardTarget = linkPosition !== undefined && (resolvedInlineSmartLinks === null || resolvedInlineSmartLinks === void 0 || (_resolvedInlineSmartL = resolvedInlineSmartLinks[0]) === null || _resolvedInlineSmartL === void 0 ? void 0 : _resolvedInlineSmartL.pos) === linkPosition;
var inlineCardContent = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(WrappedInlineCardWithAwareness, _extends({
node: node,
view: view,
getPos: getPos,
actionOptions: actionOptions,
useAlternativePreloader: useAlternativePreloader,
pluginInjectionApi: pluginInjectionApi,
onClickCallback: onClickCallback,
isPageSSRed: isPageSSRed,
provider: provider,
appearance: "inline"
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
}, enableInlineUpgradeFeatures && getAwarenessProps(view.state, getPos, allowEmbeds, allowBlockCards, mode === 'view'))), CompetitorPromptComponent);
return /*#__PURE__*/React.createElement(SmartLinkDraggable, {
url: url,
appearance: SMART_LINK_APPEARANCE.INLINE,
source: SMART_LINK_DRAG_TYPES.EDITOR,
isChangeboardTarget: isChangeboardTarget
}, inlineCardContent);
}
export var inlineCardNodeView = function inlineCardNodeView(_ref5) {
var inlineCardViewProducer = _ref5.inlineCardViewProducer;
return function (node, view, getPos, decorations) {
return inlineCardViewProducer(node, view, getPos, decorations);
};
};