@atlaskit/editor-core
Version:
A package contains Atlassian editor core functionality
349 lines (341 loc) • 19.9 kB
JavaScript
/**
* @jsxRuntime classic
* @jsx jsx
*/
import React, { useImperativeHandle, useRef } from 'react';
/* eslint-disable @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports -- Ignored via go/DSP-18766; jsx required at runtime for @jsxRuntime classic */
import { css, jsx, useTheme } from '@emotion/react';
import classnames from 'classnames';
import { injectIntl } from 'react-intl';
import { decisionListSelector, taskListSelector } from '@atlaskit/adf-schema';
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
import { fullPageMessages as messages } from '@atlaskit/editor-common/messages';
import { akEditorFullPageNarrowBreakout, akEditorGutterPaddingDynamic, akEditorGutterPaddingReduced } from '@atlaskit/editor-shared-styles';
import FeatureGates from '@atlaskit/feature-gate-js-client';
import { fg } from '@atlaskit/platform-feature-flags';
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
// Ignored via go/ees005
// eslint-disable-next-line import/no-named-as-default
import ClickAreaBlock from '../../Addon/ClickAreaBlock';
import { contentComponentClickWrapper } from '../../Addon/ClickAreaBlock/contentComponentWrapper';
import { ContextPanel } from '../../ContextPanel';
import EditorContentContainer from '../../EditorContentContainer/EditorContentContainer';
import PluginSlot from '../../PluginSlot';
import { contentAreaWrapper, sidebarArea } from './StyledComponents';
const akEditorFullWidthLayoutWidth = 1800;
const akEditorUltraWideLayoutWidth = 4000;
const akEditorSwoopCubicBezier = `cubic-bezier(0.15, 1, 0.3, 1)`;
const tableMarginFullWidthMode = 2;
const akLayoutGutterOffset = 12;
const SWOOP_ANIMATION = `0.5s ${akEditorSwoopCubicBezier}`;
const AK_NESTED_DND_GUTTER_OFFSET = 8;
const getTotalPadding = () => akEditorGutterPaddingDynamic() * 2;
const editorContentAreaProsemirrorStyle = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'& .ProseMirror': {
flexGrow: 1,
boxSizing: 'border-box',
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'& > *': {
clear: 'both'
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
[`> p, > ul, > ol:not(${taskListSelector}):not(${decisionListSelector}), > h1, > h2, > h3, > h4, > h5, > h6`]: {
clear: 'none'
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766
'> p:last-child': {
marginBottom: "var(--ds-space-300, 24px)"
}
}
});
const hideEditorContentAreaProsemirrorStyle = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'& .ProseMirror': {
display: 'none'
}
});
const hideEditorContentAreaProsemirrorWithAttributeStyle = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'&[data-markdown-mode-hide-prosemirror="true"] .ProseMirror': {
display: 'none'
}
});
const fullWidthNonChromelessBreakoutBlockTableStyle = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-1
'.fabric-editor--full-width-mode:not(:has(#chromeless-editor))': {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'.fabric-editor-breakout-mark, .extension-container.block, .pm-table-container': {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-important-styles -- Ignored via go/DSP-18766
width: '100% !important'
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'.fabric-editor-breakout-mark': {
// eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage/preview, @atlaskit/ui-styling-standard/no-important-styles -- Ignored via go/DSP-18766
marginLeft: 'unset !important',
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-important-styles -- Ignored via go/DSP-18766
transform: 'none !important'
}
}
});
// An additional spacing applied at the top of the content area reserving space when the primary toolbar
// is hidden – this avoids layout shift when the toolbar is toggled under the editor controls feature
const contentAreaReservedPrimaryToolbarSpace = css({
// extra 1px to account for the bottom border on the toolbar
marginTop: `calc(${"var(--ds-space-500, 40px)"} + 1px)`
});
// A reduced top spacing applied to the content area to compensate for the reserved space at the top
// of the page when the primary toolbar is hidden under the editor controls feature
const contentAreaReducedHeaderSpace = css({
paddingTop: "var(--ds-space-400, 32px)"
});
// new styles
const editorContentAreaNew = css({
// eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
lineHeight: '24px',
paddingTop: "var(--ds-space-600, 48px)",
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
'.ak-editor-content-area-no-toolbar &': {
// When the toolbar is hidden, we don't want content to jump up
// the extra 1px is to account for the border on the toolbar
paddingTop: `calc(${"var(--ds-space-600, 48px)"} + var(--ak-editor-fullpage-toolbar-height) + 1px)`
},
paddingBottom: "var(--ds-space-600, 48px)",
height: 'calc( 100% - 105px )',
width: '100%',
margin: 'auto',
flexDirection: 'column',
flexGrow: 1,
maxWidth: 'var(--ak-editor-content-area-max-width)',
transition: `max-width ${SWOOP_ANIMATION}`
});
const tableFullPageEditorStylesNew = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
'.ProseMirror .pm-table-wrapper > table': {
marginLeft: 0,
/* 1px border width offset added here to prevent unwanted overflow and scolling - ED-16212 */
// eslint-disable-next-line @atlaskit/design-system/use-tokens-space
marginRight: '-1px',
width: '100%'
}
});
const editorContentAreaContainerNestedDndStyle = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
'.fabric-editor--full-width-mode': {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
'[data-layout-section]': {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
maxWidth: `calc(100% + ${(akLayoutGutterOffset + AK_NESTED_DND_GUTTER_OFFSET) * 2}px)`
}
}
});
/* Prevent horizontal scroll on page in full width mode */
const editorContentAreaContainerStyleExcludeCodeBlockNew = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'.fabric-editor--full-width-mode': {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'.extension-container, .multiBodiedExtension--container': {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
maxWidth: `calc(100% - ${tableMarginFullWidthMode * 2}px)`
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'.extension-container.inline': {
maxWidth: '100%'
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'td .extension-container.inline': {
maxWidth: 'inherit'
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'[data-layout-section]': {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
maxWidth: `calc(100% + ${akLayoutGutterOffset * 2}px)`
}
}
});
/* Prevent horizontal scroll on page in full width mode */
const editorContentAreaContainerStyleNew = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'.fabric-editor--full-width-mode': {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'.code-block, .extension-container, .multiBodiedExtension--container': {
maxWidth: `calc(100% - ${tableMarginFullWidthMode * 2}px)`
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'.extension-container.inline': {
maxWidth: '100%'
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'td .extension-container.inline': {
maxWidth: 'inherit'
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'[data-layout-section]': {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
maxWidth: `calc(100% + ${akLayoutGutterOffset * 2}px)`
}
}
});
const editorContentGutterStyleFG = css({
padding: '0 72px'
});
const editorContentGutterStyles = css({
boxSizing: 'border-box',
padding: '0 52px'
});
const editorContentReducedGutterStyles = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-container-queries, @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
[`@container editor-area (max-width: ${akEditorFullPageNarrowBreakout}px)`]: {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
padding: `0 ${akEditorGutterPaddingReduced}px`
}
});
const contentAreaNew = css({
display: 'flex',
flexDirection: 'row',
height: `calc(100% - var(--ak-editor-fullpage-toolbar-height))`,
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-values
'&.ak-editor-content-area-no-toolbar': {
// The editor toolbar height is 1px off (from the border) -- so we need to add 1px to the height
// to match the toolbar height
height: `calc(100% + 1px)`
},
boxSizing: 'border-box',
margin: 0,
padding: 0,
transition: `padding 0ms ${akEditorSwoopCubicBezier}`
});
const contentAreaHeightNoToolbar = css({
height: '100%'
});
export const CONTENT_AREA_TEST_ID = 'ak-editor-fp-content-area';
export const EDITOR_CONTAINER = 'ak-editor-container';
const Content = /*#__PURE__*/React.forwardRef((props, ref) => {
var _props$editorAPI, _props$editorAPI$base, _props$editorAPI$base2, _props$editorAPI$bloc, _props$editorAPI2, _props$editorAPI2$blo, _props$editorAPI2$blo2, _props$editorAPI2$blo3, _contentAreaRef$curre, _allowScrollGutter$gu, _allowScrollGutter$gu2;
const theme = useTheme();
const fullWidthMode = props.appearance === 'full-width';
const maxWidthMode = props.appearance === 'max';
const scrollContainerRef = useRef(null);
const contentAreaRef = useRef(null);
const containerRef = useRef(null);
const allowScrollGutter = (_props$editorAPI = props.editorAPI) === null || _props$editorAPI === void 0 ? void 0 : (_props$editorAPI$base = _props$editorAPI.base) === null || _props$editorAPI$base === void 0 ? void 0 : (_props$editorAPI$base2 = _props$editorAPI$base.sharedState.currentState()) === null || _props$editorAPI$base2 === void 0 ? void 0 : _props$editorAPI$base2.allowScrollGutter;
const contentAreaMaxWidth = getTotalPadding() + (!fullWidthMode ? maxWidthMode ? akEditorUltraWideLayoutWidth : theme.layoutMaxWidth : akEditorFullWidthLayoutWidth);
// Get useStandardNodeWidth from block menu plugin shared state
// Only access editorAPI when the experiment is enabled to avoid performance impact
const useStandardNodeWidth = editorExperiment('platform_editor_controls', 'variant1') && ((_props$editorAPI$bloc = (_props$editorAPI2 = props.editorAPI) === null || _props$editorAPI2 === void 0 ? void 0 : (_props$editorAPI2$blo = _props$editorAPI2.blockMenu) === null || _props$editorAPI2$blo === void 0 ? void 0 : (_props$editorAPI2$blo2 = _props$editorAPI2$blo.sharedState) === null || _props$editorAPI2$blo2 === void 0 ? void 0 : (_props$editorAPI2$blo3 = _props$editorAPI2$blo2.currentState()) === null || _props$editorAPI2$blo3 === void 0 ? void 0 : _props$editorAPI2$blo3.useStandardNodeWidth) !== null && _props$editorAPI$bloc !== void 0 ? _props$editorAPI$bloc : false);
useImperativeHandle(ref, () => ({
get scrollContainer() {
return scrollContainerRef.current;
},
get contentArea() {
return contentAreaRef.current;
},
get containerArea() {
return containerRef.current;
}
}), []);
const markdownPluginCurrentView = useSharedPluginStateWithSelector(props.editorAPI, ['markdownMode'], states => {
var _states$markdownModeS;
return (_states$markdownModeS = states.markdownModeState) === null || _states$markdownModeS === void 0 ? void 0 : _states$markdownModeS.view;
});
const markdownPluginCurrentIsMarkdownMode = useSharedPluginStateWithSelector(props.editorAPI, ['markdownMode'], states => {
var _states$markdownModeS2;
return (_states$markdownModeS2 = states.markdownModeState) === null || _states$markdownModeS2 === void 0 ? void 0 : _states$markdownModeS2.isMarkdownMode;
});
const shouldHideProseMirrorForMarkdownMode = expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true) && markdownPluginCurrentView !== 'wysiwyg' && markdownPluginCurrentIsMarkdownMode;
return jsx("div", {
css: [contentAreaNew, props.isEditorToolbarHidden && contentAreaHeightNoToolbar],
"data-testid": CONTENT_AREA_TEST_ID,
ref: containerRef
}, jsx("div", {
css:
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage, @atlaskit/ui-styling-standard/no-imported-style-values
contentAreaWrapper,
"data-testid": EDITOR_CONTAINER,
"data-editor-container": 'true'
}, jsx(EditorContentContainer
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
, {
className: "fabric-editor-popup-scroll-parent",
featureFlags: props.featureFlags,
ref: scrollContainerRef,
viewMode: props === null || props === void 0 ? void 0 : props.viewMode,
isScrollable: true,
appearance: props.appearance,
contentMode: props.contentMode,
useStandardNodeWidth: useStandardNodeWidth
}, jsx(ClickAreaBlock, {
editorView: props.editorView,
editorDisabled: props.disabled
}, jsx("div", {
"data-markdown-mode-hide-prosemirror": shouldHideProseMirrorForMarkdownMode && fg('platform_editor_delay_markdown_view_mode_eval') ? 'true' : undefined,
css: [editorContentAreaNew, editorContentAreaProsemirrorStyle,
// EDITOR-6558: hide ProseMirror when the markdown-mode plugin
// reports a non-WYSIWYG view.
shouldHideProseMirrorForMarkdownMode && (fg('platform_editor_delay_markdown_view_mode_eval') ? hideEditorContentAreaProsemirrorWithAttributeStyle : hideEditorContentAreaProsemirrorStyle), tableFullPageEditorStylesNew, fullWidthNonChromelessBreakoutBlockTableStyle,
// for breakout resizing, there's no need to restrict the width of codeblocks as they're always wrapped in a breakout mark
expValEqualsNoExposure('platform_editor_breakout_resizing', 'isEnabled', true) ? editorContentAreaContainerStyleExcludeCodeBlockNew : editorContentAreaContainerStyleNew, fg('platform_editor_nested_dnd_styles_changes') && editorContentAreaContainerNestedDndStyle, !fg('platform_editor_controls_no_toolbar_space') && editorExperiment('platform_editor_controls', 'variant1') && contentAreaReducedHeaderSpace, !fg('platform_editor_controls_no_toolbar_space') && props.isEditorToolbarHidden && editorExperiment('platform_editor_controls', 'variant1') && contentAreaReservedPrimaryToolbarSpace],
style: {
'--ak-editor-content-area-max-width': `${contentAreaMaxWidth}px`
}
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
,
className: "ak-editor-content-area-region",
"data-editor-editable-content": true,
role: "region",
"aria-label": props.intl.formatMessage(messages.editableContentLabel),
ref: contentAreaRef,
"data-vc": "editor-content-area-region"
}, jsx("div", {
css: [editorContentGutterStyles,
// eslint-disable-next-line @atlaskit/platform/no-preconditioning
fg('platform_editor_controls_increase_full_page_gutter') && editorExperiment('platform_editor_controls', 'variant1') && editorContentGutterStyleFG, editorExperiment('platform_editor_preview_panel_responsiveness', true, {
exposure: true
}) && editorContentReducedGutterStyles]
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: classnames('ak-editor-content-area', 'appearance-full-page', {
'fabric-editor--full-width-mode': fullWidthMode,
'fabric-editor--max-width-mode': Boolean(maxWidthMode)
}),
ref: contentAreaRef
}, !!props.customContentComponents && 'before' in props.customContentComponents ? contentComponentClickWrapper(props.customContentComponents.before) : contentComponentClickWrapper(props.customContentComponents), jsx(PluginSlot, {
editorView: props.editorView,
editorActions: props.editorActions,
eventDispatcher: props.eventDispatcher,
providerFactory: props.providerFactory,
appearance: props.appearance,
items: props.contentComponents,
pluginHooks: props.pluginHooks,
contentArea: (_contentAreaRef$curre = contentAreaRef.current) !== null && _contentAreaRef$curre !== void 0 ? _contentAreaRef$curre : undefined,
popupsMountPoint: props.popupsMountPoint,
popupsBoundariesElement: props.popupsBoundariesElement,
popupsScrollableElement: props.popupsScrollableElement,
disabled: !!props.disabled,
containerElement: scrollContainerRef.current,
dispatchAnalyticsEvent: props.dispatchAnalyticsEvent,
wrapperElement: props.wrapperElement
}), props.editorDOMElement, !!props.customContentComponents && 'after' in props.customContentComponents ? contentComponentClickWrapper(props.customContentComponents.after) : null, allowScrollGutter && (FeatureGates.getExperimentValue('cc_snippets', 'isEnabled', false) ? jsx("div", {
id: "editor-scroll-gutter",
style: {
paddingBottom: `${(_allowScrollGutter$gu = allowScrollGutter.gutterSize) !== null && _allowScrollGutter$gu !== void 0 ? _allowScrollGutter$gu : '120'}px`
},
"data-vc": "scroll-gutter",
"data-editor-scroll-gutter": "true"
}) : jsx("div", {
id: "editor-scroll-gutter",
style: {
paddingBottom: `${(_allowScrollGutter$gu2 = allowScrollGutter.gutterSize) !== null && _allowScrollGutter$gu2 !== void 0 ? _allowScrollGutter$gu2 : '120'}px`
},
"data-vc": "scroll-gutter"
}))))))), jsx("div", {
css: sidebarArea
}, props.contextPanel || jsx(ContextPanel, {
editorAPI: props.editorAPI,
visible: false
})));
});
export const FullPageContentArea = injectIntl(Content, {
forwardRef: true
});