Skip to content

Commit

Permalink
Tweet block - WORK IN PROGESS! Don't merge or review or anything.
Browse files Browse the repository at this point in the history
  • Loading branch information
notnownikki committed May 10, 2017
1 parent 052a5d6 commit 3cd38d1
Show file tree
Hide file tree
Showing 7 changed files with 401 additions and 1 deletion.
3 changes: 2 additions & 1 deletion blocks/api/categories.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
*/
const categories = [
{ slug: 'common', title: 'Common Blocks' },
{ slug: 'layout', title: 'Layout Blocks' }
{ slug: 'layout', title: 'Layout Blocks' },
{ slug: 'social', title: 'Social Media' }
];

/**
Expand Down
1 change: 1 addition & 0 deletions blocks/library/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ import './quote';
import './separator';
import './button';
import './pullquote';
import './tweet';
95 changes: 95 additions & 0 deletions blocks/library/tweet/index.js
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>
);
}
} );
46 changes: 46 additions & 0 deletions components/resizable-iframe/README.md
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.
193 changes: 193 additions & 0 deletions components/resizable-iframe/index.js
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 } />
);
}
} );
Loading

0 comments on commit 3cd38d1

Please sign in to comment.