@atlaskit/editor-plugin-synced-block 10.5.0 → 10.6.1
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 +2 -2
- package/CHANGELOG.md +25 -0
- package/dist/cjs/editor-commands/index.js +6 -0
- package/dist/cjs/pm-plugins/utils/handle-bodied-sync-block-removal.js +9 -1
- package/dist/cjs/pm-plugins/utils/recompute-delete-transaction.js +66 -1
- package/dist/es2019/editor-commands/index.js +6 -0
- package/dist/es2019/pm-plugins/utils/handle-bodied-sync-block-removal.js +8 -2
- package/dist/es2019/pm-plugins/utils/recompute-delete-transaction.js +52 -0
- package/dist/esm/editor-commands/index.js +6 -0
- package/dist/esm/pm-plugins/utils/handle-bodied-sync-block-removal.js +10 -2
- package/dist/esm/pm-plugins/utils/recompute-delete-transaction.js +65 -0
- package/dist/types/pm-plugins/utils/recompute-delete-transaction.d.ts +29 -0
- package/package.json +7 -4
- package/types/package.json +8 -0
package/AGENTS.md
CHANGED
|
@@ -98,8 +98,8 @@ Behind the `editor_synced_block_perf` experiment, `main.ts`:
|
|
|
98
98
|
2. Calls `editor-commands/createSyncedBlock` → inserts `bodiedSyncBlock` node into document (marked
|
|
99
99
|
as **pending creation** — it is not persisted yet)
|
|
100
100
|
3. `pm-plugins/main.ts` detects new node → `handle-bodied-sync-block-creation.ts` updates
|
|
101
|
-
`sourceManager` state. Persistence happens later when the product layer calls `flush()`
|
|
102
|
-
|
|
101
|
+
`sourceManager` state. Persistence happens later when the product layer calls `flush()` (via
|
|
102
|
+
`flushBodiedSyncBlocks`), which creates the block in the Block Service and then reconciles
|
|
103
103
|
identifiers via `commitPendingCreation()`
|
|
104
104
|
4. `menu-and-toolbar-experiences.ts` fires the experience event
|
|
105
105
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-synced-block
|
|
2
2
|
|
|
3
|
+
## 10.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
|
|
9
|
+
## 10.6.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [`564443b22eeeb`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/564443b22eeeb) -
|
|
14
|
+
Fix source synced block unsync deleting the entire block and its content instead of unwrapping it
|
|
15
|
+
when the cursor is inside the block (behind `platform_editor_blocks_patch_6`).
|
|
16
|
+
|
|
17
|
+
This release also adds a new `./types` subpath export so consumers can import the `SyncBlockAttrs`
|
|
18
|
+
type without reaching into the package internals:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import type { SyncBlockAttrs } from '@atlaskit/editor-plugin-synced-block/types';
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- Updated dependencies
|
|
27
|
+
|
|
3
28
|
## 10.5.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
|
@@ -379,6 +379,12 @@ var unsync = exports.unsync = function unsync(storeManager, isBodiedSyncBlock, v
|
|
|
379
379
|
return false;
|
|
380
380
|
}
|
|
381
381
|
if (isBodiedSyncBlock) {
|
|
382
|
+
// Signal the unsync intent. This transaction is intercepted by the plugin's
|
|
383
|
+
// filterTransaction, which shows the deletion-confirmation modal and, on confirm,
|
|
384
|
+
// recomputes the actual document change from the live document keyed off the
|
|
385
|
+
// `deletionReason: 'source-block-unsynced'` meta (see handleBodiedSyncBlockRemoval /
|
|
386
|
+
// recomputeUnsyncTransaction). The real unwrap-vs-delete decision therefore lives in the
|
|
387
|
+
// confirm path, not here (EDITOR-8230).
|
|
382
388
|
var content = syncBlock === null || syncBlock === void 0 ? void 0 : syncBlock.node.content;
|
|
383
389
|
var tr = state.tr;
|
|
384
390
|
tr.replaceWith(syncBlock.pos, syncBlock.pos + syncBlock.node.nodeSize, content).setMeta('deletionReason', 'source-block-unsynced');
|
|
@@ -56,7 +56,15 @@ var handleBodiedSyncBlockRemoval = exports.handleBodiedSyncBlockRemoval = functi
|
|
|
56
56
|
// "Invalid content for node bodiedSyncBlock: <>". See EDITOR-7889.
|
|
57
57
|
api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(function (_ref2) {
|
|
58
58
|
var tr = _ref2.tr;
|
|
59
|
-
|
|
59
|
+
// EDITOR-8230: unsync must remove only the sync wrapper and keep the block's
|
|
60
|
+
// content inline, whereas delete removes the whole block. Before this branch,
|
|
61
|
+
// the confirm path always recomputed a full delete, so unsyncing a source block
|
|
62
|
+
// silently deleted its content. When the deletion was actually triggered by
|
|
63
|
+
// unsync, recompute an unwrap (replaceWith content) instead of a delete.
|
|
64
|
+
var isUnsync = (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_6') && deletionReason === 'source-block-unsynced';
|
|
65
|
+
var recomputedTr = isUnsync ? (0, _recomputeDeleteTransaction.recomputeUnsyncTransaction)(tr, syncBlockStore.sourceManager.isSourceBlock, bodiedSyncBlockRemoved.map(function (node) {
|
|
66
|
+
return node.attrs;
|
|
67
|
+
})) : (0, _recomputeDeleteTransaction.recomputeDeleteTransaction)(tr, syncBlockStore.sourceManager.isSourceBlock, bodiedSyncBlockRemoved.map(function (node) {
|
|
60
68
|
return node.attrs;
|
|
61
69
|
}));
|
|
62
70
|
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.recomputeDeleteTransaction = void 0;
|
|
6
|
+
exports.recomputeUnsyncTransaction = exports.recomputeDeleteTransaction = void 0;
|
|
7
|
+
var _model = require("@atlaskit/editor-prosemirror/model");
|
|
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; }
|
|
7
11
|
/**
|
|
8
12
|
* Whether `node` is the source block requested by `wanted`.
|
|
9
13
|
*
|
|
@@ -95,4 +99,65 @@ var recomputeDeleteTransaction = exports.recomputeDeleteTransaction = function r
|
|
|
95
99
|
tr.delete(pos, pos + node.nodeSize);
|
|
96
100
|
});
|
|
97
101
|
return tr;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Recompute a source `bodiedSyncBlock` *unsync* from the live document.
|
|
106
|
+
*
|
|
107
|
+
* Unsync differs from delete: the sync wrapper must be removed while its content
|
|
108
|
+
* is preserved inline in the document. Instead of `tr.delete(pos, pos +
|
|
109
|
+
* nodeSize)` (which drops the content too — EDITOR-8230), this replaces each
|
|
110
|
+
* matched source block with its own content.
|
|
111
|
+
*
|
|
112
|
+
* The replacement uses a raw `tr.replace(pos, pos + nodeSize, new Slice(content,
|
|
113
|
+
* 0, 0))` — an explicit zero-open slice inserted verbatim — rather than
|
|
114
|
+
* `tr.replaceWith(...)`. `replaceWith` routes through ProseMirror's
|
|
115
|
+
* `replaceRange`, whose range-fitting heuristics can collapse or drop a trailing
|
|
116
|
+
* block (e.g. the block's final panel/empty paragraph) when the unwrapped
|
|
117
|
+
* content meets a document boundary, silently losing content (EDITOR-8230). A
|
|
118
|
+
* zero-open `replace` drops the wrapper's own open ends while keeping every child
|
|
119
|
+
* of the fragment as its own top-level node.
|
|
120
|
+
*
|
|
121
|
+
* Like `recomputeDeleteTransaction`, targets are located in the *current*
|
|
122
|
+
* document (`tr.doc`) so the operation is robust to intervening local edits and
|
|
123
|
+
* remote collab changes while the confirmation modal is open, and it handles
|
|
124
|
+
* partial matches gracefully (missing blocks are skipped).
|
|
125
|
+
*
|
|
126
|
+
* Replacements are applied in reverse document order so earlier replacements do
|
|
127
|
+
* not invalidate the positions of later ones.
|
|
128
|
+
*
|
|
129
|
+
* @returns the mutated transaction when at least one target node was found and
|
|
130
|
+
* unwrapped, otherwise `undefined` when none of the targets exist any more.
|
|
131
|
+
*/
|
|
132
|
+
var recomputeUnsyncTransaction = exports.recomputeUnsyncTransaction = function recomputeUnsyncTransaction(tr, isSourceBlock, syncBlockIds) {
|
|
133
|
+
var matches = findSourceBlocks(tr, isSourceBlock, syncBlockIds);
|
|
134
|
+
if (matches.length === 0) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Unwrap in reverse document order so positions remain valid across edits.
|
|
139
|
+
// Use a raw zero-open Slice replace (not `replaceWith`) so the block's content
|
|
140
|
+
// is inserted verbatim as top-level nodes without ProseMirror's replaceRange
|
|
141
|
+
// heuristics collapsing a trailing block (EDITOR-8230).
|
|
142
|
+
var orderedMatches = matches.sort(function (a, b) {
|
|
143
|
+
return b.pos - a.pos;
|
|
144
|
+
});
|
|
145
|
+
var _iterator = _createForOfIteratorHelper(orderedMatches),
|
|
146
|
+
_step;
|
|
147
|
+
try {
|
|
148
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
149
|
+
var _step$value = _step.value,
|
|
150
|
+
node = _step$value.node,
|
|
151
|
+
pos = _step$value.pos;
|
|
152
|
+
// This is ProseMirror's Transaction.replace (a document edit), not String.replace — the
|
|
153
|
+
// perf rule misfires on the method name, and there is nothing to hoist.
|
|
154
|
+
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace
|
|
155
|
+
tr.replace(pos, pos + node.nodeSize, new _model.Slice(node.content, 0, 0));
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
_iterator.e(err);
|
|
159
|
+
} finally {
|
|
160
|
+
_iterator.f();
|
|
161
|
+
}
|
|
162
|
+
return tr;
|
|
98
163
|
};
|
|
@@ -381,6 +381,12 @@ export const unsync = (storeManager, isBodiedSyncBlock, view) => {
|
|
|
381
381
|
return false;
|
|
382
382
|
}
|
|
383
383
|
if (isBodiedSyncBlock) {
|
|
384
|
+
// Signal the unsync intent. This transaction is intercepted by the plugin's
|
|
385
|
+
// filterTransaction, which shows the deletion-confirmation modal and, on confirm,
|
|
386
|
+
// recomputes the actual document change from the live document keyed off the
|
|
387
|
+
// `deletionReason: 'source-block-unsynced'` meta (see handleBodiedSyncBlockRemoval /
|
|
388
|
+
// recomputeUnsyncTransaction). The real unwrap-vs-delete decision therefore lives in the
|
|
389
|
+
// confirm path, not here (EDITOR-8230).
|
|
384
390
|
const content = syncBlock === null || syncBlock === void 0 ? void 0 : syncBlock.node.content;
|
|
385
391
|
const {
|
|
386
392
|
tr
|
|
@@ -2,7 +2,7 @@ import { pmHistoryPluginKey } from '@atlaskit/editor-common/utils';
|
|
|
2
2
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
3
3
|
import { FLAG_ID } from '../../types';
|
|
4
4
|
import { syncedBlockPluginKey } from '../main';
|
|
5
|
-
import { recomputeDeleteTransaction } from './recompute-delete-transaction';
|
|
5
|
+
import { recomputeDeleteTransaction, recomputeUnsyncTransaction } from './recompute-delete-transaction';
|
|
6
6
|
const onRetry = (api, syncBlockStore) => () => {
|
|
7
7
|
var _api$core;
|
|
8
8
|
api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
|
|
@@ -42,7 +42,13 @@ export const handleBodiedSyncBlockRemoval = (bodiedSyncBlockRemoved, syncBlockSt
|
|
|
42
42
|
api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(({
|
|
43
43
|
tr
|
|
44
44
|
}) => {
|
|
45
|
-
|
|
45
|
+
// EDITOR-8230: unsync must remove only the sync wrapper and keep the block's
|
|
46
|
+
// content inline, whereas delete removes the whole block. Before this branch,
|
|
47
|
+
// the confirm path always recomputed a full delete, so unsyncing a source block
|
|
48
|
+
// silently deleted its content. When the deletion was actually triggered by
|
|
49
|
+
// unsync, recompute an unwrap (replaceWith content) instead of a delete.
|
|
50
|
+
const isUnsync = fg('platform_editor_blocks_patch_6') && deletionReason === 'source-block-unsynced';
|
|
51
|
+
const recomputedTr = isUnsync ? recomputeUnsyncTransaction(tr, syncBlockStore.sourceManager.isSourceBlock, bodiedSyncBlockRemoved.map(node => node.attrs)) : recomputeDeleteTransaction(tr, syncBlockStore.sourceManager.isSourceBlock, bodiedSyncBlockRemoved.map(node => node.attrs));
|
|
46
52
|
|
|
47
53
|
// The target node(s) no longer exist in the live document (e.g.
|
|
48
54
|
// a remote collaborator already removed them). There is nothing
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Slice } from '@atlaskit/editor-prosemirror/model';
|
|
1
2
|
/**
|
|
2
3
|
* Whether `node` is the source block requested by `wanted`.
|
|
3
4
|
*
|
|
@@ -87,4 +88,55 @@ export const recomputeDeleteTransaction = (tr, isSourceBlock, syncBlockIds) => {
|
|
|
87
88
|
tr.delete(pos, pos + node.nodeSize);
|
|
88
89
|
});
|
|
89
90
|
return tr;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Recompute a source `bodiedSyncBlock` *unsync* from the live document.
|
|
95
|
+
*
|
|
96
|
+
* Unsync differs from delete: the sync wrapper must be removed while its content
|
|
97
|
+
* is preserved inline in the document. Instead of `tr.delete(pos, pos +
|
|
98
|
+
* nodeSize)` (which drops the content too — EDITOR-8230), this replaces each
|
|
99
|
+
* matched source block with its own content.
|
|
100
|
+
*
|
|
101
|
+
* The replacement uses a raw `tr.replace(pos, pos + nodeSize, new Slice(content,
|
|
102
|
+
* 0, 0))` — an explicit zero-open slice inserted verbatim — rather than
|
|
103
|
+
* `tr.replaceWith(...)`. `replaceWith` routes through ProseMirror's
|
|
104
|
+
* `replaceRange`, whose range-fitting heuristics can collapse or drop a trailing
|
|
105
|
+
* block (e.g. the block's final panel/empty paragraph) when the unwrapped
|
|
106
|
+
* content meets a document boundary, silently losing content (EDITOR-8230). A
|
|
107
|
+
* zero-open `replace` drops the wrapper's own open ends while keeping every child
|
|
108
|
+
* of the fragment as its own top-level node.
|
|
109
|
+
*
|
|
110
|
+
* Like `recomputeDeleteTransaction`, targets are located in the *current*
|
|
111
|
+
* document (`tr.doc`) so the operation is robust to intervening local edits and
|
|
112
|
+
* remote collab changes while the confirmation modal is open, and it handles
|
|
113
|
+
* partial matches gracefully (missing blocks are skipped).
|
|
114
|
+
*
|
|
115
|
+
* Replacements are applied in reverse document order so earlier replacements do
|
|
116
|
+
* not invalidate the positions of later ones.
|
|
117
|
+
*
|
|
118
|
+
* @returns the mutated transaction when at least one target node was found and
|
|
119
|
+
* unwrapped, otherwise `undefined` when none of the targets exist any more.
|
|
120
|
+
*/
|
|
121
|
+
export const recomputeUnsyncTransaction = (tr, isSourceBlock, syncBlockIds) => {
|
|
122
|
+
const matches = findSourceBlocks(tr, isSourceBlock, syncBlockIds);
|
|
123
|
+
if (matches.length === 0) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Unwrap in reverse document order so positions remain valid across edits.
|
|
128
|
+
// Use a raw zero-open Slice replace (not `replaceWith`) so the block's content
|
|
129
|
+
// is inserted verbatim as top-level nodes without ProseMirror's replaceRange
|
|
130
|
+
// heuristics collapsing a trailing block (EDITOR-8230).
|
|
131
|
+
const orderedMatches = matches.sort((a, b) => b.pos - a.pos);
|
|
132
|
+
for (const {
|
|
133
|
+
node,
|
|
134
|
+
pos
|
|
135
|
+
} of orderedMatches) {
|
|
136
|
+
// This is ProseMirror's Transaction.replace (a document edit), not String.replace — the
|
|
137
|
+
// perf rule misfires on the method name, and there is nothing to hoist.
|
|
138
|
+
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace
|
|
139
|
+
tr.replace(pos, pos + node.nodeSize, new Slice(node.content, 0, 0));
|
|
140
|
+
}
|
|
141
|
+
return tr;
|
|
90
142
|
};
|
|
@@ -372,6 +372,12 @@ export var unsync = function unsync(storeManager, isBodiedSyncBlock, view) {
|
|
|
372
372
|
return false;
|
|
373
373
|
}
|
|
374
374
|
if (isBodiedSyncBlock) {
|
|
375
|
+
// Signal the unsync intent. This transaction is intercepted by the plugin's
|
|
376
|
+
// filterTransaction, which shows the deletion-confirmation modal and, on confirm,
|
|
377
|
+
// recomputes the actual document change from the live document keyed off the
|
|
378
|
+
// `deletionReason: 'source-block-unsynced'` meta (see handleBodiedSyncBlockRemoval /
|
|
379
|
+
// recomputeUnsyncTransaction). The real unwrap-vs-delete decision therefore lives in the
|
|
380
|
+
// confirm path, not here (EDITOR-8230).
|
|
375
381
|
var content = syncBlock === null || syncBlock === void 0 ? void 0 : syncBlock.node.content;
|
|
376
382
|
var tr = state.tr;
|
|
377
383
|
tr.replaceWith(syncBlock.pos, syncBlock.pos + syncBlock.node.nodeSize, content).setMeta('deletionReason', 'source-block-unsynced');
|
|
@@ -5,7 +5,7 @@ import { pmHistoryPluginKey } from '@atlaskit/editor-common/utils';
|
|
|
5
5
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
6
6
|
import { FLAG_ID } from '../../types';
|
|
7
7
|
import { syncedBlockPluginKey } from '../main';
|
|
8
|
-
import { recomputeDeleteTransaction } from './recompute-delete-transaction';
|
|
8
|
+
import { recomputeDeleteTransaction, recomputeUnsyncTransaction } from './recompute-delete-transaction';
|
|
9
9
|
var onRetry = function onRetry(api, syncBlockStore) {
|
|
10
10
|
return function () {
|
|
11
11
|
var _api$core;
|
|
@@ -49,7 +49,15 @@ export var handleBodiedSyncBlockRemoval = function handleBodiedSyncBlockRemoval(
|
|
|
49
49
|
// "Invalid content for node bodiedSyncBlock: <>". See EDITOR-7889.
|
|
50
50
|
api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(function (_ref2) {
|
|
51
51
|
var tr = _ref2.tr;
|
|
52
|
-
|
|
52
|
+
// EDITOR-8230: unsync must remove only the sync wrapper and keep the block's
|
|
53
|
+
// content inline, whereas delete removes the whole block. Before this branch,
|
|
54
|
+
// the confirm path always recomputed a full delete, so unsyncing a source block
|
|
55
|
+
// silently deleted its content. When the deletion was actually triggered by
|
|
56
|
+
// unsync, recompute an unwrap (replaceWith content) instead of a delete.
|
|
57
|
+
var isUnsync = fg('platform_editor_blocks_patch_6') && deletionReason === 'source-block-unsynced';
|
|
58
|
+
var recomputedTr = isUnsync ? recomputeUnsyncTransaction(tr, syncBlockStore.sourceManager.isSourceBlock, bodiedSyncBlockRemoved.map(function (node) {
|
|
59
|
+
return node.attrs;
|
|
60
|
+
})) : recomputeDeleteTransaction(tr, syncBlockStore.sourceManager.isSourceBlock, bodiedSyncBlockRemoved.map(function (node) {
|
|
53
61
|
return node.attrs;
|
|
54
62
|
}));
|
|
55
63
|
|
|
@@ -1,3 +1,7 @@
|
|
|
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 { Slice } from '@atlaskit/editor-prosemirror/model';
|
|
1
5
|
/**
|
|
2
6
|
* Whether `node` is the source block requested by `wanted`.
|
|
3
7
|
*
|
|
@@ -89,4 +93,65 @@ export var recomputeDeleteTransaction = function recomputeDeleteTransaction(tr,
|
|
|
89
93
|
tr.delete(pos, pos + node.nodeSize);
|
|
90
94
|
});
|
|
91
95
|
return tr;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Recompute a source `bodiedSyncBlock` *unsync* from the live document.
|
|
100
|
+
*
|
|
101
|
+
* Unsync differs from delete: the sync wrapper must be removed while its content
|
|
102
|
+
* is preserved inline in the document. Instead of `tr.delete(pos, pos +
|
|
103
|
+
* nodeSize)` (which drops the content too — EDITOR-8230), this replaces each
|
|
104
|
+
* matched source block with its own content.
|
|
105
|
+
*
|
|
106
|
+
* The replacement uses a raw `tr.replace(pos, pos + nodeSize, new Slice(content,
|
|
107
|
+
* 0, 0))` — an explicit zero-open slice inserted verbatim — rather than
|
|
108
|
+
* `tr.replaceWith(...)`. `replaceWith` routes through ProseMirror's
|
|
109
|
+
* `replaceRange`, whose range-fitting heuristics can collapse or drop a trailing
|
|
110
|
+
* block (e.g. the block's final panel/empty paragraph) when the unwrapped
|
|
111
|
+
* content meets a document boundary, silently losing content (EDITOR-8230). A
|
|
112
|
+
* zero-open `replace` drops the wrapper's own open ends while keeping every child
|
|
113
|
+
* of the fragment as its own top-level node.
|
|
114
|
+
*
|
|
115
|
+
* Like `recomputeDeleteTransaction`, targets are located in the *current*
|
|
116
|
+
* document (`tr.doc`) so the operation is robust to intervening local edits and
|
|
117
|
+
* remote collab changes while the confirmation modal is open, and it handles
|
|
118
|
+
* partial matches gracefully (missing blocks are skipped).
|
|
119
|
+
*
|
|
120
|
+
* Replacements are applied in reverse document order so earlier replacements do
|
|
121
|
+
* not invalidate the positions of later ones.
|
|
122
|
+
*
|
|
123
|
+
* @returns the mutated transaction when at least one target node was found and
|
|
124
|
+
* unwrapped, otherwise `undefined` when none of the targets exist any more.
|
|
125
|
+
*/
|
|
126
|
+
export var recomputeUnsyncTransaction = function recomputeUnsyncTransaction(tr, isSourceBlock, syncBlockIds) {
|
|
127
|
+
var matches = findSourceBlocks(tr, isSourceBlock, syncBlockIds);
|
|
128
|
+
if (matches.length === 0) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Unwrap in reverse document order so positions remain valid across edits.
|
|
133
|
+
// Use a raw zero-open Slice replace (not `replaceWith`) so the block's content
|
|
134
|
+
// is inserted verbatim as top-level nodes without ProseMirror's replaceRange
|
|
135
|
+
// heuristics collapsing a trailing block (EDITOR-8230).
|
|
136
|
+
var orderedMatches = matches.sort(function (a, b) {
|
|
137
|
+
return b.pos - a.pos;
|
|
138
|
+
});
|
|
139
|
+
var _iterator = _createForOfIteratorHelper(orderedMatches),
|
|
140
|
+
_step;
|
|
141
|
+
try {
|
|
142
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
143
|
+
var _step$value = _step.value,
|
|
144
|
+
node = _step$value.node,
|
|
145
|
+
pos = _step$value.pos;
|
|
146
|
+
// This is ProseMirror's Transaction.replace (a document edit), not String.replace — the
|
|
147
|
+
// perf rule misfires on the method name, and there is nothing to hoist.
|
|
148
|
+
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace
|
|
149
|
+
tr.replace(pos, pos + node.nodeSize, new Slice(node.content, 0, 0));
|
|
150
|
+
}
|
|
151
|
+
} catch (err) {
|
|
152
|
+
_iterator.e(err);
|
|
153
|
+
} finally {
|
|
154
|
+
_iterator.f();
|
|
155
|
+
}
|
|
156
|
+
return tr;
|
|
92
157
|
};
|
|
@@ -27,3 +27,32 @@ import type { SyncBlockAttrs } from '../../types';
|
|
|
27
27
|
* collaborator already removed them all — so there is nothing to delete.
|
|
28
28
|
*/
|
|
29
29
|
export declare const recomputeDeleteTransaction: (tr: Transaction, isSourceBlock: (node: PMNode) => boolean, syncBlockIds: SyncBlockAttrs[]) => Transaction | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Recompute a source `bodiedSyncBlock` *unsync* from the live document.
|
|
32
|
+
*
|
|
33
|
+
* Unsync differs from delete: the sync wrapper must be removed while its content
|
|
34
|
+
* is preserved inline in the document. Instead of `tr.delete(pos, pos +
|
|
35
|
+
* nodeSize)` (which drops the content too — EDITOR-8230), this replaces each
|
|
36
|
+
* matched source block with its own content.
|
|
37
|
+
*
|
|
38
|
+
* The replacement uses a raw `tr.replace(pos, pos + nodeSize, new Slice(content,
|
|
39
|
+
* 0, 0))` — an explicit zero-open slice inserted verbatim — rather than
|
|
40
|
+
* `tr.replaceWith(...)`. `replaceWith` routes through ProseMirror's
|
|
41
|
+
* `replaceRange`, whose range-fitting heuristics can collapse or drop a trailing
|
|
42
|
+
* block (e.g. the block's final panel/empty paragraph) when the unwrapped
|
|
43
|
+
* content meets a document boundary, silently losing content (EDITOR-8230). A
|
|
44
|
+
* zero-open `replace` drops the wrapper's own open ends while keeping every child
|
|
45
|
+
* of the fragment as its own top-level node.
|
|
46
|
+
*
|
|
47
|
+
* Like `recomputeDeleteTransaction`, targets are located in the *current*
|
|
48
|
+
* document (`tr.doc`) so the operation is robust to intervening local edits and
|
|
49
|
+
* remote collab changes while the confirmation modal is open, and it handles
|
|
50
|
+
* partial matches gracefully (missing blocks are skipped).
|
|
51
|
+
*
|
|
52
|
+
* Replacements are applied in reverse document order so earlier replacements do
|
|
53
|
+
* not invalidate the positions of later ones.
|
|
54
|
+
*
|
|
55
|
+
* @returns the mutated transaction when at least one target node was found and
|
|
56
|
+
* unwrapped, otherwise `undefined` when none of the targets exist any more.
|
|
57
|
+
*/
|
|
58
|
+
export declare const recomputeUnsyncTransaction: (tr: Transaction, isSourceBlock: (node: PMNode) => boolean, syncBlockIds: SyncBlockAttrs[]) => Transaction | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-synced-block",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.6.1",
|
|
4
4
|
"description": "SyncedBlock plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"atlaskit:src": "src/index.ts",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@atlaskit/adf-schema": "^56.1.0",
|
|
24
|
-
"@atlaskit/button": "24.3.
|
|
24
|
+
"@atlaskit/button": "24.3.8",
|
|
25
25
|
"@atlaskit/dropdown-menu": "17.2.0",
|
|
26
26
|
"@atlaskit/editor-json-transformer": "^9.1.0",
|
|
27
27
|
"@atlaskit/editor-plugin-analytics": "^12.0.0",
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"@atlaskit/platform-feature-flags": "^2.1.0",
|
|
47
47
|
"@atlaskit/primitives": "^22.2.0",
|
|
48
48
|
"@atlaskit/spinner": "20.1.1",
|
|
49
|
-
"@atlaskit/tmp-editor-statsig": "^135.
|
|
49
|
+
"@atlaskit/tmp-editor-statsig": "^135.6.0",
|
|
50
50
|
"@atlaskit/tokens": "16.3.0",
|
|
51
|
-
"@atlaskit/tooltip": "^
|
|
51
|
+
"@atlaskit/tooltip": "^24.0.0",
|
|
52
52
|
"@atlaskit/visually-hidden": "^4.2.0",
|
|
53
53
|
"@babel/runtime": "^7.0.0",
|
|
54
54
|
"@compiled/react": "^1.0.0",
|
|
@@ -110,6 +110,9 @@
|
|
|
110
110
|
"platform_editor_blocks_patch_4": {
|
|
111
111
|
"type": "boolean"
|
|
112
112
|
},
|
|
113
|
+
"platform_editor_blocks_patch_6": {
|
|
114
|
+
"type": "boolean"
|
|
115
|
+
},
|
|
113
116
|
"platform_synced_block_patch_13": {
|
|
114
117
|
"type": "boolean"
|
|
115
118
|
},
|