@atlaskit/editor-plugin-mentions 16.1.5 → 16.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/compass.yml +2 -2
  3. package/dist/cjs/entry-points/mention-node-data-provider.js +12 -0
  4. package/dist/cjs/nodeviews/mentionAvatarRenderer.js +75 -0
  5. package/dist/cjs/nodeviews/mentionNodeView.js +79 -10
  6. package/dist/cjs/nodeviews/profileCardRenderer.js +11 -9
  7. package/dist/cjs/pm-plugins/main.js +1 -1
  8. package/dist/cjs/providers/mention-node-data-provider.js +205 -0
  9. package/dist/es2019/entry-points/mention-node-data-provider.js +3 -0
  10. package/dist/es2019/nodeviews/mentionAvatarRenderer.js +71 -0
  11. package/dist/es2019/nodeviews/mentionNodeView.js +75 -10
  12. package/dist/es2019/nodeviews/profileCardRenderer.js +7 -5
  13. package/dist/es2019/pm-plugins/main.js +1 -1
  14. package/dist/es2019/providers/mention-node-data-provider.js +115 -0
  15. package/dist/esm/entry-points/mention-node-data-provider.js +3 -0
  16. package/dist/esm/nodeviews/mentionAvatarRenderer.js +69 -0
  17. package/dist/esm/nodeviews/mentionNodeView.js +79 -10
  18. package/dist/esm/nodeviews/profileCardRenderer.js +11 -9
  19. package/dist/esm/pm-plugins/main.js +1 -1
  20. package/dist/esm/providers/mention-node-data-provider.js +198 -0
  21. package/dist/types/entry-points/mention-node-data-provider.d.ts +2 -0
  22. package/dist/types/nodeviews/mentionAvatarRenderer.d.ts +12 -0
  23. package/dist/types/nodeviews/mentionNodeView.d.ts +6 -0
  24. package/dist/types/providers/mention-node-data-provider.d.ts +39 -0
  25. package/dist/types/types/index.d.ts +6 -0
  26. package/mention-node-data-provider/package.json +10 -0
  27. package/package.json +14 -6
@@ -1,14 +1,23 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
2
  import { getBrowserInfo } from '@atlaskit/editor-common/browser';
2
3
  import { ZERO_WIDTH_SPACE } from '@atlaskit/editor-common/whitespace';
3
4
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
5
+ import { UNKNOWN_USER_ID } from '@atlaskit/mention/constants';
4
6
  import { isResolvingMentionProvider, MentionNameStatus } from '@atlaskit/mention/resource';
5
7
  import { isRestricted } from '@atlaskit/mention/types';
8
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
6
9
  import { fg } from '@atlaskit/platform-feature-flags';
7
10
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
11
  import { expVal } from '@atlaskit/tmp-editor-statsig/expVal';
9
12
  import { disabledTooltipRenderer } from './disabledTooltipRenderer';
13
+ import { mentionAvatarRenderer } from './mentionAvatarRenderer';
10
14
  import { profileCardRenderer } from './profileCardRenderer';
11
15
  const primitiveClassName = 'editor-mention-primitive';
16
+ const primitiveWithAvatarClassName = 'editor-mention-primitive-with-avatar';
17
+ const avatarContainerClassName = 'editor-mention-avatar';
18
+ const mentionTextClassName = 'editor-mention-text';
19
+ const genericMentionIds = ['HipChat', 'all', 'here'];
20
+ const unknownMentionText = `@${UNKNOWN_USER_ID}`;
12
21
  // eslint-disable-next-line require-unicode-regexp
13
22
  const AT_PREFIX_REGEX = /^@/;
14
23
  const getAccessibilityLabelFromName = name => name.replace(AT_PREFIX_REGEX, '');
@@ -31,7 +40,7 @@ const isAgentMentionsExperimentEnabled = () => expVal('platform_editor_agent_men
31
40
  * consistent and can't drift apart.
32
41
  */
33
42
  const isAgentAaidText = (node, isAgentMentionsEnabled) => isAgentMentionsEnabled && (node.attrs.userType === 'APP' || node.attrs.userType === 'AGENT') && !!node.attrs.text && isMentionTextAnAaid(node.attrs.text);
34
- const toDOM = node => {
43
+ const toDOM = (node, hasAvatarSlot) => {
35
44
  // packages/elements/mention/src/components/Mention/index.tsx
36
45
  let mentionAttrs = {
37
46
  contenteditable: 'false',
@@ -58,14 +67,25 @@ const toDOM = node => {
58
67
  }
59
68
  const browser = getBrowserInfo();
60
69
  const hasAgentAaidText = isAgentAaidText(node, isAgentMentionsEnabled);
70
+ const mentionText = node.attrs.text && !hasAgentAaidText ? node.attrs.text : '@…';
71
+ const visibleMentionText = hasAvatarSlot && mentionText.startsWith('@') ? mentionText.slice(1) : mentionText;
72
+ const mentionContentSpec = hasAvatarSlot ? ['span', {
73
+ spellcheck: 'false',
74
+ class: `${primitiveClassName} ${primitiveWithAvatarClassName}`
75
+ }, ['span', {
76
+ class: avatarContainerClassName,
77
+ 'aria-hidden': 'true'
78
+ }], ['span', {
79
+ class: mentionTextClassName
80
+ }, visibleMentionText]] : ['span', {
81
+ spellcheck: 'false',
82
+ class: primitiveClassName
83
+ }, mentionText];
61
84
  return ['span', mentionAttrs, ['span', {
62
85
  class: 'zeroWidthSpaceContainer'
63
86
  }, ['span', {
64
87
  class: 'inlineNodeViewAddZeroWidthSpace'
65
- }, ZERO_WIDTH_SPACE]], ['span', {
66
- spellcheck: 'false',
67
- class: primitiveClassName
68
- }, node.attrs.text && !hasAgentAaidText ? node.attrs.text : '@…'], browser.android ? ['span', {
88
+ }, ZERO_WIDTH_SPACE]], mentionContentSpec, browser.android ? ['span', {
69
89
  class: 'zeroWidthSpaceContainer',
70
90
  contenteditable: 'false'
71
91
  }, ['span', {
@@ -75,7 +95,7 @@ const toDOM = node => {
75
95
  }, ZERO_WIDTH_SPACE]];
76
96
  };
77
97
  const processName = name => {
78
- return name.status === MentionNameStatus.OK ? `@${name.name || ''}` : `@_|unknown|_`;
98
+ return name.status === MentionNameStatus.OK ? `@${name.name || ''}` : unknownMentionText;
79
99
  };
80
100
  const handleProviderName = async (mentionProvider, node, isAgentMentionsEnabled) => {
81
101
  // Also resolve when text is a raw AAID — provider will return the real display name.
@@ -101,22 +121,37 @@ const getNewState = (isHighlighted, isRestricted, isDisabled) => {
101
121
  export class MentionNodeView {
102
122
  constructor(node, config) {
103
123
  var _this$domElement$quer, _api$mention$sharedSt;
124
+ _defineProperty(this, "hasAvatarSlot", false);
125
+ _defineProperty(this, "isDestroyed", false);
104
126
  const {
105
127
  options,
106
128
  api,
107
129
  portalProviderAPI,
108
130
  editorView
109
131
  } = config;
132
+ this.hasAvatarSlot = Boolean(options === null || options === void 0 ? void 0 : options.mentionNodeDataProvider) && node.attrs.userType !== 'SPECIAL' && !genericMentionIds.includes(node.attrs.id) && isExperimentEnabled('platform_editor_mention_node_avatar');
110
133
  const {
111
134
  dom,
112
135
  contentDOM
113
- } = DOMSerializer.renderSpec(document, toDOM(node));
136
+ } = DOMSerializer.renderSpec(document, toDOM(node, this.hasAvatarSlot));
114
137
  this.dom = dom;
115
138
  this.contentDOM = contentDOM;
116
139
  this.config = config;
117
140
  this.node = node;
118
141
  this.domElement = dom instanceof HTMLElement ? dom : undefined;
119
142
  this.mentionPrimitiveElement = this.domElement ? (_this$domElement$quer = this.domElement.querySelector(`.${primitiveClassName}`)) !== null && _this$domElement$quer !== void 0 ? _this$domElement$quer : undefined : undefined;
143
+ this.mentionTextElement = this.mentionPrimitiveElement;
144
+ if (this.hasAvatarSlot) {
145
+ var _this$domElement$quer2, _this$domElement, _this$domElement2;
146
+ this.mentionTextElement = (_this$domElement$quer2 = (_this$domElement = this.domElement) === null || _this$domElement === void 0 ? void 0 : _this$domElement.querySelector(`.${mentionTextClassName}`)) !== null && _this$domElement$quer2 !== void 0 ? _this$domElement$quer2 : this.mentionPrimitiveElement;
147
+ const avatarContainer = (_this$domElement2 = this.domElement) === null || _this$domElement2 === void 0 ? void 0 : _this$domElement2.querySelector(`.${avatarContainerClassName}`);
148
+ if (avatarContainer) {
149
+ this.mentionAvatar = mentionAvatarRenderer({
150
+ container: avatarContainer
151
+ });
152
+ this.resolveMentionAvatar(options === null || options === void 0 ? void 0 : options.mentionNodeDataProvider);
153
+ }
154
+ }
120
155
  const {
121
156
  mentionProvider
122
157
  } = (_api$mention$sharedSt = api === null || api === void 0 ? void 0 : api.mention.sharedState.currentState()) !== null && _api$mention$sharedSt !== void 0 ? _api$mention$sharedSt : {};
@@ -242,9 +277,36 @@ export class MentionNodeView {
242
277
  setTextContent(name, isAgentMentionsEnabled) {
243
278
  // Also overwrite when text is a raw AAID so the resolved name takes precedence.
244
279
  const textIsAaid = isAgentAaidText(this.node, isAgentMentionsEnabled);
245
- if (name && (!this.node.attrs.text || textIsAaid) && this.mentionPrimitiveElement) {
246
- this.mentionPrimitiveElement.textContent = name;
280
+ if (name && (!this.node.attrs.text || textIsAaid) && this.mentionTextElement) {
281
+ this.setVisibleText(name);
282
+ }
283
+ }
284
+ setVisibleText(text) {
285
+ if (!this.mentionTextElement) {
286
+ return;
287
+ }
288
+ this.mentionTextElement.textContent = this.hasAvatarSlot && text.startsWith('@') && text !== unknownMentionText ? text.slice(1) : text;
289
+ }
290
+ resolveMentionAvatar(mentionNodeDataProvider) {
291
+ if (!this.hasAvatarSlot || !mentionNodeDataProvider || this.node.attrs.userType === 'SPECIAL') {
292
+ return;
247
293
  }
294
+ const mention = {
295
+ id: this.node.attrs.id,
296
+ userType: this.node.attrs.userType
297
+ };
298
+ const applyData = data => {
299
+ var _this$mentionAvatar;
300
+ if (this.isDestroyed || !(data !== null && data !== void 0 && data.avatarUrl)) {
301
+ return;
302
+ }
303
+ (_this$mentionAvatar = this.mentionAvatar) === null || _this$mentionAvatar === void 0 ? void 0 : _this$mentionAvatar.render(data);
304
+ };
305
+ mentionNodeDataProvider.getMentionData(mention, payload => {
306
+ if (payload.data) {
307
+ applyData(payload.data);
308
+ }
309
+ });
248
310
  }
249
311
  shouldHighlightMention(mentionProvider) {
250
312
  var _this$config$options;
@@ -316,7 +378,8 @@ export class MentionNodeView {
316
378
  return true;
317
379
  }
318
380
  destroy() {
319
- var _this$cleanup, _this$destroyProfileC, _this$disabledTooltip, _this$unsubscribeFrom2;
381
+ var _this$cleanup, _this$destroyProfileC, _this$mentionAvatar2, _this$disabledTooltip, _this$unsubscribeFrom2;
382
+ this.isDestroyed = true;
320
383
  // Surface the destruction to the provider before tearing down so the
321
384
  // chat layer can react (e.g. drop the agent id from `selectedAgentIds`).
322
385
  // This is the lowest-level deletion signal — fires for backspace,
@@ -332,6 +395,8 @@ export class MentionNodeView {
332
395
  }
333
396
  (_this$cleanup = this.cleanup) === null || _this$cleanup === void 0 ? void 0 : _this$cleanup.call(this);
334
397
  (_this$destroyProfileC = this.destroyProfileCard) === null || _this$destroyProfileC === void 0 ? void 0 : _this$destroyProfileC.call(this);
398
+ (_this$mentionAvatar2 = this.mentionAvatar) === null || _this$mentionAvatar2 === void 0 ? void 0 : _this$mentionAvatar2.destroy();
399
+ this.mentionAvatar = undefined;
335
400
  (_this$disabledTooltip = this.disabledTooltip) === null || _this$disabledTooltip === void 0 ? void 0 : _this$disabledTooltip.destroy();
336
401
  this.disabledTooltip = undefined;
337
402
  (_this$unsubscribeFrom2 = this.unsubscribeFromDisabledStateChanges) === null || _this$unsubscribeFrom2 === void 0 ? void 0 : _this$unsubscribeFrom2.call(this);
@@ -57,14 +57,16 @@ export const profileCardRenderer = ({
57
57
  const renderEditorProfileCard = () => {
58
58
  const isReducedProfileCards = expVal('platform_editor_reduced_agent_profile_cards', 'isEnabled', false);
59
59
  const clickedNode = isReducedProfileCards ? currentNode : node;
60
- const activeMention = isReducedProfileCards ? ((_dom$querySelector, _dom$querySelector$te, _currentNode, _currentNode2, _currentNode2$attrs) => {
61
- // Read the display name from the DOM element at click time — this is
62
- // always up-to-date even when node.attrs.text is absent
63
- const primitiveText = dom instanceof HTMLElement ? (_dom$querySelector = dom.querySelector('.editor-mention-primitive')) === null || _dom$querySelector === void 0 ? void 0 : (_dom$querySelector$te = _dom$querySelector.textContent) === null || _dom$querySelector$te === void 0 ? void 0 : _dom$querySelector$te.trim() : undefined;
60
+ const activeMention = isReducedProfileCards ? ((_ref, _ref$textContent, _currentNode, _currentNode2, _currentNode2$attrs, _currentNode3, _currentNode3$attrs) => {
61
+ // Avatar mentions keep their resolved display name in the nested text span.
62
+ // Control mentions preserve the legacy attrs-first fallback.
63
+ const mentionTextElement = dom instanceof HTMLElement ? dom.querySelector('.editor-mention-text') : undefined;
64
+ 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();
65
+ const resolvedText = primitiveText && !primitiveText.startsWith('@') ? `@${primitiveText}` : primitiveText;
64
66
  return {
65
67
  attrs: {
66
68
  ...((_currentNode = currentNode) === null || _currentNode === void 0 ? void 0 : _currentNode.attrs),
67
- text: ((_currentNode2 = currentNode) === null || _currentNode2 === void 0 ? void 0 : (_currentNode2$attrs = _currentNode2.attrs) === null || _currentNode2$attrs === void 0 ? void 0 : _currentNode2$attrs.text) || primitiveText || undefined
69
+ 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
68
70
  }
69
71
  };
70
72
  })() : {
@@ -51,7 +51,7 @@ const AI_STREAMING_TRANSFORMATION_META_KEY = 'isAIStreamingTransformation';
51
51
  const AGENT_MENTION_INACTIVITY_MS = 3000;
52
52
  const MAX_PENDING_TYPED_AGENT_MENTION_FOCUS_DEFERS = 20;
53
53
  const PACKAGE_NAME = "@atlaskit/editor-plugin-mentions";
54
- const PACKAGE_VERSION = "16.1.4";
54
+ const PACKAGE_VERSION = "16.1.5";
55
55
  const setProvider = provider => (state, dispatch) => {
56
56
  if (dispatch) {
57
57
  dispatch(state.tr.setMeta(mentionPluginKey, {
@@ -0,0 +1,115 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { NodeDataProvider } from '@atlaskit/node-data-provider/node-data-provider';
3
+ const getAppType = userType => userType === 'APP' || userType === 'AGENT' ? 'agent' : 'user';
4
+ const enrichMentionNodeData = (mention, data) => {
5
+ var _data$appType;
6
+ return {
7
+ ...data,
8
+ appType: (_data$appType = data === null || data === void 0 ? void 0 : data.appType) !== null && _data$appType !== void 0 ? _data$appType : getAppType(mention.userType)
9
+ };
10
+ };
11
+ const toMentionNode = ({
12
+ id,
13
+ userType
14
+ }) => ({
15
+ attrs: {
16
+ id,
17
+ // NodeDataProvider uses an ADF node internally, so normalize the runtime-only value.
18
+ userType: userType === 'AGENT' ? 'APP' : userType
19
+ },
20
+ type: 'mention'
21
+ });
22
+ const isMentionNode = node => node.type === 'mention' && node.attrs !== undefined && 'id' in node.attrs && typeof node.attrs.id === 'string';
23
+
24
+ /**
25
+ * Page-scoped data provider for mention avatars.
26
+ *
27
+ * Products inject their resolution strategy so this package remains independent
28
+ * of Jira and Confluence APIs. Reuse the instance for the lifetime of a renderer
29
+ * or editor rather than creating one for every mention.
30
+ *
31
+ * Deterministic products can supply `resolveMentionNodeData` to return an
32
+ * SSR-safe avatar URL synchronously. It must return the same value on the server
33
+ * and the client's first render to preserve hydration parity.
34
+ *
35
+ * Network-backed products supply `fetchMentionNodeData`. Calls made in the same
36
+ * microtask are combined and account IDs are deduplicated before the fetcher is
37
+ * invoked. A Relay adapter can query `users(accountIds:)`, then map each user's
38
+ * `picture` and app type to a `MentionNodeData` record keyed by account ID. The
39
+ * resolved result is cached by the underlying `NodeDataProvider`.
40
+ */
41
+ export class MentionNodeDataProvider extends NodeDataProvider {
42
+ constructor(fetchMentionNodeData, resolveMentionNodeData = undefined) {
43
+ super();
44
+ _defineProperty(this, "name", 'mentionNodeDataProvider');
45
+ _defineProperty(this, "queuedFetches", []);
46
+ _defineProperty(this, "isFlushScheduled", false);
47
+ this.fetchMentionNodeData = fetchMentionNodeData;
48
+ this.resolveMentionNodeData = resolveMentionNodeData;
49
+ }
50
+ isNodeSupported(node) {
51
+ return isMentionNode(node) && node.attrs.userType !== 'SPECIAL';
52
+ }
53
+ nodeDataKey(node) {
54
+ return node.attrs.id;
55
+ }
56
+ async fetchNodesData(nodes) {
57
+ const accountIds = Array.from(new Set(nodes.map(({
58
+ attrs
59
+ }) => attrs.id)));
60
+ const dataByAccountId = await this.enqueueFetch(accountIds);
61
+ return nodes.map(({
62
+ attrs
63
+ }) => enrichMentionNodeData(attrs, dataByAccountId[attrs.id]));
64
+ }
65
+ getMentionData(mention, callback) {
66
+ var _this$resolveMentionN;
67
+ const resolvedData = (_this$resolveMentionN = this.resolveMentionNodeData) === null || _this$resolveMentionN === void 0 ? void 0 : _this$resolveMentionN.call(this, mention);
68
+ if (resolvedData) {
69
+ callback({
70
+ data: enrichMentionNodeData(mention, resolvedData)
71
+ });
72
+ return;
73
+ }
74
+ this.getData(toMentionNode(mention), callback);
75
+ }
76
+ getMentionDataFromCache(mention) {
77
+ var _this$resolveMentionN2, _this$getNodeDataFrom;
78
+ const resolvedData = (_this$resolveMentionN2 = this.resolveMentionNodeData) === null || _this$resolveMentionN2 === void 0 ? void 0 : _this$resolveMentionN2.call(this, mention);
79
+ if (resolvedData) {
80
+ return enrichMentionNodeData(mention, resolvedData);
81
+ }
82
+ const data = (_this$getNodeDataFrom = this.getNodeDataFromCache(toMentionNode(mention))) === null || _this$getNodeDataFrom === void 0 ? void 0 : _this$getNodeDataFrom.data;
83
+ return data ? enrichMentionNodeData(mention, data) : undefined;
84
+ }
85
+ enqueueFetch(accountIds) {
86
+ const result = new Promise((resolve, reject) => {
87
+ this.queuedFetches.push({
88
+ accountIds,
89
+ reject,
90
+ resolve
91
+ });
92
+ });
93
+ if (!this.isFlushScheduled) {
94
+ this.isFlushScheduled = true;
95
+ void Promise.resolve().then(() => this.flushFetches());
96
+ }
97
+ return result;
98
+ }
99
+ async flushFetches() {
100
+ this.isFlushScheduled = false;
101
+ const queuedFetches = this.queuedFetches;
102
+ this.queuedFetches = [];
103
+ const accountIds = Array.from(new Set(queuedFetches.flatMap(request => request.accountIds)));
104
+ try {
105
+ const data = await this.fetchMentionNodeData(accountIds);
106
+ queuedFetches.forEach(({
107
+ resolve
108
+ }) => resolve(data));
109
+ } catch (error) {
110
+ queuedFetches.forEach(({
111
+ reject
112
+ }) => reject(error));
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,3 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+
3
+ export { MentionNodeDataProvider } from '../providers/mention-node-data-provider';
@@ -0,0 +1,69 @@
1
+ import { bind } from 'bind-event-listener';
2
+ var avatarImageClassName = 'editor-mention-avatar-image';
3
+ var agentAvatarClassName = 'editor-mention-avatar-agent';
4
+ var fallbackAvatarClassName = 'editor-mention-avatar-fallback';
5
+ var avatarSize = 16;
6
+ /**
7
+ * Populates the fixed-size avatar slot owned by the vanilla ProseMirror NodeView.
8
+ * No React root or editor portal is created for individual mention nodes.
9
+ */
10
+ export var mentionAvatarRenderer = function mentionAvatarRenderer(_ref) {
11
+ var container = _ref.container;
12
+ var image;
13
+ var fallback;
14
+ var unbindImageError;
15
+ var removeImage = function removeImage() {
16
+ var _unbindImageError, _image;
17
+ (_unbindImageError = unbindImageError) === null || _unbindImageError === void 0 || _unbindImageError();
18
+ unbindImageError = undefined;
19
+ (_image = image) === null || _image === void 0 || _image.remove();
20
+ image = undefined;
21
+ };
22
+ var removeFallback = function removeFallback() {
23
+ var _fallback;
24
+ (_fallback = fallback) === null || _fallback === void 0 || _fallback.remove();
25
+ fallback = undefined;
26
+ container.classList.remove(fallbackAvatarClassName);
27
+ };
28
+ var showFallback = function showFallback() {
29
+ removeImage();
30
+ removeFallback();
31
+ fallback = container.ownerDocument.createTextNode('@');
32
+ container.appendChild(fallback);
33
+ container.classList.add(fallbackAvatarClassName);
34
+ };
35
+ var destroy = function destroy() {
36
+ removeImage();
37
+ removeFallback();
38
+ };
39
+ return {
40
+ destroy: destroy,
41
+ render: function render(_ref2) {
42
+ var appType = _ref2.appType,
43
+ avatarUrl = _ref2.avatarUrl,
44
+ isAvatarImagePreShaped = _ref2.isAvatarImagePreShaped;
45
+ container.classList.toggle(agentAvatarClassName, !isAvatarImagePreShaped && appType === 'agent');
46
+ removeFallback();
47
+ if (!avatarUrl) {
48
+ removeImage();
49
+ return;
50
+ }
51
+ if (!image) {
52
+ image = container.ownerDocument.createElement('img');
53
+ image.alt = '';
54
+ image.className = avatarImageClassName;
55
+ image.decoding = 'async';
56
+ image.draggable = false;
57
+ image.height = avatarSize;
58
+ image.loading = 'lazy';
59
+ image.width = avatarSize;
60
+ unbindImageError = bind(image, {
61
+ type: 'error',
62
+ listener: showFallback
63
+ });
64
+ container.appendChild(image);
65
+ }
66
+ image.src = avatarUrl;
67
+ }
68
+ };
69
+ };
@@ -8,14 +8,22 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
8
8
  import { getBrowserInfo } from '@atlaskit/editor-common/browser';
9
9
  import { ZERO_WIDTH_SPACE } from '@atlaskit/editor-common/whitespace';
10
10
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
11
+ import { UNKNOWN_USER_ID } from '@atlaskit/mention/constants';
11
12
  import { isResolvingMentionProvider, MentionNameStatus } from '@atlaskit/mention/resource';
12
13
  import { isRestricted } from '@atlaskit/mention/types';
14
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
13
15
  import { fg } from '@atlaskit/platform-feature-flags';
14
16
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
15
17
  import { expVal } from '@atlaskit/tmp-editor-statsig/expVal';
16
18
  import { disabledTooltipRenderer } from './disabledTooltipRenderer';
19
+ import { mentionAvatarRenderer } from './mentionAvatarRenderer';
17
20
  import { profileCardRenderer } from './profileCardRenderer';
18
21
  var primitiveClassName = 'editor-mention-primitive';
22
+ var primitiveWithAvatarClassName = 'editor-mention-primitive-with-avatar';
23
+ var avatarContainerClassName = 'editor-mention-avatar';
24
+ var mentionTextClassName = 'editor-mention-text';
25
+ var genericMentionIds = ['HipChat', 'all', 'here'];
26
+ var unknownMentionText = "@".concat(UNKNOWN_USER_ID);
19
27
  // eslint-disable-next-line require-unicode-regexp
20
28
  var AT_PREFIX_REGEX = /^@/;
21
29
  var getAccessibilityLabelFromName = function getAccessibilityLabelFromName(name) {
@@ -46,7 +54,7 @@ var isAgentMentionsExperimentEnabled = function isAgentMentionsExperimentEnabled
46
54
  var isAgentAaidText = function isAgentAaidText(node, isAgentMentionsEnabled) {
47
55
  return isAgentMentionsEnabled && (node.attrs.userType === 'APP' || node.attrs.userType === 'AGENT') && !!node.attrs.text && isMentionTextAnAaid(node.attrs.text);
48
56
  };
49
- var toDOM = function toDOM(node) {
57
+ var toDOM = function toDOM(node, hasAvatarSlot) {
50
58
  // packages/elements/mention/src/components/Mention/index.tsx
51
59
  var mentionAttrs = {
52
60
  contenteditable: 'false',
@@ -71,14 +79,25 @@ var toDOM = function toDOM(node) {
71
79
  }
72
80
  var browser = getBrowserInfo();
73
81
  var hasAgentAaidText = isAgentAaidText(node, isAgentMentionsEnabled);
82
+ var mentionText = node.attrs.text && !hasAgentAaidText ? node.attrs.text : '@…';
83
+ var visibleMentionText = hasAvatarSlot && mentionText.startsWith('@') ? mentionText.slice(1) : mentionText;
84
+ var mentionContentSpec = hasAvatarSlot ? ['span', {
85
+ spellcheck: 'false',
86
+ class: "".concat(primitiveClassName, " ").concat(primitiveWithAvatarClassName)
87
+ }, ['span', {
88
+ class: avatarContainerClassName,
89
+ 'aria-hidden': 'true'
90
+ }], ['span', {
91
+ class: mentionTextClassName
92
+ }, visibleMentionText]] : ['span', {
93
+ spellcheck: 'false',
94
+ class: primitiveClassName
95
+ }, mentionText];
74
96
  return ['span', mentionAttrs, ['span', {
75
97
  class: 'zeroWidthSpaceContainer'
76
98
  }, ['span', {
77
99
  class: 'inlineNodeViewAddZeroWidthSpace'
78
- }, ZERO_WIDTH_SPACE]], ['span', {
79
- spellcheck: 'false',
80
- class: primitiveClassName
81
- }, node.attrs.text && !hasAgentAaidText ? node.attrs.text : '@…'], browser.android ? ['span', {
100
+ }, ZERO_WIDTH_SPACE]], mentionContentSpec, browser.android ? ['span', {
82
101
  class: 'zeroWidthSpaceContainer',
83
102
  contenteditable: 'false'
84
103
  }, ['span', {
@@ -88,7 +107,7 @@ var toDOM = function toDOM(node) {
88
107
  }, ZERO_WIDTH_SPACE]];
89
108
  };
90
109
  var processName = function processName(name) {
91
- return name.status === MentionNameStatus.OK ? "@".concat(name.name || '') : "@_|unknown|_";
110
+ return name.status === MentionNameStatus.OK ? "@".concat(name.name || '') : unknownMentionText;
92
111
  };
93
112
  var handleProviderName = /*#__PURE__*/function () {
94
113
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(mentionProvider, node, isAgentMentionsEnabled) {
@@ -136,11 +155,14 @@ export var MentionNodeView = /*#__PURE__*/function () {
136
155
  _api$mention$sharedSt,
137
156
  _this = this;
138
157
  _classCallCheck(this, MentionNodeView);
158
+ _defineProperty(this, "hasAvatarSlot", false);
159
+ _defineProperty(this, "isDestroyed", false);
139
160
  var options = config.options,
140
161
  api = config.api,
141
162
  portalProviderAPI = config.portalProviderAPI,
142
163
  editorView = config.editorView;
143
- var _DOMSerializer$render = DOMSerializer.renderSpec(document, toDOM(node)),
164
+ this.hasAvatarSlot = Boolean(options === null || options === void 0 ? void 0 : options.mentionNodeDataProvider) && node.attrs.userType !== 'SPECIAL' && !genericMentionIds.includes(node.attrs.id) && isExperimentEnabled('platform_editor_mention_node_avatar');
165
+ var _DOMSerializer$render = DOMSerializer.renderSpec(document, toDOM(node, this.hasAvatarSlot)),
144
166
  dom = _DOMSerializer$render.dom,
145
167
  contentDOM = _DOMSerializer$render.contentDOM;
146
168
  this.dom = dom;
@@ -149,6 +171,18 @@ export var MentionNodeView = /*#__PURE__*/function () {
149
171
  this.node = node;
150
172
  this.domElement = dom instanceof HTMLElement ? dom : undefined;
151
173
  this.mentionPrimitiveElement = this.domElement ? (_this$domElement$quer = this.domElement.querySelector(".".concat(primitiveClassName))) !== null && _this$domElement$quer !== void 0 ? _this$domElement$quer : undefined : undefined;
174
+ this.mentionTextElement = this.mentionPrimitiveElement;
175
+ if (this.hasAvatarSlot) {
176
+ var _this$domElement$quer2, _this$domElement, _this$domElement2;
177
+ this.mentionTextElement = (_this$domElement$quer2 = (_this$domElement = this.domElement) === null || _this$domElement === void 0 ? void 0 : _this$domElement.querySelector(".".concat(mentionTextClassName))) !== null && _this$domElement$quer2 !== void 0 ? _this$domElement$quer2 : this.mentionPrimitiveElement;
178
+ var avatarContainer = (_this$domElement2 = this.domElement) === null || _this$domElement2 === void 0 ? void 0 : _this$domElement2.querySelector(".".concat(avatarContainerClassName));
179
+ if (avatarContainer) {
180
+ this.mentionAvatar = mentionAvatarRenderer({
181
+ container: avatarContainer
182
+ });
183
+ this.resolveMentionAvatar(options === null || options === void 0 ? void 0 : options.mentionNodeDataProvider);
184
+ }
185
+ }
152
186
  var _ref2 = (_api$mention$sharedSt = api === null || api === void 0 ? void 0 : api.mention.sharedState.currentState()) !== null && _api$mention$sharedSt !== void 0 ? _api$mention$sharedSt : {},
153
187
  mentionProvider = _ref2.mentionProvider;
154
188
  this.updateState(mentionProvider);
@@ -280,9 +314,41 @@ export var MentionNodeView = /*#__PURE__*/function () {
280
314
  value: function setTextContent(name, isAgentMentionsEnabled) {
281
315
  // Also overwrite when text is a raw AAID so the resolved name takes precedence.
282
316
  var textIsAaid = isAgentAaidText(this.node, isAgentMentionsEnabled);
283
- if (name && (!this.node.attrs.text || textIsAaid) && this.mentionPrimitiveElement) {
284
- this.mentionPrimitiveElement.textContent = name;
317
+ if (name && (!this.node.attrs.text || textIsAaid) && this.mentionTextElement) {
318
+ this.setVisibleText(name);
319
+ }
320
+ }
321
+ }, {
322
+ key: "setVisibleText",
323
+ value: function setVisibleText(text) {
324
+ if (!this.mentionTextElement) {
325
+ return;
326
+ }
327
+ this.mentionTextElement.textContent = this.hasAvatarSlot && text.startsWith('@') && text !== unknownMentionText ? text.slice(1) : text;
328
+ }
329
+ }, {
330
+ key: "resolveMentionAvatar",
331
+ value: function resolveMentionAvatar(mentionNodeDataProvider) {
332
+ var _this3 = this;
333
+ if (!this.hasAvatarSlot || !mentionNodeDataProvider || this.node.attrs.userType === 'SPECIAL') {
334
+ return;
285
335
  }
336
+ var mention = {
337
+ id: this.node.attrs.id,
338
+ userType: this.node.attrs.userType
339
+ };
340
+ var applyData = function applyData(data) {
341
+ var _this3$mentionAvatar;
342
+ if (_this3.isDestroyed || !(data !== null && data !== void 0 && data.avatarUrl)) {
343
+ return;
344
+ }
345
+ (_this3$mentionAvatar = _this3.mentionAvatar) === null || _this3$mentionAvatar === void 0 || _this3$mentionAvatar.render(data);
346
+ };
347
+ mentionNodeDataProvider.getMentionData(mention, function (payload) {
348
+ if (payload.data) {
349
+ applyData(payload.data);
350
+ }
351
+ });
286
352
  }
287
353
  }, {
288
354
  key: "shouldHighlightMention",
@@ -379,7 +445,8 @@ export var MentionNodeView = /*#__PURE__*/function () {
379
445
  }, {
380
446
  key: "destroy",
381
447
  value: function destroy() {
382
- var _this$cleanup, _this$destroyProfileC, _this$disabledTooltip, _this$unsubscribeFrom2;
448
+ var _this$cleanup, _this$destroyProfileC, _this$mentionAvatar, _this$disabledTooltip, _this$unsubscribeFrom2;
449
+ this.isDestroyed = true;
383
450
  // Surface the destruction to the provider before tearing down so the
384
451
  // chat layer can react (e.g. drop the agent id from `selectedAgentIds`).
385
452
  // This is the lowest-level deletion signal — fires for backspace,
@@ -395,6 +462,8 @@ export var MentionNodeView = /*#__PURE__*/function () {
395
462
  }
396
463
  (_this$cleanup = this.cleanup) === null || _this$cleanup === void 0 || _this$cleanup.call(this);
397
464
  (_this$destroyProfileC = this.destroyProfileCard) === null || _this$destroyProfileC === void 0 || _this$destroyProfileC.call(this);
465
+ (_this$mentionAvatar = this.mentionAvatar) === null || _this$mentionAvatar === void 0 || _this$mentionAvatar.destroy();
466
+ this.mentionAvatar = undefined;
398
467
  (_this$disabledTooltip = this.disabledTooltip) === null || _this$disabledTooltip === void 0 || _this$disabledTooltip.destroy();
399
468
  this.disabledTooltip = undefined;
400
469
  (_this$unsubscribeFrom2 = this.unsubscribeFromDisabledStateChanges) === null || _this$unsubscribeFrom2 === void 0 || _this$unsubscribeFrom2.call(this);
@@ -60,13 +60,15 @@ export var profileCardRenderer = function profileCardRenderer(_ref) {
60
60
  var renderEditorProfileCard = function renderEditorProfileCard() {
61
61
  var isReducedProfileCards = expVal('platform_editor_reduced_agent_profile_cards', 'isEnabled', false);
62
62
  var clickedNode = isReducedProfileCards ? currentNode : node;
63
- var activeMention = isReducedProfileCards ? function (_dom$querySelector, _currentNode, _currentNode2) {
64
- // Read the display name from the DOM element at click time — this is
65
- // always up-to-date even when node.attrs.text is absent
66
- var primitiveText = dom instanceof HTMLElement ? (_dom$querySelector = dom.querySelector('.editor-mention-primitive')) === null || _dom$querySelector === void 0 || (_dom$querySelector = _dom$querySelector.textContent) === null || _dom$querySelector === void 0 ? void 0 : _dom$querySelector.trim() : undefined;
63
+ var activeMention = isReducedProfileCards ? function (_ref3, _currentNode, _currentNode2, _currentNode3) {
64
+ // Avatar mentions keep their resolved display name in the nested text span.
65
+ // Control mentions preserve the legacy attrs-first fallback.
66
+ var mentionTextElement = dom instanceof HTMLElement ? dom.querySelector('.editor-mention-text') : undefined;
67
+ var primitiveText = (_ref3 = mentionTextElement !== null && mentionTextElement !== void 0 ? mentionTextElement : dom instanceof HTMLElement ? dom.querySelector('.editor-mention-primitive') : undefined) === null || _ref3 === void 0 || (_ref3 = _ref3.textContent) === null || _ref3 === void 0 ? void 0 : _ref3.trim();
68
+ var resolvedText = primitiveText && !primitiveText.startsWith('@') ? "@".concat(primitiveText) : primitiveText;
67
69
  return {
68
70
  attrs: _objectSpread(_objectSpread({}, (_currentNode = currentNode) === null || _currentNode === void 0 ? void 0 : _currentNode.attrs), {}, {
69
- text: ((_currentNode2 = currentNode) === null || _currentNode2 === void 0 || (_currentNode2 = _currentNode2.attrs) === null || _currentNode2 === void 0 ? void 0 : _currentNode2.text) || primitiveText || undefined
71
+ text: mentionTextElement ? resolvedText || ((_currentNode2 = currentNode) === null || _currentNode2 === void 0 || (_currentNode2 = _currentNode2.attrs) === null || _currentNode2 === void 0 ? void 0 : _currentNode2.text) || undefined : ((_currentNode3 = currentNode) === null || _currentNode3 === void 0 || (_currentNode3 = _currentNode3.attrs) === null || _currentNode3 === void 0 ? void 0 : _currentNode3.text) || primitiveText || undefined
70
72
  })
71
73
  };
72
74
  }() : {
@@ -102,10 +104,10 @@ export var profileCardRenderer = function profileCardRenderer(_ref) {
102
104
  });
103
105
  });
104
106
  };
105
- var renderUserProfileCard = function renderUserProfileCard(_ref3) {
106
- var userId = _ref3.userId,
107
- cloudId = _ref3.cloudId,
108
- renderUserMentionCard = _ref3.renderUserMentionCard;
107
+ var renderUserProfileCard = function renderUserProfileCard(_ref4) {
108
+ var userId = _ref4.userId,
109
+ cloudId = _ref4.cloudId,
110
+ renderUserMentionCard = _ref4.renderUserMentionCard;
109
111
  if (!renderUserMentionCard) {
110
112
  return false;
111
113
  }
@@ -58,7 +58,7 @@ var AI_STREAMING_TRANSFORMATION_META_KEY = 'isAIStreamingTransformation';
58
58
  var AGENT_MENTION_INACTIVITY_MS = 3000;
59
59
  var MAX_PENDING_TYPED_AGENT_MENTION_FOCUS_DEFERS = 20;
60
60
  var PACKAGE_NAME = "@atlaskit/editor-plugin-mentions";
61
- var PACKAGE_VERSION = "16.1.4";
61
+ var PACKAGE_VERSION = "16.1.5";
62
62
  var setProvider = function setProvider(provider) {
63
63
  return function (state, dispatch) {
64
64
  if (dispatch) {