Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix/170: wait for DOM manipulations before saving post #171

Closed
wants to merge 9 commits into from
6 changes: 3 additions & 3 deletions assets/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ class ConvertToBlocksEditorSupport {
loaded = true;

// This delay allows Gutenberg to initialize legacy content into freeform blocks
setTimeout(() => {
const result = transformer.execute();
window._wpLoadBlockEditor.then(async () => {
const result = await transformer.execute();
const config = window.convert_to_blocks_agent || false;

// if no migration config, then ignore this request
Expand All @@ -69,7 +69,7 @@ class ConvertToBlocksEditorSupport {
client.save();

return null;
}, 500);
});

return null;
},
Expand Down
112 changes: 95 additions & 17 deletions assets/js/editor/transform/ClassicBlockTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ class ClassicBlockTransformer {
*
* @returns {boolean} The result of the transformation.
*/
execute() {
async execute() {
const coreEditor = this.wp.data.select('core/block-editor');
const blocks = coreEditor.getBlocks();

if (this.validBlocks(blocks)) {
/* Currently set to do 3 levels of recursion */
this.convertBlocks(blocks, 1, 3);
await this.convertBlocks(blocks, 1, 3);
}

return this.didTransform;
Expand All @@ -41,22 +41,18 @@ class ClassicBlockTransformer {
* @param {number} depth The current call stack depth
* @param {number} maxDepth The maximum allowed depth
*/
convertBlocks(blocks, depth = 1, maxDepth = 3) {
const n = blocks.length;
let i;
let block;
let innerBlocks;
async convertBlocks(blocks, depth = 1, maxDepth = 3) {
const promises = blocks.map(async (block) => {
const innerBlocks = { block };

for (i = 0; i < n; i++) {
block = blocks[i];
innerBlocks = { block };

this.transform(block);
await this.transform(block);

if (depth <= maxDepth && this.validBlocks(innerBlocks)) {
this.convertBlocks(innerBlocks, depth + 1, maxDepth);
await this.convertBlocks(innerBlocks, depth + 1, maxDepth);
}
}
});

await Promise.all(promises);
}

/**
Expand All @@ -65,16 +61,98 @@ class ClassicBlockTransformer {
*
* @param {object} block The current block object
*/
transform(block) {
async transform(block) {
if (this.isFreeformBlock(block)) {
const gutenbergBlocks = this.blockHandler(block);

this.wp.data
.dispatch('core/block-editor')
.replaceBlocks(block.clientId, this.blockHandler(block));
.replaceBlocks(block.clientId, gutenbergBlocks);

if (Array.isArray(gutenbergBlocks)) {
const promises = gutenbergBlocks.map((block) => this.waitForDOMManipulation(block));
await Promise.all(promises);
}

this.didTransform = true;
} else if (block.innerBlocks && block.innerBlocks.length > 0) {
this.convertBlocks(block.innerBlocks);
await this.convertBlocks(block.innerBlocks);
}
}

/**
* Waits for a block's DOM manipulation to finish.
*
* @param {string} block The block being converted.
*
* @returns {Promise<void>} A promise that resolves when DOM manipulation is detected or when no DOM manipulation happens.
*/
async waitForDOMManipulation(block = '') {
let iframeElement;
let blockEl;
const { clientId } = block;

// Resolves after the editor iframe canvas is inserted.
await new Promise((resolve) => {
const intervalId = setInterval(() => {
iframeElement = document.getElementsByName('editor-canvas');

if (iframeElement.length > 0) {
iframeElement = iframeElement[0];
clearInterval(intervalId);
resolve();
}
}, 100);
});

// Resolves after the transformed block is inserted.
await new Promise((resolve) => {
const intervalId = setInterval(() => {
const iframeDocument =
iframeElement.contentDocument || iframeElement.contentWindow.document;

if (iframeDocument.body) {
blockEl = iframeDocument.getElementById(`block-${clientId}`);

if (blockEl) {
clearInterval(intervalId);
resolve();
}
}
}, 100);
});

await new Promise((resolve) => {
const observer = new MutationObserver((mutationList, observer) => {
if (observer.timeoutId) {
clearTimeout(observer.timeoutId);
}

observer.timeoutId = setTimeout(() => {
observer.disconnect();
resolve();
}, 100);
});

observer.timeoutId = null;
observer.observe(blockEl, { childList: true, subtree: true });

// We resolve if there is no DOM manipulations happening
setTimeout(() => {
observer.disconnect();
resolve();
}, 100);
});

// Recursively call waitForDOMManipulation on inner blocks
if (block.innerBlocks && block.innerBlocks.length > 0) {
await Promise.all(
block.innerBlocks.map((innerBlock) => this.waitForDOMManipulation(innerBlock)),
);
}

// Return a resolved promise to ensure function returns a promise
return Promise.resolve();
}

/**
Expand Down
Loading
Loading