@atlaskit/editor-plugin-card
Version:
Card plugin for @atlaskit/editor-core
191 lines (190 loc) • 8.32 kB
JavaScript
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 { v4 as uuid } from 'uuid';
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics/types/enums';
import { findOverflowScrollParent } from '@atlaskit/editor-common/Popup/utils';
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
import { Card as SmartCard } from '@atlaskit/smart-card/card/lazy';
import { CardSSR } from '@atlaskit/smart-card/ssr';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/editor-experiment';
import { registerCard, removeCard } from '../pm-plugins/actions';
import { visitCardLinkAnalytics } from '../ui/toolbar';
import { isIntersectionObserverSupported } from './isIntersectionObserverSupported';
export const InlineCard = /*#__PURE__*/memo(({
node,
cardContext,
actionOptions,
useAlternativePreloader,
view,
getPos,
onClick: propsOnClick,
onResolve: onRes,
isHovered,
showHoverPreview,
hoverPreviewOptions,
isPageSSRed,
pluginInjectionApi,
disablePreviewPanel
}) => {
var _cardContext$value;
const {
url,
data
} = node.attrs;
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
const refId = useRef(uuid());
const removeCardDispatched = useRef(false);
const pendingRegisterCard = useRef(null);
useEffect(() => {
return () => {
if (isExperimentEnabled('platform_editor_reduce_memory_leaks')) {
var _pendingRegisterCard$;
(_pendingRegisterCard$ = pendingRegisterCard.current) === null || _pendingRegisterCard$ === void 0 ? void 0 : _pendingRegisterCard$.cancel();
pendingRegisterCard.current = null;
}
};
}, []);
const cardActions = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.card.actions;
const registerResolvedCard = cardActions === null || cardActions === void 0 ? void 0 : cardActions.registerCard;
const removeRegisteredCard = cardActions === null || cardActions === void 0 ? void 0 : cardActions.removeRegisteredCard;
const {
getState: getSmartlinkState
} = (cardContext === null || cardContext === void 0 ? void 0 : (_cardContext$value = cardContext.value) === null || _cardContext$value === void 0 ? void 0 : _cardContext$value.store) || {};
const cardState = getSmartlinkState === null || getSmartlinkState === void 0 ? void 0 : getSmartlinkState()[url || ''];
useEffect(() => {
const id = refId.current;
removeCardDispatched.current = false;
return () => {
if (isExperimentEnabled('platform_editor_inline_card_dispatch_guard') && removeCardDispatched.current) {
return;
}
removeCardDispatched.current = true;
if (isExperimentEnabled('platform_editor_reduce_forced_layout_2')) {
removeRegisteredCard === null || removeRegisteredCard === void 0 ? void 0 : removeRegisteredCard(id);
} else {
const {
tr
} = view.state;
removeCard({
id
})(tr);
view.dispatch(tr);
}
};
}, [getPos, isExperimentEnabled('platform_editor_reduce_forced_layout_2') ? undefined : view, isExperimentEnabled('platform_editor_reduce_forced_layout_2') ? removeRegisteredCard : undefined]);
const scrollContainer = useMemo(() => isExperimentEnabled('platform_editor_reduce_forced_layout_2') && isIntersectionObserverSupported() ? undefined :
// eslint-disable-next-line @atlaskit/editor/no-as-casting
findOverflowScrollParent(view.dom) || undefined, [view.dom]);
const onResolve = useCallback(data => {
if (!getPos || typeof getPos === 'boolean') {
return;
}
const {
title,
url
} = data;
// don't dispatch immediately since we might be in the middle of
// rendering a nodeview
const scheduleRegisterCard = rafSchedule(() => {
if (isExperimentEnabled('platform_editor_reduce_forced_layout_2')) {
if (removeCardDispatched.current || getPos() === undefined) {
return;
}
const id = refId.current;
registerResolvedCard === null || registerResolvedCard === void 0 ? void 0 : registerResolvedCard({
id,
title,
url,
getPos
});
onRes === null || onRes === void 0 ? void 0 : onRes(undefined, title);
} else {
// prosemirror-bump-fix
const pos = getPos();
if (typeof pos !== 'number') {
return;
}
const tr = view.state.tr;
registerCard({
title,
url,
pos,
id: refId.current
})(tr);
onRes === null || onRes === void 0 ? void 0 : onRes(tr, title);
view.dispatch(tr);
}
});
if (isExperimentEnabled('platform_editor_reduce_memory_leaks')) {
pendingRegisterCard.current = scheduleRegisterCard;
}
scheduleRegisterCard();
}, [getPos, isExperimentEnabled('platform_editor_reduce_forced_layout_2') ? undefined : view, onRes, isExperimentEnabled('platform_editor_reduce_forced_layout_2') ? registerResolvedCard : undefined]);
const onError = useCallback(data => {
const {
url,
err
} = data;
if (err) {
throw err;
}
onResolve({
url
});
}, [onResolve]);
const handleOnClick = useCallback((event, data) => {
if (event.metaKey || event.ctrlKey) {
var _pluginInjectionApi$a, _data$destinationUrl;
const {
actions: editorAnalyticsApi
} = (_pluginInjectionApi$a = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.analytics) !== null && _pluginInjectionApi$a !== void 0 ? _pluginInjectionApi$a : {};
visitCardLinkAnalytics(editorAnalyticsApi, INPUT_METHOD.META_CLICK)(view.state, view.dispatch);
window.open((_data$destinationUrl = data === null || data === void 0 ? void 0 : data.destinationUrl) !== null && _data$destinationUrl !== void 0 ? _data$destinationUrl : url, '_blank');
} else {
// only trigger the provided onClick callback if the meta key or ctrl key is not pressed
propsOnClick === null || propsOnClick === void 0 ? void 0 : propsOnClick(event);
}
}, [propsOnClick, url, view, pluginInjectionApi]);
const onClick = editorExperiment('platform_editor_controls', 'variant1') ? handleOnClick : propsOnClick;
const card = useMemo(() => {
if ((isPageSSRed || cardState) && 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;
});