@atlaskit/editor-synced-block-provider 12.1.5 → 12.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @atlaskit/editor-synced-block-provider
2
2
 
3
+ ## 12.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`c5bc5493c5486`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/c5bc5493c5486) -
8
+ Fix migrateSyncBlockIds mutating the caller's input ADF document. traverse() falls back to
9
+ mutating a node in place whenever its visitor returns undefined, so the transform now always
10
+ returns a copy instead.
11
+ - Updated dependencies
12
+
13
+ ## 12.2.0
14
+
15
+ ### Minor Changes
16
+
17
+ - [`6cf6b735bf90a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/6cf6b735bf90a) -
18
+ Add migrateSyncBlockIds to the utils/resourceId entry point. Rewrites syncBlock and
19
+ bodiedSyncBlock identifiers so a document stays internally consistent after cloud-to-cloud
20
+ migration, preserving block UUIDs by default and supporting optional UUID regeneration.
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies
25
+
3
26
  ## 12.1.5
4
27
 
5
28
  ### Patch Changes
@@ -9,10 +9,17 @@ Object.defineProperty(exports, "createResourceIdForReference", {
9
9
  return _resourceId.createResourceIdForReference;
10
10
  }
11
11
  });
12
+ Object.defineProperty(exports, "migrateSyncBlockIds", {
13
+ enumerable: true,
14
+ get: function get() {
15
+ return _migrateSyncBlockIds.migrateSyncBlockIds;
16
+ }
17
+ });
12
18
  Object.defineProperty(exports, "parseResourceId", {
13
19
  enumerable: true,
14
20
  get: function get() {
15
21
  return _resourceId.parseResourceId;
16
22
  }
17
23
  });
18
- var _resourceId = require("../utils/resourceId");
24
+ var _resourceId = require("../utils/resourceId");
25
+ var _migrateSyncBlockIds = require("../utils/migrateSyncBlockIds");
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.migrateSyncBlockIds = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _traverse = require("@atlaskit/adf-utils/traverse");
10
+ var _resourceId = require("./resourceId");
11
+ 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; }
12
+ 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; }
13
+ /*
14
+ * Rewrites `syncBlock`/`bodiedSyncBlock` identifiers so a document stays internally
15
+ * consistent after cloud-to-cloud migration or a copy to different host content.
16
+ *
17
+ * Source nodes (`bodiedSyncBlock`) carry a bare UUID as both `resourceId` and `localId` -
18
+ * the host content ID never appears in the ADF, only in the block ARI, which Block
19
+ * Service derives from the cloud ID and parent content ID at runtime. Reference nodes
20
+ * (`syncBlock`) carry `{product}/{contentId}/{sourceUuid}`. So preserving the UUID (the
21
+ * default) needs no edit on source nodes, and only remaps the `contentId` segment on
22
+ * references - which also keeps re-runs idempotent.
23
+ */
24
+
25
+ /** Bails to `undefined` on anything that isn't a syncBlock/bodiedSyncBlock attrs shape. */
26
+ var parseSyncBlockAttrs = function parseSyncBlockAttrs(attrs) {
27
+ var resourceId = attrs.resourceId,
28
+ localId = attrs.localId;
29
+ if (typeof resourceId !== 'string' || resourceId === '') {
30
+ return undefined;
31
+ }
32
+ return {
33
+ resourceId: resourceId,
34
+ localId: typeof localId === 'string' ? localId : undefined
35
+ };
36
+ };
37
+ var migrateAttrs = function migrateAttrs(attrs, resolvers, unresolvedContentIds) {
38
+ var _resolvers$resolveUui3;
39
+ var resourceId = attrs.resourceId,
40
+ localId = attrs.localId;
41
+ var parsed = (0, _resourceId.parseResourceId)(resourceId);
42
+ if (parsed) {
43
+ var _resolvers$resolveUui, _resolvers$resolveUui2;
44
+ var mappedContentId = resolvers.resolveContentId(parsed.contentId, parsed.product);
45
+ if (mappedContentId === undefined) {
46
+ unresolvedContentIds.add(parsed.contentId);
47
+ }
48
+ var nextContentId = mappedContentId !== null && mappedContentId !== void 0 ? mappedContentId : parsed.contentId;
49
+ var _nextUuid = (_resolvers$resolveUui = (_resolvers$resolveUui2 = resolvers.resolveUuid) === null || _resolvers$resolveUui2 === void 0 ? void 0 : _resolvers$resolveUui2.call(resolvers, parsed.uuid)) !== null && _resolvers$resolveUui !== void 0 ? _resolvers$resolveUui : parsed.uuid;
50
+ if (nextContentId === parsed.contentId && _nextUuid === parsed.uuid) {
51
+ return undefined;
52
+ }
53
+ return {
54
+ resourceId: (0, _resourceId.createResourceIdForReference)(parsed.product, nextContentId, _nextUuid),
55
+ // Only mirror the UUID when this node's localId tracked it in the first place.
56
+ localId: localId === parsed.uuid ? _nextUuid : localId
57
+ };
58
+ }
59
+
60
+ // bare-UUID source node: no content ID segment to remap
61
+ var nextUuid = (_resolvers$resolveUui3 = resolvers.resolveUuid) === null || _resolvers$resolveUui3 === void 0 ? void 0 : _resolvers$resolveUui3.call(resolvers, resourceId);
62
+ if (nextUuid === undefined || nextUuid === resourceId) {
63
+ return undefined;
64
+ }
65
+ return {
66
+ resourceId: nextUuid,
67
+ localId: localId === resourceId ? nextUuid : localId
68
+ };
69
+ };
70
+
71
+ /** `isTransformed` is `false` when no node changed, so callers can skip persisting the result. */
72
+ var migrateSyncBlockIds = exports.migrateSyncBlockIds = function migrateSyncBlockIds(adf, resolvers) {
73
+ var unresolvedContentIds = new Set();
74
+ var isTransformed = false;
75
+
76
+ // Always returns a copy, even when nothing changes: traverse() falls back to mutating
77
+ // the original node in place (its `content` array gets reassigned) whenever a visitor
78
+ // returns undefined, so returning undefined here would leak that mutation onto the
79
+ // caller's input document.
80
+ var migrateNode = function migrateNode(node) {
81
+ var syncBlockAttrs = node.attrs && parseSyncBlockAttrs(node.attrs);
82
+ if (!syncBlockAttrs) {
83
+ return _objectSpread({}, node);
84
+ }
85
+ var nextAttrs = migrateAttrs(syncBlockAttrs, resolvers, unresolvedContentIds);
86
+ if (!nextAttrs) {
87
+ return _objectSpread({}, node);
88
+ }
89
+ isTransformed = true;
90
+ return _objectSpread(_objectSpread({}, node), {}, {
91
+ attrs: _objectSpread(_objectSpread({}, node.attrs), nextAttrs)
92
+ });
93
+ };
94
+ var transformedAdf = (0, _traverse.traverse)(adf, {
95
+ syncBlock: migrateNode,
96
+ bodiedSyncBlock: migrateNode
97
+ });
98
+
99
+ // traverse() only returns false when a visitor deletes the root node; migrateNode never does.
100
+ if (transformedAdf === false) {
101
+ throw new Error('migrateSyncBlockIds: traverse unexpectedly removed the root node');
102
+ }
103
+ return {
104
+ transformedAdf: transformedAdf,
105
+ isTransformed: isTransformed,
106
+ unresolvedContentIds: Array.from(unresolvedContentIds)
107
+ };
108
+ };
@@ -1,2 +1,3 @@
1
1
  /* eslint-disable @atlaskit/editor/no-re-export */
2
- export { parseResourceId, createResourceIdForReference } from '../utils/resourceId';
2
+ export { parseResourceId, createResourceIdForReference } from '../utils/resourceId';
3
+ export { migrateSyncBlockIds } from '../utils/migrateSyncBlockIds';
@@ -0,0 +1,111 @@
1
+ import { traverse } from '@atlaskit/adf-utils/traverse';
2
+ import { createResourceIdForReference, parseResourceId } from './resourceId';
3
+
4
+ /*
5
+ * Rewrites `syncBlock`/`bodiedSyncBlock` identifiers so a document stays internally
6
+ * consistent after cloud-to-cloud migration or a copy to different host content.
7
+ *
8
+ * Source nodes (`bodiedSyncBlock`) carry a bare UUID as both `resourceId` and `localId` -
9
+ * the host content ID never appears in the ADF, only in the block ARI, which Block
10
+ * Service derives from the cloud ID and parent content ID at runtime. Reference nodes
11
+ * (`syncBlock`) carry `{product}/{contentId}/{sourceUuid}`. So preserving the UUID (the
12
+ * default) needs no edit on source nodes, and only remaps the `contentId` segment on
13
+ * references - which also keeps re-runs idempotent.
14
+ */
15
+
16
+ /** Bails to `undefined` on anything that isn't a syncBlock/bodiedSyncBlock attrs shape. */
17
+ const parseSyncBlockAttrs = attrs => {
18
+ const {
19
+ resourceId,
20
+ localId
21
+ } = attrs;
22
+ if (typeof resourceId !== 'string' || resourceId === '') {
23
+ return undefined;
24
+ }
25
+ return {
26
+ resourceId,
27
+ localId: typeof localId === 'string' ? localId : undefined
28
+ };
29
+ };
30
+ const migrateAttrs = (attrs, resolvers, unresolvedContentIds) => {
31
+ var _resolvers$resolveUui3;
32
+ const {
33
+ resourceId,
34
+ localId
35
+ } = attrs;
36
+ const parsed = parseResourceId(resourceId);
37
+ if (parsed) {
38
+ var _resolvers$resolveUui, _resolvers$resolveUui2;
39
+ const mappedContentId = resolvers.resolveContentId(parsed.contentId, parsed.product);
40
+ if (mappedContentId === undefined) {
41
+ unresolvedContentIds.add(parsed.contentId);
42
+ }
43
+ const nextContentId = mappedContentId !== null && mappedContentId !== void 0 ? mappedContentId : parsed.contentId;
44
+ const nextUuid = (_resolvers$resolveUui = (_resolvers$resolveUui2 = resolvers.resolveUuid) === null || _resolvers$resolveUui2 === void 0 ? void 0 : _resolvers$resolveUui2.call(resolvers, parsed.uuid)) !== null && _resolvers$resolveUui !== void 0 ? _resolvers$resolveUui : parsed.uuid;
45
+ if (nextContentId === parsed.contentId && nextUuid === parsed.uuid) {
46
+ return undefined;
47
+ }
48
+ return {
49
+ resourceId: createResourceIdForReference(parsed.product, nextContentId, nextUuid),
50
+ // Only mirror the UUID when this node's localId tracked it in the first place.
51
+ localId: localId === parsed.uuid ? nextUuid : localId
52
+ };
53
+ }
54
+
55
+ // bare-UUID source node: no content ID segment to remap
56
+ const nextUuid = (_resolvers$resolveUui3 = resolvers.resolveUuid) === null || _resolvers$resolveUui3 === void 0 ? void 0 : _resolvers$resolveUui3.call(resolvers, resourceId);
57
+ if (nextUuid === undefined || nextUuid === resourceId) {
58
+ return undefined;
59
+ }
60
+ return {
61
+ resourceId: nextUuid,
62
+ localId: localId === resourceId ? nextUuid : localId
63
+ };
64
+ };
65
+
66
+ /** `isTransformed` is `false` when no node changed, so callers can skip persisting the result. */
67
+ export const migrateSyncBlockIds = (adf, resolvers) => {
68
+ const unresolvedContentIds = new Set();
69
+ let isTransformed = false;
70
+
71
+ // Always returns a copy, even when nothing changes: traverse() falls back to mutating
72
+ // the original node in place (its `content` array gets reassigned) whenever a visitor
73
+ // returns undefined, so returning undefined here would leak that mutation onto the
74
+ // caller's input document.
75
+ const migrateNode = node => {
76
+ const syncBlockAttrs = node.attrs && parseSyncBlockAttrs(node.attrs);
77
+ if (!syncBlockAttrs) {
78
+ return {
79
+ ...node
80
+ };
81
+ }
82
+ const nextAttrs = migrateAttrs(syncBlockAttrs, resolvers, unresolvedContentIds);
83
+ if (!nextAttrs) {
84
+ return {
85
+ ...node
86
+ };
87
+ }
88
+ isTransformed = true;
89
+ return {
90
+ ...node,
91
+ attrs: {
92
+ ...node.attrs,
93
+ ...nextAttrs
94
+ }
95
+ };
96
+ };
97
+ const transformedAdf = traverse(adf, {
98
+ syncBlock: migrateNode,
99
+ bodiedSyncBlock: migrateNode
100
+ });
101
+
102
+ // traverse() only returns false when a visitor deletes the root node; migrateNode never does.
103
+ if (transformedAdf === false) {
104
+ throw new Error('migrateSyncBlockIds: traverse unexpectedly removed the root node');
105
+ }
106
+ return {
107
+ transformedAdf,
108
+ isTransformed,
109
+ unresolvedContentIds: Array.from(unresolvedContentIds)
110
+ };
111
+ };
@@ -1,2 +1,3 @@
1
1
  /* eslint-disable @atlaskit/editor/no-re-export */
2
- export { parseResourceId, createResourceIdForReference } from '../utils/resourceId';
2
+ export { parseResourceId, createResourceIdForReference } from '../utils/resourceId';
3
+ export { migrateSyncBlockIds } from '../utils/migrateSyncBlockIds';
@@ -0,0 +1,102 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ 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; }
3
+ 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) { _defineProperty(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; }
4
+ import { traverse } from '@atlaskit/adf-utils/traverse';
5
+ import { createResourceIdForReference, parseResourceId } from './resourceId';
6
+
7
+ /*
8
+ * Rewrites `syncBlock`/`bodiedSyncBlock` identifiers so a document stays internally
9
+ * consistent after cloud-to-cloud migration or a copy to different host content.
10
+ *
11
+ * Source nodes (`bodiedSyncBlock`) carry a bare UUID as both `resourceId` and `localId` -
12
+ * the host content ID never appears in the ADF, only in the block ARI, which Block
13
+ * Service derives from the cloud ID and parent content ID at runtime. Reference nodes
14
+ * (`syncBlock`) carry `{product}/{contentId}/{sourceUuid}`. So preserving the UUID (the
15
+ * default) needs no edit on source nodes, and only remaps the `contentId` segment on
16
+ * references - which also keeps re-runs idempotent.
17
+ */
18
+
19
+ /** Bails to `undefined` on anything that isn't a syncBlock/bodiedSyncBlock attrs shape. */
20
+ var parseSyncBlockAttrs = function parseSyncBlockAttrs(attrs) {
21
+ var resourceId = attrs.resourceId,
22
+ localId = attrs.localId;
23
+ if (typeof resourceId !== 'string' || resourceId === '') {
24
+ return undefined;
25
+ }
26
+ return {
27
+ resourceId: resourceId,
28
+ localId: typeof localId === 'string' ? localId : undefined
29
+ };
30
+ };
31
+ var migrateAttrs = function migrateAttrs(attrs, resolvers, unresolvedContentIds) {
32
+ var _resolvers$resolveUui3;
33
+ var resourceId = attrs.resourceId,
34
+ localId = attrs.localId;
35
+ var parsed = parseResourceId(resourceId);
36
+ if (parsed) {
37
+ var _resolvers$resolveUui, _resolvers$resolveUui2;
38
+ var mappedContentId = resolvers.resolveContentId(parsed.contentId, parsed.product);
39
+ if (mappedContentId === undefined) {
40
+ unresolvedContentIds.add(parsed.contentId);
41
+ }
42
+ var nextContentId = mappedContentId !== null && mappedContentId !== void 0 ? mappedContentId : parsed.contentId;
43
+ var _nextUuid = (_resolvers$resolveUui = (_resolvers$resolveUui2 = resolvers.resolveUuid) === null || _resolvers$resolveUui2 === void 0 ? void 0 : _resolvers$resolveUui2.call(resolvers, parsed.uuid)) !== null && _resolvers$resolveUui !== void 0 ? _resolvers$resolveUui : parsed.uuid;
44
+ if (nextContentId === parsed.contentId && _nextUuid === parsed.uuid) {
45
+ return undefined;
46
+ }
47
+ return {
48
+ resourceId: createResourceIdForReference(parsed.product, nextContentId, _nextUuid),
49
+ // Only mirror the UUID when this node's localId tracked it in the first place.
50
+ localId: localId === parsed.uuid ? _nextUuid : localId
51
+ };
52
+ }
53
+
54
+ // bare-UUID source node: no content ID segment to remap
55
+ var nextUuid = (_resolvers$resolveUui3 = resolvers.resolveUuid) === null || _resolvers$resolveUui3 === void 0 ? void 0 : _resolvers$resolveUui3.call(resolvers, resourceId);
56
+ if (nextUuid === undefined || nextUuid === resourceId) {
57
+ return undefined;
58
+ }
59
+ return {
60
+ resourceId: nextUuid,
61
+ localId: localId === resourceId ? nextUuid : localId
62
+ };
63
+ };
64
+
65
+ /** `isTransformed` is `false` when no node changed, so callers can skip persisting the result. */
66
+ export var migrateSyncBlockIds = function migrateSyncBlockIds(adf, resolvers) {
67
+ var unresolvedContentIds = new Set();
68
+ var isTransformed = false;
69
+
70
+ // Always returns a copy, even when nothing changes: traverse() falls back to mutating
71
+ // the original node in place (its `content` array gets reassigned) whenever a visitor
72
+ // returns undefined, so returning undefined here would leak that mutation onto the
73
+ // caller's input document.
74
+ var migrateNode = function migrateNode(node) {
75
+ var syncBlockAttrs = node.attrs && parseSyncBlockAttrs(node.attrs);
76
+ if (!syncBlockAttrs) {
77
+ return _objectSpread({}, node);
78
+ }
79
+ var nextAttrs = migrateAttrs(syncBlockAttrs, resolvers, unresolvedContentIds);
80
+ if (!nextAttrs) {
81
+ return _objectSpread({}, node);
82
+ }
83
+ isTransformed = true;
84
+ return _objectSpread(_objectSpread({}, node), {}, {
85
+ attrs: _objectSpread(_objectSpread({}, node.attrs), nextAttrs)
86
+ });
87
+ };
88
+ var transformedAdf = traverse(adf, {
89
+ syncBlock: migrateNode,
90
+ bodiedSyncBlock: migrateNode
91
+ });
92
+
93
+ // traverse() only returns false when a visitor deletes the root node; migrateNode never does.
94
+ if (transformedAdf === false) {
95
+ throw new Error('migrateSyncBlockIds: traverse unexpectedly removed the root node');
96
+ }
97
+ return {
98
+ transformedAdf: transformedAdf,
99
+ isTransformed: isTransformed,
100
+ unresolvedContentIds: Array.from(unresolvedContentIds)
101
+ };
102
+ };
@@ -1 +1,3 @@
1
1
  export { parseResourceId, createResourceIdForReference } from '../utils/resourceId';
2
+ export { migrateSyncBlockIds } from '../utils/migrateSyncBlockIds';
3
+ export type { SyncBlockIdResolvers, SyncBlockIdMigrationResult, } from '../utils/migrateSyncBlockIds';
@@ -0,0 +1,16 @@
1
+ import type { ADFEntity } from '@atlaskit/adf-utils/types';
2
+ import type { SyncBlockProduct } from '../common/types';
3
+ export type SyncBlockIdResolvers = {
4
+ /** Return `undefined` for an unknown content ID - it's left untouched and reported via `unresolvedContentIds`. */
5
+ resolveContentId: (contentId: string, product: SyncBlockProduct) => string | undefined;
6
+ /** Omit to preserve UUIDs (recommended). Return `undefined` to preserve one UUID while remapping others. */
7
+ resolveUuid?: (uuid: string) => string | undefined;
8
+ };
9
+ export type SyncBlockIdMigrationResult = {
10
+ isTransformed: boolean;
11
+ transformedAdf: ADFEntity;
12
+ /** Reference content IDs with no mapping - a signal to investigate, not necessarily a failure. */
13
+ unresolvedContentIds: string[];
14
+ };
15
+ /** `isTransformed` is `false` when no node changed, so callers can skip persisting the result. */
16
+ export declare const migrateSyncBlockIds: (adf: ADFEntity, resolvers: SyncBlockIdResolvers) => SyncBlockIdMigrationResult;
package/package.json CHANGED
@@ -16,14 +16,14 @@
16
16
  ],
17
17
  "atlaskit:src": "src/index.ts",
18
18
  "dependencies": {
19
- "@atlaskit/adf-utils": "^20.8.0",
19
+ "@atlaskit/adf-utils": "^20.9.0",
20
20
  "@atlaskit/browser-apis": "^1.2.0",
21
21
  "@atlaskit/editor-json-transformer": "^9.8.0",
22
22
  "@atlaskit/editor-prosemirror": "^8.0.0",
23
23
  "@atlaskit/node-data-provider": "^17.0.0",
24
24
  "@atlaskit/platform-feature-experiments": "^0.3.0",
25
25
  "@atlaskit/platform-feature-flags": "^2.2.0",
26
- "@atlaskit/tmp-editor-statsig": "^173.0.0",
26
+ "@atlaskit/tmp-editor-statsig": "^174.0.0",
27
27
  "@babel/runtime": "^7.0.0",
28
28
  "@compiled/react": "^1.0.2",
29
29
  "bind-event-listener": "^3.0.0",
@@ -33,7 +33,7 @@
33
33
  "uuid": "^3.1.0"
34
34
  },
35
35
  "peerDependencies": {
36
- "@atlaskit/editor-common": "^120.11.0",
36
+ "@atlaskit/editor-common": "^120.13.0",
37
37
  "react": "^18.2.0 || ^19.2.0"
38
38
  },
39
39
  "devDependencies": {
@@ -42,7 +42,7 @@
42
42
  "react-dom": "^19.2.0"
43
43
  },
44
44
  "name": "@atlaskit/editor-synced-block-provider",
45
- "version": "12.1.5",
45
+ "version": "12.2.1",
46
46
  "description": "Synced Block Provider for @atlaskit/editor-plugin-synced-block",
47
47
  "author": "Atlassian Pty Ltd",
48
48
  "license": "Apache-2.0",