@atlaskit/renderer
Version:
Renderer component
150 lines (148 loc) • 6.19 kB
JavaScript
/**
* @jsxRuntime classic
* @jsx jsx
*/
// eslint-disable-next-line @typescript-eslint/consistent-type-imports, @atlaskit/ui-styling-standard/use-compiled -- emotion jsx pragma; go/DSP-18766
import { css, jsx } from '@emotion/react'; // oxlint-ignore @typescript-eslint/consistent-type-imports -- classic @jsx jsx factory + jsx.JSX.Element types
import React from 'react';
import memoizeOne from 'memoize-one';
import { getNodeRenderer } from '@atlaskit/editor-common/extensions';
import { WithProviders } from '@atlaskit/editor-common/provider-factory';
import { getExtensionRenderer } from '@atlaskit/editor-common/utils';
const inlineExtensionStyle = css({
display: 'inline-block',
maxWidth: '100%',
verticalAlign: 'middle',
// 0px margin top is important here.
// When running on server-side emotion will generate style tags before elements.
// This caused packages/editor/editor-common/src/styles/shared/block-marks.ts to override the margin-top.
// However as soon as the styles are extracted to <head> it adds back the margin.
// The timing is tricky as it happens to be when UFO collects the dimension for the placeholder for TTVC calculation.
// This resulted 1px mismatch on the image. Further cause everything on page to shift by 1px.
// es-lint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
margin: `0px 1px ${"var(--ds-space-050, 4px)"}`,
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'& .rich-media-item': {
maxWidth: '100%'
}
});
const plainTextMacroStyle = css({
display: 'inline',
verticalAlign: 'baseline',
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
'[data-macro-body]': {
display: 'inline'
}
});
/** Renders extension (macro) nodes inside the ADF renderer. */
export default function ExtensionRenderer(props) {
const {
extensionHandlers,
rendererContext,
extensionType,
extensionKey,
parameters,
content,
text,
type,
localId,
marks,
actions,
children
} = props;
const isMounted = React.useRef(true);
const localGetNodeRenderer = React.useMemo(() => memoizeOne(getNodeRenderer), []);
const [extensionProvider, setExtensionProvider] = React.useState(null);
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleProvider = React.useCallback((_name, providerPromise) => {
providerPromise && providerPromise.then(provider => {
if (isMounted.current) {
setExtensionProvider(provider);
}
});
}, []);
const renderExtensionNode = React.useCallback(extensionProvider => {
var _marks$find, _marks$find$attrs, _node$parameters, _node$parameters$macr;
const fragmentLocalId = marks === null || marks === void 0 ? void 0 : (_marks$find = marks.find(m => m.type.name === 'fragment')) === null || _marks$find === void 0 ? void 0 : (_marks$find$attrs = _marks$find.attrs) === null || _marks$find$attrs === void 0 ? void 0 : _marks$find$attrs.localId;
const node = {
type,
extensionKey,
extensionType,
parameters,
content: content || text,
localId,
fragmentLocalId
};
const isPlainTextMacro = Boolean(node === null || node === void 0 ? void 0 : (_node$parameters = node.parameters) === null || _node$parameters === void 0 ? void 0 : (_node$parameters$macr = _node$parameters.macroParams) === null || _node$parameters$macr === void 0 ? void 0 : _node$parameters$macr.__bodyContent);
let result = null;
try {
if (extensionHandlers && extensionHandlers[extensionType]) {
const render = getExtensionRenderer(extensionHandlers[extensionType]);
result = render(node, rendererContext.adDoc);
}
if (!result && extensionProvider) {
const NodeRenderer = localGetNodeRenderer(extensionProvider, extensionType, extensionKey);
if (node.type === 'multiBodiedExtension') {
result = jsx(NodeRenderer, {
node: node,
actions: actions
});
} else if (node.type === 'inlineExtension') {
result = jsx(InlineNodeRendererWrapper, {
isPlainTextMacro: isPlainTextMacro
}, jsx(NodeRenderer, {
node: node
}));
} else {
result = jsx(NodeRenderer, {
node: node
});
}
}
} catch {
/** We don't want this error to block renderer */
/** We keep rendering the default content */
}
return children({
node,
result
});
}, [actions, children, content, extensionHandlers, extensionKey, extensionType, localGetNodeRenderer, localId, marks, parameters, rendererContext === null || rendererContext === void 0 ? void 0 : rendererContext.adDoc, text, type]);
const setupAndRenderExtensionNode = React.useCallback(providers => {
if (!extensionProvider && providers.extensionProvider) {
handleProvider('extensionProvider', providers.extensionProvider);
}
return renderExtensionNode(extensionProvider);
}, [extensionProvider, handleProvider, renderExtensionNode]);
React.useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
if (!props.providers) {
return setupAndRenderExtensionNode({});
}
return jsx(WithProviders
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
, {
providers: ['extensionProvider'],
providerFactory: props.providers,
renderNode: setupAndRenderExtensionNode
});
}
export const InlineNodeRendererWrapper = ({
children,
isPlainTextMacro,
ssrPlaceholder,
ssrPlaceholderReplace
}) => {
return jsx("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: `inline-extension-renderer ${isPlainTextMacro ? 'plain-text-macro' : ''}`,
css: [inlineExtensionStyle, isPlainTextMacro && plainTextMacroStyle],
"data-ssr-placeholder": ssrPlaceholder,
"data-ssr-placeholder-replace": ssrPlaceholderReplace
}, children);
};