@wordpress/editor 14.39.0 → 14.39.1-next.v.0
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/build/components/post-revisions-preview/preserve-client-ids.cjs +65 -0
- package/build/components/post-revisions-preview/preserve-client-ids.cjs.map +7 -0
- package/build/components/post-revisions-preview/revisions-canvas.cjs +10 -3
- package/build/components/post-revisions-preview/revisions-canvas.cjs.map +2 -2
- package/build-module/components/post-revisions-preview/preserve-client-ids.mjs +40 -0
- package/build-module/components/post-revisions-preview/preserve-client-ids.mjs.map +7 -0
- package/build-module/components/post-revisions-preview/revisions-canvas.mjs +11 -4
- package/build-module/components/post-revisions-preview/revisions-canvas.mjs.map +2 -2
- package/build-style/style-rtl.css +5 -0
- package/build-style/style.css +5 -0
- package/build-types/components/post-revisions-preview/preserve-client-ids.d.ts +13 -0
- package/build-types/components/post-revisions-preview/preserve-client-ids.d.ts.map +1 -0
- package/build-types/components/post-revisions-preview/revisions-canvas.d.ts.map +1 -1
- package/package.json +43 -42
- package/src/components/post-revisions-preview/preserve-client-ids.js +59 -0
- package/src/components/post-revisions-preview/revisions-canvas.js +18 -4
- package/src/components/post-revisions-preview/test/preserve-client-ids.js +246 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// packages/editor/src/components/post-revisions-preview/preserve-client-ids.js
|
|
21
|
+
var preserve_client_ids_exports = {};
|
|
22
|
+
__export(preserve_client_ids_exports, {
|
|
23
|
+
preserveClientIds: () => preserveClientIds
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(preserve_client_ids_exports);
|
|
26
|
+
var import_array = require("diff/lib/diff/array");
|
|
27
|
+
function preserveClientIds(newBlocks, prevBlocks) {
|
|
28
|
+
if (!prevBlocks?.length || !newBlocks?.length) {
|
|
29
|
+
return newBlocks;
|
|
30
|
+
}
|
|
31
|
+
const newSigs = newBlocks.map((block) => block.name);
|
|
32
|
+
const prevSigs = prevBlocks.map((block) => block.name);
|
|
33
|
+
const diffResult = (0, import_array.diffArrays)(prevSigs, newSigs);
|
|
34
|
+
let newIndex = 0;
|
|
35
|
+
let prevIndex = 0;
|
|
36
|
+
const result = [];
|
|
37
|
+
for (const chunk of diffResult) {
|
|
38
|
+
if (chunk.removed) {
|
|
39
|
+
prevIndex += chunk.count;
|
|
40
|
+
} else if (chunk.added) {
|
|
41
|
+
for (let i = 0; i < chunk.count; i++) {
|
|
42
|
+
result.push(newBlocks[newIndex++]);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
for (let i = 0; i < chunk.count; i++) {
|
|
46
|
+
const newBlock = newBlocks[newIndex++];
|
|
47
|
+
const prevBlock = prevBlocks[prevIndex++];
|
|
48
|
+
result.push({
|
|
49
|
+
...newBlock,
|
|
50
|
+
clientId: prevBlock.clientId,
|
|
51
|
+
innerBlocks: preserveClientIds(
|
|
52
|
+
newBlock.innerBlocks,
|
|
53
|
+
prevBlock.innerBlocks
|
|
54
|
+
)
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
62
|
+
0 && (module.exports = {
|
|
63
|
+
preserveClientIds
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=preserve-client-ids.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/components/post-revisions-preview/preserve-client-ids.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * External dependencies\n */\nimport { diffArrays } from 'diff/lib/diff/array';\n\n/**\n * Preserves clientIds from previously rendered blocks to prevent flashing.\n * Uses LCS algorithm to match blocks by name.\n *\n * This compares the newly parsed blocks against the last rendered blocks\n * to maintain React key stability.\n *\n * @param {Array} newBlocks Newly parsed blocks with fresh clientIds.\n * @param {Array} prevBlocks Previously rendered blocks with stable clientIds.\n * @return {Array} Blocks with preserved clientIds where possible.\n */\nexport function preserveClientIds( newBlocks, prevBlocks ) {\n\tif ( ! prevBlocks?.length || ! newBlocks?.length ) {\n\t\treturn newBlocks;\n\t}\n\n\t// Create signatures for LCS matching using block name.\n\tconst newSigs = newBlocks.map( ( block ) => block.name );\n\tconst prevSigs = prevBlocks.map( ( block ) => block.name );\n\n\tconst diffResult = diffArrays( prevSigs, newSigs );\n\n\tlet newIndex = 0;\n\tlet prevIndex = 0;\n\tconst result = [];\n\n\tfor ( const chunk of diffResult ) {\n\t\tif ( chunk.removed ) {\n\t\t\t// Blocks only in prev render - skip them.\n\t\t\tprevIndex += chunk.count;\n\t\t} else if ( chunk.added ) {\n\t\t\t// Blocks only in new render - keep new clientIds.\n\t\t\tfor ( let i = 0; i < chunk.count; i++ ) {\n\t\t\t\tresult.push( newBlocks[ newIndex++ ] );\n\t\t\t}\n\t\t} else {\n\t\t\t// Matched blocks - preserve clientIds from prev render.\n\t\t\tfor ( let i = 0; i < chunk.count; i++ ) {\n\t\t\t\tconst newBlock = newBlocks[ newIndex++ ];\n\t\t\t\tconst prevBlock = prevBlocks[ prevIndex++ ];\n\t\t\t\tresult.push( {\n\t\t\t\t\t...newBlock,\n\t\t\t\t\tclientId: prevBlock.clientId,\n\t\t\t\t\tinnerBlocks: preserveClientIds(\n\t\t\t\t\t\tnewBlock.innerBlocks,\n\t\t\t\t\t\tprevBlock.innerBlocks\n\t\t\t\t\t),\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAA2B;AAapB,SAAS,kBAAmB,WAAW,YAAa;AAC1D,MAAK,CAAE,YAAY,UAAU,CAAE,WAAW,QAAS;AAClD,WAAO;AAAA,EACR;AAGA,QAAM,UAAU,UAAU,IAAK,CAAE,UAAW,MAAM,IAAK;AACvD,QAAM,WAAW,WAAW,IAAK,CAAE,UAAW,MAAM,IAAK;AAEzD,QAAM,iBAAa,yBAAY,UAAU,OAAQ;AAEjD,MAAI,WAAW;AACf,MAAI,YAAY;AAChB,QAAM,SAAS,CAAC;AAEhB,aAAY,SAAS,YAAa;AACjC,QAAK,MAAM,SAAU;AAEpB,mBAAa,MAAM;AAAA,IACpB,WAAY,MAAM,OAAQ;AAEzB,eAAU,IAAI,GAAG,IAAI,MAAM,OAAO,KAAM;AACvC,eAAO,KAAM,UAAW,UAAW,CAAE;AAAA,MACtC;AAAA,IACD,OAAO;AAEN,eAAU,IAAI,GAAG,IAAI,MAAM,OAAO,KAAM;AACvC,cAAM,WAAW,UAAW,UAAW;AACvC,cAAM,YAAY,WAAY,WAAY;AAC1C,eAAO,KAAM;AAAA,UACZ,GAAG;AAAA,UACH,UAAU,UAAU;AAAA,UACpB,aAAa;AAAA,YACZ,SAAS;AAAA,YACT,UAAU;AAAA,UACX;AAAA,QACD,CAAE;AAAA,MACH;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -41,6 +41,7 @@ var import_element = require("@wordpress/element");
|
|
|
41
41
|
var import_lock_unlock = require("../../lock-unlock.cjs");
|
|
42
42
|
var import_store = require("../../store/index.cjs");
|
|
43
43
|
var import_visual_editor = __toESM(require("../visual-editor/index.cjs"));
|
|
44
|
+
var import_preserve_client_ids = require("./preserve-client-ids.cjs");
|
|
44
45
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
45
46
|
var { ExperimentalBlockEditorProvider } = (0, import_lock_unlock.unlock)(import_block_editor.privateApis);
|
|
46
47
|
function RevisionsCanvas() {
|
|
@@ -57,10 +58,11 @@ function RevisionsCanvas() {
|
|
|
57
58
|
},
|
|
58
59
|
[]
|
|
59
60
|
);
|
|
61
|
+
const previousBlocksRef = (0, import_element.useRef)([]);
|
|
60
62
|
const blocks = (0, import_element.useMemo)(() => {
|
|
61
|
-
|
|
63
|
+
let parsedBlocks = (0, import_blocks.parse)(revision?.content?.raw ?? "");
|
|
62
64
|
if (postType === "wp_navigation") {
|
|
63
|
-
|
|
65
|
+
parsedBlocks = [
|
|
64
66
|
(0, import_blocks.createBlock)(
|
|
65
67
|
"core/navigation",
|
|
66
68
|
{ templateLock: false },
|
|
@@ -68,7 +70,12 @@ function RevisionsCanvas() {
|
|
|
68
70
|
)
|
|
69
71
|
];
|
|
70
72
|
}
|
|
71
|
-
|
|
73
|
+
const blocksWithStableIds = (0, import_preserve_client_ids.preserveClientIds)(
|
|
74
|
+
parsedBlocks,
|
|
75
|
+
previousBlocksRef.current
|
|
76
|
+
);
|
|
77
|
+
previousBlocksRef.current = blocksWithStableIds;
|
|
78
|
+
return blocksWithStableIds;
|
|
72
79
|
}, [revision?.content?.raw, postType]);
|
|
73
80
|
const settings = (0, import_element.useMemo)(
|
|
74
81
|
() => ({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/components/post-revisions-preview/revisions-canvas.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { Spinner } from '@wordpress/components';\nimport {\n\tprivateApis as blockEditorPrivateApis,\n\tstore as blockEditorStore,\n} from '@wordpress/block-editor';\nimport { createBlock, parse } from '@wordpress/blocks';\nimport { useSelect } from '@wordpress/data';\nimport { useMemo } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport { unlock } from '../../lock-unlock';\nimport { store as editorStore } from '../../store';\nimport VisualEditor from '../visual-editor';\n\nconst { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis );\n\n/**\n * Canvas component that renders a post revision in read-only mode.\n *\n * @return {JSX.Element} The revisions canvas component.\n */\nexport default function RevisionsCanvas() {\n\tconst { revision, postType, blockEditorSettings } = useSelect(\n\t\t( select ) => {\n\t\t\tconst { getCurrentRevision, getCurrentPostType } = unlock(\n\t\t\t\tselect( editorStore )\n\t\t\t);\n\t\t\treturn {\n\t\t\t\trevision: getCurrentRevision(),\n\t\t\t\tpostType: getCurrentPostType(),\n\t\t\t\tblockEditorSettings: select( blockEditorStore ).getSettings(),\n\t\t\t};\n\t\t},\n\t\t[]\n\t);\n\n\tconst blocks = useMemo( () => {\n\t\
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,wBAAwB;AACxB,0BAGO;AACP,oBAAmC;AACnC,kBAA0B;AAC1B,
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { Spinner } from '@wordpress/components';\nimport {\n\tprivateApis as blockEditorPrivateApis,\n\tstore as blockEditorStore,\n} from '@wordpress/block-editor';\nimport { createBlock, parse } from '@wordpress/blocks';\nimport { useSelect } from '@wordpress/data';\nimport { useMemo, useRef } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport { unlock } from '../../lock-unlock';\nimport { store as editorStore } from '../../store';\nimport VisualEditor from '../visual-editor';\nimport { preserveClientIds } from './preserve-client-ids';\n\nconst { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis );\n\n/**\n * Canvas component that renders a post revision in read-only mode.\n *\n * @return {JSX.Element} The revisions canvas component.\n */\nexport default function RevisionsCanvas() {\n\tconst { revision, postType, blockEditorSettings } = useSelect(\n\t\t( select ) => {\n\t\t\tconst { getCurrentRevision, getCurrentPostType } = unlock(\n\t\t\t\tselect( editorStore )\n\t\t\t);\n\t\t\treturn {\n\t\t\t\trevision: getCurrentRevision(),\n\t\t\t\tpostType: getCurrentPostType(),\n\t\t\t\tblockEditorSettings: select( blockEditorStore ).getSettings(),\n\t\t\t};\n\t\t},\n\t\t[]\n\t);\n\n\t// Track previously rendered blocks to preserve clientIds between renders.\n\tconst previousBlocksRef = useRef( [] );\n\n\tconst blocks = useMemo( () => {\n\t\tlet parsedBlocks = parse( revision?.content?.raw ?? '' );\n\t\tif ( postType === 'wp_navigation' ) {\n\t\t\tparsedBlocks = [\n\t\t\t\tcreateBlock(\n\t\t\t\t\t'core/navigation',\n\t\t\t\t\t{ templateLock: false },\n\t\t\t\t\tparsedBlocks\n\t\t\t\t),\n\t\t\t];\n\t\t}\n\n\t\t// Preserve clientIds from previous render to prevent React unmount/remount.\n\t\tconst blocksWithStableIds = preserveClientIds(\n\t\t\tparsedBlocks,\n\t\t\tpreviousBlocksRef.current\n\t\t);\n\n\t\t// Update ref for next render.\n\t\tpreviousBlocksRef.current = blocksWithStableIds;\n\n\t\treturn blocksWithStableIds;\n\t}, [ revision?.content?.raw, postType ] );\n\n\tconst settings = useMemo(\n\t\t() => ( {\n\t\t\t...blockEditorSettings,\n\t\t\tisPreviewMode: true,\n\t\t} ),\n\t\t[ blockEditorSettings ]\n\t);\n\n\treturn revision ? (\n\t\t<ExperimentalBlockEditorProvider value={ blocks } settings={ settings }>\n\t\t\t<VisualEditor />\n\t\t</ExperimentalBlockEditorProvider>\n\t) : (\n\t\t<div className=\"editor-revisions-canvas__loading\">\n\t\t\t<Spinner />\n\t\t</div>\n\t);\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,wBAAwB;AACxB,0BAGO;AACP,oBAAmC;AACnC,kBAA0B;AAC1B,qBAAgC;AAKhC,yBAAuB;AACvB,mBAAqC;AACrC,2BAAyB;AACzB,iCAAkC;AA6D/B;AA3DH,IAAM,EAAE,gCAAgC,QAAI,2BAAQ,oBAAAA,WAAuB;AAO5D,SAAR,kBAAmC;AACzC,QAAM,EAAE,UAAU,UAAU,oBAAoB,QAAI;AAAA,IACnD,CAAE,WAAY;AACb,YAAM,EAAE,oBAAoB,mBAAmB,QAAI;AAAA,QAClD,OAAQ,aAAAC,KAAY;AAAA,MACrB;AACA,aAAO;AAAA,QACN,UAAU,mBAAmB;AAAA,QAC7B,UAAU,mBAAmB;AAAA,QAC7B,qBAAqB,OAAQ,oBAAAC,KAAiB,EAAE,YAAY;AAAA,MAC7D;AAAA,IACD;AAAA,IACA,CAAC;AAAA,EACF;AAGA,QAAM,wBAAoB,uBAAQ,CAAC,CAAE;AAErC,QAAM,aAAS,wBAAS,MAAM;AAC7B,QAAI,mBAAe,qBAAO,UAAU,SAAS,OAAO,EAAG;AACvD,QAAK,aAAa,iBAAkB;AACnC,qBAAe;AAAA,YACd;AAAA,UACC;AAAA,UACA,EAAE,cAAc,MAAM;AAAA,UACtB;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAGA,UAAM,0BAAsB;AAAA,MAC3B;AAAA,MACA,kBAAkB;AAAA,IACnB;AAGA,sBAAkB,UAAU;AAE5B,WAAO;AAAA,EACR,GAAG,CAAE,UAAU,SAAS,KAAK,QAAS,CAAE;AAExC,QAAM,eAAW;AAAA,IAChB,OAAQ;AAAA,MACP,GAAG;AAAA,MACH,eAAe;AAAA,IAChB;AAAA,IACA,CAAE,mBAAoB;AAAA,EACvB;AAEA,SAAO,WACN,4CAAC,mCAAgC,OAAQ,QAAS,UACjD,sDAAC,qBAAAC,SAAA,EAAa,GACf,IAEA,4CAAC,SAAI,WAAU,oCACd,sDAAC,6BAAQ,GACV;AAEF;",
|
|
6
6
|
"names": ["blockEditorPrivateApis", "editorStore", "blockEditorStore", "VisualEditor"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// packages/editor/src/components/post-revisions-preview/preserve-client-ids.js
|
|
2
|
+
import { diffArrays } from "diff/lib/diff/array";
|
|
3
|
+
function preserveClientIds(newBlocks, prevBlocks) {
|
|
4
|
+
if (!prevBlocks?.length || !newBlocks?.length) {
|
|
5
|
+
return newBlocks;
|
|
6
|
+
}
|
|
7
|
+
const newSigs = newBlocks.map((block) => block.name);
|
|
8
|
+
const prevSigs = prevBlocks.map((block) => block.name);
|
|
9
|
+
const diffResult = diffArrays(prevSigs, newSigs);
|
|
10
|
+
let newIndex = 0;
|
|
11
|
+
let prevIndex = 0;
|
|
12
|
+
const result = [];
|
|
13
|
+
for (const chunk of diffResult) {
|
|
14
|
+
if (chunk.removed) {
|
|
15
|
+
prevIndex += chunk.count;
|
|
16
|
+
} else if (chunk.added) {
|
|
17
|
+
for (let i = 0; i < chunk.count; i++) {
|
|
18
|
+
result.push(newBlocks[newIndex++]);
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
for (let i = 0; i < chunk.count; i++) {
|
|
22
|
+
const newBlock = newBlocks[newIndex++];
|
|
23
|
+
const prevBlock = prevBlocks[prevIndex++];
|
|
24
|
+
result.push({
|
|
25
|
+
...newBlock,
|
|
26
|
+
clientId: prevBlock.clientId,
|
|
27
|
+
innerBlocks: preserveClientIds(
|
|
28
|
+
newBlock.innerBlocks,
|
|
29
|
+
prevBlock.innerBlocks
|
|
30
|
+
)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
preserveClientIds
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=preserve-client-ids.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/components/post-revisions-preview/preserve-client-ids.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * External dependencies\n */\nimport { diffArrays } from 'diff/lib/diff/array';\n\n/**\n * Preserves clientIds from previously rendered blocks to prevent flashing.\n * Uses LCS algorithm to match blocks by name.\n *\n * This compares the newly parsed blocks against the last rendered blocks\n * to maintain React key stability.\n *\n * @param {Array} newBlocks Newly parsed blocks with fresh clientIds.\n * @param {Array} prevBlocks Previously rendered blocks with stable clientIds.\n * @return {Array} Blocks with preserved clientIds where possible.\n */\nexport function preserveClientIds( newBlocks, prevBlocks ) {\n\tif ( ! prevBlocks?.length || ! newBlocks?.length ) {\n\t\treturn newBlocks;\n\t}\n\n\t// Create signatures for LCS matching using block name.\n\tconst newSigs = newBlocks.map( ( block ) => block.name );\n\tconst prevSigs = prevBlocks.map( ( block ) => block.name );\n\n\tconst diffResult = diffArrays( prevSigs, newSigs );\n\n\tlet newIndex = 0;\n\tlet prevIndex = 0;\n\tconst result = [];\n\n\tfor ( const chunk of diffResult ) {\n\t\tif ( chunk.removed ) {\n\t\t\t// Blocks only in prev render - skip them.\n\t\t\tprevIndex += chunk.count;\n\t\t} else if ( chunk.added ) {\n\t\t\t// Blocks only in new render - keep new clientIds.\n\t\t\tfor ( let i = 0; i < chunk.count; i++ ) {\n\t\t\t\tresult.push( newBlocks[ newIndex++ ] );\n\t\t\t}\n\t\t} else {\n\t\t\t// Matched blocks - preserve clientIds from prev render.\n\t\t\tfor ( let i = 0; i < chunk.count; i++ ) {\n\t\t\t\tconst newBlock = newBlocks[ newIndex++ ];\n\t\t\t\tconst prevBlock = prevBlocks[ prevIndex++ ];\n\t\t\t\tresult.push( {\n\t\t\t\t\t...newBlock,\n\t\t\t\t\tclientId: prevBlock.clientId,\n\t\t\t\t\tinnerBlocks: preserveClientIds(\n\t\t\t\t\t\tnewBlock.innerBlocks,\n\t\t\t\t\t\tprevBlock.innerBlocks\n\t\t\t\t\t),\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n"],
|
|
5
|
+
"mappings": ";AAGA,SAAS,kBAAkB;AAapB,SAAS,kBAAmB,WAAW,YAAa;AAC1D,MAAK,CAAE,YAAY,UAAU,CAAE,WAAW,QAAS;AAClD,WAAO;AAAA,EACR;AAGA,QAAM,UAAU,UAAU,IAAK,CAAE,UAAW,MAAM,IAAK;AACvD,QAAM,WAAW,WAAW,IAAK,CAAE,UAAW,MAAM,IAAK;AAEzD,QAAM,aAAa,WAAY,UAAU,OAAQ;AAEjD,MAAI,WAAW;AACf,MAAI,YAAY;AAChB,QAAM,SAAS,CAAC;AAEhB,aAAY,SAAS,YAAa;AACjC,QAAK,MAAM,SAAU;AAEpB,mBAAa,MAAM;AAAA,IACpB,WAAY,MAAM,OAAQ;AAEzB,eAAU,IAAI,GAAG,IAAI,MAAM,OAAO,KAAM;AACvC,eAAO,KAAM,UAAW,UAAW,CAAE;AAAA,MACtC;AAAA,IACD,OAAO;AAEN,eAAU,IAAI,GAAG,IAAI,MAAM,OAAO,KAAM;AACvC,cAAM,WAAW,UAAW,UAAW;AACvC,cAAM,YAAY,WAAY,WAAY;AAC1C,eAAO,KAAM;AAAA,UACZ,GAAG;AAAA,UACH,UAAU,UAAU;AAAA,UACpB,aAAa;AAAA,YACZ,SAAS;AAAA,YACT,UAAU;AAAA,UACX;AAAA,QACD,CAAE;AAAA,MACH;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
} from "@wordpress/block-editor";
|
|
7
7
|
import { createBlock, parse } from "@wordpress/blocks";
|
|
8
8
|
import { useSelect } from "@wordpress/data";
|
|
9
|
-
import { useMemo } from "@wordpress/element";
|
|
9
|
+
import { useMemo, useRef } from "@wordpress/element";
|
|
10
10
|
import { unlock } from "../../lock-unlock.mjs";
|
|
11
11
|
import { store as editorStore } from "../../store/index.mjs";
|
|
12
12
|
import VisualEditor from "../visual-editor/index.mjs";
|
|
13
|
+
import { preserveClientIds } from "./preserve-client-ids.mjs";
|
|
13
14
|
import { jsx } from "react/jsx-runtime";
|
|
14
15
|
var { ExperimentalBlockEditorProvider } = unlock(blockEditorPrivateApis);
|
|
15
16
|
function RevisionsCanvas() {
|
|
@@ -26,10 +27,11 @@ function RevisionsCanvas() {
|
|
|
26
27
|
},
|
|
27
28
|
[]
|
|
28
29
|
);
|
|
30
|
+
const previousBlocksRef = useRef([]);
|
|
29
31
|
const blocks = useMemo(() => {
|
|
30
|
-
|
|
32
|
+
let parsedBlocks = parse(revision?.content?.raw ?? "");
|
|
31
33
|
if (postType === "wp_navigation") {
|
|
32
|
-
|
|
34
|
+
parsedBlocks = [
|
|
33
35
|
createBlock(
|
|
34
36
|
"core/navigation",
|
|
35
37
|
{ templateLock: false },
|
|
@@ -37,7 +39,12 @@ function RevisionsCanvas() {
|
|
|
37
39
|
)
|
|
38
40
|
];
|
|
39
41
|
}
|
|
40
|
-
|
|
42
|
+
const blocksWithStableIds = preserveClientIds(
|
|
43
|
+
parsedBlocks,
|
|
44
|
+
previousBlocksRef.current
|
|
45
|
+
);
|
|
46
|
+
previousBlocksRef.current = blocksWithStableIds;
|
|
47
|
+
return blocksWithStableIds;
|
|
41
48
|
}, [revision?.content?.raw, postType]);
|
|
42
49
|
const settings = useMemo(
|
|
43
50
|
() => ({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/components/post-revisions-preview/revisions-canvas.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { Spinner } from '@wordpress/components';\nimport {\n\tprivateApis as blockEditorPrivateApis,\n\tstore as blockEditorStore,\n} from '@wordpress/block-editor';\nimport { createBlock, parse } from '@wordpress/blocks';\nimport { useSelect } from '@wordpress/data';\nimport { useMemo } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport { unlock } from '../../lock-unlock';\nimport { store as editorStore } from '../../store';\nimport VisualEditor from '../visual-editor';\n\nconst { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis );\n\n/**\n * Canvas component that renders a post revision in read-only mode.\n *\n * @return {JSX.Element} The revisions canvas component.\n */\nexport default function RevisionsCanvas() {\n\tconst { revision, postType, blockEditorSettings } = useSelect(\n\t\t( select ) => {\n\t\t\tconst { getCurrentRevision, getCurrentPostType } = unlock(\n\t\t\t\tselect( editorStore )\n\t\t\t);\n\t\t\treturn {\n\t\t\t\trevision: getCurrentRevision(),\n\t\t\t\tpostType: getCurrentPostType(),\n\t\t\t\tblockEditorSettings: select( blockEditorStore ).getSettings(),\n\t\t\t};\n\t\t},\n\t\t[]\n\t);\n\n\tconst blocks = useMemo( () => {\n\t\
|
|
5
|
-
"mappings": ";AAGA,SAAS,eAAe;AACxB;AAAA,EACC,eAAe;AAAA,EACf,SAAS;AAAA,OACH;AACP,SAAS,aAAa,aAAa;AACnC,SAAS,iBAAiB;AAC1B,SAAS,
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { Spinner } from '@wordpress/components';\nimport {\n\tprivateApis as blockEditorPrivateApis,\n\tstore as blockEditorStore,\n} from '@wordpress/block-editor';\nimport { createBlock, parse } from '@wordpress/blocks';\nimport { useSelect } from '@wordpress/data';\nimport { useMemo, useRef } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport { unlock } from '../../lock-unlock';\nimport { store as editorStore } from '../../store';\nimport VisualEditor from '../visual-editor';\nimport { preserveClientIds } from './preserve-client-ids';\n\nconst { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis );\n\n/**\n * Canvas component that renders a post revision in read-only mode.\n *\n * @return {JSX.Element} The revisions canvas component.\n */\nexport default function RevisionsCanvas() {\n\tconst { revision, postType, blockEditorSettings } = useSelect(\n\t\t( select ) => {\n\t\t\tconst { getCurrentRevision, getCurrentPostType } = unlock(\n\t\t\t\tselect( editorStore )\n\t\t\t);\n\t\t\treturn {\n\t\t\t\trevision: getCurrentRevision(),\n\t\t\t\tpostType: getCurrentPostType(),\n\t\t\t\tblockEditorSettings: select( blockEditorStore ).getSettings(),\n\t\t\t};\n\t\t},\n\t\t[]\n\t);\n\n\t// Track previously rendered blocks to preserve clientIds between renders.\n\tconst previousBlocksRef = useRef( [] );\n\n\tconst blocks = useMemo( () => {\n\t\tlet parsedBlocks = parse( revision?.content?.raw ?? '' );\n\t\tif ( postType === 'wp_navigation' ) {\n\t\t\tparsedBlocks = [\n\t\t\t\tcreateBlock(\n\t\t\t\t\t'core/navigation',\n\t\t\t\t\t{ templateLock: false },\n\t\t\t\t\tparsedBlocks\n\t\t\t\t),\n\t\t\t];\n\t\t}\n\n\t\t// Preserve clientIds from previous render to prevent React unmount/remount.\n\t\tconst blocksWithStableIds = preserveClientIds(\n\t\t\tparsedBlocks,\n\t\t\tpreviousBlocksRef.current\n\t\t);\n\n\t\t// Update ref for next render.\n\t\tpreviousBlocksRef.current = blocksWithStableIds;\n\n\t\treturn blocksWithStableIds;\n\t}, [ revision?.content?.raw, postType ] );\n\n\tconst settings = useMemo(\n\t\t() => ( {\n\t\t\t...blockEditorSettings,\n\t\t\tisPreviewMode: true,\n\t\t} ),\n\t\t[ blockEditorSettings ]\n\t);\n\n\treturn revision ? (\n\t\t<ExperimentalBlockEditorProvider value={ blocks } settings={ settings }>\n\t\t\t<VisualEditor />\n\t\t</ExperimentalBlockEditorProvider>\n\t) : (\n\t\t<div className=\"editor-revisions-canvas__loading\">\n\t\t\t<Spinner />\n\t\t</div>\n\t);\n}\n"],
|
|
5
|
+
"mappings": ";AAGA,SAAS,eAAe;AACxB;AAAA,EACC,eAAe;AAAA,EACf,SAAS;AAAA,OACH;AACP,SAAS,aAAa,aAAa;AACnC,SAAS,iBAAiB;AAC1B,SAAS,SAAS,cAAc;AAKhC,SAAS,cAAc;AACvB,SAAS,SAAS,mBAAmB;AACrC,OAAO,kBAAkB;AACzB,SAAS,yBAAyB;AA6D/B;AA3DH,IAAM,EAAE,gCAAgC,IAAI,OAAQ,sBAAuB;AAO5D,SAAR,kBAAmC;AACzC,QAAM,EAAE,UAAU,UAAU,oBAAoB,IAAI;AAAA,IACnD,CAAE,WAAY;AACb,YAAM,EAAE,oBAAoB,mBAAmB,IAAI;AAAA,QAClD,OAAQ,WAAY;AAAA,MACrB;AACA,aAAO;AAAA,QACN,UAAU,mBAAmB;AAAA,QAC7B,UAAU,mBAAmB;AAAA,QAC7B,qBAAqB,OAAQ,gBAAiB,EAAE,YAAY;AAAA,MAC7D;AAAA,IACD;AAAA,IACA,CAAC;AAAA,EACF;AAGA,QAAM,oBAAoB,OAAQ,CAAC,CAAE;AAErC,QAAM,SAAS,QAAS,MAAM;AAC7B,QAAI,eAAe,MAAO,UAAU,SAAS,OAAO,EAAG;AACvD,QAAK,aAAa,iBAAkB;AACnC,qBAAe;AAAA,QACd;AAAA,UACC;AAAA,UACA,EAAE,cAAc,MAAM;AAAA,UACtB;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAGA,UAAM,sBAAsB;AAAA,MAC3B;AAAA,MACA,kBAAkB;AAAA,IACnB;AAGA,sBAAkB,UAAU;AAE5B,WAAO;AAAA,EACR,GAAG,CAAE,UAAU,SAAS,KAAK,QAAS,CAAE;AAExC,QAAM,WAAW;AAAA,IAChB,OAAQ;AAAA,MACP,GAAG;AAAA,MACH,eAAe;AAAA,IAChB;AAAA,IACA,CAAE,mBAAoB;AAAA,EACvB;AAEA,SAAO,WACN,oBAAC,mCAAgC,OAAQ,QAAS,UACjD,8BAAC,gBAAa,GACf,IAEA,oBAAC,SAAI,WAAU,oCACd,8BAAC,WAAQ,GACV;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3947,6 +3947,11 @@ div.dataviews-view-list {
|
|
|
3947
3947
|
gap: 16px;
|
|
3948
3948
|
}
|
|
3949
3949
|
|
|
3950
|
+
.dataforms-layouts-details__summary-content {
|
|
3951
|
+
display: inline-flex;
|
|
3952
|
+
min-height: 24px;
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3950
3955
|
.dataforms-layouts-details__content {
|
|
3951
3956
|
padding-top: 12px;
|
|
3952
3957
|
}
|
package/build-style/style.css
CHANGED
|
@@ -3950,6 +3950,11 @@ div.dataviews-view-list {
|
|
|
3950
3950
|
gap: 16px;
|
|
3951
3951
|
}
|
|
3952
3952
|
|
|
3953
|
+
.dataforms-layouts-details__summary-content {
|
|
3954
|
+
display: inline-flex;
|
|
3955
|
+
min-height: 24px;
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3953
3958
|
.dataforms-layouts-details__content {
|
|
3954
3959
|
padding-top: 12px;
|
|
3955
3960
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preserves clientIds from previously rendered blocks to prevent flashing.
|
|
3
|
+
* Uses LCS algorithm to match blocks by name.
|
|
4
|
+
*
|
|
5
|
+
* This compares the newly parsed blocks against the last rendered blocks
|
|
6
|
+
* to maintain React key stability.
|
|
7
|
+
*
|
|
8
|
+
* @param {Array} newBlocks Newly parsed blocks with fresh clientIds.
|
|
9
|
+
* @param {Array} prevBlocks Previously rendered blocks with stable clientIds.
|
|
10
|
+
* @return {Array} Blocks with preserved clientIds where possible.
|
|
11
|
+
*/
|
|
12
|
+
export function preserveClientIds(newBlocks: any[], prevBlocks: any[]): any[];
|
|
13
|
+
//# sourceMappingURL=preserve-client-ids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preserve-client-ids.d.ts","sourceRoot":"","sources":["../../../src/components/post-revisions-preview/preserve-client-ids.js"],"names":[],"mappings":"AAKA;;;;;;;;;;GAUG;AACH,8EA0CC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revisions-canvas.d.ts","sourceRoot":"","sources":["../../../src/components/post-revisions-preview/revisions-canvas.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"revisions-canvas.d.ts","sourceRoot":"","sources":["../../../src/components/post-revisions-preview/revisions-canvas.js"],"names":[],"mappings":"AAsBA;;;;GAIG;AACH,2CAFY,GAAG,CAAC,OAAO,CA6DtB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/editor",
|
|
3
|
-
"version": "14.39.0",
|
|
3
|
+
"version": "14.39.1-next.v.0+5aba098fc",
|
|
4
4
|
"description": "Enhanced block editor for WordPress posts.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -61,50 +61,51 @@
|
|
|
61
61
|
],
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@floating-ui/react-dom": "2.0.8",
|
|
64
|
-
"@wordpress/a11y": "^4.39.0",
|
|
65
|
-
"@wordpress/api-fetch": "^7.39.0",
|
|
66
|
-
"@wordpress/base-styles": "^6.15.0",
|
|
67
|
-
"@wordpress/blob": "^4.39.0",
|
|
68
|
-
"@wordpress/block-editor": "^15.12.0",
|
|
69
|
-
"@wordpress/blocks": "^15.12.0",
|
|
70
|
-
"@wordpress/commands": "^1.39.0",
|
|
71
|
-
"@wordpress/components": "^32.1.0",
|
|
72
|
-
"@wordpress/compose": "^7.39.0",
|
|
73
|
-
"@wordpress/core-data": "^7.39.0",
|
|
74
|
-
"@wordpress/data": "^10.39.0",
|
|
75
|
-
"@wordpress/dataviews": "^11.
|
|
76
|
-
"@wordpress/date": "^5.39.0",
|
|
77
|
-
"@wordpress/deprecated": "^4.39.0",
|
|
78
|
-
"@wordpress/dom": "^4.39.0",
|
|
79
|
-
"@wordpress/element": "^6.39.0",
|
|
80
|
-
"@wordpress/fields": "^0.31.0",
|
|
81
|
-
"@wordpress/global-styles-engine": "^1.6.0",
|
|
82
|
-
"@wordpress/global-styles-ui": "^1.6.0",
|
|
83
|
-
"@wordpress/hooks": "^4.39.0",
|
|
84
|
-
"@wordpress/html-entities": "^4.39.0",
|
|
85
|
-
"@wordpress/i18n": "^6.12.0",
|
|
86
|
-
"@wordpress/icons": "^11.6.0",
|
|
87
|
-
"@wordpress/interface": "^9.24.0",
|
|
88
|
-
"@wordpress/keyboard-shortcuts": "^5.39.0",
|
|
89
|
-
"@wordpress/keycodes": "^4.39.0",
|
|
90
|
-
"@wordpress/media-editor": "^0.2.0",
|
|
91
|
-
"@wordpress/media-fields": "^0.4.0",
|
|
92
|
-
"@wordpress/media-utils": "^5.39.0",
|
|
93
|
-
"@wordpress/notices": "^5.39.0",
|
|
94
|
-
"@wordpress/patterns": "^2.39.0",
|
|
95
|
-
"@wordpress/plugins": "^7.39.0",
|
|
96
|
-
"@wordpress/preferences": "^4.39.0",
|
|
97
|
-
"@wordpress/private-apis": "^1.39.0",
|
|
98
|
-
"@wordpress/reusable-blocks": "^5.39.0",
|
|
99
|
-
"@wordpress/rich-text": "^7.39.0",
|
|
100
|
-
"@wordpress/server-side-render": "^6.15.0",
|
|
101
|
-
"@wordpress/url": "^4.39.0",
|
|
102
|
-
"@wordpress/warning": "^3.39.0",
|
|
103
|
-
"@wordpress/wordcount": "^4.39.0",
|
|
64
|
+
"@wordpress/a11y": "^4.39.1-next.v.0+5aba098fc",
|
|
65
|
+
"@wordpress/api-fetch": "^7.39.1-next.v.0+5aba098fc",
|
|
66
|
+
"@wordpress/base-styles": "^6.15.1-next.v.0+5aba098fc",
|
|
67
|
+
"@wordpress/blob": "^4.39.1-next.v.0+5aba098fc",
|
|
68
|
+
"@wordpress/block-editor": "^15.12.1-next.v.0+5aba098fc",
|
|
69
|
+
"@wordpress/blocks": "^15.12.1-next.v.0+5aba098fc",
|
|
70
|
+
"@wordpress/commands": "^1.39.1-next.v.0+5aba098fc",
|
|
71
|
+
"@wordpress/components": "^32.1.1-next.v.0+5aba098fc",
|
|
72
|
+
"@wordpress/compose": "^7.39.1-next.v.0+5aba098fc",
|
|
73
|
+
"@wordpress/core-data": "^7.39.1-next.v.0+5aba098fc",
|
|
74
|
+
"@wordpress/data": "^10.39.1-next.v.0+5aba098fc",
|
|
75
|
+
"@wordpress/dataviews": "^11.4.1-next.v.0+5aba098fc",
|
|
76
|
+
"@wordpress/date": "^5.39.1-next.v.0+5aba098fc",
|
|
77
|
+
"@wordpress/deprecated": "^4.39.1-next.v.0+5aba098fc",
|
|
78
|
+
"@wordpress/dom": "^4.39.1-next.v.0+5aba098fc",
|
|
79
|
+
"@wordpress/element": "^6.39.1-next.v.0+5aba098fc",
|
|
80
|
+
"@wordpress/fields": "^0.31.1-next.v.0+5aba098fc",
|
|
81
|
+
"@wordpress/global-styles-engine": "^1.6.1-next.v.0+5aba098fc",
|
|
82
|
+
"@wordpress/global-styles-ui": "^1.6.1-next.v.0+5aba098fc",
|
|
83
|
+
"@wordpress/hooks": "^4.39.1-next.v.0+5aba098fc",
|
|
84
|
+
"@wordpress/html-entities": "^4.39.1-next.v.0+5aba098fc",
|
|
85
|
+
"@wordpress/i18n": "^6.12.1-next.v.0+5aba098fc",
|
|
86
|
+
"@wordpress/icons": "^11.6.1-next.v.0+5aba098fc",
|
|
87
|
+
"@wordpress/interface": "^9.24.1-next.v.0+5aba098fc",
|
|
88
|
+
"@wordpress/keyboard-shortcuts": "^5.39.1-next.v.0+5aba098fc",
|
|
89
|
+
"@wordpress/keycodes": "^4.39.1-next.v.0+5aba098fc",
|
|
90
|
+
"@wordpress/media-editor": "^0.2.1-next.v.0+5aba098fc",
|
|
91
|
+
"@wordpress/media-fields": "^0.4.1-next.v.0+5aba098fc",
|
|
92
|
+
"@wordpress/media-utils": "^5.39.1-next.v.0+5aba098fc",
|
|
93
|
+
"@wordpress/notices": "^5.39.1-next.v.0+5aba098fc",
|
|
94
|
+
"@wordpress/patterns": "^2.39.1-next.v.0+5aba098fc",
|
|
95
|
+
"@wordpress/plugins": "^7.39.1-next.v.0+5aba098fc",
|
|
96
|
+
"@wordpress/preferences": "^4.39.1-next.v.0+5aba098fc",
|
|
97
|
+
"@wordpress/private-apis": "^1.39.1-next.v.0+5aba098fc",
|
|
98
|
+
"@wordpress/reusable-blocks": "^5.39.1-next.v.0+5aba098fc",
|
|
99
|
+
"@wordpress/rich-text": "^7.39.1-next.v.0+5aba098fc",
|
|
100
|
+
"@wordpress/server-side-render": "^6.15.1-next.v.0+5aba098fc",
|
|
101
|
+
"@wordpress/url": "^4.39.1-next.v.0+5aba098fc",
|
|
102
|
+
"@wordpress/warning": "^3.39.1-next.v.0+5aba098fc",
|
|
103
|
+
"@wordpress/wordcount": "^4.39.1-next.v.0+5aba098fc",
|
|
104
104
|
"change-case": "^4.1.2",
|
|
105
105
|
"client-zip": "^2.4.5",
|
|
106
106
|
"clsx": "^2.1.1",
|
|
107
107
|
"date-fns": "^3.6.0",
|
|
108
|
+
"diff": "^4.0.2",
|
|
108
109
|
"fast-deep-equal": "^3.1.3",
|
|
109
110
|
"memize": "^2.1.0",
|
|
110
111
|
"react-autosize-textarea": "^7.1.0",
|
|
@@ -121,5 +122,5 @@
|
|
|
121
122
|
"publishConfig": {
|
|
122
123
|
"access": "public"
|
|
123
124
|
},
|
|
124
|
-
"gitHead": "
|
|
125
|
+
"gitHead": "d730f9e00f5462d1b9d2660632850f5f43ccff44"
|
|
125
126
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { diffArrays } from 'diff/lib/diff/array';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Preserves clientIds from previously rendered blocks to prevent flashing.
|
|
8
|
+
* Uses LCS algorithm to match blocks by name.
|
|
9
|
+
*
|
|
10
|
+
* This compares the newly parsed blocks against the last rendered blocks
|
|
11
|
+
* to maintain React key stability.
|
|
12
|
+
*
|
|
13
|
+
* @param {Array} newBlocks Newly parsed blocks with fresh clientIds.
|
|
14
|
+
* @param {Array} prevBlocks Previously rendered blocks with stable clientIds.
|
|
15
|
+
* @return {Array} Blocks with preserved clientIds where possible.
|
|
16
|
+
*/
|
|
17
|
+
export function preserveClientIds( newBlocks, prevBlocks ) {
|
|
18
|
+
if ( ! prevBlocks?.length || ! newBlocks?.length ) {
|
|
19
|
+
return newBlocks;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Create signatures for LCS matching using block name.
|
|
23
|
+
const newSigs = newBlocks.map( ( block ) => block.name );
|
|
24
|
+
const prevSigs = prevBlocks.map( ( block ) => block.name );
|
|
25
|
+
|
|
26
|
+
const diffResult = diffArrays( prevSigs, newSigs );
|
|
27
|
+
|
|
28
|
+
let newIndex = 0;
|
|
29
|
+
let prevIndex = 0;
|
|
30
|
+
const result = [];
|
|
31
|
+
|
|
32
|
+
for ( const chunk of diffResult ) {
|
|
33
|
+
if ( chunk.removed ) {
|
|
34
|
+
// Blocks only in prev render - skip them.
|
|
35
|
+
prevIndex += chunk.count;
|
|
36
|
+
} else if ( chunk.added ) {
|
|
37
|
+
// Blocks only in new render - keep new clientIds.
|
|
38
|
+
for ( let i = 0; i < chunk.count; i++ ) {
|
|
39
|
+
result.push( newBlocks[ newIndex++ ] );
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
// Matched blocks - preserve clientIds from prev render.
|
|
43
|
+
for ( let i = 0; i < chunk.count; i++ ) {
|
|
44
|
+
const newBlock = newBlocks[ newIndex++ ];
|
|
45
|
+
const prevBlock = prevBlocks[ prevIndex++ ];
|
|
46
|
+
result.push( {
|
|
47
|
+
...newBlock,
|
|
48
|
+
clientId: prevBlock.clientId,
|
|
49
|
+
innerBlocks: preserveClientIds(
|
|
50
|
+
newBlock.innerBlocks,
|
|
51
|
+
prevBlock.innerBlocks
|
|
52
|
+
),
|
|
53
|
+
} );
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from '@wordpress/block-editor';
|
|
9
9
|
import { createBlock, parse } from '@wordpress/blocks';
|
|
10
10
|
import { useSelect } from '@wordpress/data';
|
|
11
|
-
import { useMemo } from '@wordpress/element';
|
|
11
|
+
import { useMemo, useRef } from '@wordpress/element';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Internal dependencies
|
|
@@ -16,6 +16,7 @@ import { useMemo } from '@wordpress/element';
|
|
|
16
16
|
import { unlock } from '../../lock-unlock';
|
|
17
17
|
import { store as editorStore } from '../../store';
|
|
18
18
|
import VisualEditor from '../visual-editor';
|
|
19
|
+
import { preserveClientIds } from './preserve-client-ids';
|
|
19
20
|
|
|
20
21
|
const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis );
|
|
21
22
|
|
|
@@ -39,10 +40,13 @@ export default function RevisionsCanvas() {
|
|
|
39
40
|
[]
|
|
40
41
|
);
|
|
41
42
|
|
|
43
|
+
// Track previously rendered blocks to preserve clientIds between renders.
|
|
44
|
+
const previousBlocksRef = useRef( [] );
|
|
45
|
+
|
|
42
46
|
const blocks = useMemo( () => {
|
|
43
|
-
|
|
47
|
+
let parsedBlocks = parse( revision?.content?.raw ?? '' );
|
|
44
48
|
if ( postType === 'wp_navigation' ) {
|
|
45
|
-
|
|
49
|
+
parsedBlocks = [
|
|
46
50
|
createBlock(
|
|
47
51
|
'core/navigation',
|
|
48
52
|
{ templateLock: false },
|
|
@@ -50,7 +54,17 @@ export default function RevisionsCanvas() {
|
|
|
50
54
|
),
|
|
51
55
|
];
|
|
52
56
|
}
|
|
53
|
-
|
|
57
|
+
|
|
58
|
+
// Preserve clientIds from previous render to prevent React unmount/remount.
|
|
59
|
+
const blocksWithStableIds = preserveClientIds(
|
|
60
|
+
parsedBlocks,
|
|
61
|
+
previousBlocksRef.current
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Update ref for next render.
|
|
65
|
+
previousBlocksRef.current = blocksWithStableIds;
|
|
66
|
+
|
|
67
|
+
return blocksWithStableIds;
|
|
54
68
|
}, [ revision?.content?.raw, postType ] );
|
|
55
69
|
|
|
56
70
|
const settings = useMemo(
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { preserveClientIds } from '../preserve-client-ids';
|
|
5
|
+
|
|
6
|
+
describe( 'preserveClientIds', () => {
|
|
7
|
+
it( 'should return newBlocks when prevBlocks is empty', () => {
|
|
8
|
+
const newBlocks = [
|
|
9
|
+
{ name: 'core/paragraph', clientId: 'new-1', attributes: {} },
|
|
10
|
+
];
|
|
11
|
+
expect( preserveClientIds( newBlocks, [] ) ).toBe( newBlocks );
|
|
12
|
+
expect( preserveClientIds( newBlocks, null ) ).toBe( newBlocks );
|
|
13
|
+
expect( preserveClientIds( newBlocks, undefined ) ).toBe( newBlocks );
|
|
14
|
+
} );
|
|
15
|
+
|
|
16
|
+
it( 'should return newBlocks when newBlocks is empty', () => {
|
|
17
|
+
const prevBlocks = [
|
|
18
|
+
{ name: 'core/paragraph', clientId: 'prev-1', attributes: {} },
|
|
19
|
+
];
|
|
20
|
+
expect( preserveClientIds( [], prevBlocks ) ).toEqual( [] );
|
|
21
|
+
expect( preserveClientIds( null, prevBlocks ) ).toBe( null );
|
|
22
|
+
expect( preserveClientIds( undefined, prevBlocks ) ).toBe( undefined );
|
|
23
|
+
} );
|
|
24
|
+
|
|
25
|
+
it( 'should preserve clientIds for identical blocks', () => {
|
|
26
|
+
const prevBlocks = [
|
|
27
|
+
{
|
|
28
|
+
name: 'core/paragraph',
|
|
29
|
+
clientId: 'prev-1',
|
|
30
|
+
attributes: { content: 'Hello' },
|
|
31
|
+
originalContent: 'Hello',
|
|
32
|
+
innerBlocks: [],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'core/heading',
|
|
36
|
+
clientId: 'prev-2',
|
|
37
|
+
attributes: { level: 2 },
|
|
38
|
+
originalContent: 'Title',
|
|
39
|
+
innerBlocks: [],
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
const newBlocks = [
|
|
43
|
+
{
|
|
44
|
+
name: 'core/paragraph',
|
|
45
|
+
clientId: 'new-1',
|
|
46
|
+
attributes: { content: 'Hello' },
|
|
47
|
+
originalContent: 'Hello',
|
|
48
|
+
innerBlocks: [],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'core/heading',
|
|
52
|
+
clientId: 'new-2',
|
|
53
|
+
attributes: { level: 2 },
|
|
54
|
+
originalContent: 'Title',
|
|
55
|
+
innerBlocks: [],
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const result = preserveClientIds( newBlocks, prevBlocks );
|
|
60
|
+
|
|
61
|
+
expect( result[ 0 ].clientId ).toBe( 'prev-1' );
|
|
62
|
+
expect( result[ 1 ].clientId ).toBe( 'prev-2' );
|
|
63
|
+
} );
|
|
64
|
+
|
|
65
|
+
it( 'should keep new clientIds for added blocks', () => {
|
|
66
|
+
const prevBlocks = [
|
|
67
|
+
{
|
|
68
|
+
name: 'core/paragraph',
|
|
69
|
+
clientId: 'prev-1',
|
|
70
|
+
attributes: { content: 'First' },
|
|
71
|
+
originalContent: 'First',
|
|
72
|
+
innerBlocks: [],
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
const newBlocks = [
|
|
76
|
+
{
|
|
77
|
+
name: 'core/paragraph',
|
|
78
|
+
clientId: 'new-1',
|
|
79
|
+
attributes: { content: 'First' },
|
|
80
|
+
originalContent: 'First',
|
|
81
|
+
innerBlocks: [],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'core/paragraph',
|
|
85
|
+
clientId: 'new-2',
|
|
86
|
+
attributes: { content: 'Second' },
|
|
87
|
+
originalContent: 'Second',
|
|
88
|
+
innerBlocks: [],
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const result = preserveClientIds( newBlocks, prevBlocks );
|
|
93
|
+
|
|
94
|
+
expect( result[ 0 ].clientId ).toBe( 'prev-1' );
|
|
95
|
+
expect( result[ 1 ].clientId ).toBe( 'new-2' );
|
|
96
|
+
} );
|
|
97
|
+
|
|
98
|
+
it( 'should handle removed blocks', () => {
|
|
99
|
+
const prevBlocks = [
|
|
100
|
+
{
|
|
101
|
+
name: 'core/paragraph',
|
|
102
|
+
clientId: 'prev-1',
|
|
103
|
+
attributes: { content: 'First' },
|
|
104
|
+
originalContent: 'First',
|
|
105
|
+
innerBlocks: [],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'core/paragraph',
|
|
109
|
+
clientId: 'prev-2',
|
|
110
|
+
attributes: { content: 'Second' },
|
|
111
|
+
originalContent: 'Second',
|
|
112
|
+
innerBlocks: [],
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
const newBlocks = [
|
|
116
|
+
{
|
|
117
|
+
name: 'core/paragraph',
|
|
118
|
+
clientId: 'new-2',
|
|
119
|
+
attributes: { content: 'Second' },
|
|
120
|
+
originalContent: 'Second',
|
|
121
|
+
innerBlocks: [],
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
const result = preserveClientIds( newBlocks, prevBlocks );
|
|
126
|
+
|
|
127
|
+
expect( result ).toHaveLength( 1 );
|
|
128
|
+
// Matches by name only, so first paragraph matches first paragraph.
|
|
129
|
+
expect( result[ 0 ].clientId ).toBe( 'prev-1' );
|
|
130
|
+
} );
|
|
131
|
+
|
|
132
|
+
it( 'should preserve clientIds for inner blocks recursively', () => {
|
|
133
|
+
const prevBlocks = [
|
|
134
|
+
{
|
|
135
|
+
name: 'core/group',
|
|
136
|
+
clientId: 'prev-group',
|
|
137
|
+
attributes: {},
|
|
138
|
+
originalContent: '',
|
|
139
|
+
innerBlocks: [
|
|
140
|
+
{
|
|
141
|
+
name: 'core/paragraph',
|
|
142
|
+
clientId: 'prev-inner-1',
|
|
143
|
+
attributes: { content: 'Inner' },
|
|
144
|
+
originalContent: 'Inner',
|
|
145
|
+
innerBlocks: [],
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
const newBlocks = [
|
|
151
|
+
{
|
|
152
|
+
name: 'core/group',
|
|
153
|
+
clientId: 'new-group',
|
|
154
|
+
attributes: {},
|
|
155
|
+
originalContent: '',
|
|
156
|
+
innerBlocks: [
|
|
157
|
+
{
|
|
158
|
+
name: 'core/paragraph',
|
|
159
|
+
clientId: 'new-inner-1',
|
|
160
|
+
attributes: { content: 'Inner' },
|
|
161
|
+
originalContent: 'Inner',
|
|
162
|
+
innerBlocks: [],
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
const result = preserveClientIds( newBlocks, prevBlocks );
|
|
169
|
+
|
|
170
|
+
expect( result[ 0 ].clientId ).toBe( 'prev-group' );
|
|
171
|
+
expect( result[ 0 ].innerBlocks[ 0 ].clientId ).toBe( 'prev-inner-1' );
|
|
172
|
+
} );
|
|
173
|
+
|
|
174
|
+
it( 'should preserve clientId even when attributes differ (matches by name only)', () => {
|
|
175
|
+
const prevBlocks = [
|
|
176
|
+
{
|
|
177
|
+
name: 'core/paragraph',
|
|
178
|
+
clientId: 'prev-1',
|
|
179
|
+
attributes: { content: 'Old content' },
|
|
180
|
+
originalContent: 'Old content',
|
|
181
|
+
innerBlocks: [],
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
const newBlocks = [
|
|
185
|
+
{
|
|
186
|
+
name: 'core/paragraph',
|
|
187
|
+
clientId: 'new-1',
|
|
188
|
+
attributes: { content: 'New content' },
|
|
189
|
+
originalContent: 'New content',
|
|
190
|
+
innerBlocks: [],
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const result = preserveClientIds( newBlocks, prevBlocks );
|
|
195
|
+
|
|
196
|
+
expect( result[ 0 ].clientId ).toBe( 'prev-1' );
|
|
197
|
+
} );
|
|
198
|
+
|
|
199
|
+
it( 'should handle blocks with same name but different content using LCS', () => {
|
|
200
|
+
const prevBlocks = [
|
|
201
|
+
{
|
|
202
|
+
name: 'core/paragraph',
|
|
203
|
+
clientId: 'prev-a',
|
|
204
|
+
attributes: { content: 'A' },
|
|
205
|
+
originalContent: 'A',
|
|
206
|
+
innerBlocks: [],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'core/paragraph',
|
|
210
|
+
clientId: 'prev-b',
|
|
211
|
+
attributes: { content: 'B' },
|
|
212
|
+
originalContent: 'B',
|
|
213
|
+
innerBlocks: [],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'core/paragraph',
|
|
217
|
+
clientId: 'prev-c',
|
|
218
|
+
attributes: { content: 'C' },
|
|
219
|
+
originalContent: 'C',
|
|
220
|
+
innerBlocks: [],
|
|
221
|
+
},
|
|
222
|
+
];
|
|
223
|
+
const newBlocks = [
|
|
224
|
+
{
|
|
225
|
+
name: 'core/paragraph',
|
|
226
|
+
clientId: 'new-a',
|
|
227
|
+
attributes: { content: 'A' },
|
|
228
|
+
originalContent: 'A',
|
|
229
|
+
innerBlocks: [],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: 'core/paragraph',
|
|
233
|
+
clientId: 'new-c',
|
|
234
|
+
attributes: { content: 'C' },
|
|
235
|
+
originalContent: 'C',
|
|
236
|
+
innerBlocks: [],
|
|
237
|
+
},
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
const result = preserveClientIds( newBlocks, prevBlocks );
|
|
241
|
+
|
|
242
|
+
// Matches by name only, so matches in order (first to first, second to second).
|
|
243
|
+
expect( result[ 0 ].clientId ).toBe( 'prev-a' );
|
|
244
|
+
expect( result[ 1 ].clientId ).toBe( 'prev-b' );
|
|
245
|
+
} );
|
|
246
|
+
} );
|