@atlaskit/editor-plugin-mentions
Version:
Mentions plugin for @atlaskit/editor-core
255 lines (254 loc) • 10 kB
JavaScript
import React, { useEffect, useMemo } from 'react';
import { useIntl } from 'react-intl';
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
import uuid from 'uuid';
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
import { toolbarInsertBlockMessages as messages, mentionMessages } from '@atlaskit/editor-common/messages';
import { WithProviders } from '@atlaskit/editor-common/provider-factory';
import { IconMention } from '@atlaskit/editor-common/quick-insert';
import { isResolvingMentionProvider } from '@atlaskit/mention/resource';
import { MentionNameStatus, isPromise } from '@atlaskit/mention/types';
import { fg } from '@atlaskit/platform-feature-flags';
import { insertMention } from './editor-commands';
import { mentionNodeSpec } from './nodeviews/mentionNodeSpec';
import { mentionPluginKey } from './pm-plugins/key';
import { ACTIONS, createMentionPlugin } from './pm-plugins/main';
import { createMentionPlaceholderPlugin } from './pm-plugins/mentionPlaceholder';
import { InlineInviteRecaptchaContainer } from './ui/InlineInviteRecaptchaContainer';
import { SecondaryToolbarComponent } from './ui/SecondaryToolbarComponent';
import { createTypeAheadConfig } from './ui/type-ahead';
const processName = (name, intl) => {
const unknownLabel = intl.formatMessage(mentionMessages.unknownLabel);
if (name.status === MentionNameStatus.OK) {
return `@${name.name || unknownLabel}`;
} else {
return `@${unknownLabel}`;
}
};
/**
* We will need to clean this up once mentionProvider is
* put inside mention plugin.
* See: https://product-fabric.atlassian.net/browse/ED-26011
*/
function Component({
mentionProvider,
api
}) {
const mentionProviderMemo = useMemo(() => {
return mentionProvider;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const intl = useIntl();
useEffect(() => {
mentionProviderMemo === null || mentionProviderMemo === void 0 ? void 0 : mentionProviderMemo.then(mentionProviderSync => {
var _api$base, _api$base$actions;
api === null || api === void 0 ? void 0 : (_api$base = api.base) === null || _api$base === void 0 ? void 0 : (_api$base$actions = _api$base.actions) === null || _api$base$actions === void 0 ? void 0 : _api$base$actions.registerMarks(({
tr,
node,
pos
}) => {
const {
doc
} = tr;
const {
schema
} = doc.type;
const {
mention: mentionNodeType
} = schema.nodes;
const {
id
} = node.attrs;
if (node.type === mentionNodeType) {
if (isResolvingMentionProvider(mentionProviderSync)) {
const nameDetail = mentionProviderSync === null || mentionProviderSync === void 0 ? void 0 : mentionProviderSync.resolveMentionName(id);
let newText;
if (isPromise(nameDetail)) {
newText = `@${intl.formatMessage(mentionMessages.unknownLabel)}`;
} else {
newText = processName(nameDetail, intl);
}
const currentPos = tr.mapping.map(pos);
tr.replaceWith(currentPos, currentPos + node.nodeSize, schema.text(newText, node.marks));
}
}
});
});
}, [mentionProviderMemo, api, intl]);
return null;
}
const mentionsPlugin = ({
config: options,
api
}) => {
var _options$sanitizePriv, _options$insertDispla;
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
const sessionId = uuid();
let previousMediaProvider;
const fireEvent = (payload, channel) => {
var _api$analytics, _api$analytics$action;
const fireAnalyticsEvent = api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : (_api$analytics$action = _api$analytics.actions) === null || _api$analytics$action === void 0 ? void 0 : _api$analytics$action.fireAnalyticsEvent;
if (!fireAnalyticsEvent) {
return;
}
if (payload.attributes && !payload.attributes.sessionId) {
payload.attributes.sessionId = sessionId;
}
fireAnalyticsEvent(payload, channel);
};
const typeAhead = createTypeAheadConfig({
sanitizePrivateContent: options === null || options === void 0 ? void 0 : options.sanitizePrivateContent,
mentionInsertDisplayName: options === null || options === void 0 ? void 0 : options.insertDisplayName,
HighlightComponent: options === null || options === void 0 ? void 0 : options.HighlightComponent,
handleMentionsChanged: options === null || options === void 0 ? void 0 : options.handleMentionsChanged,
fireEvent,
api
});
return {
name: 'mention',
nodes() {
return [{
name: 'mention',
node: mentionNodeSpec()
}];
},
pmPlugins() {
const plugins = [{
name: 'mention',
plugin: pmPluginFactoryParams => createMentionPlugin({
pmPluginFactoryParams,
fireEvent,
options,
api
})
}];
if (fg('jira_invites_auto_tag_new_user_in_mentions_fg')) {
plugins.push({
name: 'mentionPlaceholder',
plugin: () => createMentionPlaceholderPlugin()
});
}
return plugins;
},
contentComponent({
editorView,
providerFactory
}) {
if (!editorView) {
return null;
}
return /*#__PURE__*/React.createElement(WithProviders
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
, {
providers: ['mentionProvider'],
providerFactory: providerFactory
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
renderNode: ({
mentionProvider
}) => {
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Component, {
mentionProvider: mentionProvider,
api: api
}), fg('jira_invites_auto_tag_new_user_in_mentions_fg') && /*#__PURE__*/React.createElement(InlineInviteRecaptchaContainer, {
mentionProvider: mentionProvider,
api: api
}));
}
});
},
secondaryToolbarComponent({
editorView,
disabled
}) {
if (!editorView) {
return null;
}
return /*#__PURE__*/React.createElement(SecondaryToolbarComponent, {
editorView: editorView,
api: api,
disabled: disabled,
typeAhead: typeAhead
});
},
commands: {
insertMention: insertMention({
sanitizePrivateContent: (_options$sanitizePriv = options === null || options === void 0 ? void 0 : options.sanitizePrivateContent) !== null && _options$sanitizePriv !== void 0 ? _options$sanitizePriv : false,
mentionInsertDisplayName: (_options$insertDispla = options === null || options === void 0 ? void 0 : options.insertDisplayName) !== null && _options$insertDispla !== void 0 ? _options$insertDispla : false,
api
})
},
actions: {
openTypeAhead(inputMethod) {
var _api$typeAhead, _api$typeAhead$action;
return Boolean(api === null || api === void 0 ? void 0 : (_api$typeAhead = api.typeAhead) === null || _api$typeAhead === void 0 ? void 0 : (_api$typeAhead$action = _api$typeAhead.actions) === null || _api$typeAhead$action === void 0 ? void 0 : _api$typeAhead$action.open({
triggerHandler: typeAhead,
inputMethod
}));
},
announceMentionsInsertion: mentionChanges => {
if (options !== null && options !== void 0 && options.handleMentionsChanged) {
options.handleMentionsChanged(mentionChanges);
}
},
setProvider: async providerPromise => {
var _api$core$actions$exe;
if (!fg('platform_editor_mention_provider_via_plugin_config')) {
return false;
}
const provider = await providerPromise;
// Prevent someone trying to set the exact same provider twice for performance reasons
if (previousMediaProvider === provider) {
return false;
}
previousMediaProvider = provider;
return (_api$core$actions$exe = api === null || api === void 0 ? void 0 : api.core.actions.execute(({
tr
}) => tr.setMeta(mentionPluginKey, {
action: ACTIONS.SET_PROVIDER,
params: {
provider
}
}))) !== null && _api$core$actions$exe !== void 0 ? _api$core$actions$exe : false;
}
},
getSharedState(editorState) {
if (!editorState) {
return undefined;
}
const mentionPluginState = mentionPluginKey.getState(editorState);
return {
...mentionPluginState,
typeAheadHandler: typeAhead
};
},
pluginsOptions: {
quickInsert: ({
formatMessage
}) => [{
id: 'mention',
title: formatMessage(messages.mention),
description: formatMessage(messages.mentionDescription),
keywords: ['team', 'user'],
priority: 400,
keyshortcut: '@',
icon: () => /*#__PURE__*/React.createElement(IconMention, null),
action(insert, state) {
var _api$typeAhead2;
const tr = insert(undefined);
const pluginState = mentionPluginKey.getState(state);
if (pluginState && pluginState.canInsertMention === false) {
return false;
}
api === null || api === void 0 ? void 0 : (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.openAtTransaction({
triggerHandler: typeAhead,
inputMethod: INPUT_METHOD.QUICK_INSERT
})(tr);
return tr;
}
}],
typeAhead
}
};
};
export { mentionsPlugin };