@atlaskit/renderer
Version:
Renderer component
386 lines (384 loc) • 18.3 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
/**
* @jsxRuntime classic
* @jsx jsx
*/
/* eslint-disable jsdoc/check-tag-names */
import { Fragment, useState, useMemo, useEffect } from 'react';
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
import { jsx } from '@emotion/react';
import { useSmartCardContext } from '@atlaskit/link-provider';
import { Card, getObjectAri, getObjectIconUrl, getObjectName } from '@atlaskit/smart-card';
import { isWithinPreviewPanelIFrame } from '@atlaskit/linking-common/utils';
import { useSmartLinkActions } from '@atlaskit/smart-card/hooks';
import { CardSSR } from '@atlaskit/smart-card/ssr';
import { HoverLinkOverlay, UnsupportedInline } from '@atlaskit/editor-common/ui';
import { fg } from '@atlaskit/platform-feature-flags';
import { AnalyticsContext } from '@atlaskit/analytics-next';
import { componentWithCondition } from '@atlaskit/platform-feature-flags-react';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { SmartLinkDraggable, SMART_LINK_DRAG_TYPES, SMART_LINK_APPEARANCE } from '@atlaskit/editor-smart-link-draggable';
import { useProvider } from '@atlaskit/editor-common/provider-factory';
import { CardErrorBoundary } from './fallback';
import { withSmartCardStorage } from '../../ui/SmartCardStorage';
import { getCardClickHandler } from '../utils/getCardClickHandler';
import { useInlineAnnotationProps } from '../../ui/annotations/element/useInlineAnnotationProps';
import { usePortal } from '../../ui/Renderer/PortalContext';
import { extractSmartLinkEmbed } from '@atlaskit/link-extractors';
const HoverLinkOverlayNoop = props => jsx(Fragment, null, props.children);
const HoverLinkOverlayWithCondition = componentWithCondition(() => editorExperiment('platform_editor_preview_panel_linking_exp', true, {
exposure: true
}), HoverLinkOverlay, HoverLinkOverlayNoop);
const OverlayWithCardContext = ({
rendererAppearance,
isResolvedViewRendered,
url,
fireAnalyticsEvent,
children
}) => {
var _cardContext$value2, _cardContext$value2$s, _cardContext$value3, _cardContext$value3$i, _cardContext$value4;
const cardContext = useSmartCardContext();
// Note: useSmartLinkActions throws without smart card context. Using it here is safe
// because we checked cardContext availability in the parent component
const actions = useSmartLinkActions({
url,
appearance: 'inline'
});
const preview = useMemo(() => actions.find(action => action.id === 'preview-content'), [actions]);
const fireHoverLabelAEP = previewType => {
if (fireAnalyticsEvent) {
var _cardContext$value, _urlState$details$met, _urlState$details, _urlState$details$met2, _urlState$details$met3, _urlState$details2, _urlState$details2$me;
const store = cardContext === null || cardContext === void 0 ? void 0 : (_cardContext$value = cardContext.value) === null || _cardContext$value === void 0 ? void 0 : _cardContext$value.store;
const urlState = store === null || store === void 0 ? void 0 : store.getState()[url || ''];
fireAnalyticsEvent({
action: ACTION.CLICKED,
actionSubject: ACTION_SUBJECT.SMART_LINK,
actionSubjectId: ACTION_SUBJECT_ID.HOVER_LABEL,
eventType: EVENT_TYPE.UI,
attributes: {
previewType,
destinationProduct: (_urlState$details$met = urlState === null || urlState === void 0 ? void 0 : (_urlState$details = urlState.details) === null || _urlState$details === void 0 ? void 0 : (_urlState$details$met2 = _urlState$details.meta) === null || _urlState$details$met2 === void 0 ? void 0 : _urlState$details$met2.product) !== null && _urlState$details$met !== void 0 ? _urlState$details$met : null,
destinationSubproduct: (_urlState$details$met3 = urlState === null || urlState === void 0 ? void 0 : (_urlState$details2 = urlState.details) === null || _urlState$details2 === void 0 ? void 0 : (_urlState$details2$me = _urlState$details2.meta) === null || _urlState$details2$me === void 0 ? void 0 : _urlState$details2$me.subproduct) !== null && _urlState$details$met3 !== void 0 ? _urlState$details$met3 : null
}
});
}
};
const cardState = cardContext === null || cardContext === void 0 ? void 0 : (_cardContext$value2 = cardContext.value) === null || _cardContext$value2 === void 0 ? void 0 : (_cardContext$value2$s = _cardContext$value2.store) === null || _cardContext$value2$s === void 0 ? void 0 : _cardContext$value2$s.getState()[url || ''];
const ari = getObjectAri(cardState === null || cardState === void 0 ? void 0 : cardState.details);
const name = getObjectName(cardState === null || cardState === void 0 ? void 0 : cardState.details);
const iconUrl = getObjectIconUrl(cardState === null || cardState === void 0 ? void 0 : cardState.details);
// Get resolved URL from card state, fallback to original URL if not available
let resolvedUrl = url;
if (expValEquals('platform_hover_card_preview_panel', 'cohort', 'test')) {
var _cardState$details;
const cardStateUrl = cardState !== null && cardState !== void 0 && (_cardState$details = cardState.details) !== null && _cardState$details !== void 0 && _cardState$details.data && 'url' in cardState.details.data ? cardState.details.data.url : undefined;
resolvedUrl = cardStateUrl || url;
}
const isPanelAvailable = ari && (cardContext === null || cardContext === void 0 ? void 0 : (_cardContext$value3 = cardContext.value) === null || _cardContext$value3 === void 0 ? void 0 : (_cardContext$value3$i = _cardContext$value3.isPreviewPanelAvailable) === null || _cardContext$value3$i === void 0 ? void 0 : _cardContext$value3$i.call(_cardContext$value3, {
ari
}));
const openPreviewPanel = cardContext === null || cardContext === void 0 ? void 0 : (_cardContext$value4 = cardContext.value) === null || _cardContext$value4 === void 0 ? void 0 : _cardContext$value4.openPreviewPanel;
const isPreviewPanelAvailable = Boolean(openPreviewPanel && isPanelAvailable);
const isPreviewModalAvailable = Boolean(preview);
const isPreviewAvailable = isPreviewModalAvailable || isPreviewPanelAvailable;
const showPanelButtonIcon = isPreviewPanelAvailable ? 'panel' : isPreviewModalAvailable ? 'modal' : undefined;
// When inside preview panel iframe, hide the overlay button
const isInPreviewPanel = isWithinPreviewPanelIFrame();
const showPanelButton = isInPreviewPanel ? isPreviewPanelAvailable : isPreviewAvailable;
const Overlay = isPreviewAvailable ? HoverLinkOverlayWithCondition : HoverLinkOverlayNoop;
return jsx(Overlay, {
isVisible: isResolvedViewRendered,
url: url,
compactPadding: rendererAppearance === 'comment',
showPanelButton: showPanelButton,
showPanelButtonIcon: showPanelButtonIcon
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onClick: event => {
if (isPreviewPanelAvailable) {
var _extractSmartLinkEmbe;
// Prevent anchor default behaviour(click to open the anchor link)
// When glance panel is available, let openPreviewPanel handle it
event.preventDefault();
openPreviewPanel === null || openPreviewPanel === void 0 ? void 0 : openPreviewPanel({
url: resolvedUrl || '',
ari: ari || '',
name: name || '',
iconUrl,
panelData: {
embedUrl: expValEquals('platform_hover_card_preview_panel', 'cohort', 'test') ? (_extractSmartLinkEmbe = extractSmartLinkEmbed(cardState === null || cardState === void 0 ? void 0 : cardState.details)) === null || _extractSmartLinkEmbe === void 0 ? void 0 : _extractSmartLinkEmbe.src : undefined
}
});
editorExperiment('platform_editor_preview_panel_linking_exp', true, {
exposure: true
}) && fireHoverLabelAEP('panel');
} else if (isPreviewModalAvailable) {
event.preventDefault();
if (preview) {
preview.invoke();
}
editorExperiment('platform_editor_preview_panel_linking_exp', true, {
exposure: true
}) && fireHoverLabelAEP('modal');
}
}
}, children);
};
const InlineCard = props => {
var _cardContext$value5;
const {
url,
data,
eventHandlers,
fireAnalyticsEvent,
smartLinks,
rendererAppearance,
onSetLinkTarget
} = props;
const portal = usePortal(props);
const cardContext = useSmartCardContext();
const provider = useProvider('cardProvider');
const SuspenseWrapperForUrl = smartLinks === null || smartLinks === void 0 ? void 0 : smartLinks.SuspenseWrapperForUrl;
// Helper fn to conditionally wrap cards when suspense boundary is passed in via product
const wrapWithSuspense = card => {
if (SuspenseWrapperForUrl && url) {
return jsx(SuspenseWrapperForUrl, {
url: url
}, card);
}
return card;
};
const {
getState: getSmartlinkState
} = (cardContext === null || cardContext === void 0 ? void 0 : (_cardContext$value5 = cardContext.value) === null || _cardContext$value5 === void 0 ? void 0 : _cardContext$value5.store) || {};
const [isResolvedViewRendered, setIsResolvedViewRendered] = useState(false);
const cardState = getSmartlinkState === null || getSmartlinkState === void 0 ? void 0 : getSmartlinkState()[url || ''];
const onClick = getCardClickHandler(eventHandlers, url);
const cardProps = {
url,
data,
onClick,
container: portal
};
const {
hideHoverPreview,
actionOptions,
ssr,
getResolvingPlaceholder
} = smartLinks || {};
const resolvingPlaceholder = url && getResolvingPlaceholder ? getResolvingPlaceholder(url) : undefined;
const analyticsData = {
attributes: {
location: 'renderer'
},
// Below is added for the future implementation of Linking Platform namespaced analytic context
location: 'renderer'
};
const inlineAnnotationProps = useInlineAnnotationProps(props);
const CompetitorPrompt = smartLinks === null || smartLinks === void 0 ? void 0 : smartLinks.CompetitorPrompt;
const CompetitorPromptComponent = CompetitorPrompt && url ? jsx(CompetitorPrompt, {
sourceUrl: url,
linkType: "inline"
}) : null;
const onError = ({
err
}) => {
if (err) {
throw err;
}
};
useEffect(() => {
if (expValEquals('platform_editor_smartlink_local_cache', 'isEnabled', true) && url) {
// Refresh cache in the background
provider === null || provider === void 0 ? void 0 : provider.then(providerInstance => {
var _refreshCache, _ref;
(_refreshCache = (_ref = providerInstance).refreshCache) === null || _refreshCache === void 0 ? void 0 : _refreshCache.call(_ref, {
// It's ok to cast any resourceUrl to inlineCard here, because only URL is important for the request.
type: 'inlineCard',
attrs: {
url
}
});
});
}
}, [provider, url]);
const MaybeOverlay = cardContext !== null && cardContext !== void 0 && cardContext.value ? OverlayWithCardContext : HoverLinkOverlayNoop;
if ((ssr || cardState && expValEquals('platform_editor_smartlink_local_cache', 'isEnabled', true)) && url && !editorExperiment('platform_editor_preview_panel_linking_exp', true, {
exposure: true
})) {
if (
// eslint-disable-next-line @atlaskit/platform/no-invalid-feature-flag-usage
fg('editor_inline_comments_on_inline_nodes')) {
return jsx(SmartLinkDraggable, {
url: url,
appearance: SMART_LINK_APPEARANCE.INLINE,
source: SMART_LINK_DRAG_TYPES.RENDERER
}, jsx("span", _extends({
"data-inline-card": true
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- Ignored via go/ees017 (to be fixed)
,
"data-card-data": data ? JSON.stringify(data) : undefined,
"data-card-url": url
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
}, inlineAnnotationProps), jsx(AnalyticsContext, {
data: analyticsData
}, wrapWithSuspense(jsx(CardSSR, {
appearance: "inline",
url: url,
showHoverPreview: !hideHoverPreview,
actionOptions: actionOptions,
onClick: onClick,
resolvingPlaceholder: resolvingPlaceholder
})))));
}
return jsx(SmartLinkDraggable, {
url: url,
appearance: SMART_LINK_APPEARANCE.INLINE,
source: SMART_LINK_DRAG_TYPES.RENDERER
}, jsx(AnalyticsContext, {
data: analyticsData
}, wrapWithSuspense(jsx(CardSSR, {
appearance: "inline",
url: url,
showHoverPreview: !hideHoverPreview,
actionOptions: actionOptions,
onClick: onClick,
resolvingPlaceholder: resolvingPlaceholder
})), CompetitorPromptComponent));
} else if ((ssr || cardState && expValEquals('platform_editor_smartlink_local_cache', 'isEnabled', true)) && url && editorExperiment('platform_editor_preview_panel_linking_exp', true, {
exposure: true
})) {
if (
// eslint-disable-next-line @atlaskit/platform/no-invalid-feature-flag-usage
fg('editor_inline_comments_on_inline_nodes')) {
return jsx(SmartLinkDraggable, {
url: url,
appearance: SMART_LINK_APPEARANCE.INLINE,
source: SMART_LINK_DRAG_TYPES.RENDERER
}, jsx("span", {
"data-inline-card": true
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- Ignored via go/ees017 (to be fixed)
,
"data-card-data": data ? JSON.stringify(data) : undefined,
"data-card-url": url,
"data-renderer-mark": inlineAnnotationProps['data-renderer-mark'],
"data-annotation-draft-mark": inlineAnnotationProps['data-annotation-draft-mark'],
"data-annotation-inline-node": inlineAnnotationProps['data-annotation-inline-node'],
"data-renderer-start-pos": inlineAnnotationProps['data-renderer-start-pos'],
"data-annotation-mark": inlineAnnotationProps['data-annotation-mark']
}, jsx(AnalyticsContext, {
data: analyticsData
}, jsx(MaybeOverlay, {
url: url || '',
rendererAppearance: rendererAppearance,
isResolvedViewRendered: isResolvedViewRendered,
fireAnalyticsEvent: fireAnalyticsEvent
}, wrapWithSuspense(jsx(CardSSR, {
appearance: "inline",
url: url,
showHoverPreview: !hideHoverPreview,
actionOptions: actionOptions,
onClick: onClick,
resolvingPlaceholder: resolvingPlaceholder
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onResolve: data => {
if (!data.url || !data.title) {
return;
}
props.smartCardStorage.set(data.url, data.title);
if (data.title) {
setIsResolvedViewRendered(true);
}
},
onError: onError,
disablePreviewPanel: true
}))))));
}
return jsx(SmartLinkDraggable, {
url: url,
appearance: SMART_LINK_APPEARANCE.INLINE,
source: SMART_LINK_DRAG_TYPES.RENDERER
}, jsx(AnalyticsContext, {
data: analyticsData
}, jsx(MaybeOverlay, {
url: url || '',
rendererAppearance: rendererAppearance,
isResolvedViewRendered: isResolvedViewRendered,
fireAnalyticsEvent: fireAnalyticsEvent
}, wrapWithSuspense(jsx(CardSSR, {
appearance: "inline",
url: url,
showHoverPreview: !hideHoverPreview,
actionOptions: actionOptions,
onClick: onClick,
resolvingPlaceholder: resolvingPlaceholder
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onResolve: data => {
if (!data.url || !data.title) {
return;
}
props.smartCardStorage.set(data.url, data.title);
if (data.title) {
setIsResolvedViewRendered(true);
}
},
onError: onError,
disablePreviewPanel: true
}))), CompetitorPromptComponent));
}
return jsx(SmartLinkDraggable, {
url: url || '',
appearance: SMART_LINK_APPEARANCE.INLINE,
source: SMART_LINK_DRAG_TYPES.RENDERER
}, jsx(AnalyticsContext, {
data: analyticsData
}, jsx("span", _extends({
"data-inline-card": true
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- Ignored via go/ees017 (to be fixed)
,
"data-card-data": data ? JSON.stringify(data) : undefined,
"data-card-url": url
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
}, inlineAnnotationProps), jsx(CardErrorBoundary, _extends({
unsupportedComponent: UnsupportedInline
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
}, cardProps, {
onSetLinkTarget: onSetLinkTarget
}), jsx(MaybeOverlay, {
url: url || '',
rendererAppearance: rendererAppearance,
isResolvedViewRendered: isResolvedViewRendered,
fireAnalyticsEvent: fireAnalyticsEvent
}, wrapWithSuspense(jsx(Card, _extends({
appearance: "inline",
showHoverPreview: !hideHoverPreview,
actionOptions: actionOptions,
resolvingPlaceholder: resolvingPlaceholder
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
}, cardProps, {
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
onResolve: data => {
if (!data.url || !data.title) {
return;
}
props.smartCardStorage.set(data.url, data.title);
if (data.title) {
setIsResolvedViewRendered(true);
}
},
onError: onError,
disablePreviewPanel: editorExperiment('platform_editor_preview_panel_linking_exp', true, {
exposure: true
})
})))), CompetitorPromptComponent))));
};
const _default_1 = withSmartCardStorage(InlineCard);
export default _default_1;