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

Backport fixes to include on WordPress 5.5 beta 4 #24218

Merged
merged 20 commits into from Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
44982a4
useBlockSync: Fix race condition when onChange callback changes (#24012)
noahtallen Jul 20, 2020
6040b20
I18N: Fix missing plural forms for block related strings (#24071)
ocean90 Jul 22, 2020
697669c
Block Breadcrumb: Fix arrow direction in RTL (#24074)
ocean90 Jul 21, 2020
c1aa594
Fix Button block colors in the editor (#24153)
youknowriad Jul 23, 2020
8c93e30
Fix the gallery buttons focus style (#24157)
youknowriad Jul 23, 2020
2f329c7
Fix dragging multiple blocks dowards resulted in blocks inserted in w…
talldan Jul 24, 2020
ace3d02
Block Directory: Use local assets with automatic asset detection (#24…
dd32 Jul 27, 2020
bed3ae4
Fix save shortcut in code editor (#24151)
talldan Jul 27, 2020
f56a81f
Fix dropdown menu focus loss when using arrow keys with Safari and Vo…
talldan Jul 27, 2020
dcfb930
Avoid rendering the clipboard textarea inside the button (#24194)
youknowriad Jul 27, 2020
126b890
Avoid focusing the block selection button on each render (#24195)
youknowriad Jul 27, 2020
05e0756
Update the editor landmark regions (#24196)
youknowriad Jul 27, 2020
91deccb
Avoid focus style from being cut on the categories panel (#24197)
youknowriad Jul 27, 2020
a7c8e0c
Popover: Fix arrow color to match content border color (#24208)
ocean90 Jul 27, 2020
f1fbd9d
Fix gradient RGBA/HSLA inputs' width (#24214)
ntsekouras Jul 27, 2020
a0a1566
i18n: Merge similar translation strings in rss block (#24159)
ramiy Jul 23, 2020
64badeb
Show the default style variation if none provided (#24217)
youknowriad Jul 27, 2020
9cda154
Remove link control create suggestions if not needed
youknowriad Jul 27, 2020
6e29088
fix #22550 and allow enter to insert line breaks even if template is …
oxyc Jul 27, 2020
3211901
Support overriding core block patterns
youknowriad Jul 20, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 );
} );
} );
} );