UNPKG

@atlaskit/editor-core

Version:

A package contains Atlassian editor core functionality

143 lines (141 loc) 6.78 kB
import React from 'react'; import Loadable from 'react-loadable'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, fireAnalyticsEvent, INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { getQuickInsertItemsFromModule, resolveImport } from '@atlaskit/editor-common/extensions'; import { combineProviders } from '@atlaskit/editor-common/provider-helpers'; import { findInsertLocation } from '@atlaskit/editor-common/utils/analytics'; import { fg } from '@atlaskit/platform-feature-flags'; // Structural shape of the markdown-mode plugin's slice of the injection API. // Used to read `isMarkdownMode` without importing the `MarkdownModePlugin` // type — that would pull editor-plugin-markdown-mode into editor-core's // dependency graph and force every consuming product to rebuild. /** * Utils to send analytics event when a extension is inserted using quickInsert */ function sendExtensionQuickInsertAnalytics(item, selection, createAnalyticsEvent, source) { if (createAnalyticsEvent) { const insertLocation = findInsertLocation(selection); fireAnalyticsEvent(createAnalyticsEvent)({ payload: { action: ACTION.INSERTED, actionSubject: ACTION_SUBJECT.DOCUMENT, actionSubjectId: ACTION_SUBJECT_ID.EXTENSION, attributes: { extensionType: item.extensionType, extensionKey: item.extensionKey, key: item.key, // @note inputMethod defaults to QUICK_INSERT if not provided inputMethod: source || INPUT_METHOD.QUICK_INSERT, ...(insertLocation ? { insertLocation } : {}) }, eventType: EVENT_TYPE.TRACK } }); } } const showDummyAPIWarning = location => { if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line no-console console.warn(`Extension plugin not attached to editor - cannot use extension API in ${location}`); } }; const dummyExtensionAPI = { editInContextPanel: () => showDummyAPIWarning('editInContextPanel'), _editInLegacyMacroBrowser: () => showDummyAPIWarning('_editInLegacyMacroBrowser'), getNodeWithPosByLocalId: () => ({ node: null, pos: null }), doc: { insertAfter: () => showDummyAPIWarning('doc:insertAfter'), scrollTo: () => showDummyAPIWarning('doc:scrollTo'), update: () => showDummyAPIWarning('doc:update') } }; export async function extensionProviderToQuickInsertProvider(extensionProvider, editorActions, apiRef, createAnalyticsEvent) { const extensions = await extensionProvider.getExtensions(); return { getItems: () => { var _apiRef$current, _apiRef$current$markd, _apiRef$current$markd2; // `extensionProvider` is supplied independently of the preset, so // suppress its items in markdown mode where rich-only content cannot // be inserted. See `MarkdownModeReader` above for why this is read // via a structural cast rather than the typed plugin API. const isMarkdownMode = (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 ? void 0 : (_apiRef$current$markd = _apiRef$current.markdownMode) === null || _apiRef$current$markd === void 0 ? void 0 : (_apiRef$current$markd2 = _apiRef$current$markd.sharedState.currentState()) === null || _apiRef$current$markd2 === void 0 ? void 0 : _apiRef$current$markd2.isMarkdownMode; if (isMarkdownMode) { return Promise.resolve([]); } const quickInsertItems = getQuickInsertItemsFromModule(extensions, item => { const Icon = Loadable({ loader: item.icon, loading: () => null }); return { // Add module key so typeahead/quick-insert can identify items // **locale-agnostically**! nb: we _already_ send key in analytics // events, this standardises and makes our items more predictable. key: item.key, title: item.title, description: item.description, icon: () => /*#__PURE__*/React.createElement(Icon, { label: "" }), keywords: item.keywords, featured: item.featured, ...((fg('cc_fd_wb_create_priority_in_slash_menu_enabled') || fg('rovo_chat_enable_skills_ui_m1')) && { priority: item.priority }), categories: item.categories, ...(item.lozenge != null && { lozenge: item.lozenge }), isDisabledOffline: true, action: (insert, state, source) => { if (typeof item.node === 'function') { var _apiRef$current2, _apiRef$current2$exte, _apiRef$current2$exte2; const extensionAPI = apiRef === null || apiRef === void 0 ? void 0 : (_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 ? void 0 : (_apiRef$current2$exte = _apiRef$current2.extension) === null || _apiRef$current2$exte === void 0 ? void 0 : (_apiRef$current2$exte2 = _apiRef$current2$exte.actions) === null || _apiRef$current2$exte2 === void 0 ? void 0 : _apiRef$current2$exte2.api(); // While this should only run when the extension some setups of editor // may not have the extension API if (extensionAPI) { resolveImport(item.node(extensionAPI)).then(node => { sendExtensionQuickInsertAnalytics(item, state.selection, createAnalyticsEvent, source); if (node) { editorActions.replaceSelection(node); } }); } else { // Originally it was understood we could only use this if we were using the extension plugin // However there are some edge cases where this is not true (ie. in jira) // Since making it optional now would be a breaking change - instead we can just pass a dummy // extension API to consumers that warns them of using the methods. resolveImport(item.node(dummyExtensionAPI)).then(node => { sendExtensionQuickInsertAnalytics(item, state.selection, createAnalyticsEvent, source); if (node) { editorActions.replaceSelection(node); } }); } return insert(''); } else { sendExtensionQuickInsertAnalytics(item, state.selection, createAnalyticsEvent, source); return insert(item.node); } } }; }); return Promise.all(quickInsertItems); } }; } export function combineQuickInsertProviders(quickInsertProviders) { const { invokeList } = combineProviders(quickInsertProviders); return { getItems() { return invokeList('getItems'); } }; }