@atlaskit/editor-plugin-synced-block 8.2.3 → 8.2.5
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/AGENTS.md +36 -4
- package/CHANGELOG.md +17 -0
- package/dist/cjs/pm-plugins/main.js +66 -14
- package/dist/cjs/pm-plugins/utils/has-synced-blocks.js +25 -0
- package/dist/cjs/pm-plugins/utils/transaction-inserts-synced-block.js +42 -0
- package/dist/cjs/syncedBlockPlugin.js +2 -0
- package/dist/cjs/ui/block-menu-components.js +2 -3
- package/dist/es2019/pm-plugins/main.js +66 -14
- package/dist/es2019/pm-plugins/utils/has-synced-blocks.js +19 -0
- package/dist/es2019/pm-plugins/utils/transaction-inserts-synced-block.js +25 -0
- package/dist/es2019/syncedBlockPlugin.js +2 -0
- package/dist/es2019/ui/block-menu-components.js +3 -4
- package/dist/esm/pm-plugins/main.js +66 -14
- package/dist/esm/pm-plugins/utils/has-synced-blocks.js +19 -0
- package/dist/esm/pm-plugins/utils/transaction-inserts-synced-block.js +37 -0
- package/dist/esm/syncedBlockPlugin.js +2 -0
- package/dist/esm/ui/block-menu-components.js +3 -4
- package/dist/types/pm-plugins/main.d.ts +11 -0
- package/dist/types/pm-plugins/utils/has-synced-blocks.d.ts +12 -0
- package/dist/types/pm-plugins/utils/transaction-inserts-synced-block.d.ts +7 -0
- package/dist/types/types/index.d.ts +7 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +11 -0
- package/dist/types-ts4.5/pm-plugins/utils/has-synced-blocks.d.ts +12 -0
- package/dist/types-ts4.5/pm-plugins/utils/transaction-inserts-synced-block.d.ts +7 -0
- package/dist/types-ts4.5/types/index.d.ts +7 -0
- package/package.json +3 -6
package/AGENTS.md
CHANGED
|
@@ -26,20 +26,31 @@ src/
|
|
|
26
26
|
├── index.ts # Re-exports plugin + type
|
|
27
27
|
├── syncedBlockPlugin.tsx # Top-level: registers nodes, commands, UI, pm-plugins
|
|
28
28
|
├── syncedBlockPluginType.ts # TypeScript interfaces for options, shared state, dependencies
|
|
29
|
+
├── editor-actions/
|
|
30
|
+
│ └── index.ts # flushBodiedSyncBlocks, flushSyncBlocks,
|
|
31
|
+
│ discardUnpublishedSyncBlocks (EDITOR-6473)
|
|
29
32
|
├── editor-commands/
|
|
30
33
|
│ └── index.ts # createSyncedBlock, copySyncedBlockReferenceToClipboardEditorCommand,
|
|
31
34
|
│ removeSyncedBlockAtPos, unsyncSyncBlock
|
|
32
35
|
├── nodeviews/
|
|
33
36
|
│ ├── syncedBlock.tsx # NodeView for reference (syncBlock) — read-only, fetches from BE
|
|
37
|
+
│ ├── lazySyncedBlock.tsx # Lazy-loaded wrapper for syncedBlock (EDITOR-6928)
|
|
34
38
|
│ ├── bodiedSyncedBlock.tsx # NodeView for source (bodiedSyncBlock) — nested editor with content
|
|
35
39
|
│ └── bodiedSyncBlockNodeWithToDOMFixed.ts # DOM serialization fix variant (experiment-gated)
|
|
36
40
|
├── pm-plugins/
|
|
37
|
-
│ ├── main.ts # Core state machine:
|
|
41
|
+
│ ├── main.ts # Core state machine: lifecycle, creation, deletion, cache,
|
|
42
|
+
│ │ status decoration apply path (gated by editor_synced_block_perf)
|
|
38
43
|
│ ├── menu-and-toolbar-experiences.ts # Experience tracking for menu/toolbar interactions
|
|
39
44
|
│ └── utils/
|
|
40
|
-
│ ├── track-sync-blocks.ts
|
|
41
|
-
│ ├── handle-bodied-sync-block-creation.ts
|
|
42
|
-
│
|
|
45
|
+
│ ├── track-sync-blocks.ts # Tracks mutations, updates shared state
|
|
46
|
+
│ ├── handle-bodied-sync-block-creation.ts # Creation flow, local cache, retry logic
|
|
47
|
+
│ ├── handle-bodied-sync-block-removal.ts # Deletion flow, BE synchronization
|
|
48
|
+
│ ├── has-synced-blocks.ts # O(childCount) presence check (EDITOR-6928 lazy init)
|
|
49
|
+
│ ├── transaction-inserts-synced-block.ts # Detect tr inserts a synced block (lazy init)
|
|
50
|
+
│ ├── selection-decorations.ts # Selection decoration helpers
|
|
51
|
+
│ ├── rebase-transaction.ts # Rebase helpers used by main.ts
|
|
52
|
+
│ ├── ignore-dom-event.ts # DOM event guard
|
|
53
|
+
│ └── utils.ts # Misc shared helpers
|
|
43
54
|
├── ui/
|
|
44
55
|
│ ├── toolbar-components.tsx # Primary toolbar button ("Create Synced Block")
|
|
45
56
|
│ ├── floating-toolbar.tsx # Node-level actions: delete, unsync, copy link, view locations
|
|
@@ -52,6 +63,27 @@ src/
|
|
|
52
63
|
└── index.ts # FLAG_ID, SyncedBlockSharedState, BodiedSyncBlockDeletionStatus
|
|
53
64
|
```
|
|
54
65
|
|
|
66
|
+
### Editor Actions
|
|
67
|
+
|
|
68
|
+
This package exposes top-level **editor actions** (in `editor-actions/index.ts`)
|
|
69
|
+
that products call from outside the plugin lifecycle:
|
|
70
|
+
|
|
71
|
+
- `flushBodiedSyncBlocks(store)` — flush all dirty source blocks
|
|
72
|
+
- `flushSyncBlocks(store)` — flush reference manager (e.g. on save)
|
|
73
|
+
- `discardUnpublishedSyncBlocks(store)` — delete unpublished blocks on cancel
|
|
74
|
+
(added in EDITOR-6473; used by Confluence's editor cancel flow)
|
|
75
|
+
|
|
76
|
+
### Lazy Init & Perf (EDITOR-6928 / EDITOR-6930)
|
|
77
|
+
|
|
78
|
+
Behind the `editor_synced_block_perf` experiment, `main.ts`:
|
|
79
|
+
- Skips creating synced-block plugin state and node-views for documents
|
|
80
|
+
with no synced blocks (`hasSyncedBlocks(doc)`).
|
|
81
|
+
- Computes `statusDecorationSet` inside `apply()` and stores it on plugin
|
|
82
|
+
state, then exposes it via an O(1) `decorations` prop instead of an
|
|
83
|
+
O(n) `doc.descendants()` walk on every transaction.
|
|
84
|
+
- Uses `sourceSyncBlockStoreManager.hasPendingCreations()` for an O(1)
|
|
85
|
+
pending-creation early return in `buildStatusDecorations()`.
|
|
86
|
+
|
|
55
87
|
### Key Code Patterns
|
|
56
88
|
|
|
57
89
|
**Creating a sync block** (flow through the code):
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-synced-block
|
|
2
2
|
|
|
3
|
+
## 8.2.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
|
|
9
|
+
## 8.2.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`ca8aefc573cc5`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ca8aefc573cc5) -
|
|
14
|
+
Clean up feature gates `platform_editor_block_menu_divider_patch` and
|
|
15
|
+
`platform_editor_block_menu_copy_section` (both rolled out as true).
|
|
16
|
+
- [`826bc966b7b64`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/826bc966b7b64) -
|
|
17
|
+
ED-6586: Lazy init sync block
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
|
|
3
20
|
## 8.2.3
|
|
4
21
|
|
|
5
22
|
### 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
|
-
|
|
283
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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:
|
|
19
|
-
rank:
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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 {
|
|
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:
|
|
12
|
-
rank:
|
|
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
|
-
|
|
276
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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 {
|
|
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:
|
|
12
|
-
rank:
|
|
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
|
+
"version": "8.2.5",
|
|
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": "^
|
|
57
|
+
"@atlaskit/tmp-editor-statsig": "^76.0.0",
|
|
58
58
|
"@atlaskit/tokens": "13.0.3",
|
|
59
59
|
"@atlaskit/tooltip": "^22.0.0",
|
|
60
60
|
"@atlaskit/visually-hidden": "^3.1.0",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"date-fns": "^2.17.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@atlaskit/editor-common": "^114.
|
|
67
|
+
"@atlaskit/editor-common": "^114.20.0",
|
|
68
68
|
"react": "^18.2.0",
|
|
69
69
|
"react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
|
|
70
70
|
},
|
|
@@ -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
|
},
|