diff --git a/assets/css/amp-default.css b/assets/css/amp-default.css index c06e0895dbe..2c837e2f469 100644 --- a/assets/css/amp-default.css +++ b/assets/css/amp-default.css @@ -4,7 +4,7 @@ object-fit: contain; } -.entry__content amp-fit-text blockquote, +amp-fit-text blockquote, amp-fit-text h1, amp-fit-text h2, amp-fit-text h3, @@ -12,4 +12,5 @@ amp-fit-text h4, amp-fit-text h5, amp-fit-text h6 { font-size: inherit; -} \ No newline at end of file +} + diff --git a/assets/js/amp-editor-blocks.js b/assets/js/amp-editor-blocks.js index aebf1da26f6..7b9128f5d3f 100644 --- a/assets/js/amp-editor-blocks.js +++ b/assets/js/amp-editor-blocks.js @@ -97,14 +97,22 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars fontSizes: { small: 14, larger: 48 - } - } + }, + ampPanelLabel: __( 'AMP Settings' ) + }, + hasThemeSupport: true }; /** - * Set data, add filters. + * Add filters. + * + * @param {Object} data Data. */ - component.boot = function boot() { + component.boot = function boot( data ) { + if ( data ) { + _.extend( component.data, data ); + } + wp.hooks.addFilter( 'blocks.registerBlockType', 'ampEditorBlocks/addAttributes', component.addAMPAttributes ); wp.hooks.addFilter( 'blocks.getSaveElement', 'ampEditorBlocks/filterSave', component.filterBlocksSave ); wp.hooks.addFilter( 'blocks.BlockEdit', 'ampEditorBlocks/filterEdit', component.filterBlocksEdit ); @@ -159,6 +167,12 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars component.addAMPExtraProps = function addAMPExtraProps( props, blockType, attributes ) { var ampAttributes = {}; + // Shortcode props are handled differently. + if ( 'core/shortcode' === blockType.name ) { + return props; + } + + // AMP blocks handle layout and other props on their own. if ( 'amp/' === blockType.name.substr( 0, 4 ) ) { return props; } @@ -169,6 +183,12 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars if ( attributes.ampNoLoading ) { ampAttributes[ 'data-amp-noloading' ] = attributes.ampNoLoading; } + if ( attributes.ampLightbox ) { + ampAttributes[ 'data-amp-lightbox' ] = attributes.ampLightbox; + } + if ( attributes.ampCarousel ) { + ampAttributes[ 'data-amp-carousel' ] = attributes.ampCarousel; + } return _.extend( ampAttributes, props ); }; @@ -181,14 +201,27 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars * @return {Object} Settings. */ component.addAMPAttributes = function addAMPAttributes( settings, name ) { - // Gallery settings for shortcode. - if ( 'core/shortcode' === name ) { + // AMP Carousel settings. + if ( 'core/shortcode' === name || 'core/gallery' === name ) { if ( ! settings.attributes ) { settings.attributes = {}; } settings.attributes.ampCarousel = { type: 'boolean' }; + settings.attributes.ampLightbox = { + type: 'boolean' + }; + } + + // Add AMP Lightbox settings. + if ( 'core/image' === name ) { + if ( ! settings.attributes ) { + settings.attributes = {}; + } + settings.attributes.ampLightbox = { + type: 'boolean' + }; } // Fit-text for text blocks. @@ -256,6 +289,15 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars ampLayout = attributes.ampLayout; if ( 'core/shortcode' === name ) { + // Lets remove amp-carousel from edit view. + if ( component.hasGalleryShortcodeCarouselAttribute( attributes.text || '' ) ) { + props.setAttributes( { text: component.removeAmpCarouselFromShortcodeAtts( attributes.text ) } ); + } + // Lets remove amp-lightbox from edit view. + if ( component.hasGalleryShortcodeLightboxAttribute( attributes.text || '' ) ) { + props.setAttributes( { text: component.removeAmpLightboxFromShortcodeAtts( attributes.text ) } ); + } + inspectorControls = component.setUpShortcodeInspectorControls( props ); if ( '' === inspectorControls ) { // Return original. @@ -265,6 +307,10 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars }, props ) ) ]; } + } else if ( 'core/gallery' === name ) { + inspectorControls = component.setUpGalleryInpsectorControls( props ); + } else if ( 'core/image' === name ) { + inspectorControls = component.setUpImageInpsectorControls( props ); } else if ( -1 !== component.data.mediaBlocks.indexOf( name ) || 0 === name.indexOf( 'core-embed/' ) ) { inspectorControls = component.setUpInspectorControls( props ); } else if ( -1 !== component.data.textBlocks.indexOf( name ) ) { @@ -300,6 +346,10 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars if ( ! attributes.height ) { props.setAttributes( { height: component.data.defaultHeight } ); } + // Lightbox doesn't work with fixed height, so unset it. + if ( attributes.ampLightbox ) { + props.setAttributes( { ampLightbox: false } ); + } break; case 'fixed': @@ -320,47 +370,72 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars * @return {Object|Element|*|{$$typeof, type, key, ref, props, _owner}} Inspector Controls. */ component.setUpInspectorControls = function setUpInspectorControls( props ) { - var ampLayout = props.attributes.ampLayout, - ampNoLoading = props.attributes.ampNoLoading, - isSelected = props.isSelected, - name = props.name, + var isSelected = props.isSelected, el = wp.element.createElement, InspectorControls = wp.editor.InspectorControls, - SelectControl = wp.components.SelectControl, - ToggleControl = wp.components.ToggleControl, - PanelBody = wp.components.PanelBody, - label = __( 'AMP Layout', 'amp' ); - - if ( 'core/image' === name ) { - label = __( 'AMP Layout (modifies width/height)', 'amp' ); - } + PanelBody = wp.components.PanelBody; return isSelected && ( el( InspectorControls, { key: 'inspector' }, - el( PanelBody, { title: __( 'AMP Settings', 'amp' ) }, - el( SelectControl, { - label: label, - value: ampLayout, - options: component.getLayoutOptions( name ), - onChange: function( value ) { - props.setAttributes( { ampLayout: value } ); - if ( 'core/image' === props.name ) { - component.setImageBlockLayoutAttributes( props, value ); - } - } - } ), - el( ToggleControl, { - label: __( 'AMP loading indicator disabled', 'amp' ), - checked: ampNoLoading, - onChange: function() { - props.setAttributes( { ampNoLoading: ! ampNoLoading } ); - } - } ) + el( PanelBody, { title: component.data.ampPanelLabel }, + component.getAmpLayoutControl( props ), + component.getAmpNoloadingToggle( props ) ) ) ); }; + /** + * Get AMP Layout select control. + * + * @param {Object} props Props. + * @return {Object} Element. + */ + component.getAmpLayoutControl = function getAmpLayoutControl( props ) { + var ampLayout = props.attributes.ampLayout, + el = wp.element.createElement, + SelectControl = wp.components.SelectControl, + name = props.name, + label = __( 'AMP Layout' ); + + if ( 'core/image' === name ) { + label = __( 'AMP Layout (modifies width/height)' ); + } + + return el( SelectControl, { + label: label, + value: ampLayout, + options: component.getLayoutOptions( name ), + onChange: function( value ) { + props.setAttributes( { ampLayout: value } ); + if ( 'core/image' === props.name ) { + component.setImageBlockLayoutAttributes( props, value ); + } + } + } ); + }; + + /** + * Get AMP Noloading toggle control. + * + * @param {Object} props Props. + * @return {Object} Element. + */ + component.getAmpNoloadingToggle = function getAmpNoloadingToggle( props ) { + var ampNoLoading = props.attributes.ampNoLoading, + el = wp.element.createElement, + ToggleControl = wp.components.ToggleControl, + label = __( 'AMP Noloading' ); + + return el( ToggleControl, { + label: label, + checked: ampNoLoading, + onChange: function() { + props.setAttributes( { ampNoLoading: ! ampNoLoading } ); + } + } ); + }; + /** * Setup inspector controls for text blocks. * @@ -495,29 +570,20 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars * Adds ampCarousel attribute in case of gallery shortcode. * * @param {Object} props Props. - * @return {*} Inspector controls. + * @return {Object} Inspector controls. */ component.setUpShortcodeInspectorControls = function setUpShortcodeInspectorControls( props ) { - var ampCarousel = props.attributes.ampCarousel, - isSelected = props.isSelected, + var isSelected = props.isSelected, el = wp.element.createElement, InspectorControls = wp.editor.InspectorControls, - ToggleControl = wp.components.ToggleControl, - PanelBody = wp.components.PanelBody, - toggleControl; + PanelBody = wp.components.PanelBody; if ( component.isGalleryShortcode( props.attributes ) ) { - toggleControl = el( ToggleControl, { - label: __( 'Display as AMP carousel', 'amp' ), - checked: ampCarousel, - onChange: function() { - props.setAttributes( { ampCarousel: ! ampCarousel } ); - } - } ); return isSelected && ( el( InspectorControls, { key: 'inspector' }, - el( PanelBody, { title: __( 'AMP Settings', 'amp' ) }, - toggleControl + el( PanelBody, { title: component.data.ampPanelLabel }, + component.data.hasThemeSupport && component.getAmpCarouselToggle( props ), + component.getAmpLightboxToggle( props ) ) ) ); @@ -526,51 +592,162 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars return ''; }; + /** + * Get AMP Lightbox toggle control. + * + * @param {Object} props Props. + * @return {Object} Element. + */ + component.getAmpLightboxToggle = function getAmpLightboxToggle( props ) { + var ampLightbox = props.attributes.ampLightbox, + el = wp.element.createElement, + ToggleControl = wp.components.ToggleControl, + label = __( 'Add lightbox effect' ); + + return el( ToggleControl, { + label: label, + checked: ampLightbox, + onChange: function( nextValue ) { + props.setAttributes( { ampLightbox: ! ampLightbox } ); + if ( nextValue ) { + // Lightbox doesn't work with fixed height, so change. + if ( 'fixed-height' === props.attributes.ampLayout ) { + props.setAttributes( { ampLayout: 'fixed' } ); + } + // In case of lightbox set linking images to 'none'. + if ( props.attributes.linkTo && 'none' !== props.attributes.linkTo ) { + props.setAttributes( { linkTo: 'none' } ); + } + } + } + } ); + }; + + /** + * Get AMP Carousel toggle control. + * + * @param {Object} props Props. + * @return {Object} Element. + */ + component.getAmpCarouselToggle = function getAmpCarouselToggle( props ) { + var ampCarousel = props.attributes.ampCarousel, + el = wp.element.createElement, + ToggleControl = wp.components.ToggleControl, + label = __( 'Display as AMP carousel' ); + + return el( ToggleControl, { + label: label, + checked: ampCarousel, + onChange: function() { + props.setAttributes( { ampCarousel: ! ampCarousel } ); + } + } ); + }; + + /** + * Set up inspector controls for Image block. + * + * @param {Object} props Props. + * @return {Object} Inspector Controls. + */ + component.setUpImageInpsectorControls = function setUpImageInpsectorControls( props ) { + var isSelected = props.isSelected, + el = wp.element.createElement, + InspectorControls = wp.editor.InspectorControls, + PanelBody = wp.components.PanelBody; + + return isSelected && ( + el( InspectorControls, { key: 'inspector' }, + el( PanelBody, { title: component.data.ampPanelLabel }, + component.getAmpLayoutControl( props ), + component.getAmpNoloadingToggle( props ), + component.getAmpLightboxToggle( props ) + ) + ) + ); + }; + + /** + * Set up inspector controls for Gallery block. + * Adds ampCarousel attribute for displaying the output as amp-carousel. + * + * @param {Object} props Props. + * @return {Object} Inspector controls. + */ + component.setUpGalleryInpsectorControls = function setUpGalleryInpsectorControls( props ) { + var isSelected = props.isSelected, + el = wp.element.createElement, + InspectorControls = wp.editor.InspectorControls, + PanelBody = wp.components.PanelBody; + + return isSelected && ( + el( InspectorControls, { key: 'inspector' }, + el( PanelBody, { title: component.data.ampPanelLabel }, + component.data.hasThemeSupport && component.getAmpCarouselToggle( props ), + component.getAmpLightboxToggle( props ) + ) + ) + ); + }; + /** * Filters blocks' save function. * * @param {Object} element Element. * @param {string} blockType Block type. * @param {Object} attributes Attributes. - * @return {*} Output element. + * @return {Object} Output element. */ component.filterBlocksSave = function filterBlocksSave( element, blockType, attributes ) { - var text, + var text = attributes.text || '', fitTextProps = { layout: 'fixed-height', children: element }; if ( 'core/shortcode' === blockType.name && component.isGalleryShortcode( attributes ) ) { + if ( ! attributes.ampLightbox ) { + if ( component.hasGalleryShortcodeLightboxAttribute( attributes.text || '' ) ) { + text = component.removeAmpLightboxFromShortcodeAtts( attributes.text ); + } + } if ( attributes.ampCarousel ) { - // If the text contains amp-carousel, lets remove it. - if ( component.hasGalleryShortcodeCarouselAttribute( attributes.text || '' ) ) { - text = component.removeAmpCarouselFromShortcodeAtts( attributes.text ); - - return wp.element.createElement( - wp.element.RawHTML, - {}, - text - ); + // If the text contains amp-carousel or amp-lightbox, lets remove it. + if ( component.hasGalleryShortcodeCarouselAttribute( text ) ) { + text = component.removeAmpCarouselFromShortcodeAtts( text ); } - // Else lets return original. - return element; - } + // If lightbox is not set, we can return here. + if ( ! attributes.ampLightbox ) { + if ( attributes.text !== text ) { + return wp.element.createElement( + wp.element.RawHTML, + {}, + text + ); + } - // If the text already contains amp-carousel, return original. - if ( component.hasGalleryShortcodeCarouselAttribute( attributes.text || '' ) ) { - return element; + // Else lets return original. + return element; + } + } else if ( ! component.hasGalleryShortcodeCarouselAttribute( attributes.text || '' ) ) { + // Add amp-carousel=false attribut to the shortcode. + text = attributes.text.replace( '[gallery', '[gallery amp-carousel=false' ); + } else { + text = attributes.text; } - // Add amp-carousel=false attribut to the shortcode. - text = attributes.text.replace( '[gallery', '[gallery amp-carousel=false' ); + if ( attributes.ampLightbox && ! component.hasGalleryShortcodeLightboxAttribute( text ) ) { + text = text.replace( '[gallery', '[gallery amp-lightbox=true' ); + } - return wp.element.createElement( - wp.element.RawHTML, - {}, - text - ); + if ( attributes.text !== text ) { + return wp.element.createElement( + wp.element.RawHTML, + {}, + text + ); + } } else if ( -1 !== component.data.textBlocks.indexOf( blockType.name ) && attributes.ampFitText ) { if ( attributes.minFont ) { fitTextProps[ 'min-font-size' ] = attributes.minFont; @@ -586,6 +763,26 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars return element; }; + /** + * Check if AMP Lightbox is set. + * + * @param {Object} attributes Attributes. + * @return {boolean} If is set. + */ + component.hasAmpLightboxSet = function hasAmpLightboxSet( attributes ) { + return attributes.ampLightbox && false !== attributes.ampLightbox; + }; + + /** + * Check if AMP Carousel is set. + * + * @param {Object} attributes Attributes. + * @return {boolean} If is set. + */ + component.hasAmpCarouselSet = function hasAmpCarouselSet( attributes ) { + return attributes.ampCarousel && false !== attributes.ampCarousel; + }; + /** * Check if AMP NoLoading is set. * @@ -616,16 +813,36 @@ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars return shortcode.replace( ' amp-carousel=false', '' ); }; + /** + * Removes amp-lightbox=true from attributes. + * + * @param {string} shortcode Shortcode text. + * @return {string} Modified shortcode. + */ + component.removeAmpLightboxFromShortcodeAtts = function removeAmpLightboxFromShortcodeAtts( shortcode ) { + return shortcode.replace( ' amp-lightbox=true', '' ); + }; + /** * Check if shortcode includes amp-carousel attribute. * * @param {string} text Shortcode. * @return {boolean} If has amp-carousel. */ - component.hasGalleryShortcodeCarouselAttribute = function galleryShortcodeHasCarouselAttribute( text ) { + component.hasGalleryShortcodeCarouselAttribute = function hasGalleryShortcodeCarouselAttribute( text ) { return -1 !== text.indexOf( 'amp-carousel=false' ); }; + /** + * Check if shortcode includes amp-lightbox attribute. + * + * @param {string} text Shortcode. + * @return {boolean} If has amp-lightbox. + */ + component.hasGalleryShortcodeLightboxAttribute = function hasGalleryShortcodeLightboxAttribute( text ) { + return -1 !== text.indexOf( 'amp-lightbox=true' ); + }; + /** * Check if shortcode is gallery shortcode. * diff --git a/includes/admin/class-amp-editor-blocks.php b/includes/admin/class-amp-editor-blocks.php index 5c046964285..a93e396b169 100644 --- a/includes/admin/class-amp-editor-blocks.php +++ b/includes/admin/class-amp-editor-blocks.php @@ -63,8 +63,10 @@ public function whitelist_block_atts_in_wp_kses_allowed_html( $tags, $context ) } foreach ( $tags as &$tag ) { - $tag['data-amp-layout'] = true; - $tag['data-amp-noloading'] = true; + $tag['data-amp-layout'] = true; + $tag['data-amp-noloading'] = true; + $tag['data-amp-lightbox'] = true; + $tag['data-close-button-aria-label'] = true; } foreach ( $this->amp_blocks as $amp_block ) { @@ -132,14 +134,16 @@ public function enqueue_block_editor_assets() { wp_enqueue_script( 'amp-editor-blocks', amp_get_asset_url( 'js/amp-editor-blocks.js' ), - array( 'underscore', 'wp-hooks', 'wp-i18n' ), + array( 'underscore', 'wp-hooks', 'wp-i18n', 'wp-components' ), AMP__VERSION, true ); wp_add_inline_script( 'amp-editor-blocks', - 'ampEditorBlocks.boot();' + sprintf( 'ampEditorBlocks.boot( %s );', wp_json_encode( array( + 'hasThemeSupport' => current_theme_supports( 'amp' ), + ) ) ) ); } diff --git a/includes/amp-helper-functions.php b/includes/amp-helper-functions.php index 7a72a9eaec1..066cf60f7d8 100644 --- a/includes/amp-helper-functions.php +++ b/includes/amp-helper-functions.php @@ -485,11 +485,14 @@ function amp_get_content_sanitizers( $post = null ) { 'AMP_Comments_Sanitizer' => array(), 'AMP_Video_Sanitizer' => array(), 'AMP_Audio_Sanitizer' => array(), - 'AMP_Block_Sanitizer' => array(), 'AMP_Playbuzz_Sanitizer' => array(), 'AMP_Iframe_Sanitizer' => array( 'add_placeholder' => true, ), + 'AMP_Gallery_Block_Sanitizer' => array( // Note: Gallery block sanitizer must come after image sanitizers since itÅ› logic is using the already sanitized images. + 'carousel_required' => ! current_theme_supports( 'amp' ), // For back-compat. + ), + 'AMP_Block_Sanitizer' => array(), // Note: Block sanitizer must come after embed / media sanitizers since it's logic is using the already sanitized content. 'AMP_Style_Sanitizer' => array(), 'AMP_Tag_And_Attribute_Sanitizer' => array(), // Note: This whitelist sanitizer must come at the end to clean up any remaining issues the other sanitizers didn't catch. ), diff --git a/includes/class-amp-autoloader.php b/includes/class-amp-autoloader.php index 1a11d9ba1d0..416b8e42081 100644 --- a/includes/class-amp-autoloader.php +++ b/includes/class-amp-autoloader.php @@ -73,6 +73,7 @@ class AMP_Autoloader { 'AMP_Base_Sanitizer' => 'includes/sanitizers/class-amp-base-sanitizer', 'AMP_Blacklist_Sanitizer' => 'includes/sanitizers/class-amp-blacklist-sanitizer', 'AMP_Block_Sanitizer' => 'includes/sanitizers/class-amp-block-sanitizer', + 'AMP_Gallery_Block_Sanitizer' => 'includes/sanitizers/class-amp-gallery-block-sanitizer', 'AMP_Iframe_Sanitizer' => 'includes/sanitizers/class-amp-iframe-sanitizer', 'AMP_Img_Sanitizer' => 'includes/sanitizers/class-amp-img-sanitizer', 'AMP_Comments_Sanitizer' => 'includes/sanitizers/class-amp-comments-sanitizer', diff --git a/includes/embeds/class-amp-gallery-embed.php b/includes/embeds/class-amp-gallery-embed.php index 87a80f30bbf..a2c94ff3b7d 100644 --- a/includes/embeds/class-amp-gallery-embed.php +++ b/includes/embeds/class-amp-gallery-embed.php @@ -51,6 +51,10 @@ public function shortcode( $attr ) { 'link' => 'none', ), $attr, 'gallery' ); + if ( ! empty( $attr['amp-lightbox'] ) ) { + $atts['lightbox'] = filter_var( $attr['amp-lightbox'], FILTER_VALIDATE_BOOLEAN ); + } + $id = intval( $atts['id'] ); if ( ! empty( $atts['include'] ) ) { @@ -99,10 +103,12 @@ public function shortcode( $attr ) { } $href = null; - if ( ! empty( $atts['link'] ) && 'file' === $atts['link'] ) { - $href = $url; - } elseif ( ! empty( $atts['link'] ) && 'post' === $atts['link'] ) { - $href = get_attachment_link( $attachment_id ); + if ( empty( $atts['lightbox'] ) ) { + if ( ! empty( $atts['link'] ) && 'file' === $atts['link'] ) { + $href = $url; + } elseif ( ! empty( $atts['link'] ) && 'post' === $atts['link'] ) { + $href = get_attachment_link( $attachment_id ); + } } $urls[] = array( @@ -113,9 +119,24 @@ public function shortcode( $attr ) { ); } - return $this->render( array( + $args = array( 'images' => $urls, - ) ); + ); + if ( ! empty( $atts['lightbox'] ) ) { + $args['lightbox'] = true; + $lightbox_tag = AMP_HTML_Utils::build_tag( + 'amp-image-lightbox', + array( + 'id' => AMP_Base_Sanitizer::AMP_IMAGE_LIGHTBOX_ID, + 'layout' => 'nodisplay', + 'data-close-button-aria-label' => __( 'Close', 'amp' ), + ) + ); + /* We need to add lightbox tag, too. @todo Could there be a better alternative for this? */ + return $this->render( $args ) . $lightbox_tag; + } + + return $this->render( $args ); } /** @@ -129,7 +150,15 @@ public function shortcode( $attr ) { * @return string $html Markup for the gallery. */ public function maybe_override_gallery( $html, $attributes ) { + $is_lightbox = isset( $attributes['amp-lightbox'] ) && true === filter_var( $attributes['amp-lightbox'], FILTER_VALIDATE_BOOLEAN ); if ( isset( $attributes['amp-carousel'] ) && false === filter_var( $attributes['amp-carousel'], FILTER_VALIDATE_BOOLEAN ) ) { + if ( true === $is_lightbox ) { + remove_filter( 'post_gallery', array( $this, 'maybe_override_gallery' ), 10 ); + $attributes['link'] = 'none'; + $html = '