-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Enhance the embed block to support all WP_oEmbed embeds #816
Changes from all commits
f9d17bc
2723e86
d071080
6fea1e8
830b743
b99e42c
c55973f
540c43c
42172c0
ea6ff87
19f8477
f09a47f
a235861
d934958
316fb7d
58acca1
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 |
---|---|---|
|
@@ -18,7 +18,8 @@ | |
} | ||
}, | ||
"globals": { | ||
"wp": true | ||
"wp": true, | ||
"wpApiSettings": true | ||
}, | ||
"plugins": [ | ||
"react", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Button, Placeholder } from 'components'; | ||
import { Button, Placeholder, HtmlEmbed, Spinner } from 'components'; | ||
|
||
/** | ||
* Internal dependencies | ||
|
@@ -34,7 +34,6 @@ registerBlock( 'core/embed', { | |
category: 'embed', | ||
|
||
attributes: { | ||
url: attr( 'iframe', 'src' ), | ||
title: attr( 'iframe', 'title' ), | ||
caption: children( 'figcaption' ), | ||
}, | ||
|
@@ -73,52 +72,130 @@ registerBlock( 'core/embed', { | |
} | ||
}, | ||
|
||
edit( { attributes, setAttributes, focus, setFocus } ) { | ||
const { url, title, caption } = attributes; | ||
edit: class extends wp.element.Component { | ||
constructor() { | ||
super( ...arguments ); | ||
this.doServerSideRender = this.doServerSideRender.bind( this ); | ||
this.state = { | ||
html: '', | ||
type: '', | ||
error: false, | ||
fetching: false, | ||
}; | ||
this.noPreview = [ | ||
'facebook.com', | ||
]; | ||
if ( this.props.attributes.url ) { | ||
// if the url is already there, we're loading a saved block, so we need to render | ||
this.doServerSideRender(); | ||
} | ||
} | ||
|
||
if ( ! url ) { | ||
return ( | ||
<Placeholder icon="cloud" label={ wp.i18n.__( 'Embed URL' ) } className="blocks-embed"> | ||
<input type="url" className="placeholder__input" placeholder={ wp.i18n.__( 'Enter URL to embed here...' ) } /> | ||
<Button isLarge> | ||
{ wp.i18n.__( 'Embed' ) } | ||
</Button> | ||
</Placeholder> | ||
componentWillUnmount() { | ||
// can't abort the fetch promise, so let it know we will unmount | ||
this.unmounting = true; | ||
} | ||
|
||
doServerSideRender( event ) { | ||
if ( event ) { | ||
event.preventDefault(); | ||
} | ||
const { url } = this.props.attributes; | ||
const api_url = wpApiSettings.root + 'oembed/1.0/proxy?url=' + encodeURIComponent( url ) + '&_wpnonce=' + wpApiSettings.nonce; | ||
|
||
this.setState( { error: false, fetching: true } ); | ||
fetch( api_url, { | ||
credentials: 'include', | ||
} ).then( | ||
( response ) => { | ||
if ( this.unmounting ) { | ||
return; | ||
} | ||
response.json().then( ( obj ) => { | ||
const { html, type } = obj; | ||
if ( html ) { | ||
this.setState( { html, type } ); | ||
} else { | ||
this.setState( { error: true } ); | ||
} | ||
this.setState( { fetching: false } ); | ||
} ); | ||
} | ||
); | ||
} | ||
|
||
return ( | ||
<figure className="blocks-embed"> | ||
<div className="iframe-overlay"> | ||
<iframe src={ url } title={ title } /> | ||
</div> | ||
{ ( caption && caption.length > 0 ) || !! focus ? ( | ||
<Editable | ||
tagName="figcaption" | ||
placeholder={ wp.i18n.__( 'Write caption…' ) } | ||
value={ caption } | ||
focus={ focus } | ||
onFocus={ setFocus } | ||
onChange={ ( value ) => setAttributes( { caption: value } ) } | ||
inline | ||
inlineToolbar | ||
/> | ||
) : null } | ||
</figure> | ||
); | ||
render() { | ||
const { html, type, error, fetching } = this.state; | ||
const { url, caption } = this.props.attributes; | ||
const { setAttributes, focus, setFocus } = this.props; | ||
|
||
if ( ! html ) { | ||
return ( | ||
<Placeholder icon="cloud" label={ wp.i18n.__( 'Embed URL' ) } className="blocks-embed"> | ||
<form onSubmit={ this.doServerSideRender }> | ||
<input | ||
type="url" | ||
className="components-placeholder__input" | ||
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. Why are we using 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. This was an @aduth request, if I recall correctly. 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. Yes, we are using components as prefix for the whole folder. This is so specific to blocks, though. Mmm. 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. If we are to adhere to the proposed guideline, this should be https://github.com/WordPress/gutenberg/blob/master/docs/coding-guidelines.md#naming 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. If it's truly generic to be considered a component, it should be moved to the 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. No, I agree this is not generic enough and should be |
||
placeholder={ wp.i18n.__( 'Enter URL to embed here...' ) } | ||
onChange={ ( event ) => setAttributes( { url: event.target.value } ) } /> | ||
{ ! fetching | ||
? <Button | ||
isLarge | ||
type="submit"> | ||
{ wp.i18n.__( 'Embed' ) } | ||
</Button> | ||
: <Spinner /> | ||
} | ||
{ error && <p className="components-placeholder__error">{ wp.i18n.__( 'Sorry, we could not embed that content.' ) }</p> } | ||
</form> | ||
</Placeholder> | ||
); | ||
} | ||
|
||
const domain = url.split( '/' )[ 2 ].replace( /^www\./, '' ); | ||
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. This feels a bit fragile, since it could easily throw errors if an unexpected 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. yeah, the host without www. because we want to blacklist certain embeds from previewing in the editor (facebook specifically). I'll look at the url module :) |
||
const cannotPreview = this.noPreview.includes( domain ); | ||
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.
|
||
let typeClassName = 'blocks-embed'; | ||
|
||
if ( 'video' === type ) { | ||
typeClassName = 'blocks-embed-video'; | ||
} | ||
|
||
return ( | ||
<figure className={ typeClassName }> | ||
{ ( cannotPreview ) ? ( | ||
<Placeholder icon="cloud" label={ wp.i18n.__( 'Embed URL' ) }> | ||
<p className="components-placeholder__error"><a href={ url }>{ url }</a></p> | ||
<p className="components-placeholder__error">{ wp.i18n.__( 'Previews for this are unavailable in the editor, sorry!' ) }</p> | ||
</Placeholder> | ||
) : ( | ||
<HtmlEmbed html={ html } /> | ||
) } | ||
{ ( caption && caption.length > 0 ) || !! focus ? ( | ||
<Editable | ||
tagName="figcaption" | ||
placeholder={ wp.i18n.__( 'Write caption…' ) } | ||
value={ caption } | ||
focus={ focus } | ||
onFocus={ setFocus } | ||
onChange={ ( value ) => setAttributes( { caption: value } ) } | ||
inline | ||
inlineToolbar | ||
/> | ||
) : null } | ||
</figure> | ||
); | ||
} | ||
}, | ||
|
||
save( { attributes } ) { | ||
const { url, title, caption } = attributes; | ||
const iframe = <iframe src={ url } title={ title } />; | ||
|
||
const { url, caption } = attributes; | ||
if ( ! caption || ! caption.length ) { | ||
return iframe; | ||
return url; | ||
} | ||
|
||
return ( | ||
<figure> | ||
{ iframe } | ||
{ url } | ||
<figcaption>{ caption }</figcaption> | ||
</figure> | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
<!-- wp:core/embed url="https://www.youtube.com/watch?v=Nl6U7UotA-M" --> | ||
<figure><iframe src="//www.youtube.com/embed/Nl6U7UotA-M" frameborder="0" allowfullscreen></iframe><figcaption>State of the Word 2016</figcaption></figure> | ||
<!-- /wp:core/embed --> | ||
<figure>https://www.youtube.com/embed/Nl6U7UotA-M"<figcaption>State of the Word 2016</figcaption></figure> | ||
<!-- /wp:core/embed --> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// When embedding HTML from the WP oEmbed proxy, we need to insert it | ||
// into a div and make sure any scripts get run. This component takes | ||
// HTML and puts it into a div element, and creates and adds new script | ||
// elements so all scripts get run as expected. | ||
|
||
export default class HtmlEmbed extends wp.element.Component { | ||
|
||
componentDidMount() { | ||
const body = this.node; | ||
const { html = '' } = this.props; | ||
|
||
body.innerHTML = html; | ||
|
||
const scripts = body.getElementsByTagName( 'script' ); | ||
const newScripts = Array.from( scripts ).map( ( script ) => { | ||
const newScript = document.createElement( 'script' ); | ||
if ( script.src ) { | ||
newScript.src = script.src; | ||
} else { | ||
newScript.innerHTML = script.innerHTML; | ||
} | ||
return newScript; | ||
} ); | ||
|
||
newScripts.forEach( ( script ) => body.appendChild( script ) ); | ||
} | ||
|
||
render() { | ||
return ( | ||
<div ref={ ( node ) => this.node = node } /> | ||
); | ||
} | ||
} |
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.
When loading the page on
master
now, I see the following warning from this line: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.
Ah, so this needs to be moved into
componentWillMount
?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.
Maybe. I'd have to look again at where this function is called originally, and if by
componentDidMount
, why the component would not be mounted at this point.To a more general point, I think this is a handy reference for when side effects and state changes should occur during component lifecycle:
https://gist.github.com/bvaughn/923dffb2cd9504ee440791fade8db5f9