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

Gutenberg: Make "Attempt Block Recovery" the automatic/default option. #16425

Closed
cecoates opened this issue Jul 4, 2019 · 13 comments · Fixed by #24263
Closed

Gutenberg: Make "Attempt Block Recovery" the automatic/default option. #16425

cecoates opened this issue Jul 4, 2019 · 13 comments · Fixed by #24263
Labels
[Feature] Block Validation/Deprecation Handling block validation to determine accuracy and deprecation [Status] In Progress Tracking issues with work in progress

Comments

@cecoates
Copy link

cecoates commented Jul 4, 2019

Is your feature request related to a problem? Please describe.

When there's an issue with a block (maybe a user tried to edit it as HTML, maybe they're opening it up in WP Admin and then Calypso and then WP Admin) the default options for "This block contains unexpected or invalid content." are confusing.

"Convert to HTML" is almost never what the user wants, but just having it so prominent increases the chance they'll choose it.

"Resolve" has better results, but seeing HTML here also causes users to panic. And the presence of another option to "Convert to HTML" again makes it that much more likely they'll choose it.

"Attempt Block Recovery" is almost always what they really want, but right now it's hidden behind the three dot menu, which users almost never click if there are more visible buttons available.

Describe the solution you'd like
"Attempt block recovery" should be prominently displayed where "Resolve" and "Convert to HTML" currently are. And those options can be moved behind the three dot menu.

Describe alternatives you've considered
For WP.com specifically, it should really just automatically recover the blocks. Users don't care how the sausage is made as long as it "just works". And seeing a message that blocks have broken makes them lose confidence in the new editor just when we're trying to promote it.

Maybe automatically recover blocks, but save a draft in the version history when blocks have to be recovered on the small chance "Recover" or "Convert to HTML" is what the user wanted. Then they could load the revision if necessary.

Screen Shot on 2019-07-04 at 16_13_22

Screen Shot on 2019-07-04 at 16_13_39

Screen Shot on 2019-07-04 at 16_13_53

@swissspidy swissspidy added the [Feature] Block Validation/Deprecation Handling block validation to determine accuracy and deprecation label Jul 5, 2019
@mrfoxtalbot
Copy link

+1 to this. It makes no sense that the best option is buried under the three dots. For the vast majority of users, converting the block to HTML will make it no longer editable for them (not to mention the confusion).

@railmedia
Copy link

railmedia commented Jul 30, 2019

Agreed. Is there at least a filter or some sort of inbuilt function which would allow us to do this automatically from our custom blocks?

It's a mess when you have to deal with a website (with a fair bit of content in it) for which you write custom blocks, then when a new feature is required in any given block, we have to repeat the process of recovering each of its instances, on each page / post.

For a website with 10 pages, containing the same (broken) block 10 times, we have to repeat this process 10 x 10 times... Did I mention WPML? :-)

Willing to help any dev process to achieve this.

@efc
Copy link

efc commented Dec 16, 2019

+1 the "attempt block recovery" really should be the only option outside the three-dot menu and an option to have our custom blocks automatically invoke the attempted recovery would be ideal, whether by filter or some other method.

@davepullig
Copy link

+1 for this; like others we find "attempt block recovery" works the majority of times. Had a few instances where end users have come across this and failed to find that option.

@naogify
Copy link
Contributor

naogify commented Jun 22, 2020

+ 1 for the "attempt block recovery" will be the primary option. In most case users expect the behavior of "attempt block recovery".

@AlanEGrant
Copy link

AlanEGrant commented Jul 1, 2020

+1, The block validation procedure in general needs to be much friendlier to revisions to existing blocks. Customers are (rightfully) put off by the editor complaining about blocks looking broken. It's one of those embarrassing things where I shouldn't have to remind people this sort of behaviour is expected after I sold them on using the core editor with custom blocks to support whatever needs they have that it doesn't already provide. I've found myself using dynamic blocks where I otherwise shouldn't and don't need to just to avoid the validation error, because a content team wants to start populating pages and I want to give them something that accepts data and drops a placeholder on the front end for me to worry about at my own leisure. Otherwise this forces them to wait on me as a dependency they shouldn't have.

Maybe introduce some sort of version control where the editor can check against a hash or something to notify it that the differences are expected. If the error occurs, have the editor check the hash - if that too is changed, instead show an error that says something like "This block has an update available" with a big blue "UPDATE" button next to it that runs the block recovery. No use of language like "invalid" or "recover" or other words that scare the client's management.

If the hash isn't updated, throw an error like the one we already have that insinuates there's an actual problem (but still put the block recovery function first). The verbiage alone makes a huge difference here, it always looks sloppy when clients see that "that unexpected error is expected" situation

@ghost
Copy link

ghost commented Jul 2, 2020

I built a temporary fix that i use for all my projects, until they add support for it. It just recovers all blocks and innerblocks inside of a block, have not had a problem with this script yet :)

Preview: https://imgur.com/a/zgPT1pQ

admin.js:

// IMPORT: Functions
import autoRecoverBlocks from './wordpress/autoRecoverBlocks';

// DECONSTRUCT: WP
const { wp = {} } = window || {};
const { domReady, data } = wp;

// AWAIT: jQuery to get ready
jQuery(document).ready(function ($) {
  // DEFINE: Validation variables
  const hasGutenbergClasses = $('body').hasClass('post-php') === true && $('.block-editor').length >= 1;
  const gutenbergHasObject = domReady !== undefined && data !== undefined;
  const gutenbergIsPresent = hasGutenbergClasses === true && gutenbergHasObject === true;

  // IF: Gutenberg editor is present
  if (gutenbergIsPresent === true) {
    autoRecoverBlocks(false);
  }
});

autoRecoverBlocks.js:

// FUNCTION: Recover block
const recoverBlock = (block = null, autoSave = false) => {
  // DECONSTRUCT: WP object
  const { wp = {} } = window || {};
  const { data = {}, blocks = {} } = wp;
  const { dispatch, select } = data;
  const { createBlock } = blocks;
  const { replaceBlock } = dispatch('core/block-editor');
  const wpRecoverBlock = ({ name = '', attributes = {}, innerBlocks = [] }) => createBlock(name, attributes, innerBlocks);

  // DEFINE: Validation variables
  const blockIsValid = block !== null
    && typeof block === 'object'
    && block.clientId !== null
    && typeof block.clientId === 'string';

  // IF: Block is not valid
  if (blockIsValid !== true) {
    return false;
  }

  // GET: Block based on ID, to make sure it exists
  const currentBlock = select('core/block-editor').getBlock(block.clientId);

  // IF: Block was found
  if (!currentBlock !== true) {
    // DECONSTRUCT: Block
    const {
      clientId: blockId = '',
      isValid: blockIsValid = true,
      innerBlocks: blockInnerBlocks = [],
    } = currentBlock;

    // DEFINE: Validation variables
    const blockInnerBlocksHasLength = blockInnerBlocks !== null
      && Array.isArray(blockInnerBlocks)
      && blockInnerBlocks.length >= 1;

    // IF: Block is not valid
    if (blockIsValid !== true) {
      // DEFINE: New recovered block
      const recoveredBlock = wpRecoverBlock(currentBlock);

      // REPLACE: Broke block
      replaceBlock(blockId, recoveredBlock);

      // IF: Auto save post
      if (autoSave === true) {
        wp.data.dispatch("core/editor").savePost();
      }
    }

    // IF: Inner blocks has length
    if (blockInnerBlocksHasLength) {
      blockInnerBlocks.forEach((innerBlock = {}) => {
        recoverBlock(innerBlock, autoSave);
      })
    }
  }

  // RETURN
  return false;
};

// FUNCTION: Attempt to recover broken blocks
const autoRecoverBlocks = (autoSave = false) => {
  // DECONSTRUCT: WP object
  const { wp = {} } = window || {};
  const { domReady, data = {} } = wp;
  const { select } = data;

  // AWAIT: For dom to get ready
  domReady(function () {
    setTimeout(
      function () {
        // DEFINE: Basic variables
        const blocksArray = select('core/block-editor').getBlocks();
        const blocksArrayHasLength = Array.isArray(blocksArray)
          && blocksArray.length >= 1;

        // IF: Blocks array has length
        if (blocksArrayHasLength === true) {
          blocksArray.forEach((element = {}) => {
            recoverBlock(element, autoSave);
          });
        }
      },
      1
    )
  });
}

// EXPORT
export default autoRecoverBlocks;

@grantbarrett
Copy link

grantbarrett commented Oct 8, 2021

I built a temporary fix that i use for all my projects, until they add support for it. It just recovers all blocks and innerblocks inside of a block, have not had a problem with this script yet :)

Preview: https://imgur.com/a/zgPT1pQ

admin.js:

// IMPORT: Functions
import autoRecoverBlocks from './wordpress/autoRecoverBlocks';

// DECONSTRUCT: WP
const { wp = {} } = window || {};
const { domReady, data } = wp;

// AWAIT: jQuery to get ready
jQuery(document).ready(function ($) {
  // DEFINE: Validation variables
  const hasGutenbergClasses = $('body').hasClass('post-php') === true && $('.block-editor').length >= 1;
  const gutenbergHasObject = domReady !== undefined && data !== undefined;
  const gutenbergIsPresent = hasGutenbergClasses === true && gutenbergHasObject === true;

  // IF: Gutenberg editor is present
  if (gutenbergIsPresent === true) {
    autoRecoverBlocks(false);
  }
});

autoRecoverBlocks.js:

// FUNCTION: Recover block
const recoverBlock = (block = null, autoSave = false) => {
  // DECONSTRUCT: WP object
  const { wp = {} } = window || {};
  const { data = {}, blocks = {} } = wp;
  const { dispatch, select } = data;
  const { createBlock } = blocks;
  const { replaceBlock } = dispatch('core/block-editor');
  const wpRecoverBlock = ({ name = '', attributes = {}, innerBlocks = [] }) => createBlock(name, attributes, innerBlocks);

  // DEFINE: Validation variables
  const blockIsValid = block !== null
    && typeof block === 'object'
    && block.clientId !== null
    && typeof block.clientId === 'string';

  // IF: Block is not valid
  if (blockIsValid !== true) {
    return false;
  }

  // GET: Block based on ID, to make sure it exists
  const currentBlock = select('core/block-editor').getBlock(block.clientId);

  // IF: Block was found
  if (!currentBlock !== true) {
    // DECONSTRUCT: Block
    const {
      clientId: blockId = '',
      isValid: blockIsValid = true,
      innerBlocks: blockInnerBlocks = [],
    } = currentBlock;

    // DEFINE: Validation variables
    const blockInnerBlocksHasLength = blockInnerBlocks !== null
      && Array.isArray(blockInnerBlocks)
      && blockInnerBlocks.length >= 1;

    // IF: Block is not valid
    if (blockIsValid !== true) {
      // DEFINE: New recovered block
      const recoveredBlock = wpRecoverBlock(currentBlock);

      // REPLACE: Broke block
      replaceBlock(blockId, recoveredBlock);

      // IF: Auto save post
      if (autoSave === true) {
        wp.data.dispatch("core/editor").savePost();
      }
    }

    // IF: Inner blocks has length
    if (blockInnerBlocksHasLength) {
      blockInnerBlocks.forEach((innerBlock = {}) => {
        recoverBlock(innerBlock, autoSave);
      })
    }
  }

  // RETURN
  return false;
};

// FUNCTION: Attempt to recover broken blocks
const autoRecoverBlocks = (autoSave = false) => {
  // DECONSTRUCT: WP object
  const { wp = {} } = window || {};
  const { domReady, data = {} } = wp;
  const { select } = data;

  // AWAIT: For dom to get ready
  domReady(function () {
    setTimeout(
      function () {
        // DEFINE: Basic variables
        const blocksArray = select('core/block-editor').getBlocks();
        const blocksArrayHasLength = Array.isArray(blocksArray)
          && blocksArray.length >= 1;

        // IF: Blocks array has length
        if (blocksArrayHasLength === true) {
          blocksArray.forEach((element = {}) => {
            recoverBlock(element, autoSave);
          });
        }
      },
      1
    )
  });
}

// EXPORT
export default autoRecoverBlocks;

@AjXUthaya I know it's been a while since you posted that, but I think it may solve a problem I'm having but I'm not sure where to put it. I'm not much of a WordPress developer but I have about 5000 posts with broken blocks I'd like to automatically recover. This may do the trick if I can then just use a macro to launch each edit page one at a time. Tx

On edit: I figured out. Save the two Javascript pieces as two files. Put them in a /js/ directory in your activate template's directory on your server. Then add an enqueuing function in your template's functions.php file. My code is straightforward: ```
function add_script_to_menu_page()
{
// $pagenow, is a global variable referring to the filename of the current page,
// such as ‘admin.php’, ‘post-new.php’
global $pagenow;

if ($pagenow != 'edit.php') ``
    return;
}
 
// loading js
wp_register_script( 'autoRecoverBlocks', get_template_directory_uri().'/js/autoRecoverBlocks.js', array('jquery-core'), false, true );
wp_enqueue_script( 'autoRecoverBlocks' );

}

add_action( 'admin_enqueue_scripts', 'add_script_to_menu_page' );

@TomaGap
Copy link

TomaGap commented Nov 9, 2021

Hi I've come across your script. Just wondering if you did not have to add the admin.js script to the function.php register / enque?

@Contributolo
Copy link

@grantbarrett

I added the scripts and functions like you described here: https://wordpress.stackexchange.com/a/396656/163133 .

I have a child theme but I had to put the js files in my parent theme (js folder).

I can see the js files loading fine in my source code while editing a page. When I going to edit a page, nothing changed. I cannot see this: https://imgur.com/a/zgPT1pQ

Any advice would be fantastic, because I have many pages with that problem.

Big thank you in advance, Thomas

@grantbarrett
Copy link

@Netzlichter I ended up giving up on the scripts because after some initial success, they stopped working and I could not fathom why.

@perleatspython
Copy link

perleatspython commented Aug 5, 2022

Hello,

I just came across this thread: Maybe it will help someone else. Back then, I had simply built a "dirty" autoclicker due to lack of time. You can skip the button if you want. The code can certainly be switched more elegantly. A small plug-in would probably be even better. The stage is yours ;-) For my purposes it was enough and saved me hundreds of clicks. The code is simple and typed down. With other languages, pay attention to the string comparison.

Copy to Functions.php:

image

add_action( 'enqueue_block_editor_assets', 'InsertToGutenbergFunc' );
function InsertToGutenbergFunc() {
  $html = "";
  $html = $html . '<div id="convertallblocks" style="overflow:hidden;opacity:80%;position:absolute;bottom:0px;display:none;justify-content:center;margin:auto;background-color:white;width:99%;height:50px;border: 1px solid black;z-index:9999;"><input type="button" class="components-button is-primary" value="Alle Blöcke mit Abweichnung in HTML konvertieren" onclick="clickwarnmessages()"><div style="position:absolute;right:26px;top:10px;font-size:30px;cursor:hand;" onclick="document.getElementById(\'convertallblocks\').style.display=\'none\';">&#215;</div></div>'.PHP_EOL;
  
  $html = $html . '<script>'.PHP_EOL;
  $html = $html . 'function clickwarnmessages(){'.PHP_EOL;
  $html = $html . 'var warntoogles = jQuery(".components-button.components-dropdown-menu__toggle.has-icon", ".components-dropdown.components-dropdown-menu");'.PHP_EOL;
  $html = $html . 'for (var w=0; w<warntoogles.length;w++){'.PHP_EOL;
  $html = $html . '  if (warntoogles[w].parentElement.parentElement.className.indexOf("block-editor-warning__actions") !=-1)'.PHP_EOL;
  $html = $html . '    warntoogles[w].click();'.PHP_EOL;
  $html = $html . '}'.PHP_EOL;
  $html = $html . 'var convertbuttons = jQuery(".components-button.components-menu-item__button", ".popover-slot");'.PHP_EOL;
  $html = $html . 'for (var i=0; i<convertbuttons.length;i++){'.PHP_EOL;
  $html = $html . '  if (convertbuttons[i].innerText.indexOf("In HTML umwandeln") != -1 || convertbuttons[i].innerText.indexOf("Convert to HTML") != -1)'.PHP_EOL;
  $html = $html . '  {'.PHP_EOL;
  $html = $html . '    console.log("click \"In Html umwandeln\" \"Convert to HTML\"");'.PHP_EOL;
  $html = $html . '    convertbuttons[i].click();'.PHP_EOL;
  $html = $html . '  }'.PHP_EOL;
  $html = $html . '}'.PHP_EOL;
  $html = $html . 'document.getElementById("convertallblocks").style.display="none";'.PHP_EOL;
  $html = $html . '}'.PHP_EOL;
  $html = $html . 'var loader = setInterval(function () {'.PHP_EOL;
  $html = $html . 'if(document.readyState !== "complete") return;'.PHP_EOL;
  $html = $html . 'clearInterval(loader);'.PHP_EOL;
  $html = $html . 'if (document.querySelector(".block-editor-warning") !== null) {'.PHP_EOL;
  $html = $html . '  document.getElementById("convertallblocks").style.display="flex";'.PHP_EOL;
  $html = $html . '}'.PHP_EOL;
  $html = $html . '}, 2500);'.PHP_EOL;
  $html = $html . '</script>'.PHP_EOL;
  echo $html;
}

@Bruse-Lee
Copy link

This is not convenient enough for you. If multiple articles need to automatically attempt block recovery, it will be very troublesome. I think a better way is to traverse all articles containing a certain button, and then click automatically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block Validation/Deprecation Handling block validation to determine accuracy and deprecation [Status] In Progress Tracking issues with work in progress
Projects
None yet
Development

Successfully merging a pull request may close this issue.