diff options
Diffstat (limited to 'plugins/jetpack/extensions/blocks/gif')
-rw-r--r-- | plugins/jetpack/extensions/blocks/gif/edit.js | 217 | ||||
-rw-r--r-- | plugins/jetpack/extensions/blocks/gif/editor.js | 7 | ||||
-rw-r--r-- | plugins/jetpack/extensions/blocks/gif/editor.scss | 86 | ||||
-rw-r--r-- | plugins/jetpack/extensions/blocks/gif/gif.php | 67 | ||||
-rw-r--r-- | plugins/jetpack/extensions/blocks/gif/index.js | 61 | ||||
-rw-r--r-- | plugins/jetpack/extensions/blocks/gif/style.scss | 40 | ||||
-rw-r--r-- | plugins/jetpack/extensions/blocks/gif/view.js | 4 |
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'; |