diff --git a/packages/block-library/src/embed/index.js b/packages/block-library/src/embed/index.js index 6b11992352bbc5..08713671b9d01c 100644 --- a/packages/block-library/src/embed/index.js +++ b/packages/block-library/src/embed/index.js @@ -3,7 +3,7 @@ */ import { parse } from 'url'; import { includes, kebabCase, toLower } from 'lodash'; -import classnames from 'classnames'; +import classnames from 'classnames/dedupe'; /** * WordPress dependencies @@ -43,6 +43,7 @@ export function getEmbedEdit( title, icon ) { this.setUrl = this.setUrl.bind( this ); this.maybeSwitchBlock = this.maybeSwitchBlock.bind( this ); this.setAttributesFromPreview = this.setAttributesFromPreview.bind( this ); + this.maybeSetAspectRatioClassName = this.maybeSetAspectRatioClassName.bind( this ); this.state = { editingURL: false, @@ -136,6 +137,58 @@ export function getEmbedEdit( title, icon ) { return false; } + /** + * Sets the appropriate CSS class to enforce an aspect ratio when the embed is resized + * if the HTML has an iframe with width and height set. + * + * @param {string} html The preview HTML that possibly contains an iframe with width and height set. + */ + maybeSetAspectRatioClassName( html ) { + const previewDom = document.createElement( 'div' ); + previewDom.innerHTML = html; + const iframe = previewDom.querySelector( 'iframe' ); + + if ( ! iframe ) { + return; + } + + if ( iframe.height && iframe.width ) { + const aspectRatio = ( iframe.width / iframe.height ).toFixed( 2 ); + let aspectRatioClassName; + + switch ( aspectRatio ) { + // Common video resolutions. + case '2.33': + aspectRatioClassName = 'wp-embed-aspect-21-9'; + break; + case '2.00': + aspectRatioClassName = 'wp-embed-aspect-18-9'; + break; + case '1.78': + aspectRatioClassName = 'wp-embed-aspect-16-9'; + break; + case '1.33': + aspectRatioClassName = 'wp-embed-aspect-4-3'; + break; + // Vertical video and instagram square video support. + case '1.00': + aspectRatioClassName = 'wp-embed-aspect-1-1'; + break; + case '0.56': + aspectRatioClassName = 'wp-embed-aspect-9-16'; + break; + case '0.50': + aspectRatioClassName = 'wp-embed-aspect-1-2'; + break; + } + + if ( aspectRatioClassName ) { + const className = classnames( this.props.attributes.className, 'wp-has-aspect-ratio', aspectRatioClassName ); + this.props.setAttributes( { className } ); + } + } + } + /*** * Sets block attributes based on the preview data. */ @@ -156,6 +209,8 @@ export function getEmbedEdit( title, icon ) { if ( html || 'photo' === type ) { setAttributes( { type, providerNameSlug } ); } + + this.maybeSetAspectRatioClassName( html ); } switchBackToURLInput() { @@ -218,6 +273,7 @@ export function getEmbedEdit( title, icon ) { const cannotPreview = includes( HOSTS_NO_PREVIEWS, parsedUrl.host.replace( /^www\./, '' ) ); // translators: %s: host providing embed content e.g: www.youtube.com const iframeTitle = sprintf( __( 'Embedded content from %s' ), parsedUrl.host ); + const sandboxClassnames = classnames( type, className ); const embedWrapper = 'wp-embed' === type ? (
); @@ -257,6 +313,24 @@ export function getEmbedEdit( title, icon ) { }; } +const embedAttributes = { + url: { + type: 'string', + }, + caption: { + type: 'array', + source: 'children', + selector: 'figcaption', + default: [], + }, + type: { + type: 'string', + }, + providerNameSlug: { + type: 'string', + }, +}; + function getEmbedBlockSettings( { title, description, icon, category = 'embed', transforms, keywords = [] } ) { // translators: %s: Name of service (e.g. VideoPress, YouTube) const blockDescription = description || sprintf( __( 'Add a block that displays content pulled from other sites, like Twitter, Instagram or YouTube.' ), title ); @@ -266,23 +340,7 @@ function getEmbedBlockSettings( { title, description, icon, category = 'embed', icon, category, keywords, - attributes: { - url: { - type: 'string', - }, - caption: { - type: 'array', - source: 'children', - selector: 'figcaption', - default: [], - }, - type: { - type: 'string', - }, - providerNameSlug: { - type: 'string', - }, - }, + attributes: embedAttributes, supports: { align: true, @@ -320,11 +378,38 @@ function getEmbedBlockSettings( { title, description, icon, category = 'embed', return (
- { `\n${ url }\n` /* URL needs to be on its own line. */ } +
+ { `\n${ url }\n` /* URL needs to be on its own line. */ } +
{ ! RichText.isEmpty( caption ) && }
); }, + + deprecated: [ + { + attributes: embedAttributes, + save( { attributes } ) { + const { url, caption, type, providerNameSlug } = attributes; + + if ( ! url ) { + return null; + } + + const embedClassName = classnames( 'wp-block-embed', { + [ `is-type-${ type }` ]: type, + [ `is-provider-${ providerNameSlug }` ]: providerNameSlug, + } ); + + return ( +
+ { `\n${ url }\n` /* URL needs to be on its own line. */ } + { ! RichText.isEmpty( caption ) && } +
+ ); + }, + }, + ], }; } diff --git a/packages/block-library/src/embed/style.scss b/packages/block-library/src/embed/style.scss index d6a8b0c81bcc5d..782cabefe8e705 100644 --- a/packages/block-library/src/embed/style.scss +++ b/packages/block-library/src/embed/style.scss @@ -14,4 +14,59 @@ figcaption { @include caption-style(); } + + // Add responsiveness to common aspect ratios. + &.wp-embed-aspect-21-9 .wp-block-embed__wrapper, + &.wp-embed-aspect-18-9 .wp-block-embed__wrapper, + &.wp-embed-aspect-16-9 .wp-block-embed__wrapper, + &.wp-embed-aspect-4-3 .wp-block-embed__wrapper, + &.wp-embed-aspect-1-1 .wp-block-embed__wrapper, + &.wp-embed-aspect-9-16 .wp-block-embed__wrapper, + &.wp-embed-aspect-1-2 .wp-block-embed__wrapper { + position: relative; + + &::before { + content: ""; + display: block; + padding-top: 50%; // Default to 2:1 aspect ratio. + } + + iframe { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + } + } + + &.wp-embed-aspect-21-9 .wp-block-embed__wrapper::before { + padding-top: 42.85%; // 9 / 21 * 100 + } + + &.wp-embed-aspect-18-9 .wp-block-embed__wrapper::before { + padding-top: 50%; // 9 / 18 * 100 + } + + &.wp-embed-aspect-16-9 .wp-block-embed__wrapper::before { + padding-top: 56.25%; // 9 / 16 * 100 + } + + &.wp-embed-aspect-4-3 .wp-block-embed__wrapper::before { + padding-top: 75%; // 3 / 4 * 100 + } + + &.wp-embed-aspect-1-1 .wp-block-embed__wrapper::before { + padding-top: 100%; // 1 / 1 * 100 + } + + &.wp-embed-aspect-9-6 .wp-block-embed__wrapper::before { + padding-top: 66.66%; // 6 / 9 * 100 + } + + &.wp-embed-aspect-1-2 .wp-block-embed__wrapper::before { + padding-top: 200%; // 2 / 1 * 100 + } } diff --git a/packages/components/src/sandbox/index.js b/packages/components/src/sandbox/index.js index bbc5097aa46246..82ecc72182d1f9 100644 --- a/packages/components/src/sandbox/index.js +++ b/packages/components/src/sandbox/index.js @@ -79,8 +79,6 @@ class Sandbox extends Component { const observeAndResizeJS = ` ( function() { var observer; - var aspectRatio = false; - var iframe = false; if ( ! window.MutationObserver || ! document.body || ! window.parent ) { return; @@ -88,22 +86,11 @@ class Sandbox extends Component { function sendResize() { var clientBoundingRect = document.body.getBoundingClientRect(); - var height = aspectRatio ? Math.ceil( clientBoundingRect.width / aspectRatio ) : clientBoundingRect.height; - - if ( iframe && aspectRatio ) { - // This is embedded content delivered in an iframe with a fixed aspect ratio, - // so set the height correctly and stop processing. The DOM mutation will trigger - // another event and the resize message will get posted. - if ( iframe.height != height ) { - iframe.height = height; - return; - } - } window.parent.postMessage( { action: 'resize', width: clientBoundingRect.width, - height: height, + height: clientBoundingRect.height, }, '*' ); } @@ -139,20 +126,6 @@ class Sandbox extends Component { document.body.style.width = '100%'; document.body.setAttribute( 'data-resizable-iframe-connected', '' ); - // Make embedded content in an iframe with a fixed size responsive, - // keeping the correct aspect ratio. - var potentialIframe = document.body.children[0]; - if ( 'DIV' === potentialIframe.tagName || 'SPAN' === potentialIframe.tagName ) { - potentialIframe = potentialIframe.children[0]; - } - if ( potentialIframe && 'IFRAME' === potentialIframe.tagName ) { - if ( potentialIframe.width ) { - iframe = potentialIframe; - aspectRatio = potentialIframe.width / potentialIframe.height; - potentialIframe.width = '100%'; - } - } - sendResize(); // Resize events can change the width of elements with 100% width, but we don't @@ -164,9 +137,18 @@ class Sandbox extends Component { body { margin: 0; } + html, + body, + body > div, body > div > iframe { width: 100%; } + html.wp-has-aspect-ratio, + body.wp-has-aspect-ratio, + body.wp-has-aspect-ratio > div, + body.wp-has-aspect-ratio > div > iframe { + height: 100%; + } body > div > * { margin-top: 0 !important; /* has to have !important to override inline styles */ margin-bottom: 0 !important; @@ -176,7 +158,7 @@ class Sandbox extends Component { // put the html snippet into a html document, and then write it to the iframe's document // we can use this in the future to inject custom styles or scripts const htmlDoc = ( - + { this.props.title }