@atlaskit/editor-plugin-insert-block
Version:
Insert block plugin for @atlaskit/editor-core
264 lines (260 loc) • 13.4 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
var _excluded = ["children"];
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
/**
* @jsxRuntime classic
* @jsx jsx
*/
import { useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react';
/* eslint-disable @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports, jsdoc/require-description -- Ignored via go/DSP-18766; jsdoc debt surfaced by this mechanical PR */
import { css, jsx } from '@emotion/react';
import { useIntl } from 'react-intl';
import { CellMeasurerCache } from 'react-virtualized/dist/commonjs/CellMeasurer';
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
import { ELEMENT_ITEM_HEIGHT, ElementBrowser } from '@atlaskit/editor-common/element-browser';
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
import { messages, IconCode, IconDate, IconDecision, IconDivider, IconExpand, IconPanel, IconQuote, IconStatus } from '@atlaskit/editor-common/quick-insert';
import { OutsideClickTargetRefContext, withReactEditorViewOuterListeners as withOuterListeners } from '@atlaskit/editor-common/ui-react';
import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
import { expVal, expValNoExposure } from '@atlaskit/tmp-editor-statsig/expVal';
export var DEFAULT_HEIGHT = 560;
/**
* Exported helper to allow testing of InsertMenu pinning logic.
*
* The `cc_fd_db_top_editor_toolbar` experiment adds new logic to sort elements by `priority`.
* This newer implementation matches how the quick insert menu sorts elements.
*/
export var sortFeaturedItems = function sortFeaturedItems(featuredItems, formatMessage) {
if (['new-description', 'orig-description'].includes(expVal('cc_fd_db_top_editor_toolbar', 'cohort', 'control')) || expValNoExposure('cc_fd_wb_jira_quick_insert_experiment', 'isEnabled', false) || ['slot-two', 'slot-four'].includes(expValNoExposure('cc_fd_cwr_quick_insert', 'cohort', 'control'))) {
// Sort by priority (lower first) on the concatenated list so items
// with "priority" are at the top (e.g. Whiteboard before Database)
return featuredItems.slice(0).sort(function (a, b) {
return (a.priority || Number.POSITIVE_INFINITY) - (b.priority || Number.POSITIVE_INFINITY);
});
}
// NOTE: this is *not* the ideal way to approach this. Old logic sort whiteboards to top
var DIAGRAM_KEY = 'whiteboard-extension:create-diagram';
var isDiagram = function isDiagram(item) {
return item.key === DIAGRAM_KEY;
};
var featuredWhiteboardsPresent = featuredItems.some(isDiagram);
if (featuredWhiteboardsPresent) {
var pin = function pin(key) {
var idx = featuredItems.findIndex(function (item) {
return item.key === key;
});
var filtered = featuredItems.filter(function (item) {
return !isDiagram(item);
});
if (idx === -1) {
return filtered;
}
var picked = _objectSpread(_objectSpread({}, featuredItems[idx]), {}, {
description: formatMessage(messages.featuredWhiteboardDescription)
});
return [picked].concat(_toConsumableArray(filtered));
};
return pin(DIAGRAM_KEY);
}
return featuredItems;
};
var selector = function selector(states) {
var _states$connectivityS;
return {
connectivityMode: (_states$connectivityS = states.connectivityState) === null || _states$connectivityS === void 0 ? void 0 : _states$connectivityS.mode
};
};
var InsertMenu = function InsertMenu(_ref) {
var _pluginInjectionApi$q6, _pluginInjectionApi$q7;
var editorView = _ref.editorView,
dropdownItems = _ref.dropdownItems,
showElementBrowserLink = _ref.showElementBrowserLink,
onInsert = _ref.onInsert,
toggleVisiblity = _ref.toggleVisiblity,
pluginInjectionApi = _ref.pluginInjectionApi;
var _useState = useState(0),
_useState2 = _slicedToArray(_useState, 2),
itemCount = _useState2[0],
setItemCount = _useState2[1];
var _useState3 = useState(DEFAULT_HEIGHT),
_useState4 = _slicedToArray(_useState3, 2),
height = _useState4[0],
setHeight = _useState4[1];
var _useIntl = useIntl(),
formatMessage = _useIntl.formatMessage;
var cache = useMemo(function () {
return new CellMeasurerCache({
fixedWidth: true,
defaultHeight: ELEMENT_ITEM_HEIGHT,
minHeight: ELEMENT_ITEM_HEIGHT
});
}, []);
useLayoutEffect(function () {
// Figure based on visuals to exclude the searchbar, padding/margin, and the ViewMore item.
var EXTRA_SPACE_EXCLUDING_ELEMENTLIST = 128;
var totalItemHeight =
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- Ignored via go/ees017 (to be fixed)
_toConsumableArray(Array(itemCount)).reduce(function (sum, _, index) {
return sum + cache.rowHeight({
index: index
});
}, 0) + EXTRA_SPACE_EXCLUDING_ELEMENTLIST;
if (itemCount > 0 && totalItemHeight < DEFAULT_HEIGHT) {
setHeight(totalItemHeight);
} else {
setHeight(DEFAULT_HEIGHT);
}
}, [cache, itemCount]);
var transform = useCallback(function (item) {
return {
title: item.content,
description: item.tooltipDescription,
keyshortcut: item.shortcut,
icon: function icon() {
return getSvgIconForItem({
name: item.value.name
}) || item.elemBefore;
},
/**
* @note This transformed items action is only used when a quick insert item has been
* called from the quick insert menu and a search has not been performed.
*/
action: function action() {
return onInsert({
item: item
});
},
// "insertInsertMenuItem" expects these 2 properties.
onClick: item.onClick,
value: item.value
};
}, [onInsert]);
var quickInsertDropdownItems = dropdownItems.map(transform);
var onInsertItem = useCallback(function (item) {
var _pluginInjectionApi$q;
toggleVisiblity();
if (!editorView.hasFocus()) {
editorView.focus();
}
pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$q = pluginInjectionApi.quickInsert) === null || _pluginInjectionApi$q === void 0 || _pluginInjectionApi$q.actions.insertItem(item, INPUT_METHOD.TOOLBAR)(editorView.state, editorView.dispatch);
}, [editorView, toggleVisiblity, pluginInjectionApi]);
var _useSharedPluginState = useSharedPluginStateWithSelector(pluginInjectionApi, ['connectivity'], selector),
connectivityMode = _useSharedPluginState.connectivityMode;
var getItems = useCallback(function (query, category) {
var result;
/**
* @warning The results if there is a query are not the same as the results if there is no query.
* For example: If you have a typed panel and then select the panel item then it will call a different action
* than is specified on the editor plugins quick insert
* @see above transform function for more details.
*/
if (query) {
var _pluginInjectionApi$q2, _pluginInjectionApi$q3;
result = (_pluginInjectionApi$q2 = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$q3 = pluginInjectionApi.quickInsert) === null || _pluginInjectionApi$q3 === void 0 || (_pluginInjectionApi$q3 = _pluginInjectionApi$q3.actions.getSuggestions({
query: query,
category: category
})) === null || _pluginInjectionApi$q3 === void 0 ? void 0 : _pluginInjectionApi$q3.map(function (item) {
return isOfflineMode(connectivityMode) && item.isDisabledOffline ? _objectSpread(_objectSpread({}, item), {}, {
isDisabled: true
}) : item;
})) !== null && _pluginInjectionApi$q2 !== void 0 ? _pluginInjectionApi$q2 : [];
} else {
var _pluginInjectionApi$q4, _pluginInjectionApi$q5;
var featuredQuickInsertSuggestions = (_pluginInjectionApi$q4 = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$q5 = pluginInjectionApi.quickInsert) === null || _pluginInjectionApi$q5 === void 0 || (_pluginInjectionApi$q5 = _pluginInjectionApi$q5.actions.getSuggestions({
category: category,
featuredItems: true
})) === null || _pluginInjectionApi$q5 === void 0 ? void 0 : _pluginInjectionApi$q5.map(function (item) {
return isOfflineMode(connectivityMode) && item.isDisabledOffline ? _objectSpread(_objectSpread({}, item), {}, {
isDisabled: true
}) : item;
})) !== null && _pluginInjectionApi$q4 !== void 0 ? _pluginInjectionApi$q4 : [];
var unfilteredResult = quickInsertDropdownItems.concat(featuredQuickInsertSuggestions);
// need to sort on the concatenated list so desired elements are at the top
result = sortFeaturedItems(unfilteredResult, formatMessage);
}
setItemCount(result.length);
return result;
}, [pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$q6 = pluginInjectionApi.quickInsert) === null || _pluginInjectionApi$q6 === void 0 ? void 0 : _pluginInjectionApi$q6.actions, quickInsertDropdownItems, connectivityMode, formatMessage]);
var emptyStateHandler = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$q7 = pluginInjectionApi.quickInsert) === null || _pluginInjectionApi$q7 === void 0 || (_pluginInjectionApi$q7 = _pluginInjectionApi$q7.sharedState.currentState()) === null || _pluginInjectionApi$q7 === void 0 ? void 0 : _pluginInjectionApi$q7.emptyStateHandler;
var onViewMore = useCallback(function () {
var _pluginInjectionApi$c, _pluginInjectionApi$q8;
toggleVisiblity();
pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c = pluginInjectionApi.core) === null || _pluginInjectionApi$c === void 0 || _pluginInjectionApi$c.actions.execute(pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$q8 = pluginInjectionApi.quickInsert) === null || _pluginInjectionApi$q8 === void 0 ? void 0 : _pluginInjectionApi$q8.commands.openElementBrowserModal);
}, [pluginInjectionApi, toggleVisiblity]);
return (
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
jsx("div", {
css: insertMenuWrapper(height)
}, jsx(ElementBrowserWrapper, {
handleClickOutside: toggleVisiblity,
handleEscapeKeydown: toggleVisiblity,
closeOnTab: false
}, jsx(ElementBrowser, {
mode: "inline",
getItems: getItems,
emptyStateHandler: emptyStateHandler,
onInsertItem: onInsertItem,
showSearch: true,
showCategories: false
// On page resize we want the InlineElementBrowser to show updated tools/overflow items
,
key: quickInsertDropdownItems.length,
onViewMore: showElementBrowserLink ? onViewMore : undefined,
cache: cache
})))
);
};
var getSvgIconForItem = function getSvgIconForItem(_ref2) {
var name = _ref2.name;
var Icon = {
codeblock: IconCode,
panel: IconPanel,
blockquote: IconQuote,
decision: IconDecision,
horizontalrule: IconDivider,
expand: IconExpand,
date: IconDate,
status: IconStatus
}[name];
return Icon ? jsx(Icon, {
label: ""
}) : undefined;
};
var insertMenuWrapper = function insertMenuWrapper(height) {
return css({
display: 'flex',
flexDirection: 'column',
width: '320px',
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
height: "".concat(height, "px"),
backgroundColor: "var(--ds-surface-overlay, #FFFFFF)",
borderRadius: "var(--ds-radius-small, 3px)",
boxShadow: "var(--ds-shadow-overlay, 0px 8px 12px #1E1F2126, 0px 0px 1px #1E1F214f)"
});
};
var flexWrapperStyles = css({
display: 'flex',
flex: 1,
boxSizing: 'border-box',
overflow: 'hidden'
});
var FlexWrapper = function FlexWrapper(props) {
var setOutsideClickTargetRef = useContext(OutsideClickTargetRefContext);
var children = props.children,
divProps = _objectWithoutProperties(props, _excluded);
return (
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
jsx("div", _extends({
ref: setOutsideClickTargetRef,
css: flexWrapperStyles
}, divProps), children)
);
};
var ElementBrowserWrapper = withOuterListeners(FlexWrapper);
export default InsertMenu;