diff options
Diffstat (limited to 'plugins/jetpack/extensions/blocks/slideshow')
12 files changed, 161 insertions, 1096 deletions
diff --git a/plugins/jetpack/extensions/blocks/slideshow/create-swiper.js b/plugins/jetpack/extensions/blocks/slideshow/create-swiper.js deleted file mode 100644 index 72a54f56..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/create-swiper.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * External dependencies - */ -import { mapValues, merge } from 'lodash'; - -/** - * Internal dependencies - */ -import './style.scss'; - -export default async function createSwiper( - container = '.swiper-container', - params = {}, - callbacks = {} -) { - const defaultParams = { - effect: 'slide', - grabCursor: true, - init: true, - initialSlide: 0, - navigation: { - nextEl: '.swiper-button-next', - prevEl: '.swiper-button-prev', - }, - pagination: { - bulletElement: 'button', - clickable: true, - el: '.swiper-pagination', - type: 'bullets', - }, - preventClicksPropagation: false /* Necessary for normal block interactions */, - releaseFormElements: false, - setWrapperSize: true, - touchStartPreventDefault: false, - on: mapValues( - callbacks, - callback => - function() { - callback( this ); - } - ), - }; - const [ { default: Swiper } ] = await Promise.all( [ - import( /* webpackChunkName: "swiper" */ 'swiper/dist/js/swiper.js' ), - import( /* webpackChunkName: "swiper" */ 'swiper/dist/css/swiper.css' ), - ] ); - return new Swiper( container, merge( {}, defaultParams, params ) ); -} diff --git a/plugins/jetpack/extensions/blocks/slideshow/edit.js b/plugins/jetpack/extensions/blocks/slideshow/edit.js deleted file mode 100644 index 9c03f029..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/edit.js +++ /dev/null @@ -1,249 +0,0 @@ -/** - * External dependencies - */ -import { __, _x } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; -import { compose } from '@wordpress/compose'; -import { filter, pick } from 'lodash'; -import { isBlobURL } from '@wordpress/blob'; -import { withDispatch } from '@wordpress/data'; -import { - BlockControls, - BlockIcon, - InspectorControls, - MediaPlaceholder, - MediaUpload, - mediaUpload, -} from '@wordpress/editor'; -import { - DropZone, - FormFileUpload, - IconButton, - PanelBody, - RangeControl, - SelectControl, - ToggleControl, - Toolbar, - withNotices, -} from '@wordpress/components'; - -/** - * Internal dependencies - */ -import { icon } from '.'; -import Slideshow from './slideshow'; -import './editor.scss'; - -const ALLOWED_MEDIA_TYPES = [ 'image' ]; - -const effectOptions = [ - { label: _x( 'Slide', 'Slideshow transition effect', 'jetpack' ), value: 'slide' }, - { label: _x( 'Fade', 'Slideshow transition effect', 'jetpack' ), value: 'fade' }, -]; - -export const pickRelevantMediaFiles = image => - pick( image, [ 'alt', 'id', 'link', 'url', 'caption' ] ); - -class SlideshowEdit extends Component { - constructor() { - super( ...arguments ); - this.state = { - selectedImage: null, - }; - } - setAttributes( attributes ) { - if ( attributes.ids ) { - throw new Error( - 'The "ids" attribute should not be changed directly. It is managed automatically when "images" attribute changes' - ); - } - - if ( attributes.images ) { - attributes = { - ...attributes, - ids: attributes.images.map( ( { id } ) => parseInt( id, 10 ) ), - }; - } - - this.props.setAttributes( attributes ); - } - onSelectImages = images => { - const mapped = images.map( image => pickRelevantMediaFiles( image ) ); - this.setAttributes( { - images: mapped, - } ); - }; - onRemoveImage = index => { - return () => { - const images = filter( this.props.attributes.images, ( img, i ) => index !== i ); - this.setState( { selectedImage: null } ); - this.setAttributes( { images } ); - }; - }; - addFiles = files => { - const currentImages = this.props.attributes.images || []; - const { lockPostSaving, unlockPostSaving, noticeOperations } = this.props; - const lockName = 'slideshowBlockLock'; - lockPostSaving( lockName ); - mediaUpload( { - allowedTypes: ALLOWED_MEDIA_TYPES, - filesList: files, - onFileChange: images => { - const imagesNormalized = images.map( image => pickRelevantMediaFiles( image ) ); - this.setAttributes( { - images: [ ...currentImages, ...imagesNormalized ], - } ); - if ( ! imagesNormalized.every( image => isBlobURL( image.url ) ) ) { - unlockPostSaving( lockName ); - } - }, - onError: noticeOperations.createErrorNotice, - } ); - }; - uploadFromFiles = event => this.addFiles( event.target.files ); - render() { - const { - attributes, - className, - isSelected, - noticeOperations, - noticeUI, - setAttributes, - } = this.props; - const { align, autoplay, delay, effect, images } = attributes; - const prefersReducedMotion = - typeof window !== 'undefined' && - window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches; - const controls = ( - <Fragment> - <InspectorControls> - <PanelBody title={ __( 'Autoplay', 'jetpack' ) }> - <ToggleControl - label={ __( 'Autoplay', 'jetpack' ) } - help={ __( 'Autoplay between slides', 'jetpack' ) } - checked={ autoplay } - onChange={ value => { - setAttributes( { autoplay: value } ); - } } - /> - { autoplay && ( - <RangeControl - label={ __( 'Delay between transitions (in seconds)', 'jetpack' ) } - value={ delay } - onChange={ value => { - setAttributes( { delay: value } ); - } } - min={ 1 } - max={ 5 } - /> - ) } - { autoplay && prefersReducedMotion && ( - <span> - { __( - 'The Reduce Motion accessibility option is selected, therefore autoplay will be disabled in this browser.', - 'jetpack' - ) } - </span> - ) } - </PanelBody> - <PanelBody title={ __( 'Effects', 'jetpack' ) }> - <SelectControl - label={ __( 'Transition effect', 'jetpack' ) } - value={ effect } - onChange={ value => { - setAttributes( { effect: value } ); - } } - options={ effectOptions } - /> - </PanelBody> - </InspectorControls> - <BlockControls> - { !! images.length && ( - <Toolbar> - <MediaUpload - onSelect={ this.onSelectImages } - allowedTypes={ ALLOWED_MEDIA_TYPES } - multiple - gallery - value={ images.map( img => img.id ) } - render={ ( { open } ) => ( - <IconButton - className="components-toolbar__control" - label={ __( 'Edit Slideshow', 'jetpack' ) } - icon="edit" - onClick={ open } - /> - ) } - /> - </Toolbar> - ) } - </BlockControls> - </Fragment> - ); - - if ( images.length === 0 ) { - return ( - <Fragment> - { controls } - <MediaPlaceholder - icon={ <BlockIcon icon={ icon } /> } - className={ className } - labels={ { - title: __( 'Slideshow', 'jetpack' ), - instructions: __( - 'Drag images, upload new ones or select files from your library.', - 'jetpack' - ), - } } - onSelect={ this.onSelectImages } - accept="image/*" - allowedTypes={ ALLOWED_MEDIA_TYPES } - multiple - notices={ noticeUI } - onError={ noticeOperations.createErrorNotice } - /> - </Fragment> - ); - } - return ( - <Fragment> - { controls } - { noticeUI } - <Slideshow - align={ align } - autoplay={ autoplay } - className={ className } - delay={ delay } - effect={ effect } - images={ images } - onError={ noticeOperations.createErrorNotice } - /> - <DropZone onFilesDrop={ this.addFiles } /> - { isSelected && ( - <div className="wp-block-jetpack-slideshow__add-item"> - <FormFileUpload - multiple - isLarge - className="wp-block-jetpack-slideshow__add-item-button" - onChange={ this.uploadFromFiles } - accept="image/*" - icon="insert" - > - { __( 'Upload an image', 'jetpack' ) } - </FormFileUpload> - </div> - ) } - </Fragment> - ); - } -} -export default compose( - withDispatch( dispatch => { - const { lockPostSaving, unlockPostSaving } = dispatch( 'core/editor' ); - return { - lockPostSaving, - unlockPostSaving, - }; - } ), - withNotices -)( SlideshowEdit ); diff --git a/plugins/jetpack/extensions/blocks/slideshow/editor.js b/plugins/jetpack/extensions/blocks/slideshow/editor.js deleted file mode 100644 index d05f4039..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/editor.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Internal dependencies - */ -import registerJetpackBlock from '../../shared/register-jetpack-block'; -import { name, settings } from '.'; - -registerJetpackBlock( name, settings ); diff --git a/plugins/jetpack/extensions/blocks/slideshow/editor.scss b/plugins/jetpack/extensions/blocks/slideshow/editor.scss deleted file mode 100644 index 11c07c25..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/editor.scss +++ /dev/null @@ -1,44 +0,0 @@ -@import '../../shared/styles/gutenberg-colors.scss'; - -.wp-block-jetpack-slideshow__add-item { - margin-top: 4px; - width: 100%; - - .components-form-file-upload, - .components-button.wp-block-jetpack-slideshow__add-item-button { - width: 100%; - height: 100%; - } - - .components-button.wp-block-jetpack-slideshow__add-item-button { - display: flex; - flex-direction: column; - justify-content: center; - box-shadow: none; - border: none; - border-radius: 0; - min-height: 100px; - - .dashicon { - margin-top: 10px; - } - - &:hover, - &:focus { - border: 1px solid $dark-gray-500; - } - } -} - -.wp-block-jetpack-slideshow_slide { - .components-spinner { - position: absolute; - top: 50%; - left: 50%; - margin-top: -9px; - margin-left: -9px; - } - &.is-transient img { - opacity: 0.3; - } -} diff --git a/plugins/jetpack/extensions/blocks/slideshow/index.js b/plugins/jetpack/extensions/blocks/slideshow/index.js deleted file mode 100644 index 9f4b2807..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/index.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * External dependencies - */ -import { __, _x } from '@wordpress/i18n'; -import { Path, SVG } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import edit from './edit'; -import save from './save'; -import transforms from './transforms'; - -export const icon = ( - <SVG xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> - <Path d="M0 0h24v24H0z" fill="none" /> - <Path d="M10 8v8l5-4-5-4zm9-5H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z" /> - </SVG> -); - -const attributes = { - align: { - default: 'center', - type: 'string', - }, - autoplay: { - type: 'boolean', - default: false, - }, - delay: { - type: 'number', - default: 3, - }, - ids: { - default: [], - type: 'array', - }, - images: { - type: 'array', - default: [], - source: 'query', - selector: '.swiper-slide', - query: { - alt: { - source: 'attribute', - selector: 'img', - attribute: 'alt', - default: '', - }, - caption: { - type: 'string', - source: 'html', - selector: 'figcaption', - }, - id: { - source: 'attribute', - selector: 'img', - attribute: 'data-id', - }, - url: { - source: 'attribute', - selector: 'img', - attribute: 'src', - }, - }, - }, - effect: { - type: 'string', - default: 'slide', - }, -}; - -export const name = 'slideshow'; - -export const settings = { - title: __( 'Slideshow', 'jetpack' ), - category: 'jetpack', - keywords: [ - _x( 'image', 'block search term', 'jetpack' ), - _x( 'gallery', 'block search term', 'jetpack' ), - _x( 'slider', 'block search term', 'jetpack' ), - ], - description: __( 'Add an interactive slideshow.', 'jetpack' ), - attributes, - supports: { - align: [ 'center', 'wide', 'full' ], - html: false, - }, - icon, - edit, - save, - transforms, -}; diff --git a/plugins/jetpack/extensions/blocks/slideshow/save.js b/plugins/jetpack/extensions/blocks/slideshow/save.js deleted file mode 100644 index 59879ded..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/save.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Internal dependencies - */ -import Slideshow from './slideshow'; - -export default ( { attributes: { align, autoplay, delay, effect, images }, className } ) => ( - <Slideshow - align={ align } - autoplay={ autoplay } - className={ className } - delay={ delay } - effect={ effect } - images={ images } - /> -); diff --git a/plugins/jetpack/extensions/blocks/slideshow/slideshow.js b/plugins/jetpack/extensions/blocks/slideshow/slideshow.js deleted file mode 100644 index b7d97c1a..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/slideshow.js +++ /dev/null @@ -1,232 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; -import ResizeObserver from 'resize-observer-polyfill'; -import { __ } from '@wordpress/i18n'; -import { Component, createRef } from '@wordpress/element'; -import { isBlobURL } from '@wordpress/blob'; -import { isEqual } from 'lodash'; -import { RichText } from '@wordpress/editor'; -import { Spinner } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import createSwiper from './create-swiper'; -import { - swiperApplyAria, - swiperInit, - swiperPaginationRender, - swiperResize, -} from './swiper-callbacks'; - -class Slideshow extends Component { - pendingRequestAnimationFrame = null; - resizeObserver = null; - static defaultProps = { - effect: 'slide', - }; - - constructor( props ) { - super( props ); - - this.slideshowRef = createRef(); - this.btnNextRef = createRef(); - this.btnPrevRef = createRef(); - this.paginationRef = createRef(); - } - - componentDidMount() { - const { onError } = this.props; - this.buildSwiper() - .then( swiper => { - this.swiperInstance = swiper; - this.initializeResizeObserver( swiper ); - } ) - .catch( () => { - onError( __( 'The Swiper library could not be loaded.', 'jetpack' ) ); - } ); - } - - componentWillUnmount() { - this.clearResizeObserver(); - this.clearPendingRequestAnimationFrame(); - } - - componentDidUpdate( prevProps ) { - const { align, autoplay, delay, effect, images, onError } = this.props; - - /* A change in alignment or images only needs an update */ - if ( align !== prevProps.align || ! isEqual( images, prevProps.images ) ) { - this.swiperInstance && this.swiperInstance.update(); - } - /* A change in effect requires a full rebuild */ - if ( - effect !== prevProps.effect || - autoplay !== prevProps.autoplay || - delay !== prevProps.delay || - images !== prevProps.images - ) { - let realIndex; - if ( ! this.swiperIndex ) { - realIndex = 0; - } else if ( images.length === prevProps.images.length ) { - realIndex = this.swiperInstance.realIndex; - } else { - realIndex = prevProps.images.length; - } - this.swiperInstance && this.swiperInstance.destroy( true, true ); - this.buildSwiper( realIndex ) - .then( swiper => { - this.swiperInstance = swiper; - this.initializeResizeObserver( swiper ); - } ) - .catch( () => { - onError( __( 'The Swiper library could not be loaded.', 'jetpack' ) ); - } ); - } - } - - initializeResizeObserver = swiper => { - this.clearResizeObserver(); - this.resizeObserver = new ResizeObserver( () => { - this.clearPendingRequestAnimationFrame(); - this.pendingRequestAnimationFrame = requestAnimationFrame( () => { - swiperResize( swiper ); - swiper.update(); - } ); - } ); - this.resizeObserver.observe( swiper.el ); - }; - - clearPendingRequestAnimationFrame = () => { - if ( this.pendingRequestAnimationFrame ) { - cancelAnimationFrame( this.pendingRequestAnimationFrame ); - this.pendingRequestAnimationFrame = null; - } - }; - - clearResizeObserver = () => { - if ( this.resizeObserver ) { - this.resizeObserver.disconnect(); - this.resizeObserver = null; - } - }; - - render() { - const { autoplay, className, delay, effect, images } = this.props; - // Note: React omits the data attribute if the value is null, but NOT if it is false. - // This is the reason for the unusual logic related to autoplay below. - /* eslint-disable jsx-a11y/anchor-is-valid */ - return ( - <div - className={ className } - data-autoplay={ autoplay || null } - data-delay={ autoplay ? delay : null } - data-effect={ effect } - > - <div - className="wp-block-jetpack-slideshow_container swiper-container" - ref={ this.slideshowRef } - > - <ul className="wp-block-jetpack-slideshow_swiper-wrappper swiper-wrapper"> - { images.map( ( { alt, caption, id, url } ) => ( - <li - className={ classnames( - 'wp-block-jetpack-slideshow_slide', - 'swiper-slide', - isBlobURL( url ) && 'is-transient' - ) } - key={ id } - > - <figure> - <img - alt={ alt } - className={ - `wp-block-jetpack-slideshow_image wp-image-${ id }` /* wp-image-${ id } makes WordPress add a srcset */ - } - data-id={ id } - src={ url } - /> - { isBlobURL( url ) && <Spinner /> } - { caption && ( - <RichText.Content - className="wp-block-jetpack-slideshow_caption gallery-caption" - tagName="figcaption" - value={ caption } - /> - ) } - </figure> - </li> - ) ) } - </ul> - <a - className="wp-block-jetpack-slideshow_button-prev swiper-button-prev swiper-button-white" - ref={ this.btnPrevRef } - role="button" - /> - <a - className="wp-block-jetpack-slideshow_button-next swiper-button-next swiper-button-white" - ref={ this.btnNextRef } - role="button" - /> - <a - aria-label="Pause Slideshow" - className="wp-block-jetpack-slideshow_button-pause" - role="button" - /> - <div - className="wp-block-jetpack-slideshow_pagination swiper-pagination swiper-pagination-white" - ref={ this.paginationRef } - /> - </div> - </div> - ); - /* eslint-enable jsx-a11y/anchor-is-valid */ - } - - prefersReducedMotion = () => { - return ( - typeof window !== 'undefined' && - window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches - ); - }; - - buildSwiper = ( initialSlide = 0 ) => - // Using refs instead of className-based selectors allows us to - // have multiple swipers on one page without collisions, and - // without needing to add IDs or the like. - createSwiper( - this.slideshowRef.current, - { - autoplay: - this.props.autoplay && ! this.prefersReducedMotion() - ? { - delay: this.props.delay * 1000, - disableOnInteraction: false, - } - : false, - effect: this.props.effect, - loop: true, - initialSlide, - navigation: { - nextEl: this.btnNextRef.current, - prevEl: this.btnPrevRef.current, - }, - pagination: { - clickable: true, - el: this.paginationRef.current, - type: 'bullets', - }, - }, - { - init: swiperInit, - imagesReady: swiperResize, - paginationRender: swiperPaginationRender, - transitionEnd: swiperApplyAria, - } - ); -} - -export default Slideshow; diff --git a/plugins/jetpack/extensions/blocks/slideshow/slideshow.php b/plugins/jetpack/extensions/blocks/slideshow/slideshow.php index be9ae6c0..ba18661c 100644 --- a/plugins/jetpack/extensions/blocks/slideshow/slideshow.php +++ b/plugins/jetpack/extensions/blocks/slideshow/slideshow.php @@ -24,5 +24,166 @@ jetpack_register_block( */ function jetpack_slideshow_block_load_assets( $attr, $content ) { Jetpack_Gutenberg::load_assets_as_required( 'slideshow' ); + if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) { + return jetpack_slideshow_block_render_amp( $attr ); + } return $content; } + +/** + * Render slideshow block for AMP + * + * @param array $attr Array containing the slideshow block attributes. + * + * @return string + */ +function jetpack_slideshow_block_render_amp( $attr ) { + static $wp_block_jetpack_slideshow_id = 0; + $wp_block_jetpack_slideshow_id++; + + $ids = empty( $attr['ids'] ) ? array() : $attr['ids']; + $autoplay = empty( $attr['autoplay'] ) ? false : $attr['autoplay']; + + $extras = array( + 'wp-amp-block', + $autoplay ? 'wp-block-jetpack-slideshow__autoplay' : null, + $autoplay ? 'wp-block-jetpack-slideshow__autoplay-playing' : null, + ); + $classes = Jetpack_Gutenberg::block_classes( 'slideshow', $attr, $extras ); + + return sprintf( + '<div class="%1$s" id="wp-block-jetpack-slideshow__%2$d"><div class="wp-block-jetpack-slideshow_container swiper-container">%3$s%4$s%5$s</div></div>', + esc_attr( $classes ), + absint( $wp_block_jetpack_slideshow_id ), + jetpack_slideshow_block_amp_carousel( $attr, $wp_block_jetpack_slideshow_id ), + $autoplay ? jetpack_slideshow_block_autoplay_ui( $wp_block_jetpack_slideshow_id ) : '', + jetpack_slideshow_block_bullets( $ids, $wp_block_jetpack_slideshow_id ) + ); +} + +/** + * Generate amp-carousel markup + * + * @param array $attr Array of block attributes. + * @param int $block_ordinal The ordinal number of the block, used in unique ID. + * + * @return string amp-carousel markup. + */ +function jetpack_slideshow_block_amp_carousel( $attr, $block_ordinal ) { + $ids = empty( $attr['ids'] ) ? array() : $attr['ids']; + $first_image = wp_get_attachment_metadata( $ids[0] ); + $delay = empty( $attr['delay'] ) ? 3 : absint( $attr['delay'] ); + $autoplay = empty( $attr['autoplay'] ) ? false : $attr['autoplay']; + $width = empty( $first_image['width'] ) ? 800 : $first_image['width']; + $height = empty( $first_image['height'] ) ? 600 : $first_image['height']; + return sprintf( + '<amp-carousel width="%1$d" height="%2$d" layout="responsive" type="slides" data-next-button-aria-label="%3$s" data-prev-button-aria-label="%4$s" controls loop %5$s id="wp-block-jetpack-slideshow__amp-carousel__%6$s" on="slideChange:wp-block-jetpack-slideshow__amp-pagination__%6$s.toggle(index=event.index, value=true)">%7$s</amp-carousel>', + esc_attr( $width ), + esc_attr( $height ), + esc_attr__( 'Next Slide', 'jetpack' ), + esc_attr__( 'Previous Slide', 'jetpack' ), + $autoplay ? 'autoplay delay=' . esc_attr( $delay * 1000 ) : '', + absint( $block_ordinal ), + implode( '', jetpack_slideshow_block_slides( $ids, $width, $height ) ) + ); +} + +/** + * Generate array of slides markup + * + * @param array $ids Array of image ids. + * @param int $width Width of the container. + * @param int $height Height of the container. + * + * @return array Array of slides markup. + */ +function jetpack_slideshow_block_slides( $ids = array(), $width = 400, $height = 300 ) { + return array_map( + function( $id ) use ( $width, $height ) { + $caption = wp_get_attachment_caption( $id ); + $figcaption = $caption ? sprintf( + '<figcaption class="wp-block-jetpack-slideshow_caption gallery-caption">%s</figcaption>', + wp_kses_post( $caption ) + ) : ''; + $image = wp_get_attachment_image( + $id, + array( $width, $height ), + false, + array( + 'class' => 'wp-block-jetpack-slideshow_image', + 'object-fit' => 'contain', + ) + ); + return sprintf( + '<div class="wp-block-jetpack-slideshow_slide"><figure>%s%s</figure></div>', + $image, + $figcaption + ); + }, + $ids + ); +} + +/** + * Generate array of bullets markup + * + * @param array $ids Array of image ids. + * @param int $block_ordinal The ordinal number of the block, used in unique ID. + * + * @return array Array of bullets markup. + */ +function jetpack_slideshow_block_bullets( $ids = array(), $block_ordinal = 0 ) { + $buttons = array_map( + function( $index ) { + $aria_label = sprintf( + /* translators: %d: Slide number. */ + __( 'Go to slide %d', 'jetpack' ), + absint( $index + 1 ) + ); + return sprintf( + '<button option="%d" class="swiper-pagination-bullet" tabindex="0" role="button" aria-label="%s" %s></button>', + absint( $index ), + esc_attr( $aria_label ), + 0 === $index ? 'selected' : '' + ); + }, + array_keys( $ids ) + ); + + return sprintf( + '<amp-selector id="wp-block-jetpack-slideshow__amp-pagination__%1$d" class="wp-block-jetpack-slideshow_pagination swiper-pagination swiper-pagination-bullets amp-pagination" on="select:wp-block-jetpack-slideshow__amp-carousel__%1$d.goToSlide(index=event.targetOption)" layout="container">%2$s</amp-selector>', + absint( $block_ordinal ), + implode( '', $buttons ) + ); +} + +/** + * Generate autoplay play/pause UI. + * + * @param int $block_ordinal The ordinal number of the block, used in unique ID. + * + * @return string Autoplay UI markup. + */ +function jetpack_slideshow_block_autoplay_ui( $block_ordinal = 0 ) { + $block_id = sprintf( + 'wp-block-jetpack-slideshow__%d', + absint( $block_ordinal ) + ); + $amp_carousel_id = sprintf( + 'wp-block-jetpack-slideshow__amp-carousel__%d', + absint( $block_ordinal ) + ); + $autoplay_pause = sprintf( + '<a aria-label="%s" class="wp-block-jetpack-slideshow_button-pause" role="button" on="tap:%s.toggleAutoplay(toggleOn=false),%s.toggleClass(class=wp-block-jetpack-slideshow__autoplay-playing,force=false)"></a>', + esc_attr__( 'Pause Slideshow', 'jetpack' ), + esc_attr( $amp_carousel_id ), + esc_attr( $block_id ) + ); + $autoplay_play = sprintf( + '<a aria-label="%s" class="wp-block-jetpack-slideshow_button-play" role="button" on="tap:%s.toggleAutoplay(toggleOn=true),%s.toggleClass(class=wp-block-jetpack-slideshow__autoplay-playing,force=true)"></a>', + esc_attr__( 'Play Slideshow', 'jetpack' ), + esc_attr( $amp_carousel_id ), + esc_attr( $block_id ) + ); + return $autoplay_pause . $autoplay_play; +} diff --git a/plugins/jetpack/extensions/blocks/slideshow/style.scss b/plugins/jetpack/extensions/blocks/slideshow/style.scss deleted file mode 100644 index c1d1fc50..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/style.scss +++ /dev/null @@ -1,165 +0,0 @@ -@import '../../shared/styles/gutenberg-colors.scss'; -@import '../../shared/styles/jetpack-variables.scss'; - -.wp-block-jetpack-slideshow { - margin-bottom: $jetpack-block-margin-bottom; - position: relative; - - .wp-block-jetpack-slideshow_container { - width: 100%; - overflow: hidden; - opacity: 0; - - &.wp-swiper-initialized { - opacity: 1; - } - - // High specifity to override theme styles - .wp-block-jetpack-slideshow_swiper-wrappper, - .wp-block-jetpack-slideshow_slide { - padding: 0; - margin: 0; - line-height: normal; - } - } - - .wp-block-jetpack-slideshow_slide { - background: rgba( 0, 0, 0, 0.1 ); - display: flex; - height: 100%; - width: 100%; - figure { - align-items: center; - display: flex; - height: 100%; - justify-content: center; - margin: 0; - position: relative; - width: 100%; - } - } - - .swiper-container-fade .wp-block-jetpack-slideshow_slide { - background: var( --color-neutral-0 ); - } - - .wp-block-jetpack-slideshow_image { - display: block; - height: auto; - max-height: 100%; - max-width: 100%; - width: auto; - object-fit: contain; - } - - .wp-block-jetpack-slideshow_button-prev, - .wp-block-jetpack-slideshow_button-next, - .wp-block-jetpack-slideshow_button-pause { - background-color: rgba( 0, 0, 0, 0.5 ); - background-position: center; - background-repeat: no-repeat; - background-size: 24px; - border: 0; - border-radius: 4px; - box-shadow: none; - height: 48px; - margin: -24px 0 0; - padding: 0; - transition: background-color 250ms; - width: 48px; - - &:focus, - &:hover { - background-color: rgba( 0, 0, 0, 0.75 ); - } - - &:focus { - outline: thin dotted $white; - outline-offset: -4px; - } - } - - &.swiper-container-rtl .swiper-button-prev.swiper-button-white, - &.swiper-container-rtl .wp-block-jetpack-slideshow_button-prev, - .swiper-button-next.swiper-button-white, - .wp-block-jetpack-slideshow_button-next { - background-image: url( "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M5.88 4.12L13.76 12l-7.88 7.88L8 22l10-10L8 2z' fill='white'/%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3C/svg%3E" ); - } - - &.swiper-container-rtl .swiper-button-next.swiper-button-white, - &.swiper-container-rtl .wp-block-jetpack-slideshow_button-next, - .swiper-button-prev.swiper-button-white, - .wp-block-jetpack-slideshow_button-prev { - background-image: url( "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M18 4.12L10.12 12 18 19.88 15.88 22l-10-10 10-10z' fill='white'/%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3C/svg%3E" ); - } - - .wp-block-jetpack-slideshow_button-pause { - background-image: url( "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M6 19h4V5H6v14zm8-14v14h4V5h-4z' fill='white'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E" ); - display: none; - margin-top: 0; - position: absolute; - right: 10px; - top: 10px; - z-index: 1; - } - - .wp-block-jetpack-slideshow_autoplay-paused .wp-block-jetpack-slideshow_button-pause { - background-image: url( "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M8 5v14l11-7z' fill='white'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E" ); - } - - &[data-autoplay='true'] .wp-block-jetpack-slideshow_button-pause { - display: block; - } - - .wp-block-jetpack-slideshow_caption.gallery-caption { - background-color: rgba( 0, 0, 0, 0.5 ); - box-sizing: border-box; - bottom: 0; - color: $white; - cursor: text; - left: 0; - margin: 0 !important; - padding: 0.75em; - position: absolute; - right: 0; - text-align: initial; - z-index: 1; - a { - color: inherit; - } - } - - .wp-block-jetpack-slideshow_pagination.swiper-pagination-bullets { - bottom: 0; - line-height: 24px; - padding: 10px 0 2px; - position: relative; - - .swiper-pagination-bullet { - background: currentColor; - color: currentColor; - height: 16px; - opacity: 0.5; - transform: scale( 0.75 ); - transition: opacity 250ms, transform 250ms; - vertical-align: top; - width: 16px; - - &:focus, - &:hover { - opacity: 1; - } - - &:focus { - outline: thin dotted; - outline-offset: 0; - } - } - - .swiper-pagination-bullet-active { - background-color: currentColor; - opacity: 1; - transform: scale( 1 ); - } - } -} diff --git a/plugins/jetpack/extensions/blocks/slideshow/swiper-callbacks.js b/plugins/jetpack/extensions/blocks/slideshow/swiper-callbacks.js deleted file mode 100644 index 2410f234..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/swiper-callbacks.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * External dependencies - */ -import { escapeHTML } from '@wordpress/escape-html'; -import { forEach } from 'lodash'; - -const SIXTEEN_BY_NINE = 16 / 9; -const MAX_HEIGHT_PERCENT_OF_WINDOW_HEIGHT = 0.8; -const SANITY_MAX_HEIGHT = 600; -const PAUSE_CLASS = 'wp-block-jetpack-slideshow_autoplay-paused'; - -function swiperInit( swiper ) { - swiperResize( swiper ); - swiperApplyAria( swiper ); - swiper.el - .querySelector( '.wp-block-jetpack-slideshow_button-pause' ) - .addEventListener( 'click', function() { - // Handle destroyed Swiper instances - if ( ! swiper.el ) { - return; - } - if ( swiper.el.classList.contains( PAUSE_CLASS ) ) { - swiper.el.classList.remove( PAUSE_CLASS ); - swiper.autoplay.start(); - this.setAttribute( 'aria-label', 'Pause Slideshow' ); - } else { - swiper.el.classList.add( PAUSE_CLASS ); - swiper.autoplay.stop(); - this.setAttribute( 'aria-label', 'Play Slideshow' ); - } - } ); -} - -function swiperResize( swiper ) { - if ( ! swiper || ! swiper.el ) { - return; - } - const img = swiper.el.querySelector( '.swiper-slide[data-swiper-slide-index="0"] img' ); - if ( ! img ) { - return; - } - const aspectRatio = img.clientWidth / img.clientHeight; - const sanityAspectRatio = Math.max( Math.min( aspectRatio, SIXTEEN_BY_NINE ), 1 ); - const sanityHeight = - typeof window !== 'undefined' - ? window.innerHeight * MAX_HEIGHT_PERCENT_OF_WINDOW_HEIGHT - : SANITY_MAX_HEIGHT; - const swiperHeight = Math.min( swiper.width / sanityAspectRatio, sanityHeight ); - const wrapperHeight = `${ Math.floor( swiperHeight ) }px`; - const buttonTop = `${ Math.floor( swiperHeight / 2 ) }px`; - - swiper.el.classList.add( 'wp-swiper-initialized' ); - swiper.wrapperEl.style.height = wrapperHeight; - swiper.el.querySelector( '.wp-block-jetpack-slideshow_button-prev' ).style.top = buttonTop; - swiper.el.querySelector( '.wp-block-jetpack-slideshow_button-next' ).style.top = buttonTop; -} - -function announceCurrentSlide( swiper ) { - const currentSlide = swiper.slides[ swiper.activeIndex ]; - if ( ! currentSlide ) { - return; - } - const figcaption = currentSlide.getElementsByTagName( 'FIGCAPTION' )[ 0 ]; - const img = currentSlide.getElementsByTagName( 'IMG' )[ 0 ]; - if ( swiper.a11y.liveRegion ) { - swiper.a11y.liveRegion[ 0 ].innerHTML = figcaption - ? figcaption.innerHTML - : escapeHTML( img.alt ); - } -} - -function swiperApplyAria( swiper ) { - forEach( swiper.slides, ( slide, index ) => { - slide.setAttribute( 'aria-hidden', index === swiper.activeIndex ? 'false' : 'true' ); - if ( index === swiper.activeIndex ) { - slide.setAttribute( 'tabindex', '-1' ); - } else { - slide.removeAttribute( 'tabindex' ); - } - } ); - announceCurrentSlide( swiper ); -} - -function swiperPaginationRender( swiper ) { - forEach( swiper.pagination.bullets, bullet => { - bullet.addEventListener( 'click', () => { - const currentSlide = swiper.slides[ swiper.realIndex ]; - setTimeout( () => { - currentSlide.focus(); - }, 500 ); - } ); - } ); -} - -export { swiperApplyAria, swiperInit, swiperPaginationRender, swiperResize }; diff --git a/plugins/jetpack/extensions/blocks/slideshow/transforms.js b/plugins/jetpack/extensions/blocks/slideshow/transforms.js deleted file mode 100644 index bcff28e5..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/transforms.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * External dependencies - */ -import { createBlock } from '@wordpress/blocks'; -import { filter } from 'lodash'; - -/** - * Filter valid images - * - * @param {array} images Array of image objects - * @return {array} Array of image objects which have id and url - */ -function getValidImages( images ) { - return filter( images, ( { id, url } ) => id && url ); -} - -const transforms = { - from: [ - { - type: 'block', - isMultiBlock: true, - blocks: [ 'core/image' ], - isMatch: images => getValidImages( images ).length > 0, - transform: images => { - const validImages = getValidImages( images ); - return createBlock( 'jetpack/slideshow', { - images: validImages.map( ( { alt, caption, id, url } ) => ( { - alt, - caption, - id, - url, - } ) ), - ids: validImages.map( ( { id } ) => id ), - } ); - }, - }, - { - type: 'block', - blocks: [ 'core/gallery', 'jetpack/tiled-gallery' ], - transform: ( { images } ) => { - const validImages = getValidImages( images ); - if ( validImages.length > 0 ) { - return createBlock( 'jetpack/slideshow', { - images: validImages.map( ( { alt, caption, id, url } ) => ( { - alt, - caption, - id, - url, - } ) ), - ids: validImages.map( ( { id } ) => id ), - } ); - } - return createBlock( 'jetpack/slideshow' ); - }, - }, - ], - to: [ - { - type: 'block', - blocks: [ 'core/gallery' ], - transform: ( { images, ids } ) => createBlock( 'core/gallery', { images, ids } ), - }, - { - type: 'block', - blocks: [ 'core/image' ], - transform: ( { images } ) => { - if ( images.length > 0 ) { - return images.map( ( { id, url, alt, caption } ) => - createBlock( 'core/image', { id, url, alt, caption } ) - ); - } - return createBlock( 'core/image' ); - }, - }, - ], -}; - -export default transforms; diff --git a/plugins/jetpack/extensions/blocks/slideshow/view.js b/plugins/jetpack/extensions/blocks/slideshow/view.js deleted file mode 100644 index 6d807897..00000000 --- a/plugins/jetpack/extensions/blocks/slideshow/view.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * External dependencies - */ -import { forEach } from 'lodash'; -import ResizeObserver from 'resize-observer-polyfill'; - -/** - * Internal dependencies - */ -import createSwiper from './create-swiper'; -import { - swiperApplyAria, - swiperInit, - swiperPaginationRender, - swiperResize, -} from './swiper-callbacks'; - -typeof window !== 'undefined' && - window.addEventListener( 'load', function() { - const slideshowBlocks = document.getElementsByClassName( 'wp-block-jetpack-slideshow' ); - forEach( slideshowBlocks, slideshowBlock => { - const { autoplay, delay, effect } = slideshowBlock.dataset; - const prefersReducedMotion = window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches; - const shouldAutoplay = autoplay && ! prefersReducedMotion; - const slideshowContainer = slideshowBlock.getElementsByClassName( 'swiper-container' )[ 0 ]; - let pendingRequestAnimationFrame = null; - createSwiper( - slideshowContainer, - { - autoplay: shouldAutoplay - ? { - delay: delay * 1000, - disableOnInteraction: false, - } - : false, - effect, - init: true, - initialSlide: 0, - loop: true, - keyboard: { - enabled: true, - onlyInViewport: true, - }, - }, - { - init: swiperInit, - imagesReady: swiperResize, - paginationRender: swiperPaginationRender, - transitionEnd: swiperApplyAria, - } - ) - .then( swiper => { - new ResizeObserver( () => { - if ( pendingRequestAnimationFrame ) { - cancelAnimationFrame( pendingRequestAnimationFrame ); - pendingRequestAnimationFrame = null; - } - pendingRequestAnimationFrame = requestAnimationFrame( () => { - swiperResize( swiper ); - swiper.update(); - } ); - } ).observe( swiper.el ); - } ) - .catch( () => { - slideshowBlock - .querySelector( '.wp-block-jetpack-slideshow_container' ) - .classList.add( 'wp-swiper-initialized' ); - } ); - } ); - } ); |