UNPKG

@atlaskit/editor-plugin-mentions

Version:

Mentions plugin for @atlaskit/editor-core

183 lines (182 loc) 9.14 kB
import React from 'react'; import { bind } from 'bind-event-listener'; // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead import uuid from 'uuid/v4'; import { NodeSelection } from '@atlaskit/editor-prosemirror/state'; import { findChildrenByAttr } from '@atlaskit/editor-prosemirror/utils'; import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled'; import { fg } from '@atlaskit/platform-feature-flags/fg'; import { navigateToTeamsApp } from '@atlaskit/teams-app-config/navigation'; import { expVal } from '@atlaskit/tmp-editor-statsig/expVal'; import { getAgentMentionParentContext } from '../pm-plugins/agent-mention-context'; import { isAgentMentionType, ProfileCardComponent } from '../ui/ProfileCardComponent'; export const profileCardRenderer = ({ dom, options, portalProviderAPI, node, api, editorView }) => { // Keep a mutable reference to the latest node so the click handler always // reads up-to-date attrs let currentNode = node; let renderingProfileCard = false; let navigatingToProfile = false; // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead const key = uuid(); let cleanupSelection; const removeProfileCard = () => { var _cleanupSelection; if (dom instanceof HTMLElement) { dom.setAttribute('aria-expanded', 'false'); } portalProviderAPI.remove(key); renderingProfileCard = false; (_cleanupSelection = cleanupSelection) === null || _cleanupSelection === void 0 ? void 0 : _cleanupSelection(); }; const renderProfileCardPopup = renderProfileCard => { var _api$selection; if (!(dom instanceof HTMLElement) || renderingProfileCard) { return; } const referenceElement = dom; referenceElement.setAttribute('aria-expanded', 'true'); renderingProfileCard = true; portalProviderAPI.render(() => renderProfileCard(referenceElement), referenceElement, key); cleanupSelection = api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : _api$selection.sharedState.onChange(({ nextSharedState }) => { const selection = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.selection; if (selection instanceof NodeSelection ? expVal('platform_editor_reduced_agent_profile_cards', 'isEnabled', false) ? selection.node.sameMarkup(currentNode) : selection.node === node : false) { return; } removeProfileCard === null || removeProfileCard === void 0 ? void 0 : removeProfileCard(); }); }; const renderEditorProfileCard = () => { const isReducedProfileCards = expVal('platform_editor_reduced_agent_profile_cards', 'isEnabled', false); const clickedNode = isReducedProfileCards ? currentNode : node; const activeMention = isReducedProfileCards ? ((_ref, _ref$textContent, _currentNode, _currentNode2, _currentNode2$attrs, _currentNode3, _currentNode3$attrs) => { // Avatar mentions keep their resolved display name in the nested text span. // Control mentions preserve the legacy attrs-first fallback. const mentionTextElement = dom instanceof HTMLElement ? dom.querySelector('.editor-mention-text') : undefined; const primitiveText = (_ref = mentionTextElement !== null && mentionTextElement !== void 0 ? mentionTextElement : dom instanceof HTMLElement ? dom.querySelector('.editor-mention-primitive') : undefined) === null || _ref === void 0 ? void 0 : (_ref$textContent = _ref.textContent) === null || _ref$textContent === void 0 ? void 0 : _ref$textContent.trim(); const resolvedText = primitiveText && !primitiveText.startsWith('@') ? `@${primitiveText}` : primitiveText; return { attrs: { ...((_currentNode = currentNode) === null || _currentNode === void 0 ? void 0 : _currentNode.attrs), text: mentionTextElement ? resolvedText || ((_currentNode2 = currentNode) === null || _currentNode2 === void 0 ? void 0 : (_currentNode2$attrs = _currentNode2.attrs) === null || _currentNode2$attrs === void 0 ? void 0 : _currentNode2$attrs.text) || undefined : ((_currentNode3 = currentNode) === null || _currentNode3 === void 0 ? void 0 : (_currentNode3$attrs = _currentNode3.attrs) === null || _currentNode3$attrs === void 0 ? void 0 : _currentNode3$attrs.text) || primitiveText || undefined } }; })() : { attrs: node.attrs }; // Build agent mention context at click time by finding the parent block of this mention // in the live document. let agentMentionContext; if (options !== null && options !== void 0 && options.onAgentMentionChatClick && editorView && fg('platform_editor_agent_mentions_drop_one_fixes')) { var _clickedNode$attrs; const localId = (_clickedNode$attrs = clickedNode.attrs) === null || _clickedNode$attrs === void 0 ? void 0 : _clickedNode$attrs.localId; if (localId) { const found = findChildrenByAttr(editorView.state.doc, attrs => (attrs === null || attrs === void 0 ? void 0 : attrs.localId) === localId)[0]; if (found) { const parentBlock = editorView.state.doc.resolve(found.pos).parent; agentMentionContext = getAgentMentionParentContext(parentBlock, localId); } } } renderProfileCardPopup(referenceElement => /*#__PURE__*/React.createElement(ProfileCardComponent, { activeMention: activeMention, profilecardProvider: options === null || options === void 0 ? void 0 : options.profilecardProvider, onAgentMentionChatClick: options !== null && options !== void 0 && options.onAgentMentionChatClick && fg('platform_editor_agent_mentions_drop_one_fixes') ? agentId => { var _options$onAgentMenti; return (_options$onAgentMenti = options.onAgentMentionChatClick) === null || _options$onAgentMenti === void 0 ? void 0 : _options$onAgentMenti.call(options, agentId, agentMentionContext); } : undefined, dom: referenceElement, closeComponent: removeProfileCard })); }; const renderUserProfileCard = ({ userId, cloudId, renderUserMentionCard }) => { if (!renderUserMentionCard) { return false; } renderProfileCardPopup(referenceElement => /*#__PURE__*/React.createElement(React.Fragment, null, renderUserMentionCard({ userId, cloudId, children: null, referenceElement }))); return true; }; const navigateToProfile = (userId, cloudId) => { if (navigatingToProfile) { return; } navigatingToProfile = true; const { href, target } = navigateToTeamsApp({ type: 'USER', payload: { userId }, cloudId }); window.open(href, target, 'noopener,noreferrer'); }; const renderDefaultProfileCard = (userId, provider) => { if (renderUserProfileCard({ userId, cloudId: provider.cloudId, renderUserMentionCard: provider.renderUserMentionCard })) { return; } navigateToProfile(userId, provider.cloudId); }; const listenerCleanup = bind(dom, { type: 'click', listener: () => { if (fg('people-teams_migrate-user-profile-card') || isExperimentEnabled('pt_user_profile_card_migration_exp')) { var _currentNode$attrs, _node$attrs, _options$profilecardP; const userId = expVal('platform_editor_reduced_agent_profile_cards', 'isEnabled', false) ? (_currentNode$attrs = currentNode.attrs) === null || _currentNode$attrs === void 0 ? void 0 : _currentNode$attrs.id : (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.id; if (!userId) { return; } navigatingToProfile = false; options === null || options === void 0 ? void 0 : (_options$profilecardP = options.profilecardProvider) === null || _options$profilecardP === void 0 ? void 0 : _options$profilecardP.then(provider => { var _currentNode$attrs2, _node$attrs2; if (!expVal('platform_editor_agent_mentions', 'isEnabled', false)) { renderDefaultProfileCard(userId, provider); return; } if (isAgentMentionType(expVal('platform_editor_reduced_agent_profile_cards', 'isEnabled', false) ? (_currentNode$attrs2 = currentNode.attrs) === null || _currentNode$attrs2 === void 0 ? void 0 : _currentNode$attrs2.userType : (_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.userType)) { renderEditorProfileCard(); } else { renderDefaultProfileCard(userId, provider); } }); return; } if (options !== null && options !== void 0 && options.profilecardProvider) { renderEditorProfileCard(); } } }); return { destroyProfileCard: () => { listenerCleanup(); removeProfileCard === null || removeProfileCard === void 0 ? void 0 : removeProfileCard(); }, removeProfileCard, updateNode: nextNode => { currentNode = nextNode; } }; };