@atlaskit/editor-plugin-mentions
Version:
Mentions plugin for @atlaskit/editor-core
216 lines (214 loc) • 8.33 kB
JavaScript
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
import { insm } from '@atlaskit/insm';
import { SLI_EVENT_TYPE, SMART_EVENT_TYPE } from '@atlaskit/mention/resource';
import { ComponentNames } from '@atlaskit/mention/types';
import { fg } from '@atlaskit/platform-feature-flags';
import { MentionNodeView } from '../nodeviews/mentionNodeView';
import { MENTION_PROVIDER_REJECTED, MENTION_PROVIDER_UNDEFINED } from '../types';
import { mentionPluginKey } from './key';
import { canMentionBeCreatedInRange } from './utils';
export const ACTIONS = {
SET_PROVIDER: 'SET_PROVIDER'
};
const PACKAGE_NAME = "@atlaskit/editor-plugin-mentions";
const PACKAGE_VERSION = "12.1.4";
const setProvider = provider => (state, dispatch) => {
if (dispatch) {
dispatch(state.tr.setMeta(mentionPluginKey, {
action: ACTIONS.SET_PROVIDER,
params: {
provider
}
}));
}
return true;
};
export function createMentionPlugin({
pmPluginFactoryParams,
fireEvent,
options,
api
}) {
let mentionProvider;
const sendAnalytics = (event, actionSubject, action, attributes) => {
if (event === SLI_EVENT_TYPE || event === SMART_EVENT_TYPE) {
fireEvent({
action: action,
actionSubject: actionSubject,
eventType: EVENT_TYPE.OPERATIONAL,
attributes: {
packageName: PACKAGE_NAME,
packageVersion: PACKAGE_VERSION,
componentName: ComponentNames.MENTION,
...attributes
}
}, 'fabricElements');
}
};
return new SafePlugin({
key: mentionPluginKey,
state: {
init(_, state) {
const canInsertMention = canMentionBeCreatedInRange(state.selection.from, state.selection.to)(state);
return {
canInsertMention
};
},
apply(tr, pluginState, oldState, newState) {
const {
action,
params
} = tr.getMeta(mentionPluginKey) || {
action: null,
params: null
};
let hasNewPluginState = false;
let newPluginState = pluginState;
const hasPositionChanged = oldState.selection.from !== newState.selection.from || oldState.selection.to !== newState.selection.to;
if (tr.docChanged || tr.selectionSet && hasPositionChanged) {
newPluginState = {
...pluginState,
canInsertMention: canMentionBeCreatedInRange(newState.selection.from, newState.selection.to)(newState)
};
hasNewPluginState = true;
}
switch (action) {
case ACTIONS.SET_PROVIDER:
newPluginState = {
...newPluginState,
mentionProvider: params.provider
};
hasNewPluginState = true;
break;
}
if (hasNewPluginState) {
pmPluginFactoryParams.dispatch(mentionPluginKey, newPluginState);
}
if (options !== null && options !== void 0 && options.handleMentionsChanged && tr.docChanged) {
var _insm$session, _insm$session2;
(_insm$session = insm.session) === null || _insm$session === void 0 ? void 0 : _insm$session.startFeature('mentionDeletionDetection');
const mentionSchema = newState.schema.nodes.mention;
const mentionsRemoved = new Map();
tr.steps.forEach((step, index) => {
step.getMap().forEach((from, to) => {
const newStart = tr.mapping.slice(index).map(from, -1);
const newEnd = tr.mapping.slice(index).map(to);
const oldStart = tr.mapping.invert().map(newStart, -1);
const oldEnd = tr.mapping.invert().map(newEnd);
const oldSlice = oldState.doc.slice(oldStart, oldEnd);
const newSlice = newState.doc.slice(newStart, newEnd);
const mentionsBefore = new Map();
const mentionsAfter = new Map();
oldSlice.content.descendants(node => {
if (node.type.name === mentionSchema.name && node.attrs.localId) {
mentionsBefore.set(node.attrs.localId, {
id: node.attrs.id,
localId: node.attrs.localId
});
}
});
newSlice.content.descendants(node => {
if (node.type.name === mentionSchema.name && node.attrs.localId) {
mentionsAfter.set(node.attrs.localId, {
id: node.attrs.id,
localId: node.attrs.localId
});
}
});
// Determine which mentions were removed in this step
mentionsBefore.forEach((mention, localId) => {
if (!mentionsAfter.has(localId)) {
mentionsRemoved.set(localId, mention);
}
});
// Adjust mentionsRemoved by removing any that reappear
mentionsAfter.forEach((_, localId) => {
if (mentionsRemoved.has(localId)) {
mentionsRemoved.delete(localId);
}
});
});
});
if (mentionsRemoved.size > 0) {
const changes = Array.from(mentionsRemoved.values()).map(mention => ({
id: mention.id,
localId: mention.localId,
type: 'deleted'
}));
options.handleMentionsChanged(changes);
}
(_insm$session2 = insm.session) === null || _insm$session2 === void 0 ? void 0 : _insm$session2.endFeature('mentionDeletionDetection');
}
return newPluginState;
}
},
props: {
nodeViews: {
mention: node => {
return new MentionNodeView(node, {
options,
api,
portalProviderAPI: pmPluginFactoryParams.portalProviderAPI
});
}
}
},
view(editorView) {
const providerHandler = (name, providerPromise) => {
switch (name) {
case 'mentionProvider':
if (!providerPromise) {
fireEvent({
action: ACTION.ERRORED,
actionSubject: ACTION_SUBJECT.MENTION,
actionSubjectId: ACTION_SUBJECT_ID.MENTION_PROVIDER,
eventType: EVENT_TYPE.OPERATIONAL,
attributes: {
reason: MENTION_PROVIDER_UNDEFINED
}
});
return setProvider(undefined)(editorView.state, editorView.dispatch);
}
providerPromise.then(provider => {
if (mentionProvider) {
mentionProvider.unsubscribe('mentionPlugin');
}
mentionProvider = provider;
setProvider(provider)(editorView.state, editorView.dispatch);
provider.subscribe('mentionPlugin', undefined, undefined, undefined, undefined, sendAnalytics);
}).catch(() => {
fireEvent({
action: ACTION.ERRORED,
actionSubject: ACTION_SUBJECT.MENTION,
actionSubjectId: ACTION_SUBJECT_ID.MENTION_PROVIDER,
eventType: EVENT_TYPE.OPERATIONAL,
attributes: {
reason: MENTION_PROVIDER_REJECTED
}
});
return setProvider(undefined)(editorView.state, editorView.dispatch);
});
break;
}
return;
};
const providerViaConfig = fg('platform_editor_mention_provider_via_plugin_config');
if (providerViaConfig && options !== null && options !== void 0 && options.mentionProvider) {
providerHandler('mentionProvider', options === null || options === void 0 ? void 0 : options.mentionProvider);
} else {
pmPluginFactoryParams.providerFactory.subscribe('mentionProvider', providerHandler);
}
return {
destroy() {
if (pmPluginFactoryParams.providerFactory) {
pmPluginFactoryParams.providerFactory.unsubscribe('mentionProvider', providerHandler);
}
if (mentionProvider) {
mentionProvider.unsubscribe('mentionPlugin');
}
}
};
}
});
}