UNPKG

@atlaskit/editor-plugin-card

Version:

Card plugin for @atlaskit/editor-core

191 lines (190 loc) • 8.32 kB
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; });