@atlaskit/editor-plugin-synced-block 8.2.3 → 8.2.4

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,16 @@
1
1
  # @atlaskit/editor-plugin-synced-block
2
2
 
3
+ ## 8.2.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`ca8aefc573cc5`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ca8aefc573cc5) -
8
+ Clean up feature gates `platform_editor_block_menu_divider_patch` and
9
+ `platform_editor_block_menu_copy_section` (both rolled out as true).
10
+ - [`826bc966b7b64`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/826bc966b7b64) -
11
+ ED-6586: Lazy init sync block
12
+ - Updated dependencies
13
+
3
14
  ## 8.2.3
4
15
 
5
16
  ### Patch Changes
@@ -21,15 +21,18 @@ var _transform = require("@atlaskit/editor-prosemirror/transform");
21
21
  var _view = require("@atlaskit/editor-prosemirror/view");
22
22
  var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
23
23
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
24
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
24
25
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
25
26
  var _bodiedSyncedBlock = require("../nodeviews/bodiedSyncedBlock");
26
27
  var _syncedBlock = require("../nodeviews/syncedBlock");
27
28
  var _types = require("../types");
28
29
  var _handleBodiedSyncBlockCreation = require("./utils/handle-bodied-sync-block-creation");
29
30
  var _handleBodiedSyncBlockRemoval = require("./utils/handle-bodied-sync-block-removal");
31
+ var _hasSyncedBlocks = require("./utils/has-synced-blocks");
30
32
  var _ignoreDomEvent = require("./utils/ignore-dom-event");
31
33
  var _selectionDecorations = require("./utils/selection-decorations");
32
34
  var _trackSyncBlocks7 = require("./utils/track-sync-blocks");
35
+ var _transactionInsertsSyncedBlock = require("./utils/transaction-inserts-synced-block");
33
36
  var _utils2 = require("./utils/utils");
34
37
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
35
38
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -279,21 +282,28 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
279
282
  key: syncedBlockPluginKey,
280
283
  state: {
281
284
  init: function init(_, instance) {
282
- var syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock);
283
- syncBlockStore.referenceManager.fetchSyncBlocksData((0, _editorSyncedBlockProvider.convertPMNodesToSyncBlockNodes)(syncBlockNodes));
285
+ // When `editor_synced_block_perf` is ON and the document has no
286
+ // synced blocks, we skip the eager fetch + cache walks. They will be
287
+ // re-run lazily by `apply` the first time a synced block enters the
288
+ // document (paste, collab insert, or programmatic insert).
289
+ var docHasSyncedBlocks = (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true) ? (0, _hasSyncedBlocks.hasSyncedBlocks)(instance.doc) : true;
290
+ if (docHasSyncedBlocks) {
291
+ var syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock);
292
+ syncBlockStore.referenceManager.fetchSyncBlocksData((0, _editorSyncedBlockProvider.convertPMNodesToSyncBlockNodes)(syncBlockNodes));
284
293
 
285
- // Populate source sync block cache from initial document
286
- // When fg is ON, this replaces the constructor call in the nodeview
287
- if ((0, _platformFeatureFlags.fg)('platform_synced_block_update_refactor')) {
288
- instance.doc.forEach(function (node) {
289
- if (syncBlockStore.sourceManager.isSourceBlock(node)) {
290
- syncBlockStore.sourceManager.updateSyncBlockData(node, false);
291
- }
292
- });
294
+ // Populate source sync block cache from initial document
295
+ // When fg is ON, this replaces the constructor call in the nodeview
296
+ if ((0, _platformFeatureFlags.fg)('platform_synced_block_update_refactor')) {
297
+ instance.doc.forEach(function (node) {
298
+ if (syncBlockStore.sourceManager.isSourceBlock(node)) {
299
+ syncBlockStore.sourceManager.updateSyncBlockData(node, false);
300
+ }
301
+ });
293
302
 
294
- // Fetch statuses from the backend so we can identify unpublished blocks on cancel
295
- if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_10')) {
296
- syncBlockStore.sourceManager.fetchAndCacheStatuses();
303
+ // Fetch statuses from the backend so we can identify unpublished blocks on cancel
304
+ if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_10')) {
305
+ syncBlockStore.sourceManager.fetchAndCacheStatuses();
306
+ }
297
307
  }
298
308
  }
299
309
  return {
@@ -301,6 +311,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
301
311
  activeFlag: false,
302
312
  syncBlockStore: syncBlockStore,
303
313
  retryCreationPosMap: new Map(),
314
+ hasSyncedBlocks: docHasSyncedBlocks,
304
315
  hasUnsavedBodiedSyncBlockChanges: syncBlockStore.sourceManager.hasUnsavedChanges()
305
316
  };
306
317
  },
@@ -310,7 +321,17 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
310
321
  var activeFlag = currentPluginState.activeFlag,
311
322
  selectionDecorationSet = currentPluginState.selectionDecorationSet,
312
323
  bodiedSyncBlockDeletionStatus = currentPluginState.bodiedSyncBlockDeletionStatus,
313
- retryCreationPosMap = currentPluginState.retryCreationPosMap;
324
+ retryCreationPosMap = currentPluginState.retryCreationPosMap,
325
+ prevHasSyncedBlocks = currentPluginState.hasSyncedBlocks;
326
+
327
+ // Lazy-init bookkeeping: once a synced block enters the document we
328
+ // flip `hasSyncedBlocks` to `true` for the lifetime of this editor
329
+ var nextHasSyncedBlocks = prevHasSyncedBlocks;
330
+ if (!prevHasSyncedBlocks && tr.docChanged && (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
331
+ if ((0, _transactionInsertsSyncedBlock.transactionInsertsSyncedBlock)(tr)) {
332
+ nextHasSyncedBlocks = true;
333
+ }
334
+ }
314
335
  var newDecorationSet = tr.docChanged ? selectionDecorationSet.map(tr.mapping, tr.doc) // only map if document changed
315
336
  : selectionDecorationSet;
316
337
  if (!tr.selection.eq(oldEditorState.selection)) {
@@ -333,6 +354,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
333
354
  selectionDecorationSet: newDecorationSet,
334
355
  syncBlockStore: syncBlockStore,
335
356
  retryCreationPosMap: newRetryCreationPosMap,
357
+ hasSyncedBlocks: nextHasSyncedBlocks,
336
358
  bodiedSyncBlockDeletionStatus: (_meta$bodiedSyncBlock = meta === null || meta === void 0 ? void 0 : meta.bodiedSyncBlockDeletionStatus) !== null && _meta$bodiedSyncBlock !== void 0 ? _meta$bodiedSyncBlock : bodiedSyncBlockDeletionStatus,
337
359
  hasUnsavedBodiedSyncBlockChanges: syncBlockStore.sourceManager.hasUnsavedChanges()
338
360
  };
@@ -378,6 +400,14 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
378
400
  var selectionDecorationSet = (_currentPluginState$s = currentPluginState === null || currentPluginState === void 0 ? void 0 : currentPluginState.selectionDecorationSet) !== null && _currentPluginState$s !== void 0 ? _currentPluginState$s : _view.DecorationSet.empty;
379
401
  var syncBlockStore = currentPluginState === null || currentPluginState === void 0 ? void 0 : currentPluginState.syncBlockStore;
380
402
  var doc = state.doc;
403
+
404
+ // Lazy-init: when no synced block exists in the doc, skip the
405
+ // `descendants` walk and the 4 shared-state reads below. The
406
+ // selection decoration set is the only thing that can be
407
+ // non-empty in this state.
408
+ if (currentPluginState && !currentPluginState.hasSyncedBlocks && (0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
409
+ return selectionDecorationSet;
410
+ }
381
411
  var isOffline = (0, _editorPluginConnectivity.isOfflineMode)(api === null || api === void 0 || (_api$connectivity2 = api.connectivity) === null || _api$connectivity2 === void 0 || (_api$connectivity2 = _api$connectivity2.sharedState.currentState()) === null || _api$connectivity2 === void 0 ? void 0 : _api$connectivity2.mode);
382
412
  var isViewMode = (api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode) === 'view';
383
413
  var isDragging = (api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'dragging';
@@ -474,6 +504,16 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
474
504
  },
475
505
  filterTransaction: function filterTransaction(tr, state) {
476
506
  var _api$editorViewMode2, _api$connectivity3;
507
+ // Lazy-init: when no synced block currently exists in the doc and the
508
+ // transaction does not insert one, all downstream filter logic is a
509
+ // no-op. Avoid both the shared-state reads and the `trackSyncBlocks`
510
+ // walks for the ~99.97% of pages that have no synced blocks.
511
+ if ((0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
512
+ var pluginState = syncedBlockPluginKey.getState(state);
513
+ if (pluginState && !pluginState.hasSyncedBlocks && !(0, _transactionInsertsSyncedBlock.transactionInsertsSyncedBlock)(tr)) {
514
+ return true;
515
+ }
516
+ }
477
517
  var viewMode = api === null || api === void 0 || (_api$editorViewMode2 = api.editorViewMode) === null || _api$editorViewMode2 === void 0 || (_api$editorViewMode2 = _api$editorViewMode2.sharedState.currentState()) === null || _api$editorViewMode2 === void 0 ? void 0 : _api$editorViewMode2.mode;
478
518
  if (viewMode === 'view' && (0, _platformFeatureFlags.fg)('platform_synced_block_patch_8')) {
479
519
  return true;
@@ -554,6 +594,18 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
554
594
  },
555
595
  appendTransaction: function appendTransaction(trs, oldState, newState) {
556
596
  var _api$editorViewMode3;
597
+ // Lazy-init: when neither the previous nor the new state contains a
598
+ // synced block (and none of the dispatched transactions inserts one),
599
+ // skip all downstream work. This is the hot path on the ~99.97% of
600
+ // pages that don't use synced blocks (see EDITOR-6586).
601
+ if ((0, _expValEquals.expValEquals)('editor_synced_block_perf', 'isEnabled', true)) {
602
+ var oldPluginState = syncedBlockPluginKey.getState(oldState);
603
+ var newPluginState = syncedBlockPluginKey.getState(newState);
604
+ var hadOrHasSyncedBlocks = !!(oldPluginState !== null && oldPluginState !== void 0 && oldPluginState.hasSyncedBlocks) || !!(newPluginState !== null && newPluginState !== void 0 && newPluginState.hasSyncedBlocks);
605
+ if (!hadOrHasSyncedBlocks) {
606
+ return null;
607
+ }
608
+ }
557
609
  var viewMode = api === null || api === void 0 || (_api$editorViewMode3 = api.editorViewMode) === null || _api$editorViewMode3 === void 0 || (_api$editorViewMode3 = _api$editorViewMode3.sharedState.currentState()) === null || _api$editorViewMode3 === void 0 ? void 0 : _api$editorViewMode3.mode;
558
610
  if (viewMode === 'view' && (0, _platformFeatureFlags.fg)('platform_synced_block_patch_8')) {
559
611
  return null;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.hasSyncedBlocks = void 0;
7
+ /**
8
+ * Cheap detector for the presence of any synced block (source or reference)
9
+ * at the top level of the document.
10
+ *
11
+ * Both `syncBlock` and `bodiedSyncBlock` are top-level nodes in the schema, so
12
+ * a single shallow scan is sufficient — no need for a recursive `descendants`
13
+ * walk. This is used by the `editor_synced_block_perf` experiment to avoid
14
+ * spinning up the synced block subsystem on documents that don't contain any
15
+ * synced blocks (which is ~99.97% of pages, see EDITOR-6586).
16
+ */
17
+ var hasSyncedBlocks = exports.hasSyncedBlocks = function hasSyncedBlocks(doc) {
18
+ for (var i = 0; i < doc.childCount; i++) {
19
+ var node = doc.child(i);
20
+ if (node.type.name === 'syncBlock' || node.type.name === 'bodiedSyncBlock') {
21
+ return true;
22
+ }
23
+ }
24
+ return false;
25
+ };
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.transactionInsertsSyncedBlock = void 0;
7
+ var _transform = require("@atlaskit/editor-prosemirror/transform");
8
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
9
+ 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; } }
10
+ 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; }
11
+ /**
12
+ * Cheap step-level scan to detect whether the given transaction inserts a
13
+ * synced block (source or reference). Used by the lazy-init path to know when
14
+ * a previously "no synced blocks" document has gained one.
15
+ */
16
+ var transactionInsertsSyncedBlock = exports.transactionInsertsSyncedBlock = function transactionInsertsSyncedBlock(tr) {
17
+ if (!tr.docChanged) {
18
+ return false;
19
+ }
20
+ var _iterator = _createForOfIteratorHelper(tr.steps),
21
+ _step;
22
+ try {
23
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
24
+ var step = _step.value;
25
+ if (!(step instanceof _transform.ReplaceStep || step instanceof _transform.ReplaceAroundStep)) {
26
+ continue;
27
+ }
28
+ var childCount = step.slice.content.childCount;
29
+ for (var i = 0; i < childCount; i++) {
30
+ var child = step.slice.content.child(i);
31
+ if (child.type.name === 'syncBlock' || child.type.name === 'bodiedSyncBlock') {
32
+ return true;
33
+ }
34
+ }
35
+ }
36
+ } catch (err) {
37
+ _iterator.e(err);
38
+ } finally {
39
+ _iterator.f();
40
+ }
41
+ return false;
42
+ };
@@ -127,12 +127,14 @@ var syncedBlockPlugin = exports.syncedBlockPlugin = function syncedBlockPlugin(_
127
127
  currentSyncBlockStore = _syncedBlockPluginKey.syncBlockStore,
128
128
  bodiedSyncBlockDeletionStatus = _syncedBlockPluginKey.bodiedSyncBlockDeletionStatus,
129
129
  retryCreationPosMap = _syncedBlockPluginKey.retryCreationPosMap,
130
+ hasSyncedBlocks = _syncedBlockPluginKey.hasSyncedBlocks,
130
131
  hasUnsavedBodiedSyncBlockChanges = _syncedBlockPluginKey.hasUnsavedBodiedSyncBlockChanges;
131
132
  return {
132
133
  activeFlag: activeFlag,
133
134
  syncBlockStore: currentSyncBlockStore,
134
135
  bodiedSyncBlockDeletionStatus: bodiedSyncBlockDeletionStatus,
135
136
  retryCreationPosMap: retryCreationPosMap,
137
+ hasSyncedBlocks: hasSyncedBlocks,
136
138
  hasUnsavedBodiedSyncBlockChanges: hasUnsavedBodiedSyncBlockChanges
137
139
  };
138
140
  }
@@ -7,7 +7,6 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.getBlockMenuComponents = void 0;
8
8
  var _react = _interopRequireDefault(require("react"));
9
9
  var _blockMenu = require("@atlaskit/editor-common/block-menu");
10
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
11
10
  var _CreateSyncedBlockDropdownItem = require("./CreateSyncedBlockDropdownItem");
12
11
  var getBlockMenuComponents = exports.getBlockMenuComponents = function getBlockMenuComponents(api, enableSourceSyncedBlockCreation) {
13
12
  return [{
@@ -15,8 +14,8 @@ var getBlockMenuComponents = exports.getBlockMenuComponents = function getBlockM
15
14
  key: _blockMenu.BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key,
16
15
  parent: {
17
16
  type: 'block-menu-section',
18
- key: (0, _platformFeatureFlags.fg)('platform_editor_block_menu_divider_patch') ? _blockMenu.TRANSFORM_MENU_SECTION.key : _blockMenu.BLOCK_ACTIONS_MENU_SECTION.key,
19
- rank: (0, _platformFeatureFlags.fg)('platform_editor_block_menu_divider_patch') ? _blockMenu.TRANSFORM_MENU_SECTION_RANK[_blockMenu.BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key] : _blockMenu.BLOCK_ACTIONS_MENU_SECTION_RANK[_blockMenu.BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key]
17
+ key: _blockMenu.TRANSFORM_MENU_SECTION.key,
18
+ rank: _blockMenu.TRANSFORM_MENU_SECTION_RANK[_blockMenu.BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key]
20
19
  },
21
20
  component: function component() {
22
21
  return /*#__PURE__*/_react.default.createElement(_CreateSyncedBlockDropdownItem.CreateOrCopySyncedBlockDropdownItem, {
@@ -11,15 +11,18 @@ import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/tra
11
11
  import { DecorationSet, Decoration } from '@atlaskit/editor-prosemirror/view';
12
12
  import { convertPMNodesToSyncBlockNodes, rebaseTransaction } from '@atlaskit/editor-synced-block-provider';
13
13
  import { fg } from '@atlaskit/platform-feature-flags';
14
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
14
15
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
15
16
  import { bodiedSyncBlockNodeView, bodiedSyncBlockNodeViewOld } from '../nodeviews/bodiedSyncedBlock';
16
17
  import { SyncBlock as SyncBlockView } from '../nodeviews/syncedBlock';
17
18
  import { FLAG_ID } from '../types';
18
19
  import { handleBodiedSyncBlockCreation } from './utils/handle-bodied-sync-block-creation';
19
20
  import { handleBodiedSyncBlockRemoval } from './utils/handle-bodied-sync-block-removal';
21
+ import { hasSyncedBlocks } from './utils/has-synced-blocks';
20
22
  import { shouldIgnoreDomEvent } from './utils/ignore-dom-event';
21
23
  import { calculateDecorations } from './utils/selection-decorations';
22
24
  import { hasEditInSyncBlock, trackSyncBlocks } from './utils/track-sync-blocks';
25
+ import { transactionInsertsSyncedBlock } from './utils/transaction-inserts-synced-block';
23
26
  import { deferDispatch, wasExtensionInsertedInBodiedSyncBlock, sliceFullyContainsNode } from './utils/utils';
24
27
  export const syncedBlockPluginKey = new PluginKey('syncedBlockPlugin');
25
28
  const mapRetryCreationPosMap = (oldMap, newRetryCreationPos, mapPos) => {
@@ -246,21 +249,28 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
246
249
  key: syncedBlockPluginKey,
247
250
  state: {
248
251
  init(_, instance) {
249
- const syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock);
250
- syncBlockStore.referenceManager.fetchSyncBlocksData(convertPMNodesToSyncBlockNodes(syncBlockNodes));
252
+ // When `editor_synced_block_perf` is ON and the document has no
253
+ // synced blocks, we skip the eager fetch + cache walks. They will be
254
+ // re-run lazily by `apply` the first time a synced block enters the
255
+ // document (paste, collab insert, or programmatic insert).
256
+ const docHasSyncedBlocks = expValEquals('editor_synced_block_perf', 'isEnabled', true) ? hasSyncedBlocks(instance.doc) : true;
257
+ if (docHasSyncedBlocks) {
258
+ const syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock);
259
+ syncBlockStore.referenceManager.fetchSyncBlocksData(convertPMNodesToSyncBlockNodes(syncBlockNodes));
251
260
 
252
- // Populate source sync block cache from initial document
253
- // When fg is ON, this replaces the constructor call in the nodeview
254
- if (fg('platform_synced_block_update_refactor')) {
255
- instance.doc.forEach(node => {
256
- if (syncBlockStore.sourceManager.isSourceBlock(node)) {
257
- syncBlockStore.sourceManager.updateSyncBlockData(node, false);
258
- }
259
- });
261
+ // Populate source sync block cache from initial document
262
+ // When fg is ON, this replaces the constructor call in the nodeview
263
+ if (fg('platform_synced_block_update_refactor')) {
264
+ instance.doc.forEach(node => {
265
+ if (syncBlockStore.sourceManager.isSourceBlock(node)) {
266
+ syncBlockStore.sourceManager.updateSyncBlockData(node, false);
267
+ }
268
+ });
260
269
 
261
- // Fetch statuses from the backend so we can identify unpublished blocks on cancel
262
- if (fg('platform_synced_block_patch_10')) {
263
- syncBlockStore.sourceManager.fetchAndCacheStatuses();
270
+ // Fetch statuses from the backend so we can identify unpublished blocks on cancel
271
+ if (fg('platform_synced_block_patch_10')) {
272
+ syncBlockStore.sourceManager.fetchAndCacheStatuses();
273
+ }
264
274
  }
265
275
  }
266
276
  return {
@@ -268,6 +278,7 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
268
278
  activeFlag: false,
269
279
  syncBlockStore: syncBlockStore,
270
280
  retryCreationPosMap: new Map(),
281
+ hasSyncedBlocks: docHasSyncedBlocks,
271
282
  hasUnsavedBodiedSyncBlockChanges: syncBlockStore.sourceManager.hasUnsavedChanges()
272
283
  };
273
284
  },
@@ -278,8 +289,18 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
278
289
  activeFlag,
279
290
  selectionDecorationSet,
280
291
  bodiedSyncBlockDeletionStatus,
281
- retryCreationPosMap
292
+ retryCreationPosMap,
293
+ hasSyncedBlocks: prevHasSyncedBlocks
282
294
  } = currentPluginState;
295
+
296
+ // Lazy-init bookkeeping: once a synced block enters the document we
297
+ // flip `hasSyncedBlocks` to `true` for the lifetime of this editor
298
+ let nextHasSyncedBlocks = prevHasSyncedBlocks;
299
+ if (!prevHasSyncedBlocks && tr.docChanged && expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
300
+ if (transactionInsertsSyncedBlock(tr)) {
301
+ nextHasSyncedBlocks = true;
302
+ }
303
+ }
283
304
  let newDecorationSet = tr.docChanged ? selectionDecorationSet.map(tr.mapping, tr.doc) // only map if document changed
284
305
  : selectionDecorationSet;
285
306
  if (!tr.selection.eq(oldEditorState.selection)) {
@@ -302,6 +323,7 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
302
323
  selectionDecorationSet: newDecorationSet,
303
324
  syncBlockStore: syncBlockStore,
304
325
  retryCreationPosMap: newRetryCreationPosMap,
326
+ hasSyncedBlocks: nextHasSyncedBlocks,
305
327
  bodiedSyncBlockDeletionStatus: (_meta$bodiedSyncBlock = meta === null || meta === void 0 ? void 0 : meta.bodiedSyncBlockDeletionStatus) !== null && _meta$bodiedSyncBlock !== void 0 ? _meta$bodiedSyncBlock : bodiedSyncBlockDeletionStatus,
306
328
  hasUnsavedBodiedSyncBlockChanges: syncBlockStore.sourceManager.hasUnsavedChanges()
307
329
  };
@@ -346,6 +368,14 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
346
368
  const {
347
369
  doc
348
370
  } = state;
371
+
372
+ // Lazy-init: when no synced block exists in the doc, skip the
373
+ // `descendants` walk and the 4 shared-state reads below. The
374
+ // selection decoration set is the only thing that can be
375
+ // non-empty in this state.
376
+ if (currentPluginState && !currentPluginState.hasSyncedBlocks && expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
377
+ return selectionDecorationSet;
378
+ }
349
379
  const isOffline = isOfflineMode(api === null || api === void 0 ? void 0 : (_api$connectivity2 = api.connectivity) === null || _api$connectivity2 === void 0 ? void 0 : (_api$connectivity2$sh = _api$connectivity2.sharedState.currentState()) === null || _api$connectivity2$sh === void 0 ? void 0 : _api$connectivity2$sh.mode);
350
380
  const isViewMode = (api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : (_api$editorViewMode$s = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode$s === void 0 ? void 0 : _api$editorViewMode$s.mode) === 'view';
351
381
  const isDragging = (api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : (_api$userIntent$share = _api$userIntent.sharedState.currentState()) === null || _api$userIntent$share === void 0 ? void 0 : _api$userIntent$share.currentUserIntent) === 'dragging';
@@ -444,6 +474,16 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
444
474
  },
445
475
  filterTransaction: (tr, state) => {
446
476
  var _api$editorViewMode2, _api$editorViewMode2$, _api$connectivity3, _api$connectivity3$sh;
477
+ // Lazy-init: when no synced block currently exists in the doc and the
478
+ // transaction does not insert one, all downstream filter logic is a
479
+ // no-op. Avoid both the shared-state reads and the `trackSyncBlocks`
480
+ // walks for the ~99.97% of pages that have no synced blocks.
481
+ if (expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
482
+ const pluginState = syncedBlockPluginKey.getState(state);
483
+ if (pluginState && !pluginState.hasSyncedBlocks && !transactionInsertsSyncedBlock(tr)) {
484
+ return true;
485
+ }
486
+ }
447
487
  const viewMode = api === null || api === void 0 ? void 0 : (_api$editorViewMode2 = api.editorViewMode) === null || _api$editorViewMode2 === void 0 ? void 0 : (_api$editorViewMode2$ = _api$editorViewMode2.sharedState.currentState()) === null || _api$editorViewMode2$ === void 0 ? void 0 : _api$editorViewMode2$.mode;
448
488
  if (viewMode === 'view' && fg('platform_synced_block_patch_8')) {
449
489
  return true;
@@ -517,6 +557,18 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
517
557
  },
518
558
  appendTransaction: (trs, oldState, newState) => {
519
559
  var _api$editorViewMode3, _api$editorViewMode3$;
560
+ // Lazy-init: when neither the previous nor the new state contains a
561
+ // synced block (and none of the dispatched transactions inserts one),
562
+ // skip all downstream work. This is the hot path on the ~99.97% of
563
+ // pages that don't use synced blocks (see EDITOR-6586).
564
+ if (expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
565
+ const oldPluginState = syncedBlockPluginKey.getState(oldState);
566
+ const newPluginState = syncedBlockPluginKey.getState(newState);
567
+ const hadOrHasSyncedBlocks = !!(oldPluginState !== null && oldPluginState !== void 0 && oldPluginState.hasSyncedBlocks) || !!(newPluginState !== null && newPluginState !== void 0 && newPluginState.hasSyncedBlocks);
568
+ if (!hadOrHasSyncedBlocks) {
569
+ return null;
570
+ }
571
+ }
520
572
  const viewMode = api === null || api === void 0 ? void 0 : (_api$editorViewMode3 = api.editorViewMode) === null || _api$editorViewMode3 === void 0 ? void 0 : (_api$editorViewMode3$ = _api$editorViewMode3.sharedState.currentState()) === null || _api$editorViewMode3$ === void 0 ? void 0 : _api$editorViewMode3$.mode;
521
573
  if (viewMode === 'view' && fg('platform_synced_block_patch_8')) {
522
574
  return null;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Cheap detector for the presence of any synced block (source or reference)
3
+ * at the top level of the document.
4
+ *
5
+ * Both `syncBlock` and `bodiedSyncBlock` are top-level nodes in the schema, so
6
+ * a single shallow scan is sufficient — no need for a recursive `descendants`
7
+ * walk. This is used by the `editor_synced_block_perf` experiment to avoid
8
+ * spinning up the synced block subsystem on documents that don't contain any
9
+ * synced blocks (which is ~99.97% of pages, see EDITOR-6586).
10
+ */
11
+ export const hasSyncedBlocks = doc => {
12
+ for (let i = 0; i < doc.childCount; i++) {
13
+ const node = doc.child(i);
14
+ if (node.type.name === 'syncBlock' || node.type.name === 'bodiedSyncBlock') {
15
+ return true;
16
+ }
17
+ }
18
+ return false;
19
+ };
@@ -0,0 +1,25 @@
1
+ import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
2
+
3
+ /**
4
+ * Cheap step-level scan to detect whether the given transaction inserts a
5
+ * synced block (source or reference). Used by the lazy-init path to know when
6
+ * a previously "no synced blocks" document has gained one.
7
+ */
8
+ export const transactionInsertsSyncedBlock = tr => {
9
+ if (!tr.docChanged) {
10
+ return false;
11
+ }
12
+ for (const step of tr.steps) {
13
+ if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep)) {
14
+ continue;
15
+ }
16
+ const childCount = step.slice.content.childCount;
17
+ for (let i = 0; i < childCount; i++) {
18
+ const child = step.slice.content.child(i);
19
+ if (child.type.name === 'syncBlock' || child.type.name === 'bodiedSyncBlock') {
20
+ return true;
21
+ }
22
+ }
23
+ }
24
+ return false;
25
+ };
@@ -113,6 +113,7 @@ export const syncedBlockPlugin = ({
113
113
  syncBlockStore: currentSyncBlockStore,
114
114
  bodiedSyncBlockDeletionStatus,
115
115
  retryCreationPosMap,
116
+ hasSyncedBlocks,
116
117
  hasUnsavedBodiedSyncBlockChanges
117
118
  } = syncedBlockPluginKey.getState(editorState);
118
119
  return {
@@ -120,6 +121,7 @@ export const syncedBlockPlugin = ({
120
121
  syncBlockStore: currentSyncBlockStore,
121
122
  bodiedSyncBlockDeletionStatus,
122
123
  retryCreationPosMap,
124
+ hasSyncedBlocks,
123
125
  hasUnsavedBodiedSyncBlockChanges
124
126
  };
125
127
  }
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
- import { BLOCK_ACTIONS_MENU_SECTION, BLOCK_ACTIONS_MENU_SECTION_RANK, TRANSFORM_MENU_SECTION, TRANSFORM_MENU_SECTION_RANK, BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM } from '@atlaskit/editor-common/block-menu';
3
- import { fg } from '@atlaskit/platform-feature-flags';
2
+ import { TRANSFORM_MENU_SECTION, TRANSFORM_MENU_SECTION_RANK, BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM } from '@atlaskit/editor-common/block-menu';
4
3
  import { CreateOrCopySyncedBlockDropdownItem } from './CreateSyncedBlockDropdownItem';
5
4
  export const getBlockMenuComponents = (api, enableSourceSyncedBlockCreation) => {
6
5
  return [{
@@ -8,8 +7,8 @@ export const getBlockMenuComponents = (api, enableSourceSyncedBlockCreation) =>
8
7
  key: BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key,
9
8
  parent: {
10
9
  type: 'block-menu-section',
11
- key: fg('platform_editor_block_menu_divider_patch') ? TRANSFORM_MENU_SECTION.key : BLOCK_ACTIONS_MENU_SECTION.key,
12
- rank: fg('platform_editor_block_menu_divider_patch') ? TRANSFORM_MENU_SECTION_RANK[BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key] : BLOCK_ACTIONS_MENU_SECTION_RANK[BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key]
10
+ key: TRANSFORM_MENU_SECTION.key,
11
+ rank: TRANSFORM_MENU_SECTION_RANK[BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key]
13
12
  },
14
13
  component: () => /*#__PURE__*/React.createElement(CreateOrCopySyncedBlockDropdownItem, {
15
14
  api: api,
@@ -19,15 +19,18 @@ import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/tra
19
19
  import { DecorationSet, Decoration } from '@atlaskit/editor-prosemirror/view';
20
20
  import { convertPMNodesToSyncBlockNodes, rebaseTransaction } from '@atlaskit/editor-synced-block-provider';
21
21
  import { fg } from '@atlaskit/platform-feature-flags';
22
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
22
23
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
23
24
  import { bodiedSyncBlockNodeView, bodiedSyncBlockNodeViewOld } from '../nodeviews/bodiedSyncedBlock';
24
25
  import { SyncBlock as SyncBlockView } from '../nodeviews/syncedBlock';
25
26
  import { FLAG_ID } from '../types';
26
27
  import { handleBodiedSyncBlockCreation } from './utils/handle-bodied-sync-block-creation';
27
28
  import { handleBodiedSyncBlockRemoval } from './utils/handle-bodied-sync-block-removal';
29
+ import { hasSyncedBlocks } from './utils/has-synced-blocks';
28
30
  import { shouldIgnoreDomEvent } from './utils/ignore-dom-event';
29
31
  import { calculateDecorations } from './utils/selection-decorations';
30
32
  import { hasEditInSyncBlock, trackSyncBlocks } from './utils/track-sync-blocks';
33
+ import { transactionInsertsSyncedBlock } from './utils/transaction-inserts-synced-block';
31
34
  import { deferDispatch, wasExtensionInsertedInBodiedSyncBlock, sliceFullyContainsNode } from './utils/utils';
32
35
  export var syncedBlockPluginKey = new PluginKey('syncedBlockPlugin');
33
36
  var mapRetryCreationPosMap = function mapRetryCreationPosMap(oldMap, newRetryCreationPos, mapPos) {
@@ -272,21 +275,28 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
272
275
  key: syncedBlockPluginKey,
273
276
  state: {
274
277
  init: function init(_, instance) {
275
- var syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock);
276
- syncBlockStore.referenceManager.fetchSyncBlocksData(convertPMNodesToSyncBlockNodes(syncBlockNodes));
278
+ // When `editor_synced_block_perf` is ON and the document has no
279
+ // synced blocks, we skip the eager fetch + cache walks. They will be
280
+ // re-run lazily by `apply` the first time a synced block enters the
281
+ // document (paste, collab insert, or programmatic insert).
282
+ var docHasSyncedBlocks = expValEquals('editor_synced_block_perf', 'isEnabled', true) ? hasSyncedBlocks(instance.doc) : true;
283
+ if (docHasSyncedBlocks) {
284
+ var syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock);
285
+ syncBlockStore.referenceManager.fetchSyncBlocksData(convertPMNodesToSyncBlockNodes(syncBlockNodes));
277
286
 
278
- // Populate source sync block cache from initial document
279
- // When fg is ON, this replaces the constructor call in the nodeview
280
- if (fg('platform_synced_block_update_refactor')) {
281
- instance.doc.forEach(function (node) {
282
- if (syncBlockStore.sourceManager.isSourceBlock(node)) {
283
- syncBlockStore.sourceManager.updateSyncBlockData(node, false);
284
- }
285
- });
287
+ // Populate source sync block cache from initial document
288
+ // When fg is ON, this replaces the constructor call in the nodeview
289
+ if (fg('platform_synced_block_update_refactor')) {
290
+ instance.doc.forEach(function (node) {
291
+ if (syncBlockStore.sourceManager.isSourceBlock(node)) {
292
+ syncBlockStore.sourceManager.updateSyncBlockData(node, false);
293
+ }
294
+ });
286
295
 
287
- // Fetch statuses from the backend so we can identify unpublished blocks on cancel
288
- if (fg('platform_synced_block_patch_10')) {
289
- syncBlockStore.sourceManager.fetchAndCacheStatuses();
296
+ // Fetch statuses from the backend so we can identify unpublished blocks on cancel
297
+ if (fg('platform_synced_block_patch_10')) {
298
+ syncBlockStore.sourceManager.fetchAndCacheStatuses();
299
+ }
290
300
  }
291
301
  }
292
302
  return {
@@ -294,6 +304,7 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
294
304
  activeFlag: false,
295
305
  syncBlockStore: syncBlockStore,
296
306
  retryCreationPosMap: new Map(),
307
+ hasSyncedBlocks: docHasSyncedBlocks,
297
308
  hasUnsavedBodiedSyncBlockChanges: syncBlockStore.sourceManager.hasUnsavedChanges()
298
309
  };
299
310
  },
@@ -303,7 +314,17 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
303
314
  var activeFlag = currentPluginState.activeFlag,
304
315
  selectionDecorationSet = currentPluginState.selectionDecorationSet,
305
316
  bodiedSyncBlockDeletionStatus = currentPluginState.bodiedSyncBlockDeletionStatus,
306
- retryCreationPosMap = currentPluginState.retryCreationPosMap;
317
+ retryCreationPosMap = currentPluginState.retryCreationPosMap,
318
+ prevHasSyncedBlocks = currentPluginState.hasSyncedBlocks;
319
+
320
+ // Lazy-init bookkeeping: once a synced block enters the document we
321
+ // flip `hasSyncedBlocks` to `true` for the lifetime of this editor
322
+ var nextHasSyncedBlocks = prevHasSyncedBlocks;
323
+ if (!prevHasSyncedBlocks && tr.docChanged && expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
324
+ if (transactionInsertsSyncedBlock(tr)) {
325
+ nextHasSyncedBlocks = true;
326
+ }
327
+ }
307
328
  var newDecorationSet = tr.docChanged ? selectionDecorationSet.map(tr.mapping, tr.doc) // only map if document changed
308
329
  : selectionDecorationSet;
309
330
  if (!tr.selection.eq(oldEditorState.selection)) {
@@ -326,6 +347,7 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
326
347
  selectionDecorationSet: newDecorationSet,
327
348
  syncBlockStore: syncBlockStore,
328
349
  retryCreationPosMap: newRetryCreationPosMap,
350
+ hasSyncedBlocks: nextHasSyncedBlocks,
329
351
  bodiedSyncBlockDeletionStatus: (_meta$bodiedSyncBlock = meta === null || meta === void 0 ? void 0 : meta.bodiedSyncBlockDeletionStatus) !== null && _meta$bodiedSyncBlock !== void 0 ? _meta$bodiedSyncBlock : bodiedSyncBlockDeletionStatus,
330
352
  hasUnsavedBodiedSyncBlockChanges: syncBlockStore.sourceManager.hasUnsavedChanges()
331
353
  };
@@ -371,6 +393,14 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
371
393
  var selectionDecorationSet = (_currentPluginState$s = currentPluginState === null || currentPluginState === void 0 ? void 0 : currentPluginState.selectionDecorationSet) !== null && _currentPluginState$s !== void 0 ? _currentPluginState$s : DecorationSet.empty;
372
394
  var syncBlockStore = currentPluginState === null || currentPluginState === void 0 ? void 0 : currentPluginState.syncBlockStore;
373
395
  var doc = state.doc;
396
+
397
+ // Lazy-init: when no synced block exists in the doc, skip the
398
+ // `descendants` walk and the 4 shared-state reads below. The
399
+ // selection decoration set is the only thing that can be
400
+ // non-empty in this state.
401
+ if (currentPluginState && !currentPluginState.hasSyncedBlocks && expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
402
+ return selectionDecorationSet;
403
+ }
374
404
  var isOffline = isOfflineMode(api === null || api === void 0 || (_api$connectivity2 = api.connectivity) === null || _api$connectivity2 === void 0 || (_api$connectivity2 = _api$connectivity2.sharedState.currentState()) === null || _api$connectivity2 === void 0 ? void 0 : _api$connectivity2.mode);
375
405
  var isViewMode = (api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode) === 'view';
376
406
  var isDragging = (api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'dragging';
@@ -467,6 +497,16 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
467
497
  },
468
498
  filterTransaction: function filterTransaction(tr, state) {
469
499
  var _api$editorViewMode2, _api$connectivity3;
500
+ // Lazy-init: when no synced block currently exists in the doc and the
501
+ // transaction does not insert one, all downstream filter logic is a
502
+ // no-op. Avoid both the shared-state reads and the `trackSyncBlocks`
503
+ // walks for the ~99.97% of pages that have no synced blocks.
504
+ if (expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
505
+ var pluginState = syncedBlockPluginKey.getState(state);
506
+ if (pluginState && !pluginState.hasSyncedBlocks && !transactionInsertsSyncedBlock(tr)) {
507
+ return true;
508
+ }
509
+ }
470
510
  var viewMode = api === null || api === void 0 || (_api$editorViewMode2 = api.editorViewMode) === null || _api$editorViewMode2 === void 0 || (_api$editorViewMode2 = _api$editorViewMode2.sharedState.currentState()) === null || _api$editorViewMode2 === void 0 ? void 0 : _api$editorViewMode2.mode;
471
511
  if (viewMode === 'view' && fg('platform_synced_block_patch_8')) {
472
512
  return true;
@@ -547,6 +587,18 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
547
587
  },
548
588
  appendTransaction: function appendTransaction(trs, oldState, newState) {
549
589
  var _api$editorViewMode3;
590
+ // Lazy-init: when neither the previous nor the new state contains a
591
+ // synced block (and none of the dispatched transactions inserts one),
592
+ // skip all downstream work. This is the hot path on the ~99.97% of
593
+ // pages that don't use synced blocks (see EDITOR-6586).
594
+ if (expValEquals('editor_synced_block_perf', 'isEnabled', true)) {
595
+ var oldPluginState = syncedBlockPluginKey.getState(oldState);
596
+ var newPluginState = syncedBlockPluginKey.getState(newState);
597
+ var hadOrHasSyncedBlocks = !!(oldPluginState !== null && oldPluginState !== void 0 && oldPluginState.hasSyncedBlocks) || !!(newPluginState !== null && newPluginState !== void 0 && newPluginState.hasSyncedBlocks);
598
+ if (!hadOrHasSyncedBlocks) {
599
+ return null;
600
+ }
601
+ }
550
602
  var viewMode = api === null || api === void 0 || (_api$editorViewMode3 = api.editorViewMode) === null || _api$editorViewMode3 === void 0 || (_api$editorViewMode3 = _api$editorViewMode3.sharedState.currentState()) === null || _api$editorViewMode3 === void 0 ? void 0 : _api$editorViewMode3.mode;
551
603
  if (viewMode === 'view' && fg('platform_synced_block_patch_8')) {
552
604
  return null;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Cheap detector for the presence of any synced block (source or reference)
3
+ * at the top level of the document.
4
+ *
5
+ * Both `syncBlock` and `bodiedSyncBlock` are top-level nodes in the schema, so
6
+ * a single shallow scan is sufficient — no need for a recursive `descendants`
7
+ * walk. This is used by the `editor_synced_block_perf` experiment to avoid
8
+ * spinning up the synced block subsystem on documents that don't contain any
9
+ * synced blocks (which is ~99.97% of pages, see EDITOR-6586).
10
+ */
11
+ export var hasSyncedBlocks = function hasSyncedBlocks(doc) {
12
+ for (var i = 0; i < doc.childCount; i++) {
13
+ var node = doc.child(i);
14
+ if (node.type.name === 'syncBlock' || node.type.name === 'bodiedSyncBlock') {
15
+ return true;
16
+ }
17
+ }
18
+ return false;
19
+ };
@@ -0,0 +1,37 @@
1
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
2
+ 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; } }
3
+ 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; }
4
+ import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
5
+
6
+ /**
7
+ * Cheap step-level scan to detect whether the given transaction inserts a
8
+ * synced block (source or reference). Used by the lazy-init path to know when
9
+ * a previously "no synced blocks" document has gained one.
10
+ */
11
+ export var transactionInsertsSyncedBlock = function transactionInsertsSyncedBlock(tr) {
12
+ if (!tr.docChanged) {
13
+ return false;
14
+ }
15
+ var _iterator = _createForOfIteratorHelper(tr.steps),
16
+ _step;
17
+ try {
18
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
19
+ var step = _step.value;
20
+ if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep)) {
21
+ continue;
22
+ }
23
+ var childCount = step.slice.content.childCount;
24
+ for (var i = 0; i < childCount; i++) {
25
+ var child = step.slice.content.child(i);
26
+ if (child.type.name === 'syncBlock' || child.type.name === 'bodiedSyncBlock') {
27
+ return true;
28
+ }
29
+ }
30
+ }
31
+ } catch (err) {
32
+ _iterator.e(err);
33
+ } finally {
34
+ _iterator.f();
35
+ }
36
+ return false;
37
+ };
@@ -120,12 +120,14 @@ export var syncedBlockPlugin = function syncedBlockPlugin(_ref) {
120
120
  currentSyncBlockStore = _syncedBlockPluginKey.syncBlockStore,
121
121
  bodiedSyncBlockDeletionStatus = _syncedBlockPluginKey.bodiedSyncBlockDeletionStatus,
122
122
  retryCreationPosMap = _syncedBlockPluginKey.retryCreationPosMap,
123
+ hasSyncedBlocks = _syncedBlockPluginKey.hasSyncedBlocks,
123
124
  hasUnsavedBodiedSyncBlockChanges = _syncedBlockPluginKey.hasUnsavedBodiedSyncBlockChanges;
124
125
  return {
125
126
  activeFlag: activeFlag,
126
127
  syncBlockStore: currentSyncBlockStore,
127
128
  bodiedSyncBlockDeletionStatus: bodiedSyncBlockDeletionStatus,
128
129
  retryCreationPosMap: retryCreationPosMap,
130
+ hasSyncedBlocks: hasSyncedBlocks,
129
131
  hasUnsavedBodiedSyncBlockChanges: hasUnsavedBodiedSyncBlockChanges
130
132
  };
131
133
  }
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
- import { BLOCK_ACTIONS_MENU_SECTION, BLOCK_ACTIONS_MENU_SECTION_RANK, TRANSFORM_MENU_SECTION, TRANSFORM_MENU_SECTION_RANK, BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM } from '@atlaskit/editor-common/block-menu';
3
- import { fg } from '@atlaskit/platform-feature-flags';
2
+ import { TRANSFORM_MENU_SECTION, TRANSFORM_MENU_SECTION_RANK, BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM } from '@atlaskit/editor-common/block-menu';
4
3
  import { CreateOrCopySyncedBlockDropdownItem } from './CreateSyncedBlockDropdownItem';
5
4
  export var getBlockMenuComponents = function getBlockMenuComponents(api, enableSourceSyncedBlockCreation) {
6
5
  return [{
@@ -8,8 +7,8 @@ export var getBlockMenuComponents = function getBlockMenuComponents(api, enableS
8
7
  key: BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key,
9
8
  parent: {
10
9
  type: 'block-menu-section',
11
- key: fg('platform_editor_block_menu_divider_patch') ? TRANSFORM_MENU_SECTION.key : BLOCK_ACTIONS_MENU_SECTION.key,
12
- rank: fg('platform_editor_block_menu_divider_patch') ? TRANSFORM_MENU_SECTION_RANK[BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key] : BLOCK_ACTIONS_MENU_SECTION_RANK[BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key]
10
+ key: TRANSFORM_MENU_SECTION.key,
11
+ rank: TRANSFORM_MENU_SECTION_RANK[BLOCK_ACTIONS_CREATE_SYNCED_BLOCK_MENU_ITEM.key]
13
12
  },
14
13
  component: function component() {
15
14
  return /*#__PURE__*/React.createElement(CreateOrCopySyncedBlockDropdownItem, {
@@ -9,6 +9,17 @@ export declare const syncedBlockPluginKey: PluginKey;
9
9
  type SyncedBlockPluginState = {
10
10
  activeFlag: ActiveFlag;
11
11
  bodiedSyncBlockDeletionStatus?: BodiedSyncBlockDeletionStatus;
12
+ /**
13
+ * When `editor_synced_block_perf` is ON, this flag tracks whether the
14
+ * document currently contains any synced block (source or reference). When
15
+ * `false`, downstream work in `appendTransaction`, `decorations`, and the
16
+ * `contentComponent` short-circuits to avoid the per-transition feature tax
17
+ * on the ~99.97% of pages that have no synced blocks (see EDITOR-6586).
18
+ *
19
+ * When the gate is OFF this is always `true` so existing behavior is
20
+ * preserved.
21
+ */
22
+ hasSyncedBlocks: boolean;
12
23
  hasUnsavedBodiedSyncBlockChanges?: boolean;
13
24
  retryCreationPosMap: RetryCreationPosMap;
14
25
  selectionDecorationSet: DecorationSet;
@@ -0,0 +1,12 @@
1
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
+ /**
3
+ * Cheap detector for the presence of any synced block (source or reference)
4
+ * at the top level of the document.
5
+ *
6
+ * Both `syncBlock` and `bodiedSyncBlock` are top-level nodes in the schema, so
7
+ * a single shallow scan is sufficient — no need for a recursive `descendants`
8
+ * walk. This is used by the `editor_synced_block_perf` experiment to avoid
9
+ * spinning up the synced block subsystem on documents that don't contain any
10
+ * synced blocks (which is ~99.97% of pages, see EDITOR-6586).
11
+ */
12
+ export declare const hasSyncedBlocks: (doc: PMNode) => boolean;
@@ -0,0 +1,7 @@
1
+ import type { ReadonlyTransaction, Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ /**
3
+ * Cheap step-level scan to detect whether the given transaction inserts a
4
+ * synced block (source or reference). Used by the lazy-init path to know when
5
+ * a previously "no synced blocks" document has gained one.
6
+ */
7
+ export declare const transactionInsertsSyncedBlock: (tr: ReadonlyTransaction | Transaction) => boolean;
@@ -38,6 +38,13 @@ export type SyncedBlockSharedState = {
38
38
  * Whether the plugin is currently saving bodiedSyncBlock deletion to backend
39
39
  */
40
40
  bodiedSyncBlockDeletionStatus?: BodiedSyncBlockDeletionStatus;
41
+ /**
42
+ * Whether the document currently contains any synced block (source or
43
+ * reference). Sticky once flipped to `true` for the lifetime of the
44
+ * editor session. When `editor_synced_block_perf` is OFF this is
45
+ * always `true`.
46
+ */
47
+ hasSyncedBlocks: boolean;
41
48
  /**
42
49
  * Whether there are unsaved bodiedSyncBlock changes in the cache
43
50
  */
@@ -9,6 +9,17 @@ export declare const syncedBlockPluginKey: PluginKey;
9
9
  type SyncedBlockPluginState = {
10
10
  activeFlag: ActiveFlag;
11
11
  bodiedSyncBlockDeletionStatus?: BodiedSyncBlockDeletionStatus;
12
+ /**
13
+ * When `editor_synced_block_perf` is ON, this flag tracks whether the
14
+ * document currently contains any synced block (source or reference). When
15
+ * `false`, downstream work in `appendTransaction`, `decorations`, and the
16
+ * `contentComponent` short-circuits to avoid the per-transition feature tax
17
+ * on the ~99.97% of pages that have no synced blocks (see EDITOR-6586).
18
+ *
19
+ * When the gate is OFF this is always `true` so existing behavior is
20
+ * preserved.
21
+ */
22
+ hasSyncedBlocks: boolean;
12
23
  hasUnsavedBodiedSyncBlockChanges?: boolean;
13
24
  retryCreationPosMap: RetryCreationPosMap;
14
25
  selectionDecorationSet: DecorationSet;
@@ -0,0 +1,12 @@
1
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
+ /**
3
+ * Cheap detector for the presence of any synced block (source or reference)
4
+ * at the top level of the document.
5
+ *
6
+ * Both `syncBlock` and `bodiedSyncBlock` are top-level nodes in the schema, so
7
+ * a single shallow scan is sufficient — no need for a recursive `descendants`
8
+ * walk. This is used by the `editor_synced_block_perf` experiment to avoid
9
+ * spinning up the synced block subsystem on documents that don't contain any
10
+ * synced blocks (which is ~99.97% of pages, see EDITOR-6586).
11
+ */
12
+ export declare const hasSyncedBlocks: (doc: PMNode) => boolean;
@@ -0,0 +1,7 @@
1
+ import type { ReadonlyTransaction, Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ /**
3
+ * Cheap step-level scan to detect whether the given transaction inserts a
4
+ * synced block (source or reference). Used by the lazy-init path to know when
5
+ * a previously "no synced blocks" document has gained one.
6
+ */
7
+ export declare const transactionInsertsSyncedBlock: (tr: ReadonlyTransaction | Transaction) => boolean;
@@ -38,6 +38,13 @@ export type SyncedBlockSharedState = {
38
38
  * Whether the plugin is currently saving bodiedSyncBlock deletion to backend
39
39
  */
40
40
  bodiedSyncBlockDeletionStatus?: BodiedSyncBlockDeletionStatus;
41
+ /**
42
+ * Whether the document currently contains any synced block (source or
43
+ * reference). Sticky once flipped to `true` for the lifetime of the
44
+ * editor session. When `editor_synced_block_perf` is OFF this is
45
+ * always `true`.
46
+ */
47
+ hasSyncedBlocks: boolean;
41
48
  /**
42
49
  * Whether there are unsaved bodiedSyncBlock changes in the cache
43
50
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-synced-block",
3
- "version": "8.2.3",
3
+ "version": "8.2.4",
4
4
  "description": "SyncedBlock plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.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": "^75.0.0",
57
+ "@atlaskit/tmp-editor-statsig": "^75.2.0",
58
58
  "@atlaskit/tokens": "13.0.3",
59
59
  "@atlaskit/tooltip": "^22.0.0",
60
60
  "@atlaskit/visually-hidden": "^3.1.0",
@@ -115,9 +115,6 @@
115
115
  "platform_synced_block_fix_experience_tracking": {
116
116
  "type": "boolean"
117
117
  },
118
- "platform_editor_block_menu_divider_patch": {
119
- "type": "boolean"
120
- },
121
118
  "platform_synced_block_update_refactor": {
122
119
  "type": "boolean"
123
120
  },