@atlaskit/editor-plugin-card
Version:
Card plugin for @atlaskit/editor-core
296 lines (295 loc) • 16.9 kB
JavaScript
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ACTION_SUBJECT_ID, EVENT_TYPE, ACTION, ACTION_SUBJECT } from '@atlaskit/editor-common/analytics/types/enums';
import HoverLinkOverlay from '@atlaskit/editor-common/HoverLinkOverlay';
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/useSharedPluginStateWithSelector';
import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
import { extractSmartLinkEmbed } from '@atlaskit/link-extractors/extract-smart-link-embed';
import { isWithinPreviewPanelIFrame } from '@atlaskit/linking-common/utils/is-within-preview-panel-iframe';
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
import { getObjectAri, getObjectName, getObjectIconUrl } from '@atlaskit/smart-card';
import { useSmartLinkDestinationUrl } from '@atlaskit/smart-card/hook/use-smart-link-destination-url';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { registerRemoveOverlay } from '../pm-plugins/actions';
import { pluginKey } from '../pm-plugins/plugin-key';
import { AwarenessWrapper } from '../ui/AwarenessWrapper';
import { PreviewInvoker } from '../ui/preview/PreviewInvoker';
import { InlineCard } from './inlineCardBase';
// Module scope so the stored callback's closure chain cannot reach the
// component scope (getPos, node, view) via V8 context sharing.
var createRemoveOverlay = function createRemoveOverlay(setIsInsertedRef) {
return function () {
var _setIsInsertedRef$cur;
return (_setIsInsertedRef$cur = setIsInsertedRef.current) === null || _setIsInsertedRef$cur === void 0 ? void 0 : _setIsInsertedRef$cur.call(setIsInsertedRef, false);
};
};
var selector = function selector(states) {
var _states$editorViewMod, _states$selectionStat;
return {
mode: (_states$editorViewMod = states.editorViewModeState) === null || _states$editorViewMod === void 0 ? void 0 : _states$editorViewMod.mode,
selection: (_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection
};
};
export var InlineCardWithAwareness = /*#__PURE__*/memo(function (_ref) {
var _pluginInjectionApi$c;
var node = _ref.node,
cardContext = _ref.cardContext,
actionOptions = _ref.actionOptions,
useAlternativePreloader = _ref.useAlternativePreloader,
view = _ref.view,
getPos = _ref.getPos,
pluginInjectionApi = _ref.pluginInjectionApi,
onClick = _ref.onClick,
isPulseEnabled = _ref.isPulseEnabled,
isOverlayEnabled = _ref.isOverlayEnabled,
isSelected = _ref.isSelected,
isPageSSRed = _ref.isPageSSRed,
provider = _ref.provider,
appearance = _ref.appearance;
var _useState = useState(false),
_useState2 = _slicedToArray(_useState, 2),
isHovered = _useState2[0],
setIsHovered = _useState2[1];
var _useState3 = useState(false),
_useState4 = _slicedToArray(_useState3, 2),
isInserted = _useState4[0],
setIsInserted = _useState4[1];
var setIsInsertedRef = useRef(setIsInserted);
useEffect(function () {
return function () {
if (isExperimentEnabled('platform_editor_reduce_memory_leaks')) {
setIsInsertedRef.current = undefined;
}
};
}, []);
var _useState5 = useState(false),
_useState6 = _slicedToArray(_useState5, 2),
isResolvedViewRendered = _useState6[0],
setIsResolvedViewRendered = _useState6[1];
var editorAppearance = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c = pluginInjectionApi.card.sharedState.currentState()) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.editorAppearance;
var destinationUrl = useSmartLinkDestinationUrl(node.attrs.url);
// Remove tr: Transaction | undefined parameter with platform_editor_reduce_forced_layout_2 cleanup.
var onResolve = useCallback(function (tr, title) {
if (tr) {
var metadata = tr.getMeta(pluginKey);
var removeOverlay = isExperimentEnabled('platform_editor_reduce_memory_leaks') ? createRemoveOverlay(setIsInsertedRef) : function () {
return setIsInserted(false);
};
if (metadata && metadata.type === 'REGISTER') {
registerRemoveOverlay(removeOverlay, metadata.info)(tr);
} else {
registerRemoveOverlay(removeOverlay)(tr);
}
}
if (title) {
setIsResolvedViewRendered(true);
}
}, []);
var markMostRecentlyInsertedLink = useCallback(function (isLinkMostRecentlyInserted) {
if (isOverlayEnabled && !isExperimentEnabled('platform_editor_reduce_forced_layout_2')) {
setIsInserted(isLinkMostRecentlyInserted);
}
}, [isExperimentEnabled('platform_editor_reduce_forced_layout_2') ? undefined : isOverlayEnabled]);
var setOverlayHoveredStyles = useCallback(function (isHovered) {
if (isOverlayEnabled) {
setIsHovered(isHovered);
}
}, [isOverlayEnabled]);
var _useSharedPluginState = useSharedPluginStateWithSelector(pluginInjectionApi, ['selection', 'editorViewMode'], selector),
mode = _useSharedPluginState.mode,
selection = _useSharedPluginState.selection;
var floatingToolbarNode = selection instanceof NodeSelection && selection.node;
// This is a prop to show Hover card, Hover card should be shown only in Live View and Classic Renderer (note when only Editor controls enabled we don't show in Live view)
var showHoverPreview = floatingToolbarNode !== node;
var innerCardWithOpenButtonOverlay = useMemo(function () {
var _pluginInjectionApi$a;
return /*#__PURE__*/React.createElement(HoverLinkOverlay, {
isVisible: isResolvedViewRendered,
url: node.attrs.url,
destinationUrl: destinationUrl,
compactPadding: editorAppearance === 'comment' || editorAppearance === 'chromeless',
editorAnalyticsApi: pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions,
view: view
}, /*#__PURE__*/React.createElement(InlineCard, {
node: node,
view: view,
getPos: getPos,
useAlternativePreloader: useAlternativePreloader,
actionOptions: actionOptions,
onResolve: onResolve,
onClick: onClick,
cardContext: cardContext,
isHovered: isHovered,
isPageSSRed: isPageSSRed,
provider: provider,
pluginInjectionApi: pluginInjectionApi,
disablePreviewPanel: true
}));
}, [isResolvedViewRendered, node, editorAppearance, view, getPos, useAlternativePreloader, actionOptions, onResolve, onClick, cardContext, isHovered, isPageSSRed, provider, pluginInjectionApi, destinationUrl]);
var innerCardOriginal = useMemo(function () {
return /*#__PURE__*/React.createElement(InlineCard, {
node: node,
view: view,
getPos: getPos,
useAlternativePreloader: useAlternativePreloader,
actionOptions: actionOptions,
onResolve: onResolve,
onClick: onClick,
cardContext: cardContext,
isHovered: isHovered,
isPageSSRed: isPageSSRed,
provider: provider,
pluginInjectionApi: pluginInjectionApi,
showHoverPreview: false
});
}, [actionOptions, cardContext, getPos, isHovered, node, onClick, onResolve, useAlternativePreloader, view, isPageSSRed, provider, pluginInjectionApi]);
var shouldShowOpenButtonOverlay = useMemo(function () {
var shouldShowOpenButtonOverlayInChomeless = editorAppearance === 'chromeless';
return mode === 'edit' || editorAppearance === 'comment' || shouldShowOpenButtonOverlayInChomeless;
}, [mode, editorAppearance]);
var innerCard = shouldShowOpenButtonOverlay ? innerCardWithOpenButtonOverlay : innerCardOriginal;
if (mode === 'view') {
var _cardContext$value;
var url = node.attrs.url;
var cardState = cardContext === null || cardContext === void 0 || (_cardContext$value = cardContext.value) === null || _cardContext$value === void 0 || (_cardContext$value = _cardContext$value.store) === null || _cardContext$value === void 0 ? void 0 : _cardContext$value.getState()[url];
if (cardState) {
var _cardContext$value2, _cardContext$value2$i, _cardContext$value3;
var ari = getObjectAri(cardState.details);
var name = getObjectName(cardState.details);
var iconUrl = getObjectIconUrl(cardState.details);
var isPanelAvailable = ari && (cardContext === null || cardContext === void 0 || (_cardContext$value2 = cardContext.value) === null || _cardContext$value2 === void 0 || (_cardContext$value2$i = _cardContext$value2.isPreviewPanelAvailable) === null || _cardContext$value2$i === void 0 ? void 0 : _cardContext$value2$i.call(_cardContext$value2, {
ari: ari
}));
var openPreviewPanel = cardContext === null || cardContext === void 0 || (_cardContext$value3 = cardContext.value) === null || _cardContext$value3 === void 0 ? void 0 : _cardContext$value3.openPreviewPanel;
var isPreviewPanelAvailable = Boolean(openPreviewPanel && isPanelAvailable);
var firePreviewPanelClickEvent = function firePreviewPanelClickEvent(_ref2) {
var _cardContext$value4, _pluginInjectionApi$a2;
var previewType = _ref2.previewType;
var store = cardContext === null || cardContext === void 0 || (_cardContext$value4 = cardContext.value) === null || _cardContext$value4 === void 0 ? void 0 : _cardContext$value4.store;
var urlState = store === null || store === void 0 ? void 0 : store.getState()[url || ''];
if (pluginInjectionApi !== null && pluginInjectionApi !== void 0 && (_pluginInjectionApi$a2 = pluginInjectionApi.analytics) !== null && _pluginInjectionApi$a2 !== void 0 && _pluginInjectionApi$a2.actions) {
var _pluginInjectionApi$a3, _urlState$details$met, _urlState$details, _urlState$details$met2, _urlState$details2;
pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a3 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a3 === void 0 || (_pluginInjectionApi$a3 = _pluginInjectionApi$a3.actions) === null || _pluginInjectionApi$a3 === void 0 || _pluginInjectionApi$a3.fireAnalyticsEvent({
action: ACTION.CLICKED,
actionSubject: ACTION_SUBJECT.SMART_LINK,
actionSubjectId: ACTION_SUBJECT_ID.HOVER_LABEL,
eventType: EVENT_TYPE.UI,
attributes: {
previewType: previewType,
destinationProduct: (_urlState$details$met = urlState === null || urlState === void 0 || (_urlState$details = urlState.details) === null || _urlState$details === void 0 || (_urlState$details = _urlState$details.meta) === null || _urlState$details === void 0 ? void 0 : _urlState$details.product) !== null && _urlState$details$met !== void 0 ? _urlState$details$met : null,
destinationSubproduct: (_urlState$details$met2 = urlState === null || urlState === void 0 || (_urlState$details2 = urlState.details) === null || _urlState$details2 === void 0 || (_urlState$details2 = _urlState$details2.meta) === null || _urlState$details2 === void 0 ? void 0 : _urlState$details2.subproduct) !== null && _urlState$details$met2 !== void 0 ? _urlState$details$met2 : null
}
});
}
};
var innerCardWithPanelButtonOverlay = /*#__PURE__*/React.createElement(PreviewInvoker, {
url: url,
appearance: "inline"
}, function (_ref3) {
var canPreview = _ref3.canPreview,
invokePreview = _ref3.invokePreview;
var isPreviewModalAvailable = Boolean(canPreview && invokePreview);
// In view mode we show HoverLinkOverlay only with if preview mode or panel is available
// otherwise a use can click on smartlink itself to open the link in a new tab.
var isPreviewAvailable = isPreviewPanelAvailable || isPreviewModalAvailable;
// When inside preview panel iframe, hide the overlay button
var isInPreviewPanel = isWithinPreviewPanelIFrame();
var showPanelButton = isInPreviewPanel ? isPreviewPanelAvailable : isPreviewAvailable;
if (isPreviewAvailable) {
var _pluginInjectionApi$a4;
return /*#__PURE__*/React.createElement(HoverLinkOverlay, {
isVisible: isResolvedViewRendered,
url: url,
destinationUrl: destinationUrl,
compactPadding: editorAppearance === 'comment' || editorAppearance === 'chromeless',
editorAnalyticsApi: pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a4 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a4 === void 0 ? void 0 : _pluginInjectionApi$a4.actions,
view: view,
showPanelButton: showPanelButton,
showPanelButtonIcon: isPreviewAvailable && isPreviewPanelAvailable ? 'panel' : 'modal'
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onClick: function onClick(event) {
if (isPreviewPanelAvailable) {
var _extractSmartLinkEmbe;
event.preventDefault();
openPreviewPanel === null || openPreviewPanel === void 0 || openPreviewPanel({
url: url,
ari: ari || '',
name: name || '',
iconUrl: 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
}
});
firePreviewPanelClickEvent({
previewType: 'panel'
});
} else if (isPreviewModalAvailable) {
event.preventDefault();
invokePreview === null || invokePreview === void 0 || invokePreview();
firePreviewPanelClickEvent({
previewType: 'modal'
});
}
}
}, /*#__PURE__*/React.createElement(InlineCard, {
node: node,
view: view,
getPos: getPos,
useAlternativePreloader: useAlternativePreloader,
actionOptions: actionOptions,
onResolve: onResolve,
onClick: onClick,
cardContext: cardContext,
isHovered: isHovered,
isPageSSRed: isPageSSRed,
provider: provider,
pluginInjectionApi: pluginInjectionApi,
showHoverPreview: mode === 'view' && showHoverPreview,
disablePreviewPanel: true
}));
} else {
return /*#__PURE__*/React.createElement(InlineCard, {
node: node,
view: view,
getPos: getPos,
useAlternativePreloader: useAlternativePreloader,
actionOptions: actionOptions,
onResolve: onResolve,
onClick: onClick,
cardContext: cardContext,
isHovered: isHovered,
isPageSSRed: isPageSSRed,
provider: provider,
pluginInjectionApi: pluginInjectionApi,
showHoverPreview: mode === 'view' && showHoverPreview
});
}
});
innerCard = innerCardWithPanelButtonOverlay;
}
}
var getPosFunction = typeof getPos === 'function' ? getPos : undefined;
var placeholderUniqId = (getPosFunction === null || getPosFunction === void 0 ? void 0 : getPosFunction()) || 0;
return isOverlayEnabled || isPulseEnabled ? /*#__PURE__*/React.createElement(AwarenessWrapper, {
isOverlayEnabled: isOverlayEnabled,
isPulseEnabled: isPulseEnabled,
cardContext: cardContext,
getPos: getPos,
isHovered: isHovered,
isInserted: isInserted,
url: node.attrs.url,
isSelected: isSelected,
isResolvedViewRendered: isResolvedViewRendered,
markMostRecentlyInsertedLink: markMostRecentlyInsertedLink,
pluginInjectionApi: pluginInjectionApi,
setOverlayHoveredStyles: setOverlayHoveredStyles,
appearance: appearance
}, innerCard) : /*#__PURE__*/React.createElement("span", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: "card",
"data-vc": "editor-plugin-inline-card",
"data-ssr-placeholder": "editor-plugin-inline-card-".concat(placeholderUniqId),
"data-ssr-placeholder-replace": "editor-plugin-inline-card-".concat(placeholderUniqId)
}, innerCard);
});