UNPKG

@atlaskit/editor-plugin-insert-block

Version:

Insert block plugin for @atlaskit/editor-core

350 lines (349 loc) 17.9 kB
import React, { useEffect } from 'react'; import { INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { ElementBrowser } from '@atlaskit/editor-common/element-browser'; import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'; import { WithProviders } from '@atlaskit/editor-common/provider-factory'; import { ToolbarSize } from '@atlaskit/editor-common/types'; import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector'; import { BLOCK_QUOTE, CODE_BLOCK, PANEL } from '@atlaskit/editor-plugin-block-type/consts'; import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity'; import { fg } from '@atlaskit/platform-feature-flags'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { getToolbarActionExperiencesPlugin } from './pm-plugins/experiences/toolbar-action-experiences'; import { toggleInsertBlockPmKey, toggleInsertBlockPmPlugin } from './pm-plugins/toggleInsertBlock'; import { getToolbarComponents } from './ui/toolbar-components'; // Ignored via go/ees005 // eslint-disable-next-line import/no-named-as-default import ToolbarInsertBlock from './ui/ToolbarInsertBlock'; export const toolbarSizeToButtons = (toolbarSize, appearance) => { // Different button numbers for full-page to better match full page toolbar breakpoints if (appearance === 'full-page' && fg('platform_editor_toolbar_responsive_fixes')) { switch (toolbarSize) { case ToolbarSize.XXL: case ToolbarSize.XL: case ToolbarSize.L: return 7; case ToolbarSize.M: return 3; default: return 0; } } if (fg('platform_editor_toolbar_responsive_fixes')) { switch (toolbarSize) { case ToolbarSize.XXL: case ToolbarSize.XL: return 7; case ToolbarSize.L: return 5; case ToolbarSize.M: case ToolbarSize.S: return 2; default: return 0; } } else { switch (toolbarSize) { case ToolbarSize.XXL: case ToolbarSize.XL: case ToolbarSize.L: case ToolbarSize.M: return 7; case ToolbarSize.S: return 2; default: return 0; } } }; /** * Wrapper over insertBlockTypeWithAnalytics to autobind toolbar input method * @param name Block name */ function handleInsertBlockType(insertCodeBlock, insertPanel, insertBlockQuote) { return name => { if (name === CODE_BLOCK.name && insertCodeBlock) { return insertCodeBlock(INPUT_METHOD.TOOLBAR); } if (name === PANEL.name && insertPanel) { return insertPanel(INPUT_METHOD.TOOLBAR); } if (name === BLOCK_QUOTE.name && insertBlockQuote) { return insertBlockQuote(INPUT_METHOD.INSERT_MENU); } return () => false; }; } function delayUntilIdle(cb) { if (typeof window === 'undefined') { return; } // eslint-disable-next-line compat/compat if (window.requestIdleCallback !== undefined) { // eslint-disable-next-line compat/compat return window.requestIdleCallback(() => cb(), { timeout: 500 }); } return window.requestAnimationFrame(() => cb()); } export const insertBlockPlugin = ({ config: options = {}, api }) => { const isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar); const refs = {}; const primaryToolbarComponent = ({ editorView, editorActions, dispatchAnalyticsEvent, providerFactory, popupsMountPoint, popupsBoundariesElement, popupsScrollableElement, toolbarSize, disabled, isToolbarReducedSpacing, isLastItem }) => { refs.popupsMountPoint = popupsMountPoint || undefined; const renderNode = providers => { if (!editorView) { return null; } return /*#__PURE__*/React.createElement(ToolbarInsertBlockWithInjectionApi, { pluginInjectionApi: api, editorView: editorView, editorActions: editorActions, dispatchAnalyticsEvent: dispatchAnalyticsEvent, providerFactory: providerFactory, popupsMountPoint: popupsMountPoint, popupsBoundariesElement: popupsBoundariesElement, popupsScrollableElement: popupsScrollableElement, toolbarSize: toolbarSize, disabled: disabled, isToolbarReducedSpacing: isToolbarReducedSpacing, isLastItem: isLastItem, providers: providers, options: options, appearance: options.appearance }); }; if (editorExperiment('platform_editor_prevent_toolbar_layout_shifts', true)) { if (!editorView) { return null; } return /*#__PURE__*/React.createElement(ToolbarInsertBlockWithInjectionApi, { pluginInjectionApi: api, editorView: editorView, editorActions: editorActions, dispatchAnalyticsEvent: dispatchAnalyticsEvent, providerFactory: providerFactory, popupsMountPoint: popupsMountPoint, popupsBoundariesElement: popupsBoundariesElement, popupsScrollableElement: popupsScrollableElement, toolbarSize: toolbarSize, disabled: disabled, isToolbarReducedSpacing: isToolbarReducedSpacing, isLastItem: isLastItem, options: options, appearance: options.appearance }); } return /*#__PURE__*/React.createElement(WithProviders, { providerFactory: providerFactory // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , providers: ['emojiProvider'], renderNode: renderNode }); }; if (isToolbarAIFCEnabled) { var _api$toolbar, _api$codeBlock, _api$panel, _api$blockType; api === null || api === void 0 ? void 0 : (_api$toolbar = api.toolbar) === null || _api$toolbar === void 0 ? void 0 : _api$toolbar.actions.registerComponents(getToolbarComponents({ api, onInsertBlockType: handleInsertBlockType(api === null || api === void 0 ? void 0 : (_api$codeBlock = api.codeBlock) === null || _api$codeBlock === void 0 ? void 0 : _api$codeBlock.actions.insertCodeBlock, api === null || api === void 0 ? void 0 : (_api$panel = api.panel) === null || _api$panel === void 0 ? void 0 : _api$panel.actions.insertPanel, api === null || api === void 0 ? void 0 : (_api$blockType = api.blockType) === null || _api$blockType === void 0 ? void 0 : _api$blockType.actions.insertBlockQuote), options })); } else { var _api$primaryToolbar; api === null || api === void 0 ? void 0 : (_api$primaryToolbar = api.primaryToolbar) === null || _api$primaryToolbar === void 0 ? void 0 : _api$primaryToolbar.actions.registerComponent({ name: 'insertBlock', component: primaryToolbarComponent }); } const plugin = { name: 'insertBlock', actions: { toggleAdditionalMenu: () => { var _api$core; api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({ tr }) => { return tr.setMeta(toggleInsertBlockPmKey, true); }); } }, getSharedState: editorState => { var _options$appearance; if (!editorState || !['full-page', 'full-width'].includes((_options$appearance = options.appearance) !== null && _options$appearance !== void 0 ? _options$appearance : '')) { return; } const toggleInsertBlockPluginState = toggleInsertBlockPmKey.getState(editorState); return { showElementBrowser: (toggleInsertBlockPluginState === null || toggleInsertBlockPluginState === void 0 ? void 0 : toggleInsertBlockPluginState.showElementBrowser) || false }; }, usePluginHook() { useEffect(() => { // This is to optimise the UI so that when the user first clicks on the insert // menu it opens instantly. As we're delaying the loading this won't affect the // initial editor rendering metrics. delayUntilIdle(() => { ElementBrowser.preload(); }); }, []); }, pmPlugins: () => { var _options$appearance2; if (!['full-page', 'full-width'].includes((_options$appearance2 = options.appearance) !== null && _options$appearance2 !== void 0 ? _options$appearance2 : '')) { []; } const plugins = []; plugins.push({ name: 'toggleInsertBlockPmPlugin', plugin: () => toggleInsertBlockPmPlugin() }); if (fg('platform_editor_experience_tracking_toolbar_button')) { plugins.push({ name: 'toolbarActionExperiences', plugin: () => getToolbarActionExperiencesPlugin({ dispatchAnalyticsEvent: payload => { var _api$analytics, _api$analytics$action; return api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : (_api$analytics$action = _api$analytics.actions) === null || _api$analytics$action === void 0 ? void 0 : _api$analytics$action.fireAnalyticsEvent(payload); } }) }); } return plugins; }, pluginsOptions: {}, primaryToolbarComponent: !(api !== null && api !== void 0 && api.primaryToolbar) ? primaryToolbarComponent : undefined }; return plugin; }; const selector = states => { var _states$emojiState, _states$mediaState, _states$mediaState2, _states$insertBlockSt, _states$typeAheadStat, _states$mentionState, _states$mentionState2, _states$dateState, _states$placeholderTe, _states$connectivityS, _states$imageUploadSt, _states$blockTypeStat, _states$hyperlinkStat, _states$hyperlinkStat2; return { emojiProviderSelector: (_states$emojiState = states.emojiState) === null || _states$emojiState === void 0 ? void 0 : _states$emojiState.emojiProvider, showMediaPicker: (_states$mediaState = states.mediaState) === null || _states$mediaState === void 0 ? void 0 : _states$mediaState.showMediaPicker, mediaAllowsUploads: (_states$mediaState2 = states.mediaState) === null || _states$mediaState2 === void 0 ? void 0 : _states$mediaState2.allowsUploads, showElementBrowser: (_states$insertBlockSt = states.insertBlockState) === null || _states$insertBlockSt === void 0 ? void 0 : _states$insertBlockSt.showElementBrowser, isTypeAheadAllowed: (_states$typeAheadStat = states.typeAheadState) === null || _states$typeAheadStat === void 0 ? void 0 : _states$typeAheadStat.isAllowed, mentionProvider: (_states$mentionState = states.mentionState) === null || _states$mentionState === void 0 ? void 0 : _states$mentionState.mentionProvider, canInsertMention: (_states$mentionState2 = states.mentionState) === null || _states$mentionState2 === void 0 ? void 0 : _states$mentionState2.canInsertMention, dateEnabled: (_states$dateState = states.dateState) === null || _states$dateState === void 0 ? void 0 : _states$dateState.isInitialised, placeholderTextAllowInserting: (_states$placeholderTe = states.placeholderTextState) === null || _states$placeholderTe === void 0 ? void 0 : _states$placeholderTe.allowInserting, connectivityMode: (_states$connectivityS = states.connectivityState) === null || _states$connectivityS === void 0 ? void 0 : _states$connectivityS.mode, imageUploadEnabled: (_states$imageUploadSt = states.imageUploadState) === null || _states$imageUploadSt === void 0 ? void 0 : _states$imageUploadSt.enabled, availableWrapperBlockTypes: (_states$blockTypeStat = states.blockTypeState) === null || _states$blockTypeStat === void 0 ? void 0 : _states$blockTypeStat.availableWrapperBlockTypes, canInsertLink: (_states$hyperlinkStat = states.hyperlinkState) === null || _states$hyperlinkStat === void 0 ? void 0 : _states$hyperlinkStat.canInsertLink, activeLinkMark: (_states$hyperlinkStat2 = states.hyperlinkState) === null || _states$hyperlinkStat2 === void 0 ? void 0 : _states$hyperlinkStat2.activeLinkMark }; }; function ToolbarInsertBlockWithInjectionApi({ editorView, editorActions, dispatchAnalyticsEvent, popupsMountPoint, popupsBoundariesElement, popupsScrollableElement, toolbarSize, disabled, isToolbarReducedSpacing, isLastItem, pluginInjectionApi, options, appearance }) { var _pluginInjectionApi$i, _pluginInjectionApi$c2, _pluginInjectionApi$p, _pluginInjectionApi$b, _pluginInjectionApi$e; const buttons = toolbarSizeToButtons(toolbarSize, appearance); const { emojiProviderSelector, showMediaPicker, mediaAllowsUploads, showElementBrowser, isTypeAheadAllowed, mentionProvider, canInsertMention, dateEnabled, placeholderTextAllowInserting, connectivityMode, imageUploadEnabled, availableWrapperBlockTypes, canInsertLink, activeLinkMark } = useSharedPluginStateWithSelector(pluginInjectionApi, ['hyperlink', 'date', 'imageUpload', 'mention', 'emoji', 'blockType', 'media', 'typeAhead', 'placeholderText', 'insertBlock', 'connectivity'], selector); const emojiProviderPromise = useSharedPluginStateSelector(pluginInjectionApi, 'emoji.emojiProviderPromise', { disabled: !editorExperiment('platform_editor_prevent_toolbar_layout_shifts', true) }); const getEmojiProvider = () => { if (emojiProviderSelector) { return Promise.resolve(emojiProviderSelector); } }; const emojiProvider = editorExperiment('platform_editor_prevent_toolbar_layout_shifts', true, { exposure: true }) ? emojiProviderPromise : getEmojiProvider(); const onShowMediaPicker = mountInfo => { var _pluginInjectionApi$m, _pluginInjectionApi$c, _pluginInjectionApi$m2; if (!showMediaPicker) { return; } pluginInjectionApi !== null && pluginInjectionApi !== void 0 && (_pluginInjectionApi$m = pluginInjectionApi.mediaInsert) !== null && _pluginInjectionApi$m !== void 0 && _pluginInjectionApi$m.commands.showMediaInsertPopup ? pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c = pluginInjectionApi.core) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.actions.execute(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$m2 = pluginInjectionApi.mediaInsert) === null || _pluginInjectionApi$m2 === void 0 ? void 0 : _pluginInjectionApi$m2.commands.showMediaInsertPopup(mountInfo)) : showMediaPicker(); }; return /*#__PURE__*/React.createElement(ToolbarInsertBlock, { showElementBrowser: showElementBrowser || false, pluginInjectionApi: pluginInjectionApi, buttons: buttons, isReducedSpacing: isToolbarReducedSpacing, isDisabled: disabled, isTypeAheadAllowed: Boolean(isTypeAheadAllowed), editorView: editorView, tableSupported: !!editorView.state.schema.nodes.table, tableSelectorSupported: options.tableSelectorSupported && !!editorView.state.schema.nodes.table, actionSupported: !!editorView.state.schema.nodes.taskItem, mentionsSupported: !!mentionProvider, mentionsDisabled: !canInsertMention, decisionSupported: !!editorView.state.schema.nodes.decisionItem, dateEnabled: !!dateEnabled, placeholderTextEnabled: !!placeholderTextAllowInserting, layoutSectionEnabled: Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.layout), expandEnabled: !!options.allowExpand, mediaUploadsEnabled: mediaAllowsUploads !== null && mediaAllowsUploads !== void 0 ? mediaAllowsUploads : undefined, onShowMediaPicker: onShowMediaPicker, mediaSupported: mediaAllowsUploads !== undefined, isEditorOffline: isOfflineMode(connectivityMode), imageUploadSupported: !!(pluginInjectionApi !== null && pluginInjectionApi !== void 0 && pluginInjectionApi.imageUpload), imageUploadEnabled: imageUploadEnabled, handleImageUpload: pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$i = pluginInjectionApi.imageUpload) === null || _pluginInjectionApi$i === void 0 ? void 0 : _pluginInjectionApi$i.actions.startUpload, availableWrapperBlockTypes: availableWrapperBlockTypes, linkSupported: canInsertLink !== undefined, linkDisabled: !canInsertLink || !!activeLinkMark, emojiDisabled: !emojiProvider, emojiProvider: emojiProvider, nativeStatusSupported: options.nativeStatusSupported, horizontalRuleEnabled: options.horizontalRuleEnabled, onInsertBlockType: handleInsertBlockType(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c2 = pluginInjectionApi.codeBlock) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.actions.insertCodeBlock, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$p = pluginInjectionApi.panel) === null || _pluginInjectionApi$p === void 0 ? void 0 : _pluginInjectionApi$p.actions.insertPanel, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$b = pluginInjectionApi.blockType) === null || _pluginInjectionApi$b === void 0 ? void 0 : _pluginInjectionApi$b.actions.insertBlockQuote), onInsertMacroFromMacroBrowser: pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$e = pluginInjectionApi.extension) === null || _pluginInjectionApi$e === void 0 ? void 0 : _pluginInjectionApi$e.actions.insertMacroFromMacroBrowser, popupsMountPoint: popupsMountPoint, popupsBoundariesElement: popupsBoundariesElement, popupsScrollableElement: popupsScrollableElement, insertMenuItems: options.insertMenuItems, editorActions: editorActions, dispatchAnalyticsEvent: dispatchAnalyticsEvent, showElementBrowserLink: options.showElementBrowserLink, showSeparator: !isLastItem && toolbarSize <= ToolbarSize.S, editorAppearance: options.appearance }); }