Skip to content

Commit

Permalink
Backport fixes to include on WordPress 5.5 beta 4 (#24218)
Browse files Browse the repository at this point in the history
Co-authored-by: Noah Allen <noahtallen@gmail.com>
Co-authored-by: Dominik Schilling <dominikschilling+git@gmail.com>
Co-authored-by: Daniel Richards <daniel.richards@automattic.com>
Co-authored-by: Dion Hulse <dion@wordpress.org>
Co-authored-by: Nik Tsekouras <ntsekouras@outlook.com>
Co-authored-by: Rami Yushuvaev <r_a_m_i@hotmail.com>
Co-authored-by: Oskar Schöldström <m@oxy.fi>
Co-authored-by: Ella van Durpe <ella@vandurpe.com>
  • Loading branch information
9 people committed Jul 27, 2020
1 parent 72984f3 commit 4efd890
Show file tree
Hide file tree
Showing 45 changed files with 693 additions and 268 deletions.
65 changes: 65 additions & 0 deletions lib/block-patterns.php
@@ -0,0 +1,65 @@
<?php
/**
* Add support for block patterns and register default patterns.
*
* @package gutenberg
*/

add_theme_support( 'core-block-patterns' );

/**
* Extends block editor settings to include a list of default patterns.
*
* @param array $settings Default editor settings.
*
* @return array Filtered editor settings.
*/
function gutenberg_extend_settings_block_patterns( $settings ) {
$settings['__experimentalBlockPatterns'] = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
$settings['__experimentalBlockPatternCategories'] = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered();

return $settings;
}
add_filter( 'block_editor_settings', 'gutenberg_extend_settings_block_patterns', 0 );


/**
* Load a block pattern by name.
*
* @param string $name Block Pattern File name.
*
* @return array Block Pattern Array.
*/
function gutenberg_load_block_pattern( $name ) {
return require( __DIR__ . '/patterns/' . $name . '.php' );
}

/**
* Register default patterns and categories, potentially overriding ones that were already registered in Core.
*
* This can be removed when plugin support requires WordPress 5.5.0+, and patterns have been synced back to Core.
*
* @see https://core.trac.wordpress.org/ticket/50550
*/
function gutenberg_register_block_patterns() {
$should_register_core_patterns = get_theme_support( 'core-block-patterns' );

if ( $should_register_core_patterns ) {
register_block_pattern( 'core/text-two-columns', gutenberg_load_block_pattern( 'text-two-columns' ) );
register_block_pattern( 'core/two-buttons', gutenberg_load_block_pattern( 'two-buttons' ) );
register_block_pattern( 'core/two-images', gutenberg_load_block_pattern( 'two-images' ) );
register_block_pattern( 'core/text-two-columns-with-images', gutenberg_load_block_pattern( 'text-two-columns-with-images' ) );
register_block_pattern( 'core/text-three-columns-buttons', gutenberg_load_block_pattern( 'text-three-columns-buttons' ) );
register_block_pattern( 'core/large-header', gutenberg_load_block_pattern( 'large-header' ) );
register_block_pattern( 'core/large-header-paragraph', gutenberg_load_block_pattern( 'large-header-paragraph' ) );
register_block_pattern( 'core/three-buttons', gutenberg_load_block_pattern( 'three-buttons' ) );
register_block_pattern( 'core/quote', gutenberg_load_block_pattern( 'quote' ) );
}

register_block_pattern_category( 'buttons', array( 'label' => _x( 'Buttons', 'Block pattern category', 'gutenberg' ) ) );
register_block_pattern_category( 'columns', array( 'label' => _x( 'Columns', 'Block pattern category', 'gutenberg' ) ) );
register_block_pattern_category( 'gallery', array( 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ) ) );
register_block_pattern_category( 'header', array( 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ) ) );
register_block_pattern_category( 'text', array( 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ) ) );
}
add_action( 'init', 'gutenberg_register_block_patterns' );
75 changes: 0 additions & 75 deletions lib/client-assets.php
Expand Up @@ -621,47 +621,6 @@ function gutenberg_extend_block_editor_styles( $settings ) {
}
add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_styles' );

/**
* Load a block pattern by name.
*
* @param string $name Block Pattern File name.
*
* @return array Block Pattern Array.
*/
function gutenberg_load_block_pattern( $name ) {
return require( __DIR__ . '/patterns/' . $name . '.php' );
}

/**
* Extends block editor settings to include a list of default patterns.
*
* @param array $settings Default editor settings.
*
* @return array Filtered editor settings.
*/
function gutenberg_extend_settings_block_patterns( $settings ) {
if ( empty( $settings['__experimentalBlockPatterns'] ) ) {
$settings['__experimentalBlockPatterns'] = array();
}

$settings['__experimentalBlockPatterns'] = array_merge(
WP_Block_Patterns_Registry::get_instance()->get_all_registered(),
$settings['__experimentalBlockPatterns']
);

if ( empty( $settings['__experimentalBlockPatternCategories'] ) ) {
$settings['__experimentalBlockPatternCategories'] = array();
}

$settings['__experimentalBlockPatternCategories'] = array_merge(
WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(),
$settings['__experimentalBlockPatternCategories']
);

return $settings;
}
add_filter( 'block_editor_settings', 'gutenberg_extend_settings_block_patterns', 0 );

/**
* Extends block editor settings to determine whether to use custom line height controls.
*
Expand Down Expand Up @@ -717,37 +676,3 @@ function gutenberg_extend_settings_link_color( $settings ) {
return $settings;
}
add_filter( 'block_editor_settings', 'gutenberg_extend_settings_link_color' );

/*
* Register default patterns if not registered in Core already.
*
* This can be removed when plugin support requires WordPress 5.5.0+.
*
* @see https://core.trac.wordpress.org/ticket/50550
*/
if ( class_exists( 'WP_Block_Patterns_Registry' ) && ! WP_Block_Patterns_Registry::get_instance()->is_registered( 'text-two-columns' ) ) {
register_block_pattern( 'core/text-two-columns', gutenberg_load_block_pattern( 'text-two-columns' ) );
register_block_pattern( 'core/two-buttons', gutenberg_load_block_pattern( 'two-buttons' ) );
register_block_pattern( 'core/two-images', gutenberg_load_block_pattern( 'two-images' ) );
register_block_pattern( 'core/text-two-columns-with-images', gutenberg_load_block_pattern( 'text-two-columns-with-images' ) );
register_block_pattern( 'core/text-three-columns-buttons', gutenberg_load_block_pattern( 'text-three-columns-buttons' ) );
register_block_pattern( 'core/large-header', gutenberg_load_block_pattern( 'large-header' ) );
register_block_pattern( 'core/large-header-paragraph', gutenberg_load_block_pattern( 'large-header-paragraph' ) );
register_block_pattern( 'core/three-buttons', gutenberg_load_block_pattern( 'three-buttons' ) );
register_block_pattern( 'core/quote', gutenberg_load_block_pattern( 'quote' ) );
}

/*
* Register default pattern categories if not registered in Core already.
*
* This can be removed when plugin support requires WordPress 5.5.0+.
*
* @see https://core.trac.wordpress.org/ticket/50550
*/
if ( class_exists( 'WP_Block_Pattern_Categories_Registry' ) ) {
register_block_pattern_category( 'buttons', array( 'label' => _x( 'Buttons', 'Block pattern category', 'gutenberg' ) ) );
register_block_pattern_category( 'columns', array( 'label' => _x( 'Columns', 'Block pattern category', 'gutenberg' ) ) );
register_block_pattern_category( 'gallery', array( 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ) ) );
register_block_pattern_category( 'header', array( 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ) ) );
register_block_pattern_category( 'text', array( 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ) ) );
}
1 change: 1 addition & 0 deletions lib/load.php
Expand Up @@ -86,6 +86,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require dirname( __FILE__ ) . '/compat.php';
require dirname( __FILE__ ) . '/utils.php';

require dirname( __FILE__ ) . '/block-patterns.php';
require dirname( __FILE__ ) . '/blocks.php';
require dirname( __FILE__ ) . '/templates.php';
require dirname( __FILE__ ) . '/template-parts.php';
Expand Down
3 changes: 0 additions & 3 deletions packages/block-directory/src/store/actions.js
Expand Up @@ -51,9 +51,6 @@ export function* installBlockType( block ) {
let success = false;
yield clearErrorNotice( id );
try {
if ( ! Array.isArray( assets ) || ! assets.length ) {
throw new Error( __( 'Block has no assets.' ) );
}
yield setIsInstalling( block.id, true );

// If we have a wp:plugin link, the plugin is installed but inactive.
Expand Down
116 changes: 74 additions & 42 deletions packages/block-directory/src/store/controls.js
@@ -1,50 +1,49 @@
/**
* WordPress dependencies
*/
import { getPath } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';

/**
* Loads a JavaScript file.
* Load an asset for a block.
*
* @param {string} asset The url for this file.
* This function returns a Promise that will resolve once the asset is loaded,
* or in the case of Stylesheets and Inline Javascript, will resolve immediately.
*
* @param {HTMLElement} el A HTML Element asset to inject.
*
* @return {Promise} Promise which will resolve when the asset is loaded.
*/
export const loadScript = ( asset ) => {
if ( ! asset || ! /\.js$/.test( getPath( asset ) ) ) {
return Promise.reject( new Error( 'No script found.' ) );
}
export const loadAsset = ( el ) => {
return new Promise( ( resolve, reject ) => {
const existing = document.querySelector( `script[src="${ asset }"]` );
if ( existing ) {
existing.parentNode.removeChild( existing );
/*
* Reconstruct the passed element, this is required as inserting the Node directly
* won't always fire the required onload events, even if the asset wasn't already loaded.
*/
const newNode = document.createElement( el.nodeName );

[ 'id', 'rel', 'src', 'href', 'type' ].forEach( ( attr ) => {
if ( el[ attr ] ) {
newNode[ attr ] = el[ attr ];
}
} );

// Append inline <script> contents.
if ( el.innerHTML ) {
newNode.appendChild( document.createTextNode( el.innerHTML ) );
}
const script = document.createElement( 'script' );
script.src = asset;
script.onload = () => resolve( true );
script.onerror = () => reject( new Error( 'Error loading script.' ) );
document.body.appendChild( script );
} );
};

/**
* Loads a CSS file.
*
* @param {string} asset The url for this file.
*
* @return {Promise} Promise which will resolve when the asset is added.
*/
export const loadStyle = ( asset ) => {
if ( ! asset || ! /\.css$/.test( getPath( asset ) ) ) {
return Promise.reject( new Error( 'No style found.' ) );
}
return new Promise( ( resolve, reject ) => {
const link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.href = asset;
link.onload = () => resolve( true );
link.onerror = () => reject( new Error( 'Error loading style.' ) );
document.body.appendChild( link );
newNode.onload = () => resolve( true );
newNode.onerror = () => reject( new Error( 'Error loading asset.' ) );

document.body.appendChild( newNode );

// Resolve Stylesheets and Inline JavaScript immediately.
if (
'link' === newNode.nodeName.toLowerCase() ||
( 'script' === newNode.nodeName.toLowerCase() && ! newNode.src )
) {
resolve();
}
} );
};

Expand All @@ -63,14 +62,47 @@ export function loadAssets( assets ) {
}

const controls = {
LOAD_ASSETS( { assets } ) {
const scripts = assets.map( ( asset ) =>
getPath( asset ).match( /\.js$/ ) !== null
? loadScript( asset )
: loadStyle( asset )
);
LOAD_ASSETS() {
/*
* Fetch the current URL (post-new.php, or post.php?post=1&action=edit) and compare the
* Javascript and CSS assets loaded between the pages. This imports the required assets
* for the block into the current page while not requiring that we know them up-front.
* In the future this can be improved by reliance upon block.json and/or a script-loader
* dependancy API.
*/
return apiFetch( {
url: document.location.href,
parse: false,
} )
.then( ( response ) => response.text() )
.then( ( data ) => {
const doc = new window.DOMParser().parseFromString(
data,
'text/html'
);

const newAssets = Array.from(
doc.querySelectorAll( 'link[rel="stylesheet"],script' )
).filter(
( asset ) =>
asset.id && ! document.getElementById( asset.id )
);

return Promise.all( scripts );
return new Promise( async ( resolve, reject ) => {
for ( const i in newAssets ) {
try {
/*
* Load each asset in order, as they may depend upon an earlier loaded script.
* Stylesheets and Inline Scripts will resolve immediately upon insertion.
*/
await loadAsset( newAssets[ i ] );
} catch ( e ) {
reject( e );
}
}
resolve();
} );
} );
},
};

Expand Down
25 changes: 0 additions & 25 deletions packages/block-directory/src/store/test/actions.js
Expand Up @@ -158,31 +158,6 @@ describe( 'actions', () => {
} );
} );

it( 'should set an error if the plugin has no assets', () => {
const generator = installBlockType( { ...block, assets: [] } );

expect( generator.next().value ).toEqual( {
type: 'CLEAR_ERROR_NOTICE',
blockId: block.id,
} );

expect( generator.next().value ).toMatchObject( {
type: 'SET_ERROR_NOTICE',
blockId: block.id,
} );

expect( generator.next().value ).toEqual( {
type: 'SET_INSTALLING_BLOCK',
blockId: block.id,
isInstalling: false,
} );

expect( generator.next() ).toEqual( {
value: false,
done: true,
} );
} );

it( "should set an error if the plugin can't install", () => {
const generator = installBlockType( block );

Expand Down
36 changes: 6 additions & 30 deletions packages/block-directory/src/store/test/controls.js
@@ -1,45 +1,21 @@
/**
* Internal dependencies
*/
import { loadScript, loadStyle } from '../controls';
import { loadAsset } from '../controls';

describe( 'controls', () => {
const scriptAsset = 'http://www.wordpress.org/plugins/fakeasset.js';
const styleAsset = 'http://www.wordpress.org/plugins/fakeasset.css';
describe( 'loadAsset', () => {
const script = document.createElement( 'script' );
const style = document.createElement( 'link' );

describe( 'loadScript', () => {
it( 'should return a Promise when loading a script', () => {
const result = loadScript( scriptAsset );
const result = loadAsset( script );
expect( typeof result.then ).toBe( 'function' );
} );

it( 'should reject when no script is given', async () => {
expect.assertions( 1 );
const result = loadScript( '' );
await expect( result ).rejects.toThrow( Error );
} );

it( 'should reject when a non-js file is given', async () => {
const result = loadScript( styleAsset );
await expect( result ).rejects.toThrow( Error );
} );
} );

describe( 'loadStyle', () => {
it( 'should return a Promise when loading a style', () => {
const result = loadStyle( styleAsset );
const result = loadAsset( style );
expect( typeof result.then ).toBe( 'function' );
} );

it( 'should reject when no style is given', async () => {
expect.assertions( 1 );
const result = loadStyle( '' );
await expect( result ).rejects.toThrow( Error );
} );

it( 'should reject when a non-css file is given', async () => {
const result = loadStyle( scriptAsset );
await expect( result ).rejects.toThrow( Error );
} );
} );
} );

0 comments on commit 4efd890

Please sign in to comment.