-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tweet block - WORK IN PROGESS! Don't merge or review or anything.
- Loading branch information
1 parent
052a5d6
commit 3cd38d1
Showing
7 changed files
with
401 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,4 @@ import './quote'; | |
import './separator'; | ||
import './button'; | ||
import './pullquote'; | ||
import './tweet'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { registerBlock, query } from '../../api'; | ||
import Sandbox from '../../../components/sandbox'; | ||
import Button from '../../../components/button'; | ||
import Placeholder from '../../../components/placeholder'; | ||
|
||
const { prop } = query; | ||
|
||
registerBlock( 'core/tweet', { | ||
title: wp.i18n.__( 'Tweet' ), | ||
icon: 'twitter', | ||
|
||
category: 'social', | ||
|
||
attributes: { | ||
url: prop( '*', 'innerHTML' ), // our html is just a div with the url in, for WP's oembed to process | ||
}, | ||
|
||
edit: class extends wp.element.Component { | ||
constructor() { | ||
super( ...arguments ); | ||
this.fetchTweet = this.fetchTweet.bind( this ); | ||
this.state = { | ||
url: this.props.attributes.url, | ||
html: '', | ||
error: false, | ||
fetching: false, | ||
}; | ||
if ( this.state.url ) { | ||
this.doFetch( this.state.url, this.props.setAttributes, this.setState.bind( this ) ); | ||
} | ||
} | ||
doFetch( url, setAttributes, setState ) { | ||
setState( { fetching: true, error: false } ); | ||
jQuery.ajax( { | ||
type: 'GET', | ||
dataType: 'jsonp', | ||
data: {}, | ||
timeout: 5000, | ||
url: 'https://publish.twitter.com/oembed?url=' + encodeURI( url ), | ||
error: function() { | ||
setState( { fetching: false, error: true } ); | ||
}, | ||
success: function( msg ) { | ||
setAttributes( { url: url } ); | ||
setState( { fetching: false, error: false, html: msg.html } ); | ||
}, | ||
} ); | ||
} | ||
fetchTweet() { | ||
const { url } = this.state; | ||
this.doFetch( url, this.props.setAttributes, this.setState.bind( this ) ); | ||
} | ||
render() { | ||
const { html, url, error, fetching } = this.state; | ||
|
||
if ( ! html ) { | ||
return ( | ||
<Placeholder instructions={ wp.i18n.__( 'Please paste the URL of the tweet here!' ) } icon="twitter" label={ wp.i18n.__( 'Tweet' ) } className="blocks-tweet"> | ||
<div> | ||
<input | ||
type="text" | ||
value={ url } | ||
onChange={ ( event ) => this.setState( { url: event.target.value } ) } /> | ||
{ ! fetching ? | ||
( | ||
<Button | ||
isLarge | ||
onClick={ this.fetchTweet }> | ||
{ wp.i18n.__( 'Get Tweet' ) } | ||
</Button> | ||
) : ( | ||
<span className="spinner is-active" /> | ||
) | ||
} | ||
</div> | ||
{ error && ( <div>{ wp.i18n.__( 'Sorry, we couldn\'t fetch that tweet.' ) }</div> ) } | ||
</Placeholder> | ||
); | ||
} | ||
return ( | ||
<Sandbox html={ html } /> | ||
); | ||
} | ||
}, | ||
|
||
save( { attributes } ) { | ||
const { url } = attributes; | ||
return ( | ||
<div>{ url }</div> | ||
); | ||
} | ||
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
Resizable Iframe | ||
================ | ||
|
||
Resizable Iframe is a React component for rendering an `<iframe>` element which can dynamically update its own dimensions using [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage). This is useful in cases where an inline frame of an unknown size is to be displayed on the page. | ||
|
||
## Example | ||
|
||
The `ResizableIframe` component can be used in much the same way that you would use an `<iframe>` DOM element. Props are automatically transferred to the rendered `<iframe>`, in case you need to specify additional properties or styles. | ||
|
||
```html | ||
<ResizableIframe src={ myFrameUrl } frameBorder={ 0 } /> | ||
``` | ||
|
||
## Usage | ||
|
||
To allow for resizing of the element, a `ResizableIframe` element listens for `message` events on the `window` object. If the rendered frame is not sandboxed, a script is injected in the frame to manage this behavior on your behalf. If the frame is sandboxed, any page you reference as the `src` URL is responsible for invoking this event using [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage). The message should be a JSON string with an `action` value of "resize" and a numeric `width` and `height` to define the new pixel dimensions of the element. | ||
|
||
For example, a page can trigger a resize using the following code snippet: | ||
|
||
```javascript | ||
if ( window.parent ) { | ||
window.parent.postMessage( JSON.stringify( { | ||
action: 'resize', | ||
width: document.body.clientWidth, | ||
height: document.body.clientHeight | ||
} ), '*' ); | ||
} | ||
``` | ||
|
||
## Props | ||
|
||
### `src` | ||
|
||
Treated as the `src` URL to be used in the rendered `<iframe>` DOM element. | ||
|
||
### `width` | ||
|
||
An optional fixed width value, if you don't want this to be the responsibility of the child window. | ||
|
||
### `height` | ||
|
||
An optional fixed height value, if you don't want this to be the responsibility of the child window. | ||
|
||
### `onResize` | ||
|
||
An optional function to trigger when the rendered frame has been resized. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
/** | ||
* Imported from Calypso | ||
*/ | ||
|
||
/** | ||
* External dependencies | ||
*/ | ||
import React from 'react'; | ||
import debugFactory from 'debug'; | ||
import { omit } from 'lodash'; | ||
import uuid from 'uuid/v4'; | ||
|
||
/** | ||
* Globals | ||
*/ | ||
const debug = debugFactory( 'gutenburg:resizable-iframe' ), | ||
noop = () => {}; | ||
|
||
export default React.createClass( { | ||
displayName: 'ResizableIframe', | ||
|
||
propTypes: { | ||
src: React.PropTypes.string, | ||
width: React.PropTypes.oneOfType( [ | ||
React.PropTypes.string, | ||
React.PropTypes.number | ||
] ), | ||
height: React.PropTypes.oneOfType( [ | ||
React.PropTypes.string, | ||
React.PropTypes.number | ||
] ), | ||
onLoad: React.PropTypes.func, | ||
onResize: React.PropTypes.func, | ||
title: React.PropTypes.string | ||
}, | ||
|
||
getInitialState: function() { | ||
return { width: 0, height: 0 }; | ||
}, | ||
|
||
getDefaultProps: function() { | ||
return { | ||
onLoad: noop, | ||
onResize: noop, | ||
title: uuid() | ||
}; | ||
}, | ||
|
||
componentWillMount: function() { | ||
debug( 'Mounting ' + this.constructor.displayName + ' React component.' ); | ||
}, | ||
|
||
componentDidMount: function() { | ||
window.addEventListener( 'message', this.checkMessageForResize, false ); | ||
this.maybeConnect(); | ||
}, | ||
|
||
componentDidUpdate: function() { | ||
this.maybeConnect(); | ||
}, | ||
|
||
componentWillUnmount: function() { | ||
window.removeEventListener( 'message', this.checkMessageForResize ); | ||
}, | ||
|
||
getFrameBody: function() { | ||
return this.iframe.contentDocument.body; | ||
}, | ||
|
||
maybeConnect: function() { | ||
if ( ! this.isFrameAccessible() ) { | ||
return; | ||
} | ||
|
||
const body = this.getFrameBody(); | ||
if ( null !== body.getAttribute( 'data-resizable-iframe-connected' ) ) { | ||
return; | ||
} | ||
|
||
const script = document.createElement( 'script' ); | ||
script.innerHTML = ` | ||
( function() { | ||
var observer; | ||
if ( ! window.MutationObserver || ! document.body || ! window.top ) { | ||
return; | ||
} | ||
function sendResize() { | ||
window.top.postMessage( { | ||
action: 'resize', | ||
width: document.body.offsetWidth, | ||
height: document.body.offsetHeight | ||
}, '*' ); | ||
} | ||
observer = new MutationObserver( sendResize ); | ||
observer.observe( document.body, { | ||
attributes: true, | ||
attributeOldValue: false, | ||
characterData: true, | ||
characterDataOldValue: false, | ||
childList: true, | ||
subtree: true | ||
} ); | ||
window.addEventListener( 'load', sendResize, true ); | ||
// Hack: Remove viewport unit styles, as these are relative | ||
// the iframe root and interfere with our mechanism for | ||
// determining the unconstrained page bounds. | ||
function removeViewportStyles( ruleOrNode ) { | ||
[ 'width', 'height', 'minHeight', 'maxHeight' ].forEach( function( style ) { | ||
if ( /^\\d+(vmin|vmax|vh|vw)$/.test( ruleOrNode.style[ style ] ) ) { | ||
ruleOrNode.style[ style ] = ''; | ||
} | ||
} ); | ||
} | ||
Array.prototype.forEach.call( document.querySelectorAll( '[style]' ), removeViewportStyles ); | ||
Array.prototype.forEach.call( document.styleSheets, function( stylesheet ) { | ||
Array.prototype.forEach.call( stylesheet.cssRules || stylesheet.rules, removeViewportStyles ); | ||
} ); | ||
document.body.style.position = 'absolute'; | ||
document.body.setAttribute( 'data-resizable-iframe-connected', '' ); | ||
sendResize(); | ||
} )(); | ||
`; | ||
body.appendChild( script ); | ||
}, | ||
|
||
isFrameAccessible: function() { | ||
try { | ||
return !! this.getFrameBody(); | ||
} catch ( e ) { | ||
return false; | ||
} | ||
}, | ||
|
||
checkMessageForResize: function( event ) { | ||
const iframe = this.iframe; | ||
|
||
// Attempt to parse the message data as JSON if passed as string | ||
let data = event.data || {}; | ||
if ( 'string' === typeof data ) { | ||
try { | ||
data = JSON.parse( data ); | ||
} catch ( e ) {} // eslint-disable-line no-empty | ||
} | ||
|
||
// Verify that the mounted element is the source of the message | ||
if ( ! iframe || iframe.contentWindow !== event.source ) { | ||
return; | ||
} | ||
|
||
debug( 'Received message: %o', data ); | ||
|
||
// Update the state only if the message is formatted as we expect, i.e. | ||
// as an object with a 'resize' action, width, and height | ||
const { action, width, height } = data; | ||
const { width: oldWidth, height: oldHeight } = this.state; | ||
|
||
if ( 'resize' === action && ( oldWidth !== width || oldHeight !== height ) ) { | ||
this.setState( { width, height } ); | ||
this.props.onResize(); | ||
} | ||
}, | ||
|
||
onLoad: function( event ) { | ||
this.maybeConnect(); | ||
this.props.onLoad( event ); | ||
}, | ||
|
||
render: function() { | ||
const omitProps = [ 'onResize' ]; | ||
if ( ! this.props.src ) { | ||
omitProps.push( 'src' ); | ||
} | ||
return ( | ||
<iframe | ||
ref={ ( node ) => this.iframe = node } | ||
title={ this.props.title } | ||
seamless="seamless" | ||
scrolling="no" | ||
{ ...omit( this.props, omitProps ) } | ||
onLoad={ this.onLoad } | ||
width={ this.props.width || this.state.width } | ||
height={ this.props.height || this.state.height } /> | ||
); | ||
} | ||
} ); |
Oops, something went wrong.