@atlaskit/editor-core
Version:
A package contains Atlassian editor core functionality
143 lines (141 loc) • 6.78 kB
JavaScript
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');
}
};
}