@atlaskit/editor-plugin-mentions 14.2.3 → 14.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @atlaskit/editor-plugin-mentions
2
2
 
3
+ ## 14.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`dbf22f118dd9e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/dbf22f118dd9e) -
8
+ Rewrite Rovo Chat @-mention provider on a single Relay-native RovoChatMentionResource that fires
9
+ people + agents in parallel via Promise. People render as soon as URS resolves and agents pop in
10
+ below when ready. New aliased agentStudio_getAgents query consolidates 3 prefetch round trips into
11
+ 1, drops over-fetched fields, and uses real abort plumbing. Adds opt-in subscribeToItemsUpdates
12
+ multi-emit contract to the editor mention typeahead so progressive emissions are no longer dropped
13
+ after the first frame.
14
+
15
+ ### Patch Changes
16
+
17
+ - [`ddeae9a5f8bdf`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ddeae9a5f8bdf) -
18
+ Prevent remote collaborative agent mention insertions from publishing local agent mention
19
+ insertion state.
20
+ - Updated dependencies
21
+
22
+ ## 14.3.0
23
+
24
+ ### Minor Changes
25
+
26
+ - [`cf7797df5ff1e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/cf7797df5ff1e) -
27
+ [ux] Add a Labs lozenge to the agent mention typeahead section.
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies
32
+
3
33
  ## 14.2.3
4
34
 
5
35
  ### Patch Changes
@@ -39,7 +39,7 @@ var isAgentUserType = function isAgentUserType(userType) {
39
39
  var AI_STREAMING_TRANSFORMATION_META_KEY = 'isAIStreamingTransformation';
40
40
  var AGENT_MENTION_INACTIVITY_MS = 3000;
41
41
  var PACKAGE_NAME = "@atlaskit/editor-plugin-mentions";
42
- var PACKAGE_VERSION = "14.2.2";
42
+ var PACKAGE_VERSION = "14.3.0";
43
43
  var setProvider = function setProvider(provider) {
44
44
  return function (state, dispatch) {
45
45
  if (dispatch) {
@@ -355,8 +355,7 @@ function createMentionPlugin(_ref2) {
355
355
  }
356
356
  (_insm$session2 = _insm.insm.session) === null || _insm$session2 === void 0 || _insm$session2.endFeature('mentionDeletionDetection');
357
357
  }
358
- var isAIStreaming = Boolean(tr.getMeta(AI_STREAMING_TRANSFORMATION_META_KEY));
359
- if (tr.docChanged && !tr.getMeta('replaceDocument') && !isAIStreaming && isAgentMentionsExperimentEnabled) {
358
+ if (isAgentMentionsExperimentEnabled && isQualifyingLocalUserDocChange(tr)) {
360
359
  var _mentionSchema = newState.schema.nodes.mention;
361
360
  var newDocRanges = [];
362
361
  var oldDocRanges = [];
@@ -16,6 +16,7 @@ var _messages = require("@atlaskit/editor-common/messages");
16
16
  var _typeAhead = require("@atlaskit/editor-common/type-ahead");
17
17
  var _model = require("@atlaskit/editor-prosemirror/model");
18
18
  var _utils = require("@atlaskit/editor-prosemirror/utils");
19
+ var _lozenge = _interopRequireDefault(require("@atlaskit/lozenge"));
19
20
  var _item = require("@atlaskit/mention/item");
20
21
  var _resource = require("@atlaskit/mention/resource");
21
22
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
@@ -240,17 +241,77 @@ var buildNodesForTeamMention = function buildNodesForTeamMention(schema, selecte
240
241
  inlineNodes.push(closeBracketText);
241
242
  return _model.Fragment.fromArray(inlineNodes);
242
243
  };
243
- var createTypeAheadConfig = exports.createTypeAheadConfig = function createTypeAheadConfig(_ref6) {
244
- var sanitizePrivateContent = _ref6.sanitizePrivateContent,
245
- mentionInsertDisplayName = _ref6.mentionInsertDisplayName,
246
- fireEvent = _ref6.fireEvent,
247
- HighlightComponent = _ref6.HighlightComponent,
248
- api = _ref6.api,
249
- handleMentionsChanged = _ref6.handleMentionsChanged;
244
+ /**
245
+ * Shared mentions → typeahead-items transformer used by both the
246
+ * single-shot `getItems` Promise path and the multi-emit
247
+ * `subscribeToItemsUpdates` path. Factoring this out avoids any
248
+ * chance the two paths drift in subtle item-shape behaviour (invite
249
+ * item injection, analytics emission, no-results bookkeeping).
250
+ *
251
+ * NOTE: `firstQueryWithoutResults` is captured by closure in
252
+ * `createTypeAheadConfig` and intentionally mutated here as a
253
+ * side-effect — preserves the existing single-shot semantics.
254
+ */
255
+ var makeTransformMentionsToTypeAheadItems = function makeTransformMentionsToTypeAheadItems(_ref6) {
256
+ var fireEvent = _ref6.fireEvent,
257
+ getFirstQueryWithoutResults = _ref6.getFirstQueryWithoutResults,
258
+ setFirstQueryWithoutResults = _ref6.setFirstQueryWithoutResults;
259
+ return function (_ref7) {
260
+ var mentions = _ref7.mentions,
261
+ query = _ref7.query,
262
+ stats = _ref7.stats,
263
+ mentionProvider = _ref7.mentionProvider,
264
+ contextIdentifierProvider = _ref7.contextIdentifierProvider,
265
+ sessionId = _ref7.sessionId;
266
+ var mentionItems = mentions.map(function (mention) {
267
+ return memoizedToItem.call(mention);
268
+ });
269
+ buildAndSendElementsTypeAheadAnalytics(fireEvent)({
270
+ query: query,
271
+ mentions: mentions,
272
+ stats: stats
273
+ });
274
+ if (mentions.length === 0 && getFirstQueryWithoutResults() === null) {
275
+ setFirstQueryWithoutResults(query);
276
+ }
277
+ if (!mentionProvider.shouldEnableInvite || mentionItems.length > 2) {
278
+ return mentionItems;
279
+ }
280
+ var emailDomain = mentionProvider.userEmailDomain;
281
+ return withInviteItem({
282
+ mentionProvider: mentionProvider,
283
+ firstQueryWithoutResults: getFirstQueryWithoutResults() || '',
284
+ currentQuery: query,
285
+ onInviteItemMount: function onInviteItemMount() {
286
+ var _mentionProvider$getS2;
287
+ fireEvent((0, _analytics.buildTypeAheadInviteItemViewedPayload)(sessionId, contextIdentifierProvider, mentionProvider.userRole, (0, _platformFeatureFlags.fg)('jira_invites_auto_tag_new_user_in_mentions_fg') ? {
288
+ isInlineInviteMentionsEnabled: (_mentionProvider$getS2 = mentionProvider.getShouldEnableInlineInvite) === null || _mentionProvider$getS2 === void 0 ? void 0 : _mentionProvider$getS2.call(mentionProvider)
289
+ } : {}));
290
+ },
291
+ emailDomain: emailDomain
292
+ })(mentionItems);
293
+ };
294
+ };
295
+ var createTypeAheadConfig = exports.createTypeAheadConfig = function createTypeAheadConfig(_ref8) {
296
+ var sanitizePrivateContent = _ref8.sanitizePrivateContent,
297
+ mentionInsertDisplayName = _ref8.mentionInsertDisplayName,
298
+ fireEvent = _ref8.fireEvent,
299
+ HighlightComponent = _ref8.HighlightComponent,
300
+ api = _ref8.api,
301
+ handleMentionsChanged = _ref8.handleMentionsChanged;
250
302
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
251
303
  var sessionId = (0, _uuid.default)();
252
304
  var firstQueryWithoutResults = null;
253
305
  var subscriptionKeys = new Set();
306
+ var transformMentionsToTypeAheadItems = makeTransformMentionsToTypeAheadItems({
307
+ fireEvent: fireEvent,
308
+ getFirstQueryWithoutResults: function getFirstQueryWithoutResults() {
309
+ return firstQueryWithoutResults;
310
+ },
311
+ setFirstQueryWithoutResults: function setFirstQueryWithoutResults(query) {
312
+ firstQueryWithoutResults = query;
313
+ }
314
+ });
254
315
  var typeAhead = {
255
316
  id: _typeAhead.TypeAheadAvailableNodes.MENTION,
256
317
  trigger: '@',
@@ -264,17 +325,17 @@ var createTypeAheadConfig = exports.createTypeAheadConfig = function createTypeA
264
325
  }
265
326
  return null;
266
327
  },
267
- getItems: function getItems(_ref7) {
328
+ getItems: function getItems(_ref9) {
268
329
  var _api$contextIdentifie, _api$contextIdentifie2;
269
- var query = _ref7.query,
270
- editorState = _ref7.editorState;
330
+ var query = _ref9.query,
331
+ editorState = _ref9.editorState;
271
332
  var pluginState = (0, _utils2.getMentionPluginState)(editorState);
272
333
  if (!(pluginState !== null && pluginState !== void 0 && pluginState.mentionProvider)) {
273
334
  return Promise.resolve([]);
274
335
  }
275
336
  var mentionProvider = pluginState.mentionProvider;
276
- var _ref8 = (_api$contextIdentifie = api === null || api === void 0 || (_api$contextIdentifie2 = api.contextIdentifier) === null || _api$contextIdentifie2 === void 0 ? void 0 : _api$contextIdentifie2.sharedState.currentState()) !== null && _api$contextIdentifie !== void 0 ? _api$contextIdentifie : {},
277
- contextIdentifierProvider = _ref8.contextIdentifierProvider;
337
+ var _ref0 = (_api$contextIdentifie = api === null || api === void 0 || (_api$contextIdentifie2 = api.contextIdentifier) === null || _api$contextIdentifie2 === void 0 ? void 0 : _api$contextIdentifie2.sharedState.currentState()) !== null && _api$contextIdentifie !== void 0 ? _api$contextIdentifie : {},
338
+ contextIdentifierProvider = _ref0.contextIdentifierProvider;
278
339
  return new Promise(function (resolve, reject) {
279
340
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
280
341
  var key = "loadingMentionsForTypeAhead_".concat((0, _uuid.default)());
@@ -286,35 +347,15 @@ var createTypeAheadConfig = exports.createTypeAheadConfig = function createTypeA
286
347
  }
287
348
  mentionProvider.unsubscribe(key);
288
349
  subscriptionKeys.delete(key);
289
- var mentionItems = mentions.map(function (mention) {
290
- return memoizedToItem.call(mention);
291
- });
292
- buildAndSendElementsTypeAheadAnalytics(fireEvent)({
293
- query: query,
350
+ var items = transformMentionsToTypeAheadItems({
294
351
  mentions: mentions,
295
- stats: stats
352
+ query: query,
353
+ stats: stats,
354
+ mentionProvider: mentionProvider,
355
+ contextIdentifierProvider: contextIdentifierProvider,
356
+ sessionId: sessionId
296
357
  });
297
- if (mentions.length === 0 && firstQueryWithoutResults === null) {
298
- firstQueryWithoutResults = query;
299
- }
300
- if (!mentionProvider.shouldEnableInvite || mentionItems.length > 2) {
301
- resolve(mentionItems);
302
- } else {
303
- var emailDomain = mentionProvider.userEmailDomain;
304
- var items = withInviteItem({
305
- mentionProvider: mentionProvider,
306
- firstQueryWithoutResults: firstQueryWithoutResults || '',
307
- currentQuery: query,
308
- onInviteItemMount: function onInviteItemMount() {
309
- var _mentionProvider$getS2;
310
- fireEvent((0, _analytics.buildTypeAheadInviteItemViewedPayload)(sessionId, contextIdentifierProvider, mentionProvider.userRole, (0, _platformFeatureFlags.fg)('jira_invites_auto_tag_new_user_in_mentions_fg') ? {
311
- isInlineInviteMentionsEnabled: (_mentionProvider$getS2 = mentionProvider.getShouldEnableInlineInvite) === null || _mentionProvider$getS2 === void 0 ? void 0 : _mentionProvider$getS2.call(mentionProvider)
312
- } : {}));
313
- },
314
- emailDomain: emailDomain
315
- })(mentionItems);
316
- resolve(items);
317
- }
358
+ resolve(items);
318
359
  };
319
360
  subscriptionKeys.add(key);
320
361
  mentionProvider.subscribe(key, mentionsSubscribeCallback, function () {
@@ -329,8 +370,8 @@ var createTypeAheadConfig = exports.createTypeAheadConfig = function createTypeA
329
370
  }));
330
371
  });
331
372
  },
332
- getSections: function getSections(_ref9) {
333
- var intl = _ref9.intl;
373
+ getSections: function getSections(_ref1) {
374
+ var intl = _ref1.intl;
334
375
  return [_objectSpread({
335
376
  id: 'people',
336
377
  title: intl.formatMessage(_messages.mentionMessages.typeAheadSectionPeople),
@@ -343,7 +384,7 @@ var createTypeAheadConfig = exports.createTypeAheadConfig = function createTypeA
343
384
  showWhenQueryPresent: false,
344
385
  showWhenOnlySection: true
345
386
  }
346
- } : {}), _objectSpread({
387
+ } : {}), _objectSpread(_objectSpread({
347
388
  id: 'agents',
348
389
  title: intl.formatMessage(_messages.mentionMessages.typeAheadSectionAgents),
349
390
  filter: function filter(item) {
@@ -355,17 +396,21 @@ var createTypeAheadConfig = exports.createTypeAheadConfig = function createTypeA
355
396
  showWhenQueryPresent: false,
356
397
  showWhenOnlySection: true
357
398
  }
399
+ } : {}), (0, _expVal.expVal)('platform_editor_agent_mentions', 'isEnabled', false) ? {
400
+ lozenge: /*#__PURE__*/_react.default.createElement(_lozenge.default, {
401
+ appearance: "new"
402
+ }, intl.formatMessage(_messages.mentionMessages.typeAheadSectionAgentsLabs))
358
403
  } : {})];
359
404
  },
360
405
  onOpen: function onOpen() {
361
406
  firstQueryWithoutResults = null;
362
407
  },
363
- selectItem: function selectItem(state, item, insert, _ref0) {
408
+ selectItem: function selectItem(state, item, insert, _ref10) {
364
409
  var _api$contextIdentifie3, _api$contextIdentifie4;
365
- var mode = _ref0.mode,
366
- stats = _ref0.stats,
367
- query = _ref0.query,
368
- sourceListItem = _ref0.sourceListItem;
410
+ var mode = _ref10.mode,
411
+ stats = _ref10.stats,
412
+ query = _ref10.query,
413
+ sourceListItem = _ref10.sourceListItem;
369
414
  var schema = state.schema;
370
415
  var pluginState = (0, _utils2.getMentionPluginState)(state);
371
416
  var mentionProvider = pluginState.mentionProvider;
@@ -376,8 +421,8 @@ var createTypeAheadConfig = exports.createTypeAheadConfig = function createTypeA
376
421
  accessLevel = _item$mention.accessLevel,
377
422
  userType = _item$mention.userType,
378
423
  isXProductUser = _item$mention.isXProductUser;
379
- var _ref1 = (_api$contextIdentifie3 = api === null || api === void 0 || (_api$contextIdentifie4 = api.contextIdentifier) === null || _api$contextIdentifie4 === void 0 ? void 0 : _api$contextIdentifie4.sharedState.currentState()) !== null && _api$contextIdentifie3 !== void 0 ? _api$contextIdentifie3 : {},
380
- contextIdentifierProvider = _ref1.contextIdentifierProvider;
424
+ var _ref11 = (_api$contextIdentifie3 = api === null || api === void 0 || (_api$contextIdentifie4 = api.contextIdentifier) === null || _api$contextIdentifie4 === void 0 ? void 0 : _api$contextIdentifie4.sharedState.currentState()) !== null && _api$contextIdentifie3 !== void 0 ? _api$contextIdentifie3 : {},
425
+ contextIdentifierProvider = _ref11.contextIdentifierProvider;
381
426
  var mentionContext = _objectSpread(_objectSpread({}, contextIdentifierProvider), {}, {
382
427
  sessionId: sessionId
383
428
  });
@@ -490,11 +535,11 @@ var createTypeAheadConfig = exports.createTypeAheadConfig = function createTypeA
490
535
  }
491
536
  return tr;
492
537
  },
493
- dismiss: function dismiss(_ref10) {
494
- var editorState = _ref10.editorState,
495
- query = _ref10.query,
496
- stats = _ref10.stats,
497
- wasItemInserted = _ref10.wasItemInserted;
538
+ dismiss: function dismiss(_ref12) {
539
+ var editorState = _ref12.editorState,
540
+ query = _ref12.query,
541
+ stats = _ref12.stats,
542
+ wasItemInserted = _ref12.wasItemInserted;
498
543
  firstQueryWithoutResults = null;
499
544
  var pickerElapsedTime = stats.startedAt ? performance.now() - stats.startedAt : 0;
500
545
  if (!wasItemInserted) {
@@ -522,5 +567,122 @@ var createTypeAheadConfig = exports.createTypeAheadConfig = function createTypeA
522
567
  sessionId = (0, _uuid.default)();
523
568
  }
524
569
  };
570
+
571
+ /**
572
+ * Opt-in multi-emit path. Subscribes to the provider's stream of
573
+ * results for the lifetime of the type-ahead session — does NOT
574
+ * unsubscribe after the first emission. Lets providers like
575
+ * `RovoChatMentionResource` deliver people-first then merged
576
+ * people + agents to the dropdown without the typeahead dropping
577
+ * the second emission on the floor.
578
+ *
579
+ * Gated behind `rovo_chat_agent_selection` (the same gate that drives
580
+ * agent mentions in the Rovo chat input). The generic type-ahead hook
581
+ * opts a handler into streaming purely by the presence of this method,
582
+ * so we only attach it when the gate is on — otherwise the proven
583
+ * single-shot `getItems` path continues to drive every consumer.
584
+ */
585
+ var subscribeToItemsUpdates = function subscribeToItemsUpdates(_ref13) {
586
+ var _api$contextIdentifie5, _api$contextIdentifie6;
587
+ var query = _ref13.query,
588
+ editorState = _ref13.editorState;
589
+ var pluginState = (0, _utils2.getMentionPluginState)(editorState);
590
+ if (!(pluginState !== null && pluginState !== void 0 && pluginState.mentionProvider)) {
591
+ return {
592
+ initial: Promise.resolve([]),
593
+ subscribe: function subscribe() {
594
+ return function () {};
595
+ }
596
+ };
597
+ }
598
+ var mentionProvider = pluginState.mentionProvider;
599
+ var _ref14 = (_api$contextIdentifie5 = api === null || api === void 0 || (_api$contextIdentifie6 = api.contextIdentifier) === null || _api$contextIdentifie6 === void 0 ? void 0 : _api$contextIdentifie6.sharedState.currentState()) !== null && _api$contextIdentifie5 !== void 0 ? _api$contextIdentifie5 : {},
600
+ contextIdentifierProvider = _ref14.contextIdentifierProvider;
601
+
602
+ // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
603
+ var key = "loadingMentionsForTypeAhead_".concat((0, _uuid.default)());
604
+ var initialResolve = null;
605
+ var initialReject = null;
606
+ var initialResolved = false;
607
+ var updateCallback = null;
608
+ var unsubscribed = false;
609
+ var initial = new Promise(function (resolve, reject) {
610
+ initialResolve = resolve;
611
+ initialReject = reject;
612
+ });
613
+ var mentionsSubscribeCallback = function mentionsSubscribeCallback(mentions) {
614
+ var resultQuery = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
615
+ var stats = arguments.length > 2 ? arguments[2] : undefined;
616
+ // Drop emissions tagged with a query that has moved on.
617
+ // Mirrors the same guard in the single-shot `getItems`.
618
+ if (query !== resultQuery) {
619
+ return;
620
+ }
621
+ if (unsubscribed) {
622
+ return;
623
+ }
624
+ var items = transformMentionsToTypeAheadItems({
625
+ mentions: mentions,
626
+ query: query,
627
+ stats: stats,
628
+ mentionProvider: mentionProvider,
629
+ contextIdentifierProvider: contextIdentifierProvider,
630
+ sessionId: sessionId
631
+ });
632
+ if (!initialResolved) {
633
+ var _initialResolve;
634
+ initialResolved = true;
635
+ (_initialResolve = initialResolve) === null || _initialResolve === void 0 || _initialResolve(items);
636
+ } else {
637
+ var _updateCallback;
638
+ (_updateCallback = updateCallback) === null || _updateCallback === void 0 || _updateCallback(items);
639
+ }
640
+ };
641
+ subscriptionKeys.add(key);
642
+ mentionProvider.subscribe(key, mentionsSubscribeCallback, function () {
643
+ if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true)) {
644
+ mentionProvider.unsubscribe(key);
645
+ subscriptionKeys.delete(key);
646
+ if (!initialResolved) {
647
+ var _initialReject;
648
+ initialResolved = true;
649
+ (_initialReject = initialReject) === null || _initialReject === void 0 || _initialReject('FETCH_ERROR');
650
+ }
651
+ }
652
+ });
653
+ mentionProvider.filter(query || '', _objectSpread(_objectSpread({}, contextIdentifierProvider), {}, {
654
+ sessionId: sessionId
655
+ }));
656
+ return {
657
+ initial: initial,
658
+ subscribe: function subscribe(update) {
659
+ if (updateCallback) {
660
+ throw new Error('TypeAhead mention updates support only one subscriber');
661
+ }
662
+ updateCallback = update;
663
+ return function () {
664
+ unsubscribed = true;
665
+ updateCallback = null;
666
+ mentionProvider.unsubscribe(key);
667
+ subscriptionKeys.delete(key);
668
+ // If cleanup runs before the first emission, settle the initial
669
+ // promise so its `.then` chain (and the closures it holds) is
670
+ // released instead of pending forever.
671
+ if (!initialResolved) {
672
+ var _initialResolve2;
673
+ initialResolved = true;
674
+ (_initialResolve2 = initialResolve) === null || _initialResolve2 === void 0 || _initialResolve2([]);
675
+ }
676
+ };
677
+ }
678
+ };
679
+ };
680
+
681
+ // Presence of `subscribeToItemsUpdates` is how the type-ahead hook
682
+ // opts a handler into the multi-emit path, so only expose it when the
683
+ // agent-selection gate is on.
684
+ if ((0, _platformFeatureFlags.fg)('rovo_chat_agent_selection')) {
685
+ typeAhead.subscribeToItemsUpdates = subscribeToItemsUpdates;
686
+ }
525
687
  return typeAhead;
526
688
  };
@@ -24,7 +24,7 @@ const isAgentUserType = userType => {
24
24
  const AI_STREAMING_TRANSFORMATION_META_KEY = 'isAIStreamingTransformation';
25
25
  const AGENT_MENTION_INACTIVITY_MS = 3000;
26
26
  const PACKAGE_NAME = "@atlaskit/editor-plugin-mentions";
27
- const PACKAGE_VERSION = "14.2.2";
27
+ const PACKAGE_VERSION = "14.3.0";
28
28
  const setProvider = provider => (state, dispatch) => {
29
29
  if (dispatch) {
30
30
  dispatch(state.tr.setMeta(mentionPluginKey, {
@@ -333,8 +333,7 @@ export function createMentionPlugin({
333
333
  }
334
334
  (_insm$session2 = insm.session) === null || _insm$session2 === void 0 ? void 0 : _insm$session2.endFeature('mentionDeletionDetection');
335
335
  }
336
- const isAIStreaming = Boolean(tr.getMeta(AI_STREAMING_TRANSFORMATION_META_KEY));
337
- if (tr.docChanged && !tr.getMeta('replaceDocument') && !isAIStreaming && isAgentMentionsExperimentEnabled) {
336
+ if (isAgentMentionsExperimentEnabled && isQualifyingLocalUserDocChange(tr)) {
338
337
  const mentionSchema = newState.schema.nodes.mention;
339
338
  const newDocRanges = [];
340
339
  const oldDocRanges = [];
@@ -6,6 +6,7 @@ import { mentionMessages } from '@atlaskit/editor-common/messages';
6
6
  import { TypeAheadAvailableNodes } from '@atlaskit/editor-common/type-ahead';
7
7
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
8
8
  import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
9
+ import Lozenge from '@atlaskit/lozenge';
9
10
  import { MENTION_ITEM_HEIGHT, MENTION_ITEM_HEIGHT_REFRESHED, MentionItem } from '@atlaskit/mention/item';
10
11
  import { isResolvingMentionProvider } from '@atlaskit/mention/resource';
11
12
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -213,6 +214,57 @@ const buildNodesForTeamMention = (schema, selectedMention, mentionProvider, sani
213
214
  inlineNodes.push(closeBracketText);
214
215
  return Fragment.fromArray(inlineNodes);
215
216
  };
217
+ /**
218
+ * Shared mentions → typeahead-items transformer used by both the
219
+ * single-shot `getItems` Promise path and the multi-emit
220
+ * `subscribeToItemsUpdates` path. Factoring this out avoids any
221
+ * chance the two paths drift in subtle item-shape behaviour (invite
222
+ * item injection, analytics emission, no-results bookkeeping).
223
+ *
224
+ * NOTE: `firstQueryWithoutResults` is captured by closure in
225
+ * `createTypeAheadConfig` and intentionally mutated here as a
226
+ * side-effect — preserves the existing single-shot semantics.
227
+ */
228
+ const makeTransformMentionsToTypeAheadItems = ({
229
+ fireEvent,
230
+ getFirstQueryWithoutResults,
231
+ setFirstQueryWithoutResults
232
+ }) => {
233
+ return ({
234
+ mentions,
235
+ query,
236
+ stats,
237
+ mentionProvider,
238
+ contextIdentifierProvider,
239
+ sessionId
240
+ }) => {
241
+ const mentionItems = mentions.map(mention => memoizedToItem.call(mention));
242
+ buildAndSendElementsTypeAheadAnalytics(fireEvent)({
243
+ query,
244
+ mentions,
245
+ stats
246
+ });
247
+ if (mentions.length === 0 && getFirstQueryWithoutResults() === null) {
248
+ setFirstQueryWithoutResults(query);
249
+ }
250
+ if (!mentionProvider.shouldEnableInvite || mentionItems.length > 2) {
251
+ return mentionItems;
252
+ }
253
+ const emailDomain = mentionProvider.userEmailDomain;
254
+ return withInviteItem({
255
+ mentionProvider,
256
+ firstQueryWithoutResults: getFirstQueryWithoutResults() || '',
257
+ currentQuery: query,
258
+ onInviteItemMount: () => {
259
+ var _mentionProvider$getS2;
260
+ fireEvent(buildTypeAheadInviteItemViewedPayload(sessionId, contextIdentifierProvider, mentionProvider.userRole, fg('jira_invites_auto_tag_new_user_in_mentions_fg') ? {
261
+ isInlineInviteMentionsEnabled: (_mentionProvider$getS2 = mentionProvider.getShouldEnableInlineInvite) === null || _mentionProvider$getS2 === void 0 ? void 0 : _mentionProvider$getS2.call(mentionProvider)
262
+ } : {}));
263
+ },
264
+ emailDomain
265
+ })(mentionItems);
266
+ };
267
+ };
216
268
  export const createTypeAheadConfig = ({
217
269
  sanitizePrivateContent,
218
270
  mentionInsertDisplayName,
@@ -225,6 +277,13 @@ export const createTypeAheadConfig = ({
225
277
  let sessionId = uuid();
226
278
  let firstQueryWithoutResults = null;
227
279
  const subscriptionKeys = new Set();
280
+ const transformMentionsToTypeAheadItems = makeTransformMentionsToTypeAheadItems({
281
+ fireEvent,
282
+ getFirstQueryWithoutResults: () => firstQueryWithoutResults,
283
+ setFirstQueryWithoutResults: query => {
284
+ firstQueryWithoutResults = query;
285
+ }
286
+ });
228
287
  const typeAhead = {
229
288
  id: TypeAheadAvailableNodes.MENTION,
230
289
  trigger: '@',
@@ -262,33 +321,15 @@ export const createTypeAheadConfig = ({
262
321
  }
263
322
  mentionProvider.unsubscribe(key);
264
323
  subscriptionKeys.delete(key);
265
- const mentionItems = mentions.map(mention => memoizedToItem.call(mention));
266
- buildAndSendElementsTypeAheadAnalytics(fireEvent)({
267
- query,
324
+ const items = transformMentionsToTypeAheadItems({
268
325
  mentions,
269
- stats
326
+ query,
327
+ stats,
328
+ mentionProvider,
329
+ contextIdentifierProvider,
330
+ sessionId
270
331
  });
271
- if (mentions.length === 0 && firstQueryWithoutResults === null) {
272
- firstQueryWithoutResults = query;
273
- }
274
- if (!mentionProvider.shouldEnableInvite || mentionItems.length > 2) {
275
- resolve(mentionItems);
276
- } else {
277
- const emailDomain = mentionProvider.userEmailDomain;
278
- const items = withInviteItem({
279
- mentionProvider,
280
- firstQueryWithoutResults: firstQueryWithoutResults || '',
281
- currentQuery: query,
282
- onInviteItemMount: () => {
283
- var _mentionProvider$getS2;
284
- fireEvent(buildTypeAheadInviteItemViewedPayload(sessionId, contextIdentifierProvider, mentionProvider.userRole, fg('jira_invites_auto_tag_new_user_in_mentions_fg') ? {
285
- isInlineInviteMentionsEnabled: (_mentionProvider$getS2 = mentionProvider.getShouldEnableInlineInvite) === null || _mentionProvider$getS2 === void 0 ? void 0 : _mentionProvider$getS2.call(mentionProvider)
286
- } : {}));
287
- },
288
- emailDomain
289
- })(mentionItems);
290
- resolve(items);
291
- }
332
+ resolve(items);
292
333
  };
293
334
  subscriptionKeys.add(key);
294
335
  mentionProvider.subscribe(key, mentionsSubscribeCallback, () => {
@@ -328,6 +369,11 @@ export const createTypeAheadConfig = ({
328
369
  showWhenQueryPresent: false,
329
370
  showWhenOnlySection: true
330
371
  }
372
+ } : {}),
373
+ ...(expVal('platform_editor_agent_mentions', 'isEnabled', false) ? {
374
+ lozenge: /*#__PURE__*/React.createElement(Lozenge, {
375
+ appearance: "new"
376
+ }, intl.formatMessage(mentionMessages.typeAheadSectionAgentsLabs))
331
377
  } : {})
332
378
  }];
333
379
  },
@@ -498,5 +544,123 @@ export const createTypeAheadConfig = ({
498
544
  sessionId = uuid();
499
545
  }
500
546
  };
547
+
548
+ /**
549
+ * Opt-in multi-emit path. Subscribes to the provider's stream of
550
+ * results for the lifetime of the type-ahead session — does NOT
551
+ * unsubscribe after the first emission. Lets providers like
552
+ * `RovoChatMentionResource` deliver people-first then merged
553
+ * people + agents to the dropdown without the typeahead dropping
554
+ * the second emission on the floor.
555
+ *
556
+ * Gated behind `rovo_chat_agent_selection` (the same gate that drives
557
+ * agent mentions in the Rovo chat input). The generic type-ahead hook
558
+ * opts a handler into streaming purely by the presence of this method,
559
+ * so we only attach it when the gate is on — otherwise the proven
560
+ * single-shot `getItems` path continues to drive every consumer.
561
+ */
562
+ const subscribeToItemsUpdates = ({
563
+ query,
564
+ editorState
565
+ }) => {
566
+ var _api$contextIdentifie5, _api$contextIdentifie6;
567
+ const pluginState = getMentionPluginState(editorState);
568
+ if (!(pluginState !== null && pluginState !== void 0 && pluginState.mentionProvider)) {
569
+ return {
570
+ initial: Promise.resolve([]),
571
+ subscribe: () => () => {}
572
+ };
573
+ }
574
+ const {
575
+ mentionProvider
576
+ } = pluginState;
577
+ const {
578
+ contextIdentifierProvider
579
+ } = (_api$contextIdentifie5 = api === null || api === void 0 ? void 0 : (_api$contextIdentifie6 = api.contextIdentifier) === null || _api$contextIdentifie6 === void 0 ? void 0 : _api$contextIdentifie6.sharedState.currentState()) !== null && _api$contextIdentifie5 !== void 0 ? _api$contextIdentifie5 : {};
580
+
581
+ // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
582
+ const key = `loadingMentionsForTypeAhead_${uuid()}`;
583
+ let initialResolve = null;
584
+ let initialReject = null;
585
+ let initialResolved = false;
586
+ let updateCallback = null;
587
+ let unsubscribed = false;
588
+ const initial = new Promise((resolve, reject) => {
589
+ initialResolve = resolve;
590
+ initialReject = reject;
591
+ });
592
+ const mentionsSubscribeCallback = (mentions, resultQuery = '', stats) => {
593
+ // Drop emissions tagged with a query that has moved on.
594
+ // Mirrors the same guard in the single-shot `getItems`.
595
+ if (query !== resultQuery) {
596
+ return;
597
+ }
598
+ if (unsubscribed) {
599
+ return;
600
+ }
601
+ const items = transformMentionsToTypeAheadItems({
602
+ mentions,
603
+ query,
604
+ stats,
605
+ mentionProvider,
606
+ contextIdentifierProvider,
607
+ sessionId
608
+ });
609
+ if (!initialResolved) {
610
+ var _initialResolve;
611
+ initialResolved = true;
612
+ (_initialResolve = initialResolve) === null || _initialResolve === void 0 ? void 0 : _initialResolve(items);
613
+ } else {
614
+ var _updateCallback;
615
+ (_updateCallback = updateCallback) === null || _updateCallback === void 0 ? void 0 : _updateCallback(items);
616
+ }
617
+ };
618
+ subscriptionKeys.add(key);
619
+ mentionProvider.subscribe(key, mentionsSubscribeCallback, () => {
620
+ if (editorExperiment('platform_editor_offline_editing_web', true)) {
621
+ mentionProvider.unsubscribe(key);
622
+ subscriptionKeys.delete(key);
623
+ if (!initialResolved) {
624
+ var _initialReject;
625
+ initialResolved = true;
626
+ (_initialReject = initialReject) === null || _initialReject === void 0 ? void 0 : _initialReject('FETCH_ERROR');
627
+ }
628
+ }
629
+ });
630
+ mentionProvider.filter(query || '', {
631
+ ...contextIdentifierProvider,
632
+ sessionId
633
+ });
634
+ return {
635
+ initial,
636
+ subscribe: update => {
637
+ if (updateCallback) {
638
+ throw new Error('TypeAhead mention updates support only one subscriber');
639
+ }
640
+ updateCallback = update;
641
+ return () => {
642
+ unsubscribed = true;
643
+ updateCallback = null;
644
+ mentionProvider.unsubscribe(key);
645
+ subscriptionKeys.delete(key);
646
+ // If cleanup runs before the first emission, settle the initial
647
+ // promise so its `.then` chain (and the closures it holds) is
648
+ // released instead of pending forever.
649
+ if (!initialResolved) {
650
+ var _initialResolve2;
651
+ initialResolved = true;
652
+ (_initialResolve2 = initialResolve) === null || _initialResolve2 === void 0 ? void 0 : _initialResolve2([]);
653
+ }
654
+ };
655
+ }
656
+ };
657
+ };
658
+
659
+ // Presence of `subscribeToItemsUpdates` is how the type-ahead hook
660
+ // opts a handler into the multi-emit path, so only expose it when the
661
+ // agent-selection gate is on.
662
+ if (fg('rovo_chat_agent_selection')) {
663
+ typeAhead.subscribeToItemsUpdates = subscribeToItemsUpdates;
664
+ }
501
665
  return typeAhead;
502
666
  };
@@ -31,7 +31,7 @@ var isAgentUserType = function isAgentUserType(userType) {
31
31
  var AI_STREAMING_TRANSFORMATION_META_KEY = 'isAIStreamingTransformation';
32
32
  var AGENT_MENTION_INACTIVITY_MS = 3000;
33
33
  var PACKAGE_NAME = "@atlaskit/editor-plugin-mentions";
34
- var PACKAGE_VERSION = "14.2.2";
34
+ var PACKAGE_VERSION = "14.3.0";
35
35
  var setProvider = function setProvider(provider) {
36
36
  return function (state, dispatch) {
37
37
  if (dispatch) {
@@ -347,8 +347,7 @@ export function createMentionPlugin(_ref2) {
347
347
  }
348
348
  (_insm$session2 = insm.session) === null || _insm$session2 === void 0 || _insm$session2.endFeature('mentionDeletionDetection');
349
349
  }
350
- var isAIStreaming = Boolean(tr.getMeta(AI_STREAMING_TRANSFORMATION_META_KEY));
351
- if (tr.docChanged && !tr.getMeta('replaceDocument') && !isAIStreaming && isAgentMentionsExperimentEnabled) {
350
+ if (isAgentMentionsExperimentEnabled && isQualifyingLocalUserDocChange(tr)) {
352
351
  var _mentionSchema = newState.schema.nodes.mention;
353
352
  var newDocRanges = [];
354
353
  var oldDocRanges = [];
@@ -13,6 +13,7 @@ import { mentionMessages } from '@atlaskit/editor-common/messages';
13
13
  import { TypeAheadAvailableNodes } from '@atlaskit/editor-common/type-ahead';
14
14
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
15
15
  import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
16
+ import Lozenge from '@atlaskit/lozenge';
16
17
  import { MENTION_ITEM_HEIGHT, MENTION_ITEM_HEIGHT_REFRESHED, MentionItem } from '@atlaskit/mention/item';
17
18
  import { isResolvingMentionProvider } from '@atlaskit/mention/resource';
18
19
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -231,17 +232,77 @@ var buildNodesForTeamMention = function buildNodesForTeamMention(schema, selecte
231
232
  inlineNodes.push(closeBracketText);
232
233
  return Fragment.fromArray(inlineNodes);
233
234
  };
234
- export var createTypeAheadConfig = function createTypeAheadConfig(_ref6) {
235
- var sanitizePrivateContent = _ref6.sanitizePrivateContent,
236
- mentionInsertDisplayName = _ref6.mentionInsertDisplayName,
237
- fireEvent = _ref6.fireEvent,
238
- HighlightComponent = _ref6.HighlightComponent,
239
- api = _ref6.api,
240
- handleMentionsChanged = _ref6.handleMentionsChanged;
235
+ /**
236
+ * Shared mentions → typeahead-items transformer used by both the
237
+ * single-shot `getItems` Promise path and the multi-emit
238
+ * `subscribeToItemsUpdates` path. Factoring this out avoids any
239
+ * chance the two paths drift in subtle item-shape behaviour (invite
240
+ * item injection, analytics emission, no-results bookkeeping).
241
+ *
242
+ * NOTE: `firstQueryWithoutResults` is captured by closure in
243
+ * `createTypeAheadConfig` and intentionally mutated here as a
244
+ * side-effect — preserves the existing single-shot semantics.
245
+ */
246
+ var makeTransformMentionsToTypeAheadItems = function makeTransformMentionsToTypeAheadItems(_ref6) {
247
+ var fireEvent = _ref6.fireEvent,
248
+ getFirstQueryWithoutResults = _ref6.getFirstQueryWithoutResults,
249
+ setFirstQueryWithoutResults = _ref6.setFirstQueryWithoutResults;
250
+ return function (_ref7) {
251
+ var mentions = _ref7.mentions,
252
+ query = _ref7.query,
253
+ stats = _ref7.stats,
254
+ mentionProvider = _ref7.mentionProvider,
255
+ contextIdentifierProvider = _ref7.contextIdentifierProvider,
256
+ sessionId = _ref7.sessionId;
257
+ var mentionItems = mentions.map(function (mention) {
258
+ return memoizedToItem.call(mention);
259
+ });
260
+ buildAndSendElementsTypeAheadAnalytics(fireEvent)({
261
+ query: query,
262
+ mentions: mentions,
263
+ stats: stats
264
+ });
265
+ if (mentions.length === 0 && getFirstQueryWithoutResults() === null) {
266
+ setFirstQueryWithoutResults(query);
267
+ }
268
+ if (!mentionProvider.shouldEnableInvite || mentionItems.length > 2) {
269
+ return mentionItems;
270
+ }
271
+ var emailDomain = mentionProvider.userEmailDomain;
272
+ return withInviteItem({
273
+ mentionProvider: mentionProvider,
274
+ firstQueryWithoutResults: getFirstQueryWithoutResults() || '',
275
+ currentQuery: query,
276
+ onInviteItemMount: function onInviteItemMount() {
277
+ var _mentionProvider$getS2;
278
+ fireEvent(buildTypeAheadInviteItemViewedPayload(sessionId, contextIdentifierProvider, mentionProvider.userRole, fg('jira_invites_auto_tag_new_user_in_mentions_fg') ? {
279
+ isInlineInviteMentionsEnabled: (_mentionProvider$getS2 = mentionProvider.getShouldEnableInlineInvite) === null || _mentionProvider$getS2 === void 0 ? void 0 : _mentionProvider$getS2.call(mentionProvider)
280
+ } : {}));
281
+ },
282
+ emailDomain: emailDomain
283
+ })(mentionItems);
284
+ };
285
+ };
286
+ export var createTypeAheadConfig = function createTypeAheadConfig(_ref8) {
287
+ var sanitizePrivateContent = _ref8.sanitizePrivateContent,
288
+ mentionInsertDisplayName = _ref8.mentionInsertDisplayName,
289
+ fireEvent = _ref8.fireEvent,
290
+ HighlightComponent = _ref8.HighlightComponent,
291
+ api = _ref8.api,
292
+ handleMentionsChanged = _ref8.handleMentionsChanged;
241
293
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
242
294
  var sessionId = uuid();
243
295
  var firstQueryWithoutResults = null;
244
296
  var subscriptionKeys = new Set();
297
+ var transformMentionsToTypeAheadItems = makeTransformMentionsToTypeAheadItems({
298
+ fireEvent: fireEvent,
299
+ getFirstQueryWithoutResults: function getFirstQueryWithoutResults() {
300
+ return firstQueryWithoutResults;
301
+ },
302
+ setFirstQueryWithoutResults: function setFirstQueryWithoutResults(query) {
303
+ firstQueryWithoutResults = query;
304
+ }
305
+ });
245
306
  var typeAhead = {
246
307
  id: TypeAheadAvailableNodes.MENTION,
247
308
  trigger: '@',
@@ -255,17 +316,17 @@ export var createTypeAheadConfig = function createTypeAheadConfig(_ref6) {
255
316
  }
256
317
  return null;
257
318
  },
258
- getItems: function getItems(_ref7) {
319
+ getItems: function getItems(_ref9) {
259
320
  var _api$contextIdentifie, _api$contextIdentifie2;
260
- var query = _ref7.query,
261
- editorState = _ref7.editorState;
321
+ var query = _ref9.query,
322
+ editorState = _ref9.editorState;
262
323
  var pluginState = getMentionPluginState(editorState);
263
324
  if (!(pluginState !== null && pluginState !== void 0 && pluginState.mentionProvider)) {
264
325
  return Promise.resolve([]);
265
326
  }
266
327
  var mentionProvider = pluginState.mentionProvider;
267
- var _ref8 = (_api$contextIdentifie = api === null || api === void 0 || (_api$contextIdentifie2 = api.contextIdentifier) === null || _api$contextIdentifie2 === void 0 ? void 0 : _api$contextIdentifie2.sharedState.currentState()) !== null && _api$contextIdentifie !== void 0 ? _api$contextIdentifie : {},
268
- contextIdentifierProvider = _ref8.contextIdentifierProvider;
328
+ var _ref0 = (_api$contextIdentifie = api === null || api === void 0 || (_api$contextIdentifie2 = api.contextIdentifier) === null || _api$contextIdentifie2 === void 0 ? void 0 : _api$contextIdentifie2.sharedState.currentState()) !== null && _api$contextIdentifie !== void 0 ? _api$contextIdentifie : {},
329
+ contextIdentifierProvider = _ref0.contextIdentifierProvider;
269
330
  return new Promise(function (resolve, reject) {
270
331
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
271
332
  var key = "loadingMentionsForTypeAhead_".concat(uuid());
@@ -277,35 +338,15 @@ export var createTypeAheadConfig = function createTypeAheadConfig(_ref6) {
277
338
  }
278
339
  mentionProvider.unsubscribe(key);
279
340
  subscriptionKeys.delete(key);
280
- var mentionItems = mentions.map(function (mention) {
281
- return memoizedToItem.call(mention);
282
- });
283
- buildAndSendElementsTypeAheadAnalytics(fireEvent)({
284
- query: query,
341
+ var items = transformMentionsToTypeAheadItems({
285
342
  mentions: mentions,
286
- stats: stats
343
+ query: query,
344
+ stats: stats,
345
+ mentionProvider: mentionProvider,
346
+ contextIdentifierProvider: contextIdentifierProvider,
347
+ sessionId: sessionId
287
348
  });
288
- if (mentions.length === 0 && firstQueryWithoutResults === null) {
289
- firstQueryWithoutResults = query;
290
- }
291
- if (!mentionProvider.shouldEnableInvite || mentionItems.length > 2) {
292
- resolve(mentionItems);
293
- } else {
294
- var emailDomain = mentionProvider.userEmailDomain;
295
- var items = withInviteItem({
296
- mentionProvider: mentionProvider,
297
- firstQueryWithoutResults: firstQueryWithoutResults || '',
298
- currentQuery: query,
299
- onInviteItemMount: function onInviteItemMount() {
300
- var _mentionProvider$getS2;
301
- fireEvent(buildTypeAheadInviteItemViewedPayload(sessionId, contextIdentifierProvider, mentionProvider.userRole, fg('jira_invites_auto_tag_new_user_in_mentions_fg') ? {
302
- isInlineInviteMentionsEnabled: (_mentionProvider$getS2 = mentionProvider.getShouldEnableInlineInvite) === null || _mentionProvider$getS2 === void 0 ? void 0 : _mentionProvider$getS2.call(mentionProvider)
303
- } : {}));
304
- },
305
- emailDomain: emailDomain
306
- })(mentionItems);
307
- resolve(items);
308
- }
349
+ resolve(items);
309
350
  };
310
351
  subscriptionKeys.add(key);
311
352
  mentionProvider.subscribe(key, mentionsSubscribeCallback, function () {
@@ -320,8 +361,8 @@ export var createTypeAheadConfig = function createTypeAheadConfig(_ref6) {
320
361
  }));
321
362
  });
322
363
  },
323
- getSections: function getSections(_ref9) {
324
- var intl = _ref9.intl;
364
+ getSections: function getSections(_ref1) {
365
+ var intl = _ref1.intl;
325
366
  return [_objectSpread({
326
367
  id: 'people',
327
368
  title: intl.formatMessage(mentionMessages.typeAheadSectionPeople),
@@ -334,7 +375,7 @@ export var createTypeAheadConfig = function createTypeAheadConfig(_ref6) {
334
375
  showWhenQueryPresent: false,
335
376
  showWhenOnlySection: true
336
377
  }
337
- } : {}), _objectSpread({
378
+ } : {}), _objectSpread(_objectSpread({
338
379
  id: 'agents',
339
380
  title: intl.formatMessage(mentionMessages.typeAheadSectionAgents),
340
381
  filter: function filter(item) {
@@ -346,17 +387,21 @@ export var createTypeAheadConfig = function createTypeAheadConfig(_ref6) {
346
387
  showWhenQueryPresent: false,
347
388
  showWhenOnlySection: true
348
389
  }
390
+ } : {}), expVal('platform_editor_agent_mentions', 'isEnabled', false) ? {
391
+ lozenge: /*#__PURE__*/React.createElement(Lozenge, {
392
+ appearance: "new"
393
+ }, intl.formatMessage(mentionMessages.typeAheadSectionAgentsLabs))
349
394
  } : {})];
350
395
  },
351
396
  onOpen: function onOpen() {
352
397
  firstQueryWithoutResults = null;
353
398
  },
354
- selectItem: function selectItem(state, item, insert, _ref0) {
399
+ selectItem: function selectItem(state, item, insert, _ref10) {
355
400
  var _api$contextIdentifie3, _api$contextIdentifie4;
356
- var mode = _ref0.mode,
357
- stats = _ref0.stats,
358
- query = _ref0.query,
359
- sourceListItem = _ref0.sourceListItem;
401
+ var mode = _ref10.mode,
402
+ stats = _ref10.stats,
403
+ query = _ref10.query,
404
+ sourceListItem = _ref10.sourceListItem;
360
405
  var schema = state.schema;
361
406
  var pluginState = getMentionPluginState(state);
362
407
  var mentionProvider = pluginState.mentionProvider;
@@ -367,8 +412,8 @@ export var createTypeAheadConfig = function createTypeAheadConfig(_ref6) {
367
412
  accessLevel = _item$mention.accessLevel,
368
413
  userType = _item$mention.userType,
369
414
  isXProductUser = _item$mention.isXProductUser;
370
- var _ref1 = (_api$contextIdentifie3 = api === null || api === void 0 || (_api$contextIdentifie4 = api.contextIdentifier) === null || _api$contextIdentifie4 === void 0 ? void 0 : _api$contextIdentifie4.sharedState.currentState()) !== null && _api$contextIdentifie3 !== void 0 ? _api$contextIdentifie3 : {},
371
- contextIdentifierProvider = _ref1.contextIdentifierProvider;
415
+ var _ref11 = (_api$contextIdentifie3 = api === null || api === void 0 || (_api$contextIdentifie4 = api.contextIdentifier) === null || _api$contextIdentifie4 === void 0 ? void 0 : _api$contextIdentifie4.sharedState.currentState()) !== null && _api$contextIdentifie3 !== void 0 ? _api$contextIdentifie3 : {},
416
+ contextIdentifierProvider = _ref11.contextIdentifierProvider;
372
417
  var mentionContext = _objectSpread(_objectSpread({}, contextIdentifierProvider), {}, {
373
418
  sessionId: sessionId
374
419
  });
@@ -481,11 +526,11 @@ export var createTypeAheadConfig = function createTypeAheadConfig(_ref6) {
481
526
  }
482
527
  return tr;
483
528
  },
484
- dismiss: function dismiss(_ref10) {
485
- var editorState = _ref10.editorState,
486
- query = _ref10.query,
487
- stats = _ref10.stats,
488
- wasItemInserted = _ref10.wasItemInserted;
529
+ dismiss: function dismiss(_ref12) {
530
+ var editorState = _ref12.editorState,
531
+ query = _ref12.query,
532
+ stats = _ref12.stats,
533
+ wasItemInserted = _ref12.wasItemInserted;
489
534
  firstQueryWithoutResults = null;
490
535
  var pickerElapsedTime = stats.startedAt ? performance.now() - stats.startedAt : 0;
491
536
  if (!wasItemInserted) {
@@ -513,5 +558,122 @@ export var createTypeAheadConfig = function createTypeAheadConfig(_ref6) {
513
558
  sessionId = uuid();
514
559
  }
515
560
  };
561
+
562
+ /**
563
+ * Opt-in multi-emit path. Subscribes to the provider's stream of
564
+ * results for the lifetime of the type-ahead session — does NOT
565
+ * unsubscribe after the first emission. Lets providers like
566
+ * `RovoChatMentionResource` deliver people-first then merged
567
+ * people + agents to the dropdown without the typeahead dropping
568
+ * the second emission on the floor.
569
+ *
570
+ * Gated behind `rovo_chat_agent_selection` (the same gate that drives
571
+ * agent mentions in the Rovo chat input). The generic type-ahead hook
572
+ * opts a handler into streaming purely by the presence of this method,
573
+ * so we only attach it when the gate is on — otherwise the proven
574
+ * single-shot `getItems` path continues to drive every consumer.
575
+ */
576
+ var subscribeToItemsUpdates = function subscribeToItemsUpdates(_ref13) {
577
+ var _api$contextIdentifie5, _api$contextIdentifie6;
578
+ var query = _ref13.query,
579
+ editorState = _ref13.editorState;
580
+ var pluginState = getMentionPluginState(editorState);
581
+ if (!(pluginState !== null && pluginState !== void 0 && pluginState.mentionProvider)) {
582
+ return {
583
+ initial: Promise.resolve([]),
584
+ subscribe: function subscribe() {
585
+ return function () {};
586
+ }
587
+ };
588
+ }
589
+ var mentionProvider = pluginState.mentionProvider;
590
+ var _ref14 = (_api$contextIdentifie5 = api === null || api === void 0 || (_api$contextIdentifie6 = api.contextIdentifier) === null || _api$contextIdentifie6 === void 0 ? void 0 : _api$contextIdentifie6.sharedState.currentState()) !== null && _api$contextIdentifie5 !== void 0 ? _api$contextIdentifie5 : {},
591
+ contextIdentifierProvider = _ref14.contextIdentifierProvider;
592
+
593
+ // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
594
+ var key = "loadingMentionsForTypeAhead_".concat(uuid());
595
+ var initialResolve = null;
596
+ var initialReject = null;
597
+ var initialResolved = false;
598
+ var updateCallback = null;
599
+ var unsubscribed = false;
600
+ var initial = new Promise(function (resolve, reject) {
601
+ initialResolve = resolve;
602
+ initialReject = reject;
603
+ });
604
+ var mentionsSubscribeCallback = function mentionsSubscribeCallback(mentions) {
605
+ var resultQuery = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
606
+ var stats = arguments.length > 2 ? arguments[2] : undefined;
607
+ // Drop emissions tagged with a query that has moved on.
608
+ // Mirrors the same guard in the single-shot `getItems`.
609
+ if (query !== resultQuery) {
610
+ return;
611
+ }
612
+ if (unsubscribed) {
613
+ return;
614
+ }
615
+ var items = transformMentionsToTypeAheadItems({
616
+ mentions: mentions,
617
+ query: query,
618
+ stats: stats,
619
+ mentionProvider: mentionProvider,
620
+ contextIdentifierProvider: contextIdentifierProvider,
621
+ sessionId: sessionId
622
+ });
623
+ if (!initialResolved) {
624
+ var _initialResolve;
625
+ initialResolved = true;
626
+ (_initialResolve = initialResolve) === null || _initialResolve === void 0 || _initialResolve(items);
627
+ } else {
628
+ var _updateCallback;
629
+ (_updateCallback = updateCallback) === null || _updateCallback === void 0 || _updateCallback(items);
630
+ }
631
+ };
632
+ subscriptionKeys.add(key);
633
+ mentionProvider.subscribe(key, mentionsSubscribeCallback, function () {
634
+ if (editorExperiment('platform_editor_offline_editing_web', true)) {
635
+ mentionProvider.unsubscribe(key);
636
+ subscriptionKeys.delete(key);
637
+ if (!initialResolved) {
638
+ var _initialReject;
639
+ initialResolved = true;
640
+ (_initialReject = initialReject) === null || _initialReject === void 0 || _initialReject('FETCH_ERROR');
641
+ }
642
+ }
643
+ });
644
+ mentionProvider.filter(query || '', _objectSpread(_objectSpread({}, contextIdentifierProvider), {}, {
645
+ sessionId: sessionId
646
+ }));
647
+ return {
648
+ initial: initial,
649
+ subscribe: function subscribe(update) {
650
+ if (updateCallback) {
651
+ throw new Error('TypeAhead mention updates support only one subscriber');
652
+ }
653
+ updateCallback = update;
654
+ return function () {
655
+ unsubscribed = true;
656
+ updateCallback = null;
657
+ mentionProvider.unsubscribe(key);
658
+ subscriptionKeys.delete(key);
659
+ // If cleanup runs before the first emission, settle the initial
660
+ // promise so its `.then` chain (and the closures it holds) is
661
+ // released instead of pending forever.
662
+ if (!initialResolved) {
663
+ var _initialResolve2;
664
+ initialResolved = true;
665
+ (_initialResolve2 = initialResolve) === null || _initialResolve2 === void 0 || _initialResolve2([]);
666
+ }
667
+ };
668
+ }
669
+ };
670
+ };
671
+
672
+ // Presence of `subscribeToItemsUpdates` is how the type-ahead hook
673
+ // opts a handler into the multi-emit path, so only expose it when the
674
+ // agent-selection gate is on.
675
+ if (fg('rovo_chat_agent_selection')) {
676
+ typeAhead.subscribeToItemsUpdates = subscribeToItemsUpdates;
677
+ }
516
678
  return typeAhead;
517
679
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-mentions",
3
- "version": "14.2.3",
3
+ "version": "14.4.0",
4
4
  "description": "Mentions plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -27,12 +27,13 @@
27
27
  "@atlaskit/editor-plugin-base": "^13.0.0",
28
28
  "@atlaskit/editor-plugin-context-identifier": "^12.0.0",
29
29
  "@atlaskit/editor-plugin-selection": "^12.0.0",
30
- "@atlaskit/editor-plugin-type-ahead": "^12.0.0",
30
+ "@atlaskit/editor-plugin-type-ahead": "^12.1.0",
31
31
  "@atlaskit/editor-prosemirror": "^8.0.0",
32
32
  "@atlaskit/icon": "^36.0.0",
33
33
  "@atlaskit/insm": "^1.0.0",
34
34
  "@atlaskit/link": "^4.0.0",
35
- "@atlaskit/mention": "^27.1.0",
35
+ "@atlaskit/lozenge": "^14.0.0",
36
+ "@atlaskit/mention": "^27.2.0",
36
37
  "@atlaskit/platform-feature-flags": "^2.0.0",
37
38
  "@atlaskit/popper": "^8.0.0",
38
39
  "@atlaskit/portal": "^6.0.0",
@@ -40,10 +41,10 @@
40
41
  "@atlaskit/profilecard": "^26.1.0",
41
42
  "@atlaskit/teams-app-config": "^2.0.0",
42
43
  "@atlaskit/theme": "^26.0.0",
43
- "@atlaskit/tmp-editor-statsig": "^109.0.0",
44
- "@atlaskit/tokens": "^14.0.0",
44
+ "@atlaskit/tmp-editor-statsig": "^110.0.0",
45
+ "@atlaskit/tokens": "^15.0.0",
45
46
  "@atlaskit/tooltip": "^23.0.0",
46
- "@atlaskit/user-picker": "^13.1.0",
47
+ "@atlaskit/user-picker": "^13.2.0",
47
48
  "@babel/runtime": "^7.0.0",
48
49
  "@compiled/react": "^0.20.0",
49
50
  "bind-event-listener": "^3.0.0",
@@ -52,7 +53,7 @@
52
53
  "uuid": "^3.1.0"
53
54
  },
54
55
  "peerDependencies": {
55
- "@atlaskit/editor-common": "^116.6.0",
56
+ "@atlaskit/editor-common": "^116.11.0",
56
57
  "react": "^18.2.0",
57
58
  "react-dom": "^18.2.0",
58
59
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
@@ -61,7 +62,7 @@
61
62
  "@testing-library/react": "^16.3.0",
62
63
  "react": "^18.2.0",
63
64
  "react-dom": "^18.2.0",
64
- "react-intl": "^6.6.2",
65
+ "react-intl": "^7.0.0",
65
66
  "wait-for-expect": "^1.2.0"
66
67
  },
67
68
  "techstack": {
@@ -119,6 +120,9 @@
119
120
  },
120
121
  "platform_editor_agent_mentions": {
121
122
  "type": "boolean"
123
+ },
124
+ "rovo_chat_agent_selection": {
125
+ "type": "boolean"
122
126
  }
123
127
  }
124
128
  }