-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Changes from 1 commit
3ece65d
34b62fc
934d1eb
2a097ae
9977d88
f8dcdcd
d833e4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. | ||
|
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'; | ||
|
||
/** | ||
* 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( {} ), | ||
}, | ||
]; | ||
}; |
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 = { | ||
|
@@ -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, | ||
|
@@ -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 ); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
@@ -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, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -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' ); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For folks reading there's an early exit in sandbox otherwise
|
||||
} | ||||
this.trySandbox(); | ||||
} | ||||
|
||||
|
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' ); |
There was a problem hiding this comment.
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.