UNPKG

@atlaskit/editor-plugin-insert-block

Version:

Insert block plugin for @atlaskit/editor-core

264 lines (260 loc) 13.4 kB
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;