@atlaskit/editor-plugin-synced-block 8.3.1 → 8.3.3

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,32 @@
1
1
  # @atlaskit/editor-plugin-synced-block
2
2
 
3
+ ## 8.3.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`434b508cc2368`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/434b508cc2368) -
8
+ EDITOR-7104: Add `featured-section` placement to block menu selection extension API
9
+ - Add `featured-section` to `BlockMenuPlacement` type which registers a top-level section with a
10
+ separator
11
+ - Add `BLOCK_ACTIONS_TEMPLATE_SECTION` and `BLOCK_ACTIONS_FEATURED_EXTENSION_SECTION_KEYS`
12
+ constants to `editor-common`
13
+ - Render lozenge inline next to label text (not pushed to far right) for dropdown and nested
14
+ dropdown items
15
+ - Move "New" lozenge next to label for synced block dropdown items
16
+ - Block template/menu behaviour gated behind `platform_editor_block_menu_v2_patch_2`; synced-block
17
+ lozenge placement behaviour gated behind `platform_synced_block_patch_12`
18
+
19
+ - Updated dependencies
20
+
21
+ ## 8.3.2
22
+
23
+ ### Patch Changes
24
+
25
+ - [`13169b42740a8`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/13169b42740a8) -
26
+ Cache experiment value at plugin creation to avoid redundant Statsig SDK evaluations on every
27
+ editor transaction
28
+ - Updated dependencies
29
+
3
30
  ## 8.3.1
4
31
 
5
32
  ### Patch Changes
@@ -17,7 +17,7 @@ var _analytics = require("@atlaskit/editor-common/analytics");
17
17
  var _errorBoundary = require("@atlaskit/editor-common/error-boundary");
18
18
  var _reactNodeView = _interopRequireDefault(require("@atlaskit/editor-common/react-node-view"));
19
19
  var _syncBlock = require("@atlaskit/editor-common/sync-block");
20
- var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
20
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
21
21
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
22
22
  var _editorCommands = require("../editor-commands");
23
23
  var _SyncBlockRendererWrapper = require("../ui/SyncBlockRendererWrapper");
@@ -95,7 +95,10 @@ var SyncBlock = exports.SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
95
95
  if (!syncBlockStore) {
96
96
  return null;
97
97
  }
98
- var isPerfEnabled = (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true);
98
+
99
+ // Use expValEqualsNoExposure — the exposure is already fired once at plugin
100
+ // creation time in syncedBlockPlugin.tsx and main.ts createPlugin().
101
+ var isPerfEnabled = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('editor_synced_block_perf', 'isEnabled', true);
99
102
 
100
103
  // get document node from data provider
101
104
  return /*#__PURE__*/_react.default.createElement(_errorBoundary.ErrorBoundary, {
@@ -41,13 +41,13 @@ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol
41
41
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
42
42
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
43
43
  var syncedBlockPluginKey = exports.syncedBlockPluginKey = new _state.PluginKey('syncedBlockPlugin');
44
- var mapRetryCreationPosMap = function mapRetryCreationPosMap(oldMap, newRetryCreationPos, mapPos) {
44
+ var mapRetryCreationPosMap = function mapRetryCreationPosMap(oldMap, newRetryCreationPos, mapPos, isPerfExperimentOn) {
45
45
  var resourceId = newRetryCreationPos === null || newRetryCreationPos === void 0 ? void 0 : newRetryCreationPos.resourceId;
46
46
 
47
47
  // Fast path: no new entry and nothing to remap — return the same reference.
48
48
  // This is critical for PR-E (EDITOR-6929) which relies on reference equality
49
49
  // to short-circuit SharedStateAPI deep-equality checks.
50
- if ((0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true) && !resourceId && oldMap.size === 0) {
50
+ if (isPerfExperimentOn && !resourceId && oldMap.size === 0) {
51
51
  return oldMap;
52
52
  }
53
53
  var newMap = new Map(oldMap);
@@ -306,6 +306,12 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
306
306
  var _ref6 = options || {},
307
307
  _ref6$useLongPressSel = _ref6.useLongPressSelection,
308
308
  useLongPressSelection = _ref6$useLongPressSel === void 0 ? false : _ref6$useLongPressSel;
309
+
310
+ // Cache the experiment value once at plugin creation time.
311
+ // This fires the exposure event exactly once (correct per Exposure Events 101)
312
+ // and avoids ~10 redundant Statsig SDK evaluations per keystroke in hot paths
313
+ // (apply, filterTransaction, appendTransaction, decorations).
314
+ var isPerfExperimentOn = (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true);
309
315
  var ctx = new SyncedBlockPluginContext();
310
316
  var confirmationTransactionRef = ctx.confirmationTransactionRef;
311
317
  var unpublishedFlagShown = ctx.unpublishedFlagShown;
@@ -356,7 +362,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
356
362
  // synced blocks, we skip the eager fetch + cache walks. They will be
357
363
  // re-run lazily by `apply` the first time a synced block enters the
358
364
  // document (paste, collab insert, or programmatic insert).
359
- var docHasSyncedBlocks = (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true) ? (0, _hasSyncedBlocks.hasSyncedBlocks)(instance.doc) : true;
365
+ var docHasSyncedBlocks = isPerfExperimentOn ? (0, _hasSyncedBlocks.hasSyncedBlocks)(instance.doc) : true;
360
366
  if (docHasSyncedBlocks) {
361
367
  var syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock);
362
368
  syncBlockStore.referenceManager.fetchSyncBlocksData((0, _editorSyncedBlockProvider.convertPMNodesToSyncBlockNodes)(syncBlockNodes));
@@ -386,7 +392,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
386
392
  // When the perf gate is ON and the doc has synced blocks we do a
387
393
  // single traversal here; afterwards `apply()` will map or rebuild
388
394
  // only when a status signal changes.
389
- var initStatusDecorationSet = docHasSyncedBlocks && (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true) ? buildStatusDecorations(instance.doc, syncBlockStore, initIsOffline, initIsViewMode, initIsDragging) : _view.DecorationSet.empty;
395
+ var initStatusDecorationSet = docHasSyncedBlocks && isPerfExperimentOn ? buildStatusDecorations(instance.doc, syncBlockStore, initIsOffline, initIsViewMode, initIsDragging) : _view.DecorationSet.empty;
390
396
  return {
391
397
  selectionDecorationSet: (0, _selectionDecorations.calculateDecorations)(instance.doc, instance.selection, instance.schema),
392
398
  activeFlag: false,
@@ -403,7 +409,8 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
403
409
  apply: function apply(tr, currentPluginState, oldEditorState) {
404
410
  var _meta$activeFlag, _meta$bodiedSyncBlock;
405
411
  var meta = tr.getMeta(syncedBlockPluginKey);
406
- var isPerfExperimentOn = (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true);
412
+ // isPerfExperimentOn is cached at createPlugin() level — see above
413
+
407
414
  var activeFlag = currentPluginState.activeFlag,
408
415
  selectionDecorationSet = currentPluginState.selectionDecorationSet,
409
416
  bodiedSyncBlockDeletionStatus = currentPluginState.bodiedSyncBlockDeletionStatus,
@@ -481,7 +488,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
481
488
  }
482
489
  }
483
490
  var newPosEntry = meta === null || meta === void 0 ? void 0 : meta.retryCreationPos;
484
- var newRetryCreationPosMap = mapRetryCreationPosMap(retryCreationPosMap, newPosEntry, tr.mapping.map.bind(tr.mapping));
491
+ var newRetryCreationPosMap = mapRetryCreationPosMap(retryCreationPosMap, newPosEntry, tr.mapping.map.bind(tr.mapping), isPerfExperimentOn);
485
492
  var nextActiveFlag = (_meta$activeFlag = meta === null || meta === void 0 ? void 0 : meta.activeFlag) !== null && _meta$activeFlag !== void 0 ? _meta$activeFlag : activeFlag;
486
493
  var nextBodiedSyncBlockDeletionStatus = (_meta$bodiedSyncBlock = meta === null || meta === void 0 ? void 0 : meta.bodiedSyncBlockDeletionStatus) !== null && _meta$bodiedSyncBlock !== void 0 ? _meta$bodiedSyncBlock : bodiedSyncBlockDeletionStatus;
487
494
  var nextHasUnsavedBodiedSyncBlockChanges = syncBlockStore.sourceManager.hasUnsavedChanges();
@@ -555,7 +562,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
555
562
  // `apply()`. The `decorations` prop is now an O(1) merge of
556
563
  // the two cached sets — no `doc.descendants()` walk, no
557
564
  // shared-state reads.
558
- if ((0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
565
+ if (isPerfExperimentOn) {
559
566
  var _api$focus$sharedStat, _api$focus;
560
567
  if (!docHasSyncedBlocks) {
561
568
  return selectionDecorationSet;
@@ -679,7 +686,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
679
686
  // transaction does not insert one, all downstream filter logic is a
680
687
  // no-op. Avoid both the shared-state reads and the `trackSyncBlocks`
681
688
  // walks for the ~99.97% of pages that have no synced blocks.
682
- if ((0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
689
+ if (isPerfExperimentOn) {
683
690
  var pluginState = syncedBlockPluginKey.getState(state);
684
691
  if (pluginState && !pluginState.hasSyncedBlocks && !(0, _transactionInsertsSyncedBlock.transactionInsertsSyncedBlock)(tr)) {
685
692
  return true;
@@ -769,7 +776,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
769
776
  // synced block (and none of the dispatched transactions inserts one),
770
777
  // skip all downstream work. This is the hot path on the ~99.97% of
771
778
  // pages that don't use synced blocks (see EDITOR-6586).
772
- if ((0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
779
+ if (isPerfExperimentOn) {
773
780
  var oldPluginState = syncedBlockPluginKey.getState(oldState);
774
781
  var newPluginState = syncedBlockPluginKey.getState(newState);
775
782
  var hadOrHasSyncedBlocks = !!(oldPluginState !== null && oldPluginState !== void 0 && oldPluginState.hasSyncedBlocks) || !!(newPluginState !== null && newPluginState !== void 0 && newPluginState.hasSyncedBlocks);
@@ -23,6 +23,10 @@ var targetEl;
23
23
  var getMenuAndToolbarExperiencesPlugin = exports.getMenuAndToolbarExperiencesPlugin = function getMenuAndToolbarExperiencesPlugin(_ref) {
24
24
  var refs = _ref.refs,
25
25
  dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent;
26
+ // Cache the experiment value once at plugin creation time.
27
+ // This fires the exposure event exactly once (correct per Exposure Events 101)
28
+ // and avoids redundant Statsig SDK evaluations in the view() and update() hooks.
29
+ var isPerfExperimentOn = (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true);
26
30
  var popupsTargetEl;
27
31
  var editorViewRef = {
28
32
  current: undefined
@@ -185,13 +189,13 @@ var getMenuAndToolbarExperiencesPlugin = exports.getMenuAndToolbarExperiencesPlu
185
189
  unbindClickListener = unbinders.unbindClickListener;
186
190
  unbindKeydownListener = unbinders.unbindKeydownListener;
187
191
  };
188
- if ((_syncedBlockPluginKey = _main.syncedBlockPluginKey.getState(_view.state)) !== null && _syncedBlockPluginKey !== void 0 && _syncedBlockPluginKey.hasSyncedBlocks || !(0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
192
+ if ((_syncedBlockPluginKey = _main.syncedBlockPluginKey.getState(_view.state)) !== null && _syncedBlockPluginKey !== void 0 && _syncedBlockPluginKey.hasSyncedBlocks || !isPerfExperimentOn) {
189
193
  ensureListenersBound();
190
194
  }
191
195
  return {
192
196
  update: function update(view, prevState) {
193
197
  var _syncedBlockPluginKey2;
194
- if (!listenersBound && view.state.doc !== prevState.doc && (_syncedBlockPluginKey2 = _main.syncedBlockPluginKey.getState(view.state)) !== null && _syncedBlockPluginKey2 !== void 0 && _syncedBlockPluginKey2.hasSyncedBlocks && (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
198
+ if (!listenersBound && view.state.doc !== prevState.doc && (_syncedBlockPluginKey2 = _main.syncedBlockPluginKey.getState(view.state)) !== null && _syncedBlockPluginKey2 !== void 0 && _syncedBlockPluginKey2.hasSyncedBlocks && isPerfExperimentOn) {
195
199
  // Bind listeners now that synced blocks are present.
196
200
  ensureListenersBound();
197
201
  }
@@ -10,6 +10,7 @@ var _adfSchema = require("@atlaskit/adf-schema");
10
10
  var _hooks = require("@atlaskit/editor-common/hooks");
11
11
  var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
12
12
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
13
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
13
14
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
14
15
  var _editorActions = require("./editor-actions");
15
16
  var _editorCommands = require("./editor-commands");
@@ -38,7 +39,11 @@ var LazySyncedBlockUI = function LazySyncedBlockUI(_ref) {
38
39
  var _states$syncedBlockSt;
39
40
  return (_states$syncedBlockSt = states.syncedBlockState) === null || _states$syncedBlockSt === void 0 ? void 0 : _states$syncedBlockSt.hasSyncedBlocks;
40
41
  });
41
- if (!hasSyncBlocks && (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
42
+
43
+ // Use expValEqualsNoExposure here because the exposure is already fired
44
+ // once at plugin creation time (see syncedBlockPlugin below).
45
+ // This component re-renders on every transaction — avoid redundant SDK evaluations.
46
+ if (!hasSyncBlocks && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('editor_synced_block_perf', 'isEnabled', true)) {
42
47
  return null;
43
48
  }
44
49
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_SyncBlockRefresher.SyncBlockRefresher, {
@@ -175,7 +180,7 @@ var syncedBlockPlugin = exports.syncedBlockPlugin = function syncedBlockPlugin(_
175
180
 
176
181
  // --- EDITOR-6929 / PR-F: return a stable reference when all
177
182
  // fields are unchanged to prevent unnecessary React re-renders. ---
178
- if (cachedSharedState !== undefined && cachedSharedState.activeFlag === activeFlag && cachedSharedState.syncBlockStore === currentSyncBlockStore && cachedSharedState.bodiedSyncBlockDeletionStatus === bodiedSyncBlockDeletionStatus && cachedSharedState.retryCreationPosMap === retryCreationPosMap && cachedSharedState.hasSyncedBlocks === hasSyncedBlocks && cachedSharedState.hasUnsavedBodiedSyncBlockChanges === hasUnsavedBodiedSyncBlockChanges && (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
183
+ if (cachedSharedState !== undefined && cachedSharedState.activeFlag === activeFlag && cachedSharedState.syncBlockStore === currentSyncBlockStore && cachedSharedState.bodiedSyncBlockDeletionStatus === bodiedSyncBlockDeletionStatus && cachedSharedState.retryCreationPosMap === retryCreationPosMap && cachedSharedState.hasSyncedBlocks === hasSyncedBlocks && cachedSharedState.hasUnsavedBodiedSyncBlockChanges === hasUnsavedBodiedSyncBlockChanges && isPerfExperimentOn) {
179
184
  return cachedSharedState;
180
185
  }
181
186
  var nextSharedState = {
@@ -18,8 +18,14 @@ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
18
18
  var _utils = require("../pm-plugins/utils/utils");
19
19
  var _types = require("../types");
20
20
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
21
- var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref) {
22
- var api = _ref.api;
21
+ var SyncedBlockNewLozenge = function SyncedBlockNewLozenge(_ref) {
22
+ var label = _ref.label;
23
+ return /*#__PURE__*/_react.default.createElement(_lozenge.default, {
24
+ appearance: (0, _platformFeatureFlags.fg)('confluence_fronend_labels_categorization_migration') ? 'discovery' : 'new'
25
+ }, label);
26
+ };
27
+ var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref2) {
28
+ var api = _ref2.api;
23
29
  var _useIntl = (0, _reactIntl.useIntl)(),
24
30
  formatMessage = _useIntl.formatMessage;
25
31
  var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['selection', 'blockControls', 'connectivity'], function (states) {
@@ -48,6 +54,9 @@ var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref)
48
54
  }));
49
55
  };
50
56
  var isOffline = (0, _editorPluginConnectivity.isOfflineMode)(mode);
57
+ var lozenge = /*#__PURE__*/_react.default.createElement(SyncedBlockNewLozenge, {
58
+ label: formatMessage(_messages.blockMenuMessages.newLozenge)
59
+ });
51
60
  return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItem, {
52
61
  elemBefore: /*#__PURE__*/_react.default.createElement(_editorToolbar.SyncBlocksIcon, {
53
62
  label: "",
@@ -56,13 +65,12 @@ var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref)
56
65
  onClick: onClick,
57
66
  isDisabled: isOffline,
58
67
  testId: _types.SYNCED_BLOCK_BUTTON_TEST_ID.blockMenuCreate,
59
- elemAfter: /*#__PURE__*/_react.default.createElement(_lozenge.default, {
60
- appearance: (0, _platformFeatureFlags.fg)('confluence_fronend_labels_categorization_migration') ? 'discovery' : 'new'
61
- }, formatMessage(_messages.blockMenuMessages.newLozenge))
68
+ elemAfter: (0, _platformFeatureFlags.fg)('platform_synced_block_patch_12') ? undefined : lozenge,
69
+ elemAfterText: (0, _platformFeatureFlags.fg)('platform_synced_block_patch_12') ? lozenge : undefined
62
70
  }, formatMessage(_messages.blockMenuMessages.syncBlock));
63
71
  };
64
- var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref2) {
65
- var api = _ref2.api;
72
+ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref3) {
73
+ var api = _ref3.api;
66
74
  var _useIntl2 = (0, _reactIntl.useIntl)(),
67
75
  formatMessage = _useIntl2.formatMessage;
68
76
  var _useSharedPluginState2 = (0, _hooks.useSharedPluginStateWithSelector)(api, ['connectivity'], function (states) {
@@ -79,6 +87,9 @@ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref2) {
79
87
  closeMenu: true
80
88
  }));
81
89
  };
90
+ var lozenge = /*#__PURE__*/_react.default.createElement(SyncedBlockNewLozenge, {
91
+ label: formatMessage(_messages.blockMenuMessages.newLozenge)
92
+ });
82
93
  return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItem, {
83
94
  elemBefore: /*#__PURE__*/_react.default.createElement(_editorToolbar.SyncBlocksIcon, {
84
95
  label: "",
@@ -86,14 +97,13 @@ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref2) {
86
97
  }),
87
98
  onClick: onClick,
88
99
  isDisabled: (0, _editorPluginConnectivity.isOfflineMode)(mode),
89
- elemAfter: /*#__PURE__*/_react.default.createElement(_lozenge.default, {
90
- appearance: (0, _platformFeatureFlags.fg)('confluence_fronend_labels_categorization_migration') ? 'discovery' : 'new'
91
- }, formatMessage(_messages.blockMenuMessages.newLozenge))
100
+ elemAfter: (0, _platformFeatureFlags.fg)('platform_synced_block_patch_12') ? undefined : lozenge,
101
+ elemAfterText: (0, _platformFeatureFlags.fg)('platform_synced_block_patch_12') ? lozenge : undefined
92
102
  }, formatMessage(_messages.blockMenuMessages.copySyncedBlock));
93
103
  };
94
- var CreateOrCopySyncedBlockDropdownItem = exports.CreateOrCopySyncedBlockDropdownItem = function CreateOrCopySyncedBlockDropdownItem(_ref3) {
95
- var api = _ref3.api,
96
- enableSourceSyncedBlockCreation = _ref3.enableSourceSyncedBlockCreation;
104
+ var CreateOrCopySyncedBlockDropdownItem = exports.CreateOrCopySyncedBlockDropdownItem = function CreateOrCopySyncedBlockDropdownItem(_ref4) {
105
+ var api = _ref4.api,
106
+ enableSourceSyncedBlockCreation = _ref4.enableSourceSyncedBlockCreation;
97
107
  var _useSharedPluginState3 = (0, _hooks.useSharedPluginStateWithSelector)(api, ['blockControls'], function (states) {
98
108
  var _states$blockControls3, _states$blockControls4;
99
109
  return {
@@ -4,7 +4,7 @@ import { ACTION_SUBJECT } from '@atlaskit/editor-common/analytics';
4
4
  import { ErrorBoundary } from '@atlaskit/editor-common/error-boundary';
5
5
  import ReactNodeView from '@atlaskit/editor-common/react-node-view';
6
6
  import { SyncBlockSharedCssClassName, SyncBlockActionsProvider } from '@atlaskit/editor-common/sync-block';
7
- import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
7
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
8
8
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
9
9
  import { removeSyncedBlockAtPos } from '../editor-commands';
10
10
  import { SyncBlockRendererWrapper } from '../ui/SyncBlockRendererWrapper';
@@ -64,7 +64,10 @@ export class SyncBlock extends ReactNodeView {
64
64
  if (!syncBlockStore) {
65
65
  return null;
66
66
  }
67
- const isPerfEnabled = expValEquals('editor_synced_block_perf', 'isEnabled', true);
67
+
68
+ // Use expValEqualsNoExposure — the exposure is already fired once at plugin
69
+ // creation time in syncedBlockPlugin.tsx and main.ts createPlugin().
70
+ const isPerfEnabled = expValEqualsNoExposure('editor_synced_block_perf', 'isEnabled', true);
68
71
 
69
72
  // get document node from data provider
70
73
  return /*#__PURE__*/React.createElement(ErrorBoundary, {
@@ -26,13 +26,13 @@ import { hasEditInSyncBlock, trackSyncBlocks } from './utils/track-sync-blocks';
26
26
  import { transactionInsertsSyncedBlock } from './utils/transaction-inserts-synced-block';
27
27
  import { deferDispatch, wasExtensionInsertedInBodiedSyncBlock, sliceFullyContainsNode } from './utils/utils';
28
28
  export const syncedBlockPluginKey = new PluginKey('syncedBlockPlugin');
29
- const mapRetryCreationPosMap = (oldMap, newRetryCreationPos, mapPos) => {
29
+ const mapRetryCreationPosMap = (oldMap, newRetryCreationPos, mapPos, isPerfExperimentOn) => {
30
30
  const resourceId = newRetryCreationPos === null || newRetryCreationPos === void 0 ? void 0 : newRetryCreationPos.resourceId;
31
31
 
32
32
  // Fast path: no new entry and nothing to remap — return the same reference.
33
33
  // This is critical for PR-E (EDITOR-6929) which relies on reference equality
34
34
  // to short-circuit SharedStateAPI deep-equality checks.
35
- if (expValEquals('editor_synced_block_perf', 'isEnabled', true) && !resourceId && oldMap.size === 0) {
35
+ if (isPerfExperimentOn && !resourceId && oldMap.size === 0) {
36
36
  return oldMap;
37
37
  }
38
38
  const newMap = new Map(oldMap);
@@ -276,6 +276,12 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
276
276
  const {
277
277
  useLongPressSelection = false
278
278
  } = options || {};
279
+
280
+ // Cache the experiment value once at plugin creation time.
281
+ // This fires the exposure event exactly once (correct per Exposure Events 101)
282
+ // and avoids ~10 redundant Statsig SDK evaluations per keystroke in hot paths
283
+ // (apply, filterTransaction, appendTransaction, decorations).
284
+ const isPerfExperimentOn = expValEquals('editor_synced_block_perf', 'isEnabled', true);
279
285
  const ctx = new SyncedBlockPluginContext();
280
286
  const confirmationTransactionRef = ctx.confirmationTransactionRef;
281
287
  const unpublishedFlagShown = ctx.unpublishedFlagShown;
@@ -324,7 +330,7 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
324
330
  // synced blocks, we skip the eager fetch + cache walks. They will be
325
331
  // re-run lazily by `apply` the first time a synced block enters the
326
332
  // document (paste, collab insert, or programmatic insert).
327
- const docHasSyncedBlocks = expValEquals('editor_synced_block_perf', 'isEnabled', true) ? hasSyncedBlocks(instance.doc) : true;
333
+ const docHasSyncedBlocks = isPerfExperimentOn ? hasSyncedBlocks(instance.doc) : true;
328
334
  if (docHasSyncedBlocks) {
329
335
  const syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock);
330
336
  syncBlockStore.referenceManager.fetchSyncBlocksData(convertPMNodesToSyncBlockNodes(syncBlockNodes));
@@ -354,7 +360,7 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
354
360
  // When the perf gate is ON and the doc has synced blocks we do a
355
361
  // single traversal here; afterwards `apply()` will map or rebuild
356
362
  // only when a status signal changes.
357
- const initStatusDecorationSet = docHasSyncedBlocks && expValEquals('editor_synced_block_perf', 'isEnabled', true) ? buildStatusDecorations(instance.doc, syncBlockStore, initIsOffline, initIsViewMode, initIsDragging) : DecorationSet.empty;
363
+ const initStatusDecorationSet = docHasSyncedBlocks && isPerfExperimentOn ? buildStatusDecorations(instance.doc, syncBlockStore, initIsOffline, initIsViewMode, initIsDragging) : DecorationSet.empty;
358
364
  return {
359
365
  selectionDecorationSet: calculateDecorations(instance.doc, instance.selection, instance.schema),
360
366
  activeFlag: false,
@@ -371,7 +377,8 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
371
377
  apply: (tr, currentPluginState, oldEditorState) => {
372
378
  var _meta$activeFlag, _meta$bodiedSyncBlock;
373
379
  const meta = tr.getMeta(syncedBlockPluginKey);
374
- const isPerfExperimentOn = expValEquals('editor_synced_block_perf', 'isEnabled', true);
380
+ // isPerfExperimentOn is cached at createPlugin() level — see above
381
+
375
382
  const {
376
383
  activeFlag,
377
384
  selectionDecorationSet,
@@ -451,7 +458,7 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
451
458
  }
452
459
  }
453
460
  const newPosEntry = meta === null || meta === void 0 ? void 0 : meta.retryCreationPos;
454
- const newRetryCreationPosMap = mapRetryCreationPosMap(retryCreationPosMap, newPosEntry, tr.mapping.map.bind(tr.mapping));
461
+ const newRetryCreationPosMap = mapRetryCreationPosMap(retryCreationPosMap, newPosEntry, tr.mapping.map.bind(tr.mapping), isPerfExperimentOn);
455
462
  const nextActiveFlag = (_meta$activeFlag = meta === null || meta === void 0 ? void 0 : meta.activeFlag) !== null && _meta$activeFlag !== void 0 ? _meta$activeFlag : activeFlag;
456
463
  const nextBodiedSyncBlockDeletionStatus = (_meta$bodiedSyncBlock = meta === null || meta === void 0 ? void 0 : meta.bodiedSyncBlockDeletionStatus) !== null && _meta$bodiedSyncBlock !== void 0 ? _meta$bodiedSyncBlock : bodiedSyncBlockDeletionStatus;
457
464
  const nextHasUnsavedBodiedSyncBlockChanges = syncBlockStore.sourceManager.hasUnsavedChanges();
@@ -524,7 +531,7 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
524
531
  // `apply()`. The `decorations` prop is now an O(1) merge of
525
532
  // the two cached sets — no `doc.descendants()` walk, no
526
533
  // shared-state reads.
527
- if (expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
534
+ if (isPerfExperimentOn) {
528
535
  var _api$focus$sharedStat, _api$focus, _api$focus$sharedStat2, _api$focus$sharedStat3;
529
536
  if (!docHasSyncedBlocks) {
530
537
  return selectionDecorationSet;
@@ -652,7 +659,7 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
652
659
  // transaction does not insert one, all downstream filter logic is a
653
660
  // no-op. Avoid both the shared-state reads and the `trackSyncBlocks`
654
661
  // walks for the ~99.97% of pages that have no synced blocks.
655
- if (expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
662
+ if (isPerfExperimentOn) {
656
663
  const pluginState = syncedBlockPluginKey.getState(state);
657
664
  if (pluginState && !pluginState.hasSyncedBlocks && !transactionInsertsSyncedBlock(tr)) {
658
665
  return true;
@@ -735,7 +742,7 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
735
742
  // synced block (and none of the dispatched transactions inserts one),
736
743
  // skip all downstream work. This is the hot path on the ~99.97% of
737
744
  // pages that don't use synced blocks (see EDITOR-6586).
738
- if (expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
745
+ if (isPerfExperimentOn) {
739
746
  const oldPluginState = syncedBlockPluginKey.getState(oldState);
740
747
  const newPluginState = syncedBlockPluginKey.getState(newState);
741
748
  const hadOrHasSyncedBlocks = !!(oldPluginState !== null && oldPluginState !== void 0 && oldPluginState.hasSyncedBlocks) || !!(newPluginState !== null && newPluginState !== void 0 && newPluginState.hasSyncedBlocks);
@@ -16,6 +16,10 @@ export const getMenuAndToolbarExperiencesPlugin = ({
16
16
  refs,
17
17
  dispatchAnalyticsEvent
18
18
  }) => {
19
+ // Cache the experiment value once at plugin creation time.
20
+ // This fires the exposure event exactly once (correct per Exposure Events 101)
21
+ // and avoids redundant Statsig SDK evaluations in the view() and update() hooks.
22
+ const isPerfExperimentOn = expValEquals('editor_synced_block_perf', 'isEnabled', true);
19
23
  let popupsTargetEl;
20
24
  const editorViewRef = {
21
25
  current: undefined
@@ -178,13 +182,13 @@ export const getMenuAndToolbarExperiencesPlugin = ({
178
182
  unbindClickListener = unbinders.unbindClickListener;
179
183
  unbindKeydownListener = unbinders.unbindKeydownListener;
180
184
  };
181
- if ((_syncedBlockPluginKey = syncedBlockPluginKey.getState(view.state)) !== null && _syncedBlockPluginKey !== void 0 && _syncedBlockPluginKey.hasSyncedBlocks || !expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
185
+ if ((_syncedBlockPluginKey = syncedBlockPluginKey.getState(view.state)) !== null && _syncedBlockPluginKey !== void 0 && _syncedBlockPluginKey.hasSyncedBlocks || !isPerfExperimentOn) {
182
186
  ensureListenersBound();
183
187
  }
184
188
  return {
185
189
  update: (view, prevState) => {
186
190
  var _syncedBlockPluginKey2;
187
- if (!listenersBound && view.state.doc !== prevState.doc && (_syncedBlockPluginKey2 = syncedBlockPluginKey.getState(view.state)) !== null && _syncedBlockPluginKey2 !== void 0 && _syncedBlockPluginKey2.hasSyncedBlocks && expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
191
+ if (!listenersBound && view.state.doc !== prevState.doc && (_syncedBlockPluginKey2 = syncedBlockPluginKey.getState(view.state)) !== null && _syncedBlockPluginKey2 !== void 0 && _syncedBlockPluginKey2.hasSyncedBlocks && isPerfExperimentOn) {
188
192
  // Bind listeners now that synced blocks are present.
189
193
  ensureListenersBound();
190
194
  }
@@ -3,6 +3,7 @@ import { syncBlock, bodiedSyncBlock } from '@atlaskit/adf-schema';
3
3
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
4
4
  import { SyncBlockStoreManager } from '@atlaskit/editor-synced-block-provider';
5
5
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
6
7
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
7
8
  import { flushBodiedSyncBlocks, flushSyncBlocks, discardUnpublishedSyncBlocks } from './editor-actions';
8
9
  import { copySyncedBlockReferenceToClipboardEditorCommand, createSyncedBlock } from './editor-commands';
@@ -33,7 +34,11 @@ const LazySyncedBlockUI = ({
33
34
  var _states$syncedBlockSt;
34
35
  return (_states$syncedBlockSt = states.syncedBlockState) === null || _states$syncedBlockSt === void 0 ? void 0 : _states$syncedBlockSt.hasSyncedBlocks;
35
36
  });
36
- if (!hasSyncBlocks && expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
37
+
38
+ // Use expValEqualsNoExposure here because the exposure is already fired
39
+ // once at plugin creation time (see syncedBlockPlugin below).
40
+ // This component re-renders on every transaction — avoid redundant SDK evaluations.
41
+ if (!hasSyncBlocks && expValEqualsNoExposure('editor_synced_block_perf', 'isEnabled', true)) {
37
42
  return null;
38
43
  }
39
44
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SyncBlockRefresher, {
@@ -167,7 +172,7 @@ export const syncedBlockPlugin = ({
167
172
 
168
173
  // --- EDITOR-6929 / PR-F: return a stable reference when all
169
174
  // fields are unchanged to prevent unnecessary React re-renders. ---
170
- if (cachedSharedState !== undefined && cachedSharedState.activeFlag === activeFlag && cachedSharedState.syncBlockStore === currentSyncBlockStore && cachedSharedState.bodiedSyncBlockDeletionStatus === bodiedSyncBlockDeletionStatus && cachedSharedState.retryCreationPosMap === retryCreationPosMap && cachedSharedState.hasSyncedBlocks === hasSyncedBlocks && cachedSharedState.hasUnsavedBodiedSyncBlockChanges === hasUnsavedBodiedSyncBlockChanges && expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
175
+ if (cachedSharedState !== undefined && cachedSharedState.activeFlag === activeFlag && cachedSharedState.syncBlockStore === currentSyncBlockStore && cachedSharedState.bodiedSyncBlockDeletionStatus === bodiedSyncBlockDeletionStatus && cachedSharedState.retryCreationPosMap === retryCreationPosMap && cachedSharedState.hasSyncedBlocks === hasSyncedBlocks && cachedSharedState.hasUnsavedBodiedSyncBlockChanges === hasUnsavedBodiedSyncBlockChanges && isPerfExperimentOn) {
171
176
  return cachedSharedState;
172
177
  }
173
178
  const nextSharedState = {
@@ -9,6 +9,11 @@ import Lozenge from '@atlaskit/lozenge';
9
9
  import { fg } from '@atlaskit/platform-feature-flags';
10
10
  import { canBeConvertedToSyncBlock } from '../pm-plugins/utils/utils';
11
11
  import { SYNCED_BLOCK_BUTTON_TEST_ID } from '../types';
12
+ const SyncedBlockNewLozenge = ({
13
+ label
14
+ }) => /*#__PURE__*/React.createElement(Lozenge, {
15
+ appearance: fg('confluence_fronend_labels_categorization_migration') ? 'discovery' : 'new'
16
+ }, label);
12
17
  const CreateSyncedBlockDropdownItem = ({
13
18
  api
14
19
  }) => {
@@ -40,6 +45,9 @@ const CreateSyncedBlockDropdownItem = ({
40
45
  }));
41
46
  };
42
47
  const isOffline = isOfflineMode(mode);
48
+ const lozenge = /*#__PURE__*/React.createElement(SyncedBlockNewLozenge, {
49
+ label: formatMessage(blockMenuMessages.newLozenge)
50
+ });
43
51
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
44
52
  elemBefore: /*#__PURE__*/React.createElement(SyncBlocksIcon, {
45
53
  label: "",
@@ -48,9 +56,8 @@ const CreateSyncedBlockDropdownItem = ({
48
56
  onClick: onClick,
49
57
  isDisabled: isOffline,
50
58
  testId: SYNCED_BLOCK_BUTTON_TEST_ID.blockMenuCreate,
51
- elemAfter: /*#__PURE__*/React.createElement(Lozenge, {
52
- appearance: fg('confluence_fronend_labels_categorization_migration') ? 'discovery' : 'new'
53
- }, formatMessage(blockMenuMessages.newLozenge))
59
+ elemAfter: fg('platform_synced_block_patch_12') ? undefined : lozenge,
60
+ elemAfterText: fg('platform_synced_block_patch_12') ? lozenge : undefined
54
61
  }, formatMessage(blockMenuMessages.syncBlock));
55
62
  };
56
63
  const CopySyncedBlockDropdownItem = ({
@@ -74,6 +81,9 @@ const CopySyncedBlockDropdownItem = ({
74
81
  closeMenu: true
75
82
  }));
76
83
  };
84
+ const lozenge = /*#__PURE__*/React.createElement(SyncedBlockNewLozenge, {
85
+ label: formatMessage(blockMenuMessages.newLozenge)
86
+ });
77
87
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
78
88
  elemBefore: /*#__PURE__*/React.createElement(SyncBlocksIcon, {
79
89
  label: "",
@@ -81,9 +91,8 @@ const CopySyncedBlockDropdownItem = ({
81
91
  }),
82
92
  onClick: onClick,
83
93
  isDisabled: isOfflineMode(mode),
84
- elemAfter: /*#__PURE__*/React.createElement(Lozenge, {
85
- appearance: fg('confluence_fronend_labels_categorization_migration') ? 'discovery' : 'new'
86
- }, formatMessage(blockMenuMessages.newLozenge))
94
+ elemAfter: fg('platform_synced_block_patch_12') ? undefined : lozenge,
95
+ elemAfterText: fg('platform_synced_block_patch_12') ? lozenge : undefined
87
96
  }, formatMessage(blockMenuMessages.copySyncedBlock));
88
97
  };
89
98
  export const CreateOrCopySyncedBlockDropdownItem = ({
@@ -13,7 +13,7 @@ import { ACTION_SUBJECT } from '@atlaskit/editor-common/analytics';
13
13
  import { ErrorBoundary } from '@atlaskit/editor-common/error-boundary';
14
14
  import ReactNodeView from '@atlaskit/editor-common/react-node-view';
15
15
  import { SyncBlockSharedCssClassName, SyncBlockActionsProvider } from '@atlaskit/editor-common/sync-block';
16
- import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
16
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
17
17
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
18
18
  import { removeSyncedBlockAtPos } from '../editor-commands';
19
19
  import { SyncBlockRendererWrapper } from '../ui/SyncBlockRendererWrapper';
@@ -88,7 +88,10 @@ export var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
88
88
  if (!syncBlockStore) {
89
89
  return null;
90
90
  }
91
- var isPerfEnabled = expValEquals('editor_synced_block_perf', 'isEnabled', true);
91
+
92
+ // Use expValEqualsNoExposure — the exposure is already fired once at plugin
93
+ // creation time in syncedBlockPlugin.tsx and main.ts createPlugin().
94
+ var isPerfEnabled = expValEqualsNoExposure('editor_synced_block_perf', 'isEnabled', true);
92
95
 
93
96
  // get document node from data provider
94
97
  return /*#__PURE__*/React.createElement(ErrorBoundary, {
@@ -34,13 +34,13 @@ import { hasEditInSyncBlock, trackSyncBlocks } from './utils/track-sync-blocks';
34
34
  import { transactionInsertsSyncedBlock } from './utils/transaction-inserts-synced-block';
35
35
  import { deferDispatch, wasExtensionInsertedInBodiedSyncBlock, sliceFullyContainsNode } from './utils/utils';
36
36
  export var syncedBlockPluginKey = new PluginKey('syncedBlockPlugin');
37
- var mapRetryCreationPosMap = function mapRetryCreationPosMap(oldMap, newRetryCreationPos, mapPos) {
37
+ var mapRetryCreationPosMap = function mapRetryCreationPosMap(oldMap, newRetryCreationPos, mapPos, isPerfExperimentOn) {
38
38
  var resourceId = newRetryCreationPos === null || newRetryCreationPos === void 0 ? void 0 : newRetryCreationPos.resourceId;
39
39
 
40
40
  // Fast path: no new entry and nothing to remap — return the same reference.
41
41
  // This is critical for PR-E (EDITOR-6929) which relies on reference equality
42
42
  // to short-circuit SharedStateAPI deep-equality checks.
43
- if (expValEquals('editor_synced_block_perf', 'isEnabled', true) && !resourceId && oldMap.size === 0) {
43
+ if (isPerfExperimentOn && !resourceId && oldMap.size === 0) {
44
44
  return oldMap;
45
45
  }
46
46
  var newMap = new Map(oldMap);
@@ -299,6 +299,12 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
299
299
  var _ref6 = options || {},
300
300
  _ref6$useLongPressSel = _ref6.useLongPressSelection,
301
301
  useLongPressSelection = _ref6$useLongPressSel === void 0 ? false : _ref6$useLongPressSel;
302
+
303
+ // Cache the experiment value once at plugin creation time.
304
+ // This fires the exposure event exactly once (correct per Exposure Events 101)
305
+ // and avoids ~10 redundant Statsig SDK evaluations per keystroke in hot paths
306
+ // (apply, filterTransaction, appendTransaction, decorations).
307
+ var isPerfExperimentOn = expValEquals('editor_synced_block_perf', 'isEnabled', true);
302
308
  var ctx = new SyncedBlockPluginContext();
303
309
  var confirmationTransactionRef = ctx.confirmationTransactionRef;
304
310
  var unpublishedFlagShown = ctx.unpublishedFlagShown;
@@ -349,7 +355,7 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
349
355
  // synced blocks, we skip the eager fetch + cache walks. They will be
350
356
  // re-run lazily by `apply` the first time a synced block enters the
351
357
  // document (paste, collab insert, or programmatic insert).
352
- var docHasSyncedBlocks = expValEquals('editor_synced_block_perf', 'isEnabled', true) ? hasSyncedBlocks(instance.doc) : true;
358
+ var docHasSyncedBlocks = isPerfExperimentOn ? hasSyncedBlocks(instance.doc) : true;
353
359
  if (docHasSyncedBlocks) {
354
360
  var syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock);
355
361
  syncBlockStore.referenceManager.fetchSyncBlocksData(convertPMNodesToSyncBlockNodes(syncBlockNodes));
@@ -379,7 +385,7 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
379
385
  // When the perf gate is ON and the doc has synced blocks we do a
380
386
  // single traversal here; afterwards `apply()` will map or rebuild
381
387
  // only when a status signal changes.
382
- var initStatusDecorationSet = docHasSyncedBlocks && expValEquals('editor_synced_block_perf', 'isEnabled', true) ? buildStatusDecorations(instance.doc, syncBlockStore, initIsOffline, initIsViewMode, initIsDragging) : DecorationSet.empty;
388
+ var initStatusDecorationSet = docHasSyncedBlocks && isPerfExperimentOn ? buildStatusDecorations(instance.doc, syncBlockStore, initIsOffline, initIsViewMode, initIsDragging) : DecorationSet.empty;
383
389
  return {
384
390
  selectionDecorationSet: calculateDecorations(instance.doc, instance.selection, instance.schema),
385
391
  activeFlag: false,
@@ -396,7 +402,8 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
396
402
  apply: function apply(tr, currentPluginState, oldEditorState) {
397
403
  var _meta$activeFlag, _meta$bodiedSyncBlock;
398
404
  var meta = tr.getMeta(syncedBlockPluginKey);
399
- var isPerfExperimentOn = expValEquals('editor_synced_block_perf', 'isEnabled', true);
405
+ // isPerfExperimentOn is cached at createPlugin() level — see above
406
+
400
407
  var activeFlag = currentPluginState.activeFlag,
401
408
  selectionDecorationSet = currentPluginState.selectionDecorationSet,
402
409
  bodiedSyncBlockDeletionStatus = currentPluginState.bodiedSyncBlockDeletionStatus,
@@ -474,7 +481,7 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
474
481
  }
475
482
  }
476
483
  var newPosEntry = meta === null || meta === void 0 ? void 0 : meta.retryCreationPos;
477
- var newRetryCreationPosMap = mapRetryCreationPosMap(retryCreationPosMap, newPosEntry, tr.mapping.map.bind(tr.mapping));
484
+ var newRetryCreationPosMap = mapRetryCreationPosMap(retryCreationPosMap, newPosEntry, tr.mapping.map.bind(tr.mapping), isPerfExperimentOn);
478
485
  var nextActiveFlag = (_meta$activeFlag = meta === null || meta === void 0 ? void 0 : meta.activeFlag) !== null && _meta$activeFlag !== void 0 ? _meta$activeFlag : activeFlag;
479
486
  var nextBodiedSyncBlockDeletionStatus = (_meta$bodiedSyncBlock = meta === null || meta === void 0 ? void 0 : meta.bodiedSyncBlockDeletionStatus) !== null && _meta$bodiedSyncBlock !== void 0 ? _meta$bodiedSyncBlock : bodiedSyncBlockDeletionStatus;
480
487
  var nextHasUnsavedBodiedSyncBlockChanges = syncBlockStore.sourceManager.hasUnsavedChanges();
@@ -548,7 +555,7 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
548
555
  // `apply()`. The `decorations` prop is now an O(1) merge of
549
556
  // the two cached sets — no `doc.descendants()` walk, no
550
557
  // shared-state reads.
551
- if (expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
558
+ if (isPerfExperimentOn) {
552
559
  var _api$focus$sharedStat, _api$focus;
553
560
  if (!docHasSyncedBlocks) {
554
561
  return selectionDecorationSet;
@@ -672,7 +679,7 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
672
679
  // transaction does not insert one, all downstream filter logic is a
673
680
  // no-op. Avoid both the shared-state reads and the `trackSyncBlocks`
674
681
  // walks for the ~99.97% of pages that have no synced blocks.
675
- if (expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
682
+ if (isPerfExperimentOn) {
676
683
  var pluginState = syncedBlockPluginKey.getState(state);
677
684
  if (pluginState && !pluginState.hasSyncedBlocks && !transactionInsertsSyncedBlock(tr)) {
678
685
  return true;
@@ -762,7 +769,7 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
762
769
  // synced block (and none of the dispatched transactions inserts one),
763
770
  // skip all downstream work. This is the hot path on the ~99.97% of
764
771
  // pages that don't use synced blocks (see EDITOR-6586).
765
- if (expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
772
+ if (isPerfExperimentOn) {
766
773
  var oldPluginState = syncedBlockPluginKey.getState(oldState);
767
774
  var newPluginState = syncedBlockPluginKey.getState(newState);
768
775
  var hadOrHasSyncedBlocks = !!(oldPluginState !== null && oldPluginState !== void 0 && oldPluginState.hasSyncedBlocks) || !!(newPluginState !== null && newPluginState !== void 0 && newPluginState.hasSyncedBlocks);
@@ -16,6 +16,10 @@ var targetEl;
16
16
  export var getMenuAndToolbarExperiencesPlugin = function getMenuAndToolbarExperiencesPlugin(_ref) {
17
17
  var refs = _ref.refs,
18
18
  dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent;
19
+ // Cache the experiment value once at plugin creation time.
20
+ // This fires the exposure event exactly once (correct per Exposure Events 101)
21
+ // and avoids redundant Statsig SDK evaluations in the view() and update() hooks.
22
+ var isPerfExperimentOn = expValEquals('editor_synced_block_perf', 'isEnabled', true);
19
23
  var popupsTargetEl;
20
24
  var editorViewRef = {
21
25
  current: undefined
@@ -178,13 +182,13 @@ export var getMenuAndToolbarExperiencesPlugin = function getMenuAndToolbarExperi
178
182
  unbindClickListener = unbinders.unbindClickListener;
179
183
  unbindKeydownListener = unbinders.unbindKeydownListener;
180
184
  };
181
- if ((_syncedBlockPluginKey = syncedBlockPluginKey.getState(_view.state)) !== null && _syncedBlockPluginKey !== void 0 && _syncedBlockPluginKey.hasSyncedBlocks || !expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
185
+ if ((_syncedBlockPluginKey = syncedBlockPluginKey.getState(_view.state)) !== null && _syncedBlockPluginKey !== void 0 && _syncedBlockPluginKey.hasSyncedBlocks || !isPerfExperimentOn) {
182
186
  ensureListenersBound();
183
187
  }
184
188
  return {
185
189
  update: function update(view, prevState) {
186
190
  var _syncedBlockPluginKey2;
187
- if (!listenersBound && view.state.doc !== prevState.doc && (_syncedBlockPluginKey2 = syncedBlockPluginKey.getState(view.state)) !== null && _syncedBlockPluginKey2 !== void 0 && _syncedBlockPluginKey2.hasSyncedBlocks && expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
191
+ if (!listenersBound && view.state.doc !== prevState.doc && (_syncedBlockPluginKey2 = syncedBlockPluginKey.getState(view.state)) !== null && _syncedBlockPluginKey2 !== void 0 && _syncedBlockPluginKey2.hasSyncedBlocks && isPerfExperimentOn) {
188
192
  // Bind listeners now that synced blocks are present.
189
193
  ensureListenersBound();
190
194
  }
@@ -3,6 +3,7 @@ import { syncBlock, bodiedSyncBlock } from '@atlaskit/adf-schema';
3
3
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
4
4
  import { SyncBlockStoreManager } from '@atlaskit/editor-synced-block-provider';
5
5
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
6
7
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
7
8
  import { flushBodiedSyncBlocks as _flushBodiedSyncBlocks, flushSyncBlocks, discardUnpublishedSyncBlocks as _discardUnpublishedSyncBlocks } from './editor-actions';
8
9
  import { copySyncedBlockReferenceToClipboardEditorCommand, createSyncedBlock } from './editor-commands';
@@ -32,7 +33,11 @@ var LazySyncedBlockUI = function LazySyncedBlockUI(_ref) {
32
33
  var _states$syncedBlockSt;
33
34
  return (_states$syncedBlockSt = states.syncedBlockState) === null || _states$syncedBlockSt === void 0 ? void 0 : _states$syncedBlockSt.hasSyncedBlocks;
34
35
  });
35
- if (!hasSyncBlocks && expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
36
+
37
+ // Use expValEqualsNoExposure here because the exposure is already fired
38
+ // once at plugin creation time (see syncedBlockPlugin below).
39
+ // This component re-renders on every transaction — avoid redundant SDK evaluations.
40
+ if (!hasSyncBlocks && expValEqualsNoExposure('editor_synced_block_perf', 'isEnabled', true)) {
36
41
  return null;
37
42
  }
38
43
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SyncBlockRefresher, {
@@ -169,7 +174,7 @@ export var syncedBlockPlugin = function syncedBlockPlugin(_ref2) {
169
174
 
170
175
  // --- EDITOR-6929 / PR-F: return a stable reference when all
171
176
  // fields are unchanged to prevent unnecessary React re-renders. ---
172
- if (cachedSharedState !== undefined && cachedSharedState.activeFlag === activeFlag && cachedSharedState.syncBlockStore === currentSyncBlockStore && cachedSharedState.bodiedSyncBlockDeletionStatus === bodiedSyncBlockDeletionStatus && cachedSharedState.retryCreationPosMap === retryCreationPosMap && cachedSharedState.hasSyncedBlocks === hasSyncedBlocks && cachedSharedState.hasUnsavedBodiedSyncBlockChanges === hasUnsavedBodiedSyncBlockChanges && expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
177
+ if (cachedSharedState !== undefined && cachedSharedState.activeFlag === activeFlag && cachedSharedState.syncBlockStore === currentSyncBlockStore && cachedSharedState.bodiedSyncBlockDeletionStatus === bodiedSyncBlockDeletionStatus && cachedSharedState.retryCreationPosMap === retryCreationPosMap && cachedSharedState.hasSyncedBlocks === hasSyncedBlocks && cachedSharedState.hasUnsavedBodiedSyncBlockChanges === hasUnsavedBodiedSyncBlockChanges && isPerfExperimentOn) {
173
178
  return cachedSharedState;
174
179
  }
175
180
  var nextSharedState = {
@@ -9,8 +9,14 @@ import Lozenge from '@atlaskit/lozenge';
9
9
  import { fg } from '@atlaskit/platform-feature-flags';
10
10
  import { canBeConvertedToSyncBlock } from '../pm-plugins/utils/utils';
11
11
  import { SYNCED_BLOCK_BUTTON_TEST_ID } from '../types';
12
- var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref) {
13
- var api = _ref.api;
12
+ var SyncedBlockNewLozenge = function SyncedBlockNewLozenge(_ref) {
13
+ var label = _ref.label;
14
+ return /*#__PURE__*/React.createElement(Lozenge, {
15
+ appearance: fg('confluence_fronend_labels_categorization_migration') ? 'discovery' : 'new'
16
+ }, label);
17
+ };
18
+ var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref2) {
19
+ var api = _ref2.api;
14
20
  var _useIntl = useIntl(),
15
21
  formatMessage = _useIntl.formatMessage;
16
22
  var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['selection', 'blockControls', 'connectivity'], function (states) {
@@ -39,6 +45,9 @@ var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref)
39
45
  }));
40
46
  };
41
47
  var isOffline = isOfflineMode(mode);
48
+ var lozenge = /*#__PURE__*/React.createElement(SyncedBlockNewLozenge, {
49
+ label: formatMessage(blockMenuMessages.newLozenge)
50
+ });
42
51
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
43
52
  elemBefore: /*#__PURE__*/React.createElement(SyncBlocksIcon, {
44
53
  label: "",
@@ -47,13 +56,12 @@ var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref)
47
56
  onClick: onClick,
48
57
  isDisabled: isOffline,
49
58
  testId: SYNCED_BLOCK_BUTTON_TEST_ID.blockMenuCreate,
50
- elemAfter: /*#__PURE__*/React.createElement(Lozenge, {
51
- appearance: fg('confluence_fronend_labels_categorization_migration') ? 'discovery' : 'new'
52
- }, formatMessage(blockMenuMessages.newLozenge))
59
+ elemAfter: fg('platform_synced_block_patch_12') ? undefined : lozenge,
60
+ elemAfterText: fg('platform_synced_block_patch_12') ? lozenge : undefined
53
61
  }, formatMessage(blockMenuMessages.syncBlock));
54
62
  };
55
- var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref2) {
56
- var api = _ref2.api;
63
+ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref3) {
64
+ var api = _ref3.api;
57
65
  var _useIntl2 = useIntl(),
58
66
  formatMessage = _useIntl2.formatMessage;
59
67
  var _useSharedPluginState2 = useSharedPluginStateWithSelector(api, ['connectivity'], function (states) {
@@ -70,6 +78,9 @@ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref2) {
70
78
  closeMenu: true
71
79
  }));
72
80
  };
81
+ var lozenge = /*#__PURE__*/React.createElement(SyncedBlockNewLozenge, {
82
+ label: formatMessage(blockMenuMessages.newLozenge)
83
+ });
73
84
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
74
85
  elemBefore: /*#__PURE__*/React.createElement(SyncBlocksIcon, {
75
86
  label: "",
@@ -77,14 +88,13 @@ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref2) {
77
88
  }),
78
89
  onClick: onClick,
79
90
  isDisabled: isOfflineMode(mode),
80
- elemAfter: /*#__PURE__*/React.createElement(Lozenge, {
81
- appearance: fg('confluence_fronend_labels_categorization_migration') ? 'discovery' : 'new'
82
- }, formatMessage(blockMenuMessages.newLozenge))
91
+ elemAfter: fg('platform_synced_block_patch_12') ? undefined : lozenge,
92
+ elemAfterText: fg('platform_synced_block_patch_12') ? lozenge : undefined
83
93
  }, formatMessage(blockMenuMessages.copySyncedBlock));
84
94
  };
85
- export var CreateOrCopySyncedBlockDropdownItem = function CreateOrCopySyncedBlockDropdownItem(_ref3) {
86
- var api = _ref3.api,
87
- enableSourceSyncedBlockCreation = _ref3.enableSourceSyncedBlockCreation;
95
+ export var CreateOrCopySyncedBlockDropdownItem = function CreateOrCopySyncedBlockDropdownItem(_ref4) {
96
+ var api = _ref4.api,
97
+ enableSourceSyncedBlockCreation = _ref4.enableSourceSyncedBlockCreation;
88
98
  var _useSharedPluginState3 = useSharedPluginStateWithSelector(api, ['blockControls'], function (states) {
89
99
  var _states$blockControls3, _states$blockControls4;
90
100
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-synced-block",
3
- "version": "8.3.1",
3
+ "version": "8.3.3",
4
4
  "description": "SyncedBlock plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -33,18 +33,18 @@
33
33
  "@atlaskit/dropdown-menu": "16.8.11",
34
34
  "@atlaskit/editor-json-transformer": "^8.31.0",
35
35
  "@atlaskit/editor-plugin-analytics": "^10.0.0",
36
- "@atlaskit/editor-plugin-block-menu": "^9.1.0",
37
- "@atlaskit/editor-plugin-connectivity": "10.0.1",
38
- "@atlaskit/editor-plugin-content-format": "^4.0.0",
36
+ "@atlaskit/editor-plugin-block-menu": "^9.2.0",
37
+ "@atlaskit/editor-plugin-connectivity": "10.1.0",
38
+ "@atlaskit/editor-plugin-content-format": "^4.1.0",
39
39
  "@atlaskit/editor-plugin-decorations": "^10.1.0",
40
40
  "@atlaskit/editor-plugin-floating-toolbar": "^12.1.0",
41
41
  "@atlaskit/editor-plugin-focus": "^9.1.0",
42
42
  "@atlaskit/editor-plugin-selection": "^10.1.0",
43
43
  "@atlaskit/editor-plugin-user-intent": "^8.2.0",
44
44
  "@atlaskit/editor-prosemirror": "^7.3.0",
45
- "@atlaskit/editor-shared-styles": "^3.10.0",
46
- "@atlaskit/editor-synced-block-provider": "^6.5.0",
47
- "@atlaskit/editor-toolbar": "^1.3.0",
45
+ "@atlaskit/editor-shared-styles": "^3.11.0",
46
+ "@atlaskit/editor-synced-block-provider": "^6.6.0",
47
+ "@atlaskit/editor-toolbar": "^1.4.0",
48
48
  "@atlaskit/flag": "^17.11.0",
49
49
  "@atlaskit/icon": "34.5.0",
50
50
  "@atlaskit/icon-lab": "^6.9.0",
@@ -54,7 +54,7 @@
54
54
  "@atlaskit/platform-feature-flags": "^1.1.0",
55
55
  "@atlaskit/primitives": "^19.0.0",
56
56
  "@atlaskit/spinner": "19.1.2",
57
- "@atlaskit/tmp-editor-statsig": "^80.1.0",
57
+ "@atlaskit/tmp-editor-statsig": "^80.3.0",
58
58
  "@atlaskit/tokens": "13.0.4",
59
59
  "@atlaskit/tooltip": "^22.2.0",
60
60
  "@atlaskit/visually-hidden": "^3.1.0",
@@ -129,6 +129,9 @@
129
129
  },
130
130
  "platform_synced_block_patch_11": {
131
131
  "type": "boolean"
132
+ },
133
+ "platform_synced_block_patch_12": {
134
+ "type": "boolean"
132
135
  }
133
136
  }
134
137
  }