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

WIP - Allow plugins to extend core embed blocks #14050

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/block-library/src/embed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Extending the core embed blocks

inspector: component to render in the block inspector for extra controls
preview: function that returns a preview objects, takes the preview fetched from oembed
fetching: if the preview is fetched from an API, this should return if fetching is in progress or not
save: component to save the embedded URL. can override saving the URL with, for example, a shortcode.

54 changes: 54 additions & 0 deletions packages/block-library/src/embed/deprecated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Internal dependencies
*/
import { getEmbedSaveComponent } from './save';

/**
* External dependencies
*/
import classnames from 'classnames/dedupe';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, not a typo :D https://github.com/JedWatson/classnames#alternate-dedupe-version

"This version is slower (about 5x) so it is offered as an opt-in." Both appear to be in use in Gutenberg.


/**
* WordPress dependencies
*/
import { RichText } from '@wordpress/editor';

export const getEmbedDeprecatedMigrations = ( embedAttributes, options ) => {
const deprecated = [
{
attributes: embedAttributes,
save( { attributes } ) {
const { url, caption, type, providerNameSlug } = attributes;

if ( ! url ) {
return null;
}

const embedClassName = classnames( 'wp-block-embed', {
[ `is-type-${ type }` ]: type,
[ `is-provider-${ providerNameSlug }` ]: providerNameSlug,
} );

return (
<figure className={ embedClassName }>
{ `\n${ url }\n` /* URL needs to be on its own line. */ }
{ ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> }
</figure>
);
},
},
];

if ( undefined === options.save ) {
return deprecated;
}
const extraDeprecated = options.deprecated || [];
return [
...deprecated,
...extraDeprecated,
{
attributes: embedAttributes,
save: getEmbedSaveComponent( {} ),
},
];
};
12 changes: 10 additions & 2 deletions packages/block-library/src/embed/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ import { kebabCase, toLower } from 'lodash';
import { __, sprintf } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';

export function getEmbedEditComponent( title, icon, responsive = true ) {
export function getEmbedEditComponent( options ) {
const {
title,
icon,
responsive = true,
inspector: InspectorControls,
preview: previewTransform = ( fetchedPreview ) => fetchedPreview,
} = options;
return class extends Component {
constructor() {
super( ...arguments );
Expand Down Expand Up @@ -180,7 +187,7 @@ export function getEmbedEditComponent( title, icon, responsive = true ) {
switchBackToURLInput={ this.switchBackToURLInput }
/>
<EmbedPreview
preview={ preview }
preview={ previewTransform( preview, this.props.attributes ) }
className={ className }
url={ url }
type={ type }
Expand All @@ -190,6 +197,7 @@ export function getEmbedEditComponent( title, icon, responsive = true ) {
icon={ icon }
label={ label }
/>
{ InspectorControls && <InspectorControls setAttributes={ this.props.setAttributes } attributes={ this.props.attributes } /> }
</Fragment>
);
}
Expand Down
5 changes: 3 additions & 2 deletions packages/block-library/src/embed/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getEmbedBlockSettings } from './settings';
*/
import { __, _x } from '@wordpress/i18n';
import { createBlock } from '@wordpress/blocks';
import { applyFilters } from '@wordpress/hooks';

export const name = 'core/embed';

Expand Down Expand Up @@ -38,7 +39,7 @@ export const common = commonEmbeds.map(
( embedDefinition ) => {
return {
...embedDefinition,
settings: getEmbedBlockSettings( embedDefinition.settings ),
settings: getEmbedBlockSettings( applyFilters( 'blockLibrary.Embed.coreSettings', embedDefinition.settings ) ),
};
}
);
Expand All @@ -47,7 +48,7 @@ export const others = otherEmbeds.map(
( embedDefinition ) => {
return {
...embedDefinition,
settings: getEmbedBlockSettings( embedDefinition.settings ),
settings: getEmbedBlockSettings( applyFilters( 'blockLibrary.Embed.coreSettings', embedDefinition.settings ) ),
};
}
);
43 changes: 43 additions & 0 deletions packages/block-library/src/embed/save.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* External dependencies
*/
import classnames from 'classnames/dedupe';

/**
* WordPress dependencies
*/
import { RichText } from '@wordpress/editor';
import { Fragment } from '@wordpress/element';

export function getEmbedSaveComponent( options ) {
const { save: customSave } = options;
const saveComponent = ( { attributes } ) => {
const { url, caption, type, providerNameSlug } = attributes;

if ( ! url ) {
return null;
}

const embedClassName = classnames( 'wp-block-embed', {
[ `is-type-${ type }` ]: type,
[ `is-provider-${ providerNameSlug }` ]: providerNameSlug,
} );

const urlLine = (
<Fragment>
{ `\n${ url }\n` /* URL needs to be on its own line. */ }
</Fragment>
);

return (
<figure className={ embedClassName }>
<div className="wp-block-embed__wrapper">
{ undefined !== customSave && customSave( attributes ) }
{ undefined === customSave && urlLine }
</div>
{ ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> }
</figure>
);
};
return saveComponent;
}
99 changes: 44 additions & 55 deletions packages/block-library/src/embed/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@
* Internal dependencies
*/
import { getEmbedEditComponent } from './edit';

/**
* External dependencies
*/
import classnames from 'classnames/dedupe';
import { getEmbedSaveComponent } from './save';
import { getEmbedDeprecatedMigrations } from './deprecated';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { compose } from '@wordpress/compose';
import { RichText } from '@wordpress/block-editor';
import { withSelect, withDispatch } from '@wordpress/data';

const embedAttributes = {
Expand All @@ -35,11 +31,27 @@ const embedAttributes = {
type: 'boolean',
default: true,
},
extraOptions: {
type: 'object',
default: {},
},
};

export function getEmbedBlockSettings( { title, description, icon, category = 'embed', transforms, keywords = [], supports = {}, responsive = true } ) {
export function getEmbedBlockSettings( options ) {
const {
title,
description,
icon,
transforms,
category = 'embed',
keywords = [],
supports = {},
} = options;
const blockDescription = description || __( 'Add a block that displays content pulled from other sites, like Twitter, Instagram or YouTube.' );
const edit = getEmbedEditComponent( title, icon, responsive );
const edit = getEmbedEditComponent( options );
const save = getEmbedSaveComponent( options );
const deprecated = getEmbedDeprecatedMigrations( embedAttributes, options );

return {
title,
description: blockDescription,
Expand All @@ -60,9 +72,29 @@ export function getEmbedBlockSettings( { title, description, icon, category = 'e
const { url } = ownProps.attributes;
const core = select( 'core' );
const { getEmbedPreview, isPreviewEmbedFallback, isRequestingEmbedPreview, getThemeSupports } = core;
const preview = undefined !== url && getEmbedPreview( url );

let preview = false;
let fetching = false;

if ( undefined !== url ) {
if ( options.preview ) {
// We have a custom preview, so pass it the response from oembed and the attributes
// and use whatever it returns.
preview = options.preview( getEmbedPreview( url ), ownProps.attributes );
} else {
preview = getEmbedPreview( url );
}

if ( options.requesting ) {
// To support custom previews that use a rendering API endpoint, `options.requesting`
// should return if the API request is in progress.
fetching = options.requesting( url );
} else {
fetching = isRequestingEmbedPreview( url );
}
}
Copy link
Contributor

@gwwar gwwar Mar 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another minor note here, but may be worth pulling out to helper functions, so it's easier to reason about the control flow:

getPreview = ( ownProps ) => {
    const { url } = ownProps;
    if ( ! url ) {
        return false;
    }
    if ( options.preview ) {
        options.preview( getEmbedPreview( url ), ownProps.attributes );
    }
    return getEmbedPreview( url );
}


const previewIsFallback = undefined !== url && isPreviewEmbedFallback( url );
const fetching = undefined !== url && isRequestingEmbedPreview( url );
const themeSupports = getThemeSupports();
// The external oEmbed provider does not exist. We got no type info and no html.
const badEmbedProvider = !! preview && undefined === preview.type && false === preview.html;
Expand Down Expand Up @@ -91,51 +123,8 @@ export function getEmbedBlockSettings( { title, description, icon, category = 'e
} )
)( edit ),

save( { attributes } ) {
const { url, caption, type, providerNameSlug } = attributes;

if ( ! url ) {
return null;
}

const embedClassName = classnames( 'wp-block-embed', {
[ `is-type-${ type }` ]: type,
[ `is-provider-${ providerNameSlug }` ]: providerNameSlug,
} );

return (
<figure className={ embedClassName }>
<div className="wp-block-embed__wrapper">
{ `\n${ url }\n` /* URL needs to be on its own line. */ }
</div>
{ ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> }
</figure>
);
},

deprecated: [
{
attributes: embedAttributes,
save( { attributes } ) {
const { url, caption, type, providerNameSlug } = attributes;

if ( ! url ) {
return null;
}

const embedClassName = classnames( 'wp-block-embed', {
[ `is-type-${ type }` ]: type,
[ `is-provider-${ providerNameSlug }` ]: providerNameSlug,
} );
save,

return (
<figure className={ embedClassName }>
{ `\n${ url }\n` /* URL needs to be on its own line. */ }
{ ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> }
</figure>
);
},
},
],
deprecated,
};
}
2 changes: 1 addition & 1 deletion packages/block-library/src/embed/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { findBlock, getClassNames } from '../util';

describe( 'core/embed', () => {
test( 'block edit matches snapshot', () => {
const EmbedEdit = getEmbedEditComponent( 'Embed', 'embed-generic' );
const EmbedEdit = getEmbedEditComponent( { title: 'Embed', icon: 'embed-generic' } );
const wrapper = render( <EmbedEdit attributes={ {} } /> );

expect( wrapper ).toMatchSnapshot();
Expand Down
6 changes: 5 additions & 1 deletion packages/components/src/sandbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ class Sandbox extends Component {
this.trySandbox();
}

componentDidUpdate() {
componentDidUpdate( prevProps ) {
if ( prevProps.html !== this.props.html ) {
// Allows the new html to go into the sandbox.
this.iframe.current.contentDocument.body.removeAttribute( 'data-resizable-iframe-connected' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For folks reading there's an early exit in sandbox otherwise

if ( null !== body.getAttribute( 'data-resizable-iframe-connected' ) ) {

}
this.trySandbox();
}

Expand Down
1 change: 1 addition & 0 deletions packages/e2e-tests/fixtures/blocks/core__embed.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"attributes": {
"url": "https://example.com/",
"caption": "Embedded content from an example URL",
"extraOptions": {},
"allowResponsive": true
},
"innerBlocks": [],
Expand Down
50 changes: 50 additions & 0 deletions packages/e2e-tests/plugins/extend-embeds.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php
/**
* Plugin Name: Gutenberg Test Extend Embeds
* Plugin URI: https://github.com/WordPress/gutenberg
* Author: Gutenberg Team
*
* @package gutenberg-test-extend-embeds
*/

/**
* A fake preview endpoint for use when testing embed extentions that
* have a custom preview endpoint.
*/
function extend_embeds_preview() {
return '<p>This is a preview from a custom endpoint.</p>';
}

/**
* Initialise the custom preview API endpoint.
*/
function extend_embeds_rest_api_init() {
register_rest_route(
'extend-embeds/v1',
'/preview/',
array(
'methods' => 'GET',
'callback' => 'extend_embeds_preview',
)
);
}

/**
* Registers a custom script for the plugin.
*/
function extend_embeds_init() {
wp_enqueue_script(
'gutenberg-test-extend-embeds',
plugins_url( 'extend-embeds/index.js', __FILE__ ),
array(
'wp-element',
'wp-editor',
'wp-i18n',
),
filemtime( plugin_dir_path( __FILE__ ) . 'extend-embeds/index.js' ),
true
);
add_action( 'rest_api_init', 'extend_embeds_rest_api_init' );
}

add_action( 'init', 'extend_embeds_init' );
Loading