@atlaskit/editor-plugin-insert-block
Version:
Insert block plugin for @atlaskit/editor-core
356 lines (355 loc) • 19.5 kB
JavaScript
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 var toolbarSizeToButtons = function 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 function (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 function () {
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(function () {
return cb();
}, {
timeout: 500
});
}
return window.requestAnimationFrame(function () {
return cb();
});
}
export var insertBlockPlugin = function insertBlockPlugin(_ref) {
var _ref$config = _ref.config,
options = _ref$config === void 0 ? {} : _ref$config,
api = _ref.api;
var isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar);
var refs = {};
var primaryToolbarComponent = function primaryToolbarComponent(_ref2) {
var editorView = _ref2.editorView,
editorActions = _ref2.editorActions,
dispatchAnalyticsEvent = _ref2.dispatchAnalyticsEvent,
providerFactory = _ref2.providerFactory,
popupsMountPoint = _ref2.popupsMountPoint,
popupsBoundariesElement = _ref2.popupsBoundariesElement,
popupsScrollableElement = _ref2.popupsScrollableElement,
toolbarSize = _ref2.toolbarSize,
disabled = _ref2.disabled,
isToolbarReducedSpacing = _ref2.isToolbarReducedSpacing,
isLastItem = _ref2.isLastItem;
refs.popupsMountPoint = popupsMountPoint || undefined;
var renderNode = function 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 || (_api$toolbar = api.toolbar) === null || _api$toolbar === void 0 || _api$toolbar.actions.registerComponents(getToolbarComponents({
api: api,
onInsertBlockType: handleInsertBlockType(api === null || api === void 0 || (_api$codeBlock = api.codeBlock) === null || _api$codeBlock === void 0 ? void 0 : _api$codeBlock.actions.insertCodeBlock, api === null || api === void 0 || (_api$panel = api.panel) === null || _api$panel === void 0 ? void 0 : _api$panel.actions.insertPanel, api === null || api === void 0 || (_api$blockType = api.blockType) === null || _api$blockType === void 0 ? void 0 : _api$blockType.actions.insertBlockQuote),
options: options
}));
} else {
var _api$primaryToolbar;
api === null || api === void 0 || (_api$primaryToolbar = api.primaryToolbar) === null || _api$primaryToolbar === void 0 || _api$primaryToolbar.actions.registerComponent({
name: 'insertBlock',
component: primaryToolbarComponent
});
}
var plugin = {
name: 'insertBlock',
actions: {
toggleAdditionalMenu: function toggleAdditionalMenu() {
var _api$core;
api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref3) {
var tr = _ref3.tr;
return tr.setMeta(toggleInsertBlockPmKey, true);
});
}
},
getSharedState: function getSharedState(editorState) {
var _options$appearance;
if (!editorState || !['full-page', 'full-width'].includes((_options$appearance = options.appearance) !== null && _options$appearance !== void 0 ? _options$appearance : '')) {
return;
}
var toggleInsertBlockPluginState = toggleInsertBlockPmKey.getState(editorState);
return {
showElementBrowser: (toggleInsertBlockPluginState === null || toggleInsertBlockPluginState === void 0 ? void 0 : toggleInsertBlockPluginState.showElementBrowser) || false
};
},
usePluginHook: function usePluginHook() {
useEffect(function () {
// 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(function () {
ElementBrowser.preload();
});
}, []);
},
pmPlugins: function pmPlugins() {
var _options$appearance2;
if (!['full-page', 'full-width'].includes((_options$appearance2 = options.appearance) !== null && _options$appearance2 !== void 0 ? _options$appearance2 : '')) {
[];
}
var plugins = [];
plugins.push({
name: 'toggleInsertBlockPmPlugin',
plugin: function plugin() {
return toggleInsertBlockPmPlugin();
}
});
if (fg('platform_editor_experience_tracking_toolbar_button')) {
plugins.push({
name: 'toolbarActionExperiences',
plugin: function plugin() {
return getToolbarActionExperiencesPlugin({
dispatchAnalyticsEvent: function dispatchAnalyticsEvent(payload) {
var _api$analytics;
return api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 ? void 0 : _api$analytics.fireAnalyticsEvent(payload);
}
});
}
});
}
return plugins;
},
pluginsOptions: {},
primaryToolbarComponent: !(api !== null && api !== void 0 && api.primaryToolbar) ? primaryToolbarComponent : undefined
};
return plugin;
};
var selector = function 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(_ref4) {
var _pluginInjectionApi$i, _pluginInjectionApi$c2, _pluginInjectionApi$p, _pluginInjectionApi$b, _pluginInjectionApi$e;
var editorView = _ref4.editorView,
editorActions = _ref4.editorActions,
dispatchAnalyticsEvent = _ref4.dispatchAnalyticsEvent,
popupsMountPoint = _ref4.popupsMountPoint,
popupsBoundariesElement = _ref4.popupsBoundariesElement,
popupsScrollableElement = _ref4.popupsScrollableElement,
toolbarSize = _ref4.toolbarSize,
disabled = _ref4.disabled,
isToolbarReducedSpacing = _ref4.isToolbarReducedSpacing,
isLastItem = _ref4.isLastItem,
pluginInjectionApi = _ref4.pluginInjectionApi,
options = _ref4.options,
appearance = _ref4.appearance;
var buttons = toolbarSizeToButtons(toolbarSize, appearance);
var _useSharedPluginState = useSharedPluginStateWithSelector(pluginInjectionApi, ['hyperlink', 'date', 'imageUpload', 'mention', 'emoji', 'blockType', 'media', 'typeAhead', 'placeholderText', 'insertBlock', 'connectivity'], selector),
emojiProviderSelector = _useSharedPluginState.emojiProviderSelector,
showMediaPicker = _useSharedPluginState.showMediaPicker,
mediaAllowsUploads = _useSharedPluginState.mediaAllowsUploads,
showElementBrowser = _useSharedPluginState.showElementBrowser,
isTypeAheadAllowed = _useSharedPluginState.isTypeAheadAllowed,
mentionProvider = _useSharedPluginState.mentionProvider,
canInsertMention = _useSharedPluginState.canInsertMention,
dateEnabled = _useSharedPluginState.dateEnabled,
placeholderTextAllowInserting = _useSharedPluginState.placeholderTextAllowInserting,
connectivityMode = _useSharedPluginState.connectivityMode,
imageUploadEnabled = _useSharedPluginState.imageUploadEnabled,
availableWrapperBlockTypes = _useSharedPluginState.availableWrapperBlockTypes,
canInsertLink = _useSharedPluginState.canInsertLink,
activeLinkMark = _useSharedPluginState.activeLinkMark;
var emojiProviderPromise = useSharedPluginStateSelector(pluginInjectionApi, 'emoji.emojiProviderPromise', {
disabled: !editorExperiment('platform_editor_prevent_toolbar_layout_shifts', true)
});
var getEmojiProvider = function getEmojiProvider() {
if (emojiProviderSelector) {
return Promise.resolve(emojiProviderSelector);
}
};
var emojiProvider = editorExperiment('platform_editor_prevent_toolbar_layout_shifts', true, {
exposure: true
}) ? emojiProviderPromise : getEmojiProvider();
var onShowMediaPicker = function 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 || (_pluginInjectionApi$c = pluginInjectionApi.core) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.actions.execute(pluginInjectionApi === null || pluginInjectionApi === 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 || (_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 || (_pluginInjectionApi$c2 = pluginInjectionApi.codeBlock) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.actions.insertCodeBlock, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$p = pluginInjectionApi.panel) === null || _pluginInjectionApi$p === void 0 ? void 0 : _pluginInjectionApi$p.actions.insertPanel, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$b = pluginInjectionApi.blockType) === null || _pluginInjectionApi$b === void 0 ? void 0 : _pluginInjectionApi$b.actions.insertBlockQuote),
onInsertMacroFromMacroBrowser: pluginInjectionApi === null || pluginInjectionApi === 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
});
}