UNPKG

@atlaskit/editor-core

Version:

A package contains Atlassian editor core functionality

224 lines (223 loc) 13.1 kB
/** * @jsxRuntime classic * @jsx jsx */ import React, { useEffect, useMemo, useRef, useState } from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports -- Ignored via go/DSP-18766; jsx required at runtime for @jsxRuntime classic import { jsx } from '@emotion/react'; import { getBrowserInfo } from '@atlaskit/editor-common/browser'; import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'; import { SSRRenderMeasure } from '@atlaskit/editor-common/performance/ssr-measures'; import { ContextPanelWidthProvider } from '@atlaskit/editor-common/ui'; import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector'; import { FULL_PAGE_EDITOR_TOOLBAR_HEIGHT } from '@atlaskit/editor-shared-styles'; import { fg } from '@atlaskit/platform-feature-flags'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { getPrimaryToolbarComponents } from '../../Toolbar/getPrimaryToolbarComponents'; import { FullPageContentArea } from './FullPageContentArea'; import { FullPageToolbar } from './FullPageToolbar'; import { FullPageToolbarNext } from './FullPageToolbarNext'; import { fullPageEditorWrapper } from './StyledComponents'; const SSR_TRACE_SEGMENT_NAME = 'reactEditorView/fullPageAppearance'; const useShowKeyline = contentAreaRef => { const [showKeyline, setShowKeyline] = useState(false); useEffect(() => { var _contentAreaRef$curre; if (!((_contentAreaRef$curre = contentAreaRef.current) !== null && _contentAreaRef$curre !== void 0 && _contentAreaRef$curre.contentArea)) { return; } const browser = getBrowserInfo(); const intersection = new IntersectionObserver(([entry]) => { setShowKeyline(!entry.isIntersecting && entry.boundingClientRect.top < entry.intersectionRect.top); }, { root: undefined, // Safari seems to miss events (on fast scroll) sometimes due // to differences in IntersectionObserver behaviour between browsers. // By lowering the threshold a little it gives Safari more // time to catch these events. threshold: browser.safari ? 0.98 : 1 }); intersection.observe(contentAreaRef.current.contentArea); return () => { intersection.disconnect(); }; }, [contentAreaRef]); return showKeyline; }; const hasCustomComponents = components => { if (!components) { return false; } if ('before' in components) { return Array.isArray(components.before) && components.before.length > 0 || !!components.before || Array.isArray(components.after) && components.after.length > 0 || !!components.after; } return true; }; export const FullPageEditor = props => { var _scrollContentContain, _toolbarDockingPositi, _scrollContentContain2, _scrollContentContain3, _wrapperElementRef$cu; // Should be always the first statement in the component const firstRenderStartTimestampRef = useRef(performance.now()); const wrapperElementRef = useMemo(() => props.innerRef, [props.innerRef]); const scrollContentContainerRef = useRef(null); const showKeyline = useShowKeyline(scrollContentContainerRef); const editorAPI = props.editorAPI; const state = useSharedPluginStateWithSelector(editorAPI, ['primaryToolbar', 'interaction', 'editorViewMode', 'toolbar'], states => { var _states$primaryToolba, _states$interactionSt, _states$editorViewMod, _states$toolbarState; return { primaryToolbarComponents: (_states$primaryToolba = states.primaryToolbarState) === null || _states$primaryToolba === void 0 ? void 0 : _states$primaryToolba.components, interactionState: (_states$interactionSt = states.interactionState) === null || _states$interactionSt === void 0 ? void 0 : _states$interactionSt.interactionState, editorViewMode: (_states$editorViewMod = states.editorViewModeState) === null || _states$editorViewMod === void 0 ? void 0 : _states$editorViewMod.mode, contextualFormattingModeOverride: (_states$toolbarState = states.toolbarState) === null || _states$toolbarState === void 0 ? void 0 : _states$toolbarState.contextualFormattingModeOverride }; }); // Markdown Mode forces `'always-pinned'` while in source / preview view // (no PM selection to anchor a floating toolbar to). The user's docking // pref alone is not enough to decide whether to mount the primary toolbar // in that case — the override has to short-circuit the hide gate below. const forcePrimaryToolbarPinned = state.contextualFormattingModeOverride === 'always-pinned' && fg('platform_editor_toolbar_mode_override'); const interactionState = state.interactionState; const primaryToolbarState = getPrimaryToolbarComponents(editorAPI, state.primaryToolbarComponents); const hasHadInteraction = interactionState !== 'hasNotHadInteraction'; let toolbarDocking = useSharedPluginStateSelector(editorAPI, 'selectionToolbar.toolbarDocking', { disabled: fg('platform_editor_use_preferences_plugin') }); if (!fg('platform_editor_use_preferences_plugin')) { if (!toolbarDocking) { var _editorAPI$selectionT, _editorAPI$selectionT2, _editorAPI$selectionT3; // This is a workaround for the rendering issue with the selection toolbar // where using useSharedPluginStateSelector or useSharedPluginState the state are not // available when the editor is first loaded. and cause the toolbar to blink. const defaultDocking = props.__livePage ? 'none' : 'top'; toolbarDocking = (_editorAPI$selectionT = editorAPI === null || editorAPI === void 0 ? void 0 : (_editorAPI$selectionT2 = editorAPI.selectionToolbar) === null || _editorAPI$selectionT2 === void 0 ? void 0 : (_editorAPI$selectionT3 = _editorAPI$selectionT2.sharedState.currentState()) === null || _editorAPI$selectionT3 === void 0 ? void 0 : _editorAPI$selectionT3.toolbarDocking) !== null && _editorAPI$selectionT !== void 0 ? _editorAPI$selectionT : defaultDocking; } } let { toolbarDockingPosition } = useSharedPluginStateSelector(editorAPI, 'userPreferences.preferences', { disabled: !fg('platform_editor_use_preferences_plugin') }) || {}; if (fg('platform_editor_use_preferences_plugin')) { if (!toolbarDockingPosition) { var _editorAPI$userPrefer, _editorAPI$userPrefer2, _editorAPI$userPrefer3; // This is a workaround for the rendering issue with the selection toolbar // when using useSharedPluginStateWithSelector the state is not yet // available when the editor is first loaded. // This causes the toolbar to blink creating a layout shift. const defaultDockingPosition = props.__livePage ? 'none' : 'top'; toolbarDockingPosition = (_editorAPI$userPrefer = editorAPI === null || editorAPI === void 0 ? void 0 : (_editorAPI$userPrefer2 = editorAPI.userPreferences) === null || _editorAPI$userPrefer2 === void 0 ? void 0 : (_editorAPI$userPrefer3 = _editorAPI$userPrefer2.actions.getUserPreferences()) === null || _editorAPI$userPrefer3 === void 0 ? void 0 : _editorAPI$userPrefer3.toolbarDockingPosition) !== null && _editorAPI$userPrefer !== void 0 ? _editorAPI$userPrefer : defaultDockingPosition; } } let primaryToolbarComponents = props.primaryToolbarComponents; if (Array.isArray(primaryToolbarState === null || primaryToolbarState === void 0 ? void 0 : primaryToolbarState.components) && Array.isArray(primaryToolbarComponents)) { primaryToolbarComponents = primaryToolbarState.components.concat(primaryToolbarComponents); } let isEditorToolbarHidden = state.editorViewMode === 'view'; const { customPrimaryToolbarComponents } = props; if (editorExperiment('platform_editor_controls', 'variant1', { exposure: true })) { if (fg('platform_editor_use_preferences_plugin')) { // need to check if the toolbarDockingPosition is set to 'none' or 'top' if (toolbarDockingPosition === 'none' && !forcePrimaryToolbarPinned) { primaryToolbarComponents = []; if (!hasCustomComponents(customPrimaryToolbarComponents)) { isEditorToolbarHidden = true; } } } else { if (toolbarDocking === 'none' && !forcePrimaryToolbarPinned) { primaryToolbarComponents = []; if (!hasCustomComponents(customPrimaryToolbarComponents)) { isEditorToolbarHidden = true; } } } } const isToolbarAIFCEnabled = Boolean(editorAPI === null || editorAPI === void 0 ? void 0 : editorAPI.toolbar); const popupsBoundariesElement = props.popupsBoundariesElement || (scrollContentContainerRef === null || scrollContentContainerRef === void 0 ? void 0 : (_scrollContentContain = scrollContentContainerRef.current) === null || _scrollContentContain === void 0 ? void 0 : _scrollContentContain.containerArea) || undefined; return jsx(SSRRenderMeasure, { segmentName: SSR_TRACE_SEGMENT_NAME, startTimestampRef: firstRenderStartTimestampRef, onSSRMeasure: props.onSSRMeasure }, jsx(ContextPanelWidthProvider, null, jsx("div", { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 css: fullPageEditorWrapper // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , className: "akEditor", ref: wrapperElementRef, style: { '--ak-editor-fullpage-toolbar-height': // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values FULL_PAGE_EDITOR_TOOLBAR_HEIGHT(isToolbarAIFCEnabled) } }, !isEditorToolbarHidden && (isToolbarAIFCEnabled ? jsx(FullPageToolbarNext, { disabled: !!props.disabled || !hasHadInteraction && expValEquals('platform_editor_default_toolbar_state', 'isEnabled', true), toolbarDockingPosition: (_toolbarDockingPositi = toolbarDockingPosition) !== null && _toolbarDockingPositi !== void 0 ? _toolbarDockingPositi : toolbarDocking, beforeIcon: props.primaryToolbarIconBefore, editorAPI: editorAPI, editorView: props.editorView, popupsMountPoint: props.popupsMountPoint, popupsBoundariesElement: props.popupsBoundariesElement, popupsScrollableElement: props.popupsScrollableElement, showKeyline: showKeyline, customPrimaryToolbarComponents: props.customPrimaryToolbarComponents }) : jsx(FullPageToolbar, { appearance: props.appearance, editorAPI: editorAPI, beforeIcon: props.primaryToolbarIconBefore, collabEdit: props.collabEdit, containerElement: (_scrollContentContain2 = (_scrollContentContain3 = scrollContentContainerRef.current) === null || _scrollContentContain3 === void 0 ? void 0 : _scrollContentContain3.scrollContainer) !== null && _scrollContentContain2 !== void 0 ? _scrollContentContain2 : null, customPrimaryToolbarComponents: props.customPrimaryToolbarComponents, disabled: !!props.disabled, dispatchAnalyticsEvent: props.dispatchAnalyticsEvent, editorActions: props.editorActions, editorDOMElement: props.editorDOMElement // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion , editorView: props.editorView // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion , eventDispatcher: props.eventDispatcher, hasMinWidth: props.enableToolbarMinWidth, popupsBoundariesElement: props.popupsBoundariesElement, popupsMountPoint: props.popupsMountPoint, popupsScrollableElement: props.popupsScrollableElement, primaryToolbarComponents: primaryToolbarComponents, providerFactory: props.providerFactory, showKeyline: showKeyline, featureFlags: props.featureFlags })), jsx(FullPageContentArea, { editorAPI: editorAPI, ref: scrollContentContainerRef, appearance: props.appearance, contentComponents: props.contentComponents, contextPanel: props.contextPanel, customContentComponents: props.customContentComponents, disabled: props.disabled, dispatchAnalyticsEvent: props.dispatchAnalyticsEvent, editorActions: props.editorActions, editorDOMElement: props.editorDOMElement // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion , editorView: props.editorView, eventDispatcher: props.eventDispatcher, popupsBoundariesElement: popupsBoundariesElement, popupsMountPoint: props.popupsMountPoint, popupsScrollableElement: props.popupsScrollableElement, providerFactory: props.providerFactory, wrapperElement: (_wrapperElementRef$cu = wrapperElementRef === null || wrapperElementRef === void 0 ? void 0 : wrapperElementRef.current) !== null && _wrapperElementRef$cu !== void 0 ? _wrapperElementRef$cu : null, pluginHooks: props.pluginHooks, featureFlags: props.featureFlags, isEditorToolbarHidden: isEditorToolbarHidden, viewMode: state.editorViewMode, hasHadInteraction: hasHadInteraction, contentMode: props.contentMode })))); };