summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/extensions/blocks/gif')
-rw-r--r--plugins/jetpack/extensions/blocks/gif/edit.js217
-rw-r--r--plugins/jetpack/extensions/blocks/gif/editor.js7
-rw-r--r--plugins/jetpack/extensions/blocks/gif/editor.scss86
-rw-r--r--plugins/jetpack/extensions/blocks/gif/gif.php67
-rw-r--r--plugins/jetpack/extensions/blocks/gif/index.js61
-rw-r--r--plugins/jetpack/extensions/blocks/gif/style.scss40
-rw-r--r--plugins/jetpack/extensions/blocks/gif/view.js4
7 files changed, 482 insertions, 0 deletions
diff --git a/plugins/jetpack/extensions/blocks/gif/edit.js b/plugins/jetpack/extensions/blocks/gif/edit.js
new file mode 100644
index 00000000..d16fa4db
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/gif/edit.js
@@ -0,0 +1,217 @@
+/**
+ * External dependencies
+ */
+import classNames from 'classnames';
+import { __ } from '@wordpress/i18n';
+import { Component, createRef } from '@wordpress/element';
+import { Button, PanelBody, Path, Placeholder, SVG, TextControl } from '@wordpress/components';
+import { InspectorControls, RichText } from '@wordpress/editor';
+
+/**
+ * Internal dependencies
+ */
+import { icon, title } from './';
+
+const GIPHY_API_KEY = 't1PkR1Vq0mzHueIFBvZSZErgFs9NBmYW';
+const INPUT_PROMPT = __( 'Search for a term or paste a Giphy URL', 'jetpack' );
+
+class GifEdit extends Component {
+ textControlRef = createRef();
+
+ state = {
+ captionFocus: false,
+ results: null,
+ };
+
+ onFormSubmit = event => {
+ event.preventDefault();
+ this.onSubmit();
+ };
+
+ onSubmit = () => {
+ const { attributes } = this.props;
+ const { searchText } = attributes;
+ this.parseSearch( searchText );
+ };
+
+ parseSearch = searchText => {
+ let giphyID = null;
+ // If search is hardcoded Giphy URL following this pattern: https://giphy.com/embed/4ZFekt94LMhNK
+ if ( searchText.indexOf( '//giphy.com/gifs' ) !== -1 ) {
+ giphyID = this.splitAndLast( this.splitAndLast( searchText, '/' ), '-' );
+ }
+ // If search is hardcoded Giphy URL following this patterh: http://i.giphy.com/4ZFekt94LMhNK.gif
+ if ( searchText.indexOf( '//i.giphy.com' ) !== -1 ) {
+ giphyID = this.splitAndLast( searchText, '/' ).replace( '.gif', '' );
+ }
+ // https://media.giphy.com/media/gt0hYzKlMpfOg/giphy.gif
+ const match = searchText.match(
+ /http[s]?:\/\/media.giphy.com\/media\/([A-Za-z0-9\-.]+)\/giphy.gif/
+ );
+ if ( match ) {
+ giphyID = match[ 1 ];
+ }
+ if ( giphyID ) {
+ return this.fetch( this.urlForId( giphyID ) );
+ }
+
+ return this.fetch( this.urlForSearch( searchText ) );
+ };
+
+ urlForSearch = searchText => {
+ return `https://api.giphy.com/v1/gifs/search?q=${ encodeURIComponent(
+ searchText
+ ) }&api_key=${ encodeURIComponent( GIPHY_API_KEY ) }&limit=10`;
+ };
+
+ urlForId = giphyId => {
+ return `https://api.giphy.com/v1/gifs/${ encodeURIComponent(
+ giphyId
+ ) }?api_key=${ encodeURIComponent( GIPHY_API_KEY ) }`;
+ };
+
+ splitAndLast = ( array, delimiter ) => {
+ const split = array.split( delimiter );
+ return split[ split.length - 1 ];
+ };
+
+ fetch = url => {
+ const xhr = new XMLHttpRequest();
+ xhr.open( 'GET', url );
+ xhr.onload = () => {
+ if ( xhr.status === 200 ) {
+ const res = JSON.parse( xhr.responseText );
+ // If there is only one result, Giphy's API does not return an array.
+ // The following statement normalizes the data into an array with one member in this case.
+ const results = typeof res.data.images !== 'undefined' ? [ res.data ] : res.data;
+ const giphyData = results[ 0 ];
+ // No results
+ if ( ! giphyData.images ) {
+ return;
+ }
+ this.setState( { results }, () => {
+ this.selectGiphy( giphyData );
+ } );
+ } else {
+ // Error handling TK
+ }
+ };
+ xhr.send();
+ };
+
+ selectGiphy = giphy => {
+ const { setAttributes } = this.props;
+ const calculatedPaddingTop = Math.floor(
+ ( giphy.images.original.height / giphy.images.original.width ) * 100
+ );
+ const paddingTop = `${ calculatedPaddingTop }%`;
+ const giphyUrl = giphy.embed_url;
+ setAttributes( { giphyUrl, paddingTop } );
+ };
+
+ setFocus = () => {
+ this.textControlRef.current.querySelector( 'input' ).focus();
+ this.setState( { captionFocus: false } );
+ };
+
+ hasSearchText = () => {
+ const { attributes } = this.props;
+ const { searchText } = attributes;
+ return searchText && searchText.length > 0;
+ };
+
+ thumbnailClicked = thumbnail => {
+ this.selectGiphy( thumbnail );
+ };
+
+ render() {
+ const { attributes, className, isSelected, setAttributes } = this.props;
+ const { align, caption, giphyUrl, searchText, paddingTop } = attributes;
+ const { captionFocus, results } = this.state;
+ const style = { paddingTop };
+ const classes = classNames( className, `align${ align }` );
+ const inputFields = (
+ <form
+ className="wp-block-jetpack-gif_input-container"
+ onSubmit={ this.onFormSubmit }
+ ref={ this.textControlRef }
+ >
+ <TextControl
+ className="wp-block-jetpack-gif_input"
+ label={ INPUT_PROMPT }
+ placeholder={ INPUT_PROMPT }
+ onChange={ value => setAttributes( { searchText: value } ) }
+ value={ searchText }
+ />
+ <Button isLarge onClick={ this.onSubmit }>
+ { __( 'Search', 'jetpack' ) }
+ </Button>
+ </form>
+ );
+ return (
+ <div className={ classes }>
+ <InspectorControls>
+ <PanelBody className="components-panel__body-gif-branding">
+ <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 202 22">
+ <Path d="M4.6 5.9H0v10h1.6v-3.1h3c4.8 0 4.8-6.9 0-6.9zm0 5.4h-3v-4h3c2.6.1 2.6 4 0 4zM51.2 12.3c2-.3 2.7-1.7 2.7-3.1 0-1.7-1.2-3.3-3.5-3.3h-4.6v10h1.6v-3.4h2.1l3 3.4h1.9l-.2-.3-3-3.3zM47.4 11V7.4h3c1.3 0 1.9.9 1.9 1.8s-.6 1.8-1.9 1.8h-3zM30.6 13.6L28 5.9h-1.1l-2.5 7.7-2.6-7.7H20l3.7 10H25l1.4-3.5L27.5 9l1.1 3.4 1.3 3.5h1.4l3.5-10h-1.7z" />
+ <Path d="M14.4 5.7c-3 0-5.1 2.2-5.1 5.2 0 2.6 1.6 5.1 5.1 5.1 3.5 0 5.1-2.5 5.1-5.2-.1-2.6-1.7-5.1-5.1-5.1zm-.1 8.9c-2.5 0-3.5-1.9-3.5-3.7 0-2.2 1.2-3.8 3.5-3.8 2.4 0 3.5 2 3.5 3.8.1 2-1 3.7-3.5 3.7zM57.7 11.6h5.5v-1.5h-5.5V7.4h5.7V5.9h-7.3v10h7.3v-1.6h-5.7zM38 14.3v-2.7h5.5v-1.5H38V7.4h5.7V5.9h-7.3v10h7.3v-1.6zM93 10.3l-2.7-4.4h-1.9V6l3.8 5.8v4.1h1.6v-4.1l4-5.8v-.1h-2zM69.3 5.9h-3.8v10h3.8c3.5 0 5.1-2.5 5-5.1-.1-2.5-1.6-4.9-5-4.9zm0 8.4h-2.2V7.4h2.2c2.3 0 3.4 1.7 3.4 3.4s-1 3.5-3.4 3.5zM86.3 10.7c.9-.4 1.4-1.1 1.4-2 0-2-1.5-2.8-3.4-2.8h-4.6v10h4.6c2 0 3.7-.7 3.7-2.8 0-.8-.5-2-1.7-2.4zm-5-3.4h3c1.2 0 1.8.7 1.8 1.4 0 .8-.6 1.3-1.8 1.3h-3V7.3zm3 7.1h-3v-2.9h3c.9 0 2.1.5 2.1 1.6 0 1-1.2 1.3-2.1 1.3zM113.9 13.3h5.3V16c-1.2.9-2.9 1.1-4 1.1-4.2 0-5.6-3.3-5.6-6 0-4.1 2.2-6.1 5.6-6.1 1.4 0 3.2.4 4.8 1.8l3.4-3.4C120.7.6 118.1 0 115.2 0c-7.8 0-11.4 5.6-11.4 11s3.1 10.9 11.4 10.9c4 0 7.6-1.4 8.9-4.1V8.6h-10.2v4.7zM171.9 8.5h-7.4V.6h-5.9v20.8h5.9v-7.8h7.4v7.8h5.9V.6h-5.9zM195.1.6l-4.5 7.1-4.3-7.1h-6.6v.2l7.9 12.3v8.3h5.9v-8.3L201.8.9V.6zM127.4.6h5.9v20.8h-5.9zM147.6.6h-10.1v20.8h5.9v-5.6h4.2c5.6-.1 8.3-3.4 8.3-7.6.1-4.1-2.7-7.6-8.3-7.6zm0 10.2h-4.2V5.6h4.2c1.6 0 2.5 1.2 2.5 2.6 0 1.4-.9 2.6-2.5 2.6z" />
+ </SVG>
+ </PanelBody>
+ </InspectorControls>
+ { ! giphyUrl ? (
+ <Placeholder className="wp-block-jetpack-gif_placeholder" icon={ icon } label={ title }>
+ { inputFields }
+ </Placeholder>
+ ) : (
+ <figure>
+ { isSelected && inputFields }
+ { isSelected && results && results.length > 1 && (
+ <div className="wp-block-jetpack-gif_thumbnails-container">
+ { results.map( thumbnail => {
+ const thumbnailStyle = {
+ backgroundImage: `url(${ thumbnail.images.downsized_still.url })`,
+ };
+ return (
+ <button
+ className="wp-block-jetpack-gif_thumbnail-container"
+ key={ thumbnail.id }
+ onClick={ () => {
+ this.thumbnailClicked( thumbnail );
+ } }
+ style={ thumbnailStyle }
+ />
+ );
+ } ) }
+ </div>
+ ) }
+ <div className="wp-block-jetpack-gif-wrapper" style={ style }>
+ <div
+ className="wp-block-jetpack-gif_cover"
+ onClick={ this.setFocus }
+ onKeyDown={ this.setFocus }
+ role="button"
+ tabIndex="0"
+ />
+ <iframe src={ giphyUrl } title={ searchText } />
+ </div>
+ { ( ! RichText.isEmpty( caption ) || isSelected ) && !! giphyUrl && (
+ <RichText
+ className="wp-block-jetpack-gif-caption gallery-caption"
+ inlineToolbar
+ isSelected={ captionFocus }
+ unstableOnFocus={ () => {
+ this.setState( { captionFocus: true } );
+ } }
+ onChange={ value => setAttributes( { caption: value } ) }
+ placeholder={ __( 'Write caption…', 'jetpack' ) }
+ tagName="figcaption"
+ value={ caption }
+ />
+ ) }
+ </figure>
+ ) }
+ </div>
+ );
+ }
+}
+export default GifEdit;
diff --git a/plugins/jetpack/extensions/blocks/gif/editor.js b/plugins/jetpack/extensions/blocks/gif/editor.js
new file mode 100644
index 00000000..d05f4039
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/gif/editor.js
@@ -0,0 +1,7 @@
+/**
+ * Internal dependencies
+ */
+import registerJetpackBlock from '../../shared/register-jetpack-block';
+import { name, settings } from '.';
+
+registerJetpackBlock( name, settings );
diff --git a/plugins/jetpack/extensions/blocks/gif/editor.scss b/plugins/jetpack/extensions/blocks/gif/editor.scss
new file mode 100644
index 00000000..2f1bd55c
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/gif/editor.scss
@@ -0,0 +1,86 @@
+@import '../../shared/styles/gutenberg-colors.scss';
+
+.wp-block-jetpack-gif {
+ figure {
+ transition: padding-top 125ms ease-in-out;
+ }
+ .components-base-control__field {
+ text-align: center;
+ }
+ .wp-block-jetpack-gif_cover {
+ background: none;
+ border: none;
+ height: 100%;
+ left: 0;
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ z-index: 1;
+ &:focus {
+ outline: none;
+ }
+ }
+ .wp-block-jetpack-gif_input-container {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+ margin: 0 auto;
+ max-width: 400px;
+ width: 100%;
+ z-index: 1;
+ .components-base-control__label {
+ height: 0;
+ margin: 0;
+ text-indent: -9999px;
+ }
+ }
+ .wp-block-jetpack-gif_input {
+ flex-grow: 1;
+ margin-right: 0.5em;
+ }
+ .wp-block-jetpack-gif_thumbnails-container {
+ display: flex;
+ margin: -2px 0 2px 0;
+ margin-left: calc( -4px / 2 );
+ overflow-x: auto;
+ width: calc( 100% + 4px );
+ &::-webkit-scrollbar {
+ display: none;
+ }
+ }
+ .wp-block-jetpack-gif_thumbnail-container {
+ align-items: center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
+ border: none;
+ border-radius: 3px;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ margin: 2px;
+ padding: 0;
+ padding-bottom: calc( 100% / 10 - 4px );
+ width: calc( 100% / 10 - 4px );
+ &:hover {
+ box-shadow: 0 0 0 1px $dark-gray-500;
+ }
+ &:focus {
+ box-shadow: 0 0 0 2px $blue-medium-500;
+ outline: 0;
+ }
+ }
+}
+.components-panel__body-gif-branding {
+ svg {
+ display: block;
+ margin: 0 auto;
+ max-width: 200px;
+ }
+ svg path {
+ fill: $dark-gray-150;
+ }
+}
diff --git a/plugins/jetpack/extensions/blocks/gif/gif.php b/plugins/jetpack/extensions/blocks/gif/gif.php
new file mode 100644
index 00000000..cb35f3da
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/gif/gif.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * GIF Block.
+ *
+ * @since 7.0.0
+ *
+ * @package Jetpack
+ */
+
+jetpack_register_block(
+ 'jetpack/gif',
+ array(
+ 'render_callback' => 'jetpack_gif_block_render',
+ )
+);
+
+/**
+ * Gif block registration/dependency declaration.
+ *
+ * @param array $attr - Array containing the gif block attributes.
+ *
+ * @return string
+ */
+function jetpack_gif_block_render( $attr ) {
+ $padding_top = isset( $attr['paddingTop'] ) ? $attr['paddingTop'] : 0;
+ $style = 'padding-top:' . $padding_top;
+ $giphy_url = isset( $attr['giphyUrl'] ) ? $attr['giphyUrl'] : null;
+ $search_text = isset( $attr['searchText'] ) ? $attr['searchText'] : '';
+ $caption = isset( $attr['caption'] ) ? $attr['caption'] : null;
+
+ if ( ! $giphy_url ) {
+ return null;
+ }
+
+ /* TODO: replace with centralized block_class function */
+ $align = isset( $attr['align'] ) ? $attr['align'] : 'center';
+ $type = 'gif';
+ $classes = array(
+ 'wp-block-jetpack-' . $type,
+ 'align' . $align,
+ );
+ if ( isset( $attr['className'] ) ) {
+ array_push( $classes, $attr['className'] );
+ }
+ $classes = implode( $classes, ' ' );
+
+ ob_start();
+ ?>
+ <div class="<?php echo esc_attr( $classes ); ?>">
+ <figure>
+ <div class="wp-block-jetpack-gif-wrapper" style="<?php echo esc_attr( $style ); ?>">
+ <iframe src="<?php echo esc_url( $giphy_url ); ?>"
+ title="<?php echo esc_attr( $search_text ); ?>"></iframe>
+ </div>
+ <?php if ( $caption ) : ?>
+ <figcaption
+ class="wp-block-jetpack-gif-caption gallery-caption"><?php echo wp_kses_post( $caption ); ?></figcaption>
+ <?php endif; ?>
+ </figure>
+ </div>
+ <?php
+ $html = ob_get_clean();
+
+ Jetpack_Gutenberg::load_assets_as_required( 'gif' );
+
+ return $html;
+}
diff --git a/plugins/jetpack/extensions/blocks/gif/index.js b/plugins/jetpack/extensions/blocks/gif/index.js
new file mode 100644
index 00000000..54ed026c
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/gif/index.js
@@ -0,0 +1,61 @@
+/**
+ * External dependencies
+ */
+import { __, _x } from '@wordpress/i18n';
+import { Path, SVG } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import edit from './edit';
+
+// Ordering is important! Editor overrides style!
+import './style.scss';
+import './editor.scss';
+
+export const name = 'gif';
+export const title = __( 'GIF', 'jetpack' );
+
+export const icon = (
+ <SVG xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <Path fill="none" d="M0 0h24v24H0V0z" />
+ <Path d="M18 13v7H4V6h5.02c.05-.71.22-1.38.48-2H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-5l-2-2zm-1.5 5h-11l2.75-3.53 1.96 2.36 2.75-3.54L16.5 18zm2.8-9.11c.44-.7.7-1.51.7-2.39C20 4.01 17.99 2 15.5 2S11 4.01 11 6.5s2.01 4.5 4.49 4.5c.88 0 1.7-.26 2.39-.7L21 13.42 22.42 12 19.3 8.89zM15.5 9C14.12 9 13 7.88 13 6.5S14.12 4 15.5 4 18 5.12 18 6.5 16.88 9 15.5 9z" />
+ </SVG>
+);
+
+export const settings = {
+ title,
+ icon,
+ category: 'jetpack',
+ keywords: [
+ _x( 'animated', 'block search term', 'jetpack' ),
+ _x( 'giphy', 'block search term', 'jetpack' ),
+ _x( 'image', 'block search term', 'jetpack' ),
+ ],
+ description: __( 'Search for and insert an animated image.', 'jetpack' ),
+ attributes: {
+ align: {
+ type: 'string',
+ default: 'center',
+ },
+ caption: {
+ type: 'string',
+ },
+ giphyUrl: {
+ type: 'string',
+ },
+ searchText: {
+ type: 'string',
+ },
+ paddingTop: {
+ type: 'string',
+ default: '56.2%',
+ },
+ },
+ supports: {
+ html: false,
+ align: true,
+ },
+ edit,
+ save: () => null,
+};
diff --git a/plugins/jetpack/extensions/blocks/gif/style.scss b/plugins/jetpack/extensions/blocks/gif/style.scss
new file mode 100644
index 00000000..5dc188fa
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/gif/style.scss
@@ -0,0 +1,40 @@
+@import '../../shared/styles/gutenberg-colors.scss';
+
+.wp-block-jetpack-gif {
+ clear: both;
+ margin: 0 0 20px;
+ figure {
+ margin: 0;
+ position: relative;
+ width: 100%;
+ }
+ iframe {
+ border: 0;
+ left: 0;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ &.aligncenter {
+ text-align: center;
+ }
+ &.alignright,
+ &.alignleft {
+ min-width: 300px;
+ }
+ // Mirroring Gutenberg caption-style mixin: https://github.com/WordPress/gutenberg/blob/master/assets/stylesheets/_mixins.scss#L312-L318
+ .wp-block-jetpack-gif-caption {
+ margin-top: 0.5em;
+ margin-bottom: 1em;
+ color: $dark-gray-500;
+ text-align: center;
+ }
+ .wp-block-jetpack-gif-wrapper {
+ height: 0;
+ margin: 0;
+ padding: calc( 56.2% + 12px ) 0 0 0;
+ position: relative;
+ width: 100%;
+ }
+}
diff --git a/plugins/jetpack/extensions/blocks/gif/view.js b/plugins/jetpack/extensions/blocks/gif/view.js
new file mode 100644
index 00000000..6a6dda31
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/gif/view.js
@@ -0,0 +1,4 @@
+/**
+ * Internal dependencies
+ */
+import './style.scss';