Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resizable iframes #2340

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ assets/css/*-compiled-rtl.css
assets/css/*.map
assets/js/*.js
!assets/js/amp-service-worker-runtime-precaching.js
!assets/js/wp-embed-template.js
assets/js/*.map
built
/amphtml
1 change: 1 addition & 0 deletions amp.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ function amp_init() {
add_action( 'wp_loaded', 'amp_story_templates' );
add_action( 'wp_loaded', 'amp_add_options_menu' );
add_action( 'wp_loaded', 'amp_admin_pointer' );
add_action( 'wp_loaded', 'amp_wordpress_embed_templates' );
add_action( 'parse_query', 'amp_correct_query_when_is_front_page' );
add_action( 'admin_bar_menu', 'amp_add_admin_bar_view_link', 100 );

Expand Down
234 changes: 234 additions & 0 deletions assets/js/wp-embed-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/**
* Customized version of wp-includes/js/wp-embed-template.js that sends
* AMP-compatible resize messages.
*/

(function ( window, document ) {
'use strict';

var supportedBrowser = ( document.querySelector && window.addEventListener ),
loaded = false,
secret,
secretTimeout,
resizing;

function sendEmbedMessage( message, value ) {
window.parent.postMessage( {
message: message,
value: value,
secret: secret
}, '*' );
}

function sendAmpMessage( type, key, value ) {
var message = {
sentinel: 'amp',
type: type,
};

if ( key && value ) {
message[ key ] = value;
}

window.parent.postMessage( message, '*' );
}

function onLoad() {
if ( loaded ) {
return;
}
loaded = true;

var share_dialog = document.querySelector( '.wp-embed-share-dialog' ),
share_dialog_open = document.querySelector( '.wp-embed-share-dialog-open' ),
share_dialog_close = document.querySelector( '.wp-embed-share-dialog-close' ),
share_input = document.querySelectorAll( '.wp-embed-share-input' ),
share_dialog_tabs = document.querySelectorAll( '.wp-embed-share-tab-button button' ),
featured_image = document.querySelector( '.wp-embed-featured-image img' ),
i;

if ( share_input ) {
for ( i = 0; i < share_input.length; i++ ) {
share_input[ i ].addEventListener( 'click', function ( e ) {
e.target.select();
} );
}
}

function openSharingDialog() {
share_dialog.className = share_dialog.className.replace( 'hidden', '' );
// Initial focus should go on the currently selected tab in the dialog.
document.querySelector( '.wp-embed-share-tab-button [aria-selected="true"]' ).focus();
}

function closeSharingDialog() {
share_dialog.className += ' hidden';
document.querySelector( '.wp-embed-share-dialog-open' ).focus();
}

if ( share_dialog_open ) {
share_dialog_open.addEventListener( 'click', function () {
openSharingDialog();
} );
}

if ( share_dialog_close ) {
share_dialog_close.addEventListener( 'click', function () {
closeSharingDialog();
} );
}

function shareClickHandler( e ) {
var currentTab = document.querySelector( '.wp-embed-share-tab-button [aria-selected="true"]' );
currentTab.setAttribute( 'aria-selected', 'false' );
document.querySelector( '#' + currentTab.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'true' );

e.target.setAttribute( 'aria-selected', 'true' );
document.querySelector( '#' + e.target.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'false' );
}

function shareKeyHandler( e ) {
var target = e.target,
previousSibling = target.parentElement.previousElementSibling,
nextSibling = target.parentElement.nextElementSibling,
newTab, newTabChild;

if ( 37 === e.keyCode ) {
newTab = previousSibling;
} else if ( 39 === e.keyCode ) {
newTab = nextSibling;
} else {
return false;
}

if ( 'rtl' === document.documentElement.getAttribute( 'dir' ) ) {
newTab = ( newTab === previousSibling ) ? nextSibling : previousSibling;
}

if ( newTab ) {
newTabChild = newTab.firstElementChild;

target.setAttribute( 'tabindex', '-1' );
target.setAttribute( 'aria-selected', false );
document.querySelector( '#' + target.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'true' );

newTabChild.setAttribute( 'tabindex', '0' );
newTabChild.setAttribute( 'aria-selected', 'true' );
newTabChild.focus();
document.querySelector( '#' + newTabChild.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'false' );
}
}

if ( share_dialog_tabs ) {
for ( i = 0; i < share_dialog_tabs.length; i++ ) {
share_dialog_tabs[ i ].addEventListener( 'click', shareClickHandler );

share_dialog_tabs[ i ].addEventListener( 'keydown', shareKeyHandler );
}
}

document.addEventListener( 'keydown', function ( e ) {
if ( 27 === e.keyCode && -1 === share_dialog.className.indexOf( 'hidden' ) ) {
closeSharingDialog();
} else if ( 9 === e.keyCode ) {
constrainTabbing( e );
}
}, false );

function constrainTabbing( e ) {
// Need to re-get the selected tab each time.
var firstFocusable = document.querySelector( '.wp-embed-share-tab-button [aria-selected="true"]' );

if ( share_dialog_close === e.target && ! e.shiftKey ) {
firstFocusable.focus();
e.preventDefault();
} else if ( firstFocusable === e.target && e.shiftKey ) {
share_dialog_close.focus();
e.preventDefault();
}
}

if ( window.self === window.top ) {
return;
}

/**
* Send this document's height to the parent (embedding) site.
*/
sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) );
sendAmpMessage( 'embed-size', 'height', Math.ceil( document.body.getBoundingClientRect().height ) );

// Send the document's height again after the featured image has been loaded.
if ( featured_image ) {
featured_image.addEventListener( 'load', function() {
sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) );
sendAmpMessage( 'embed-ready' );
sendAmpMessage( 'embed-size', 'height', Math.ceil( document.body.getBoundingClientRect().height ) );
} );
}

/**
* Detect clicks to external (_top) links.
*/
function linkClickHandler( e ) {
var target = e.target,
href;
if ( target.hasAttribute( 'href' ) ) {
href = target.getAttribute( 'href' );
} else {
href = target.parentElement.getAttribute( 'href' );
}

/**
* Send link target to the parent (embedding) site.
*/
if ( href ) {
sendEmbedMessage( 'link', href );
e.preventDefault();
}
}

document.addEventListener( 'click', linkClickHandler );
}

/**
* Iframe resize handler.
*/
function onResize() {
if ( window.self === window.top ) {
return;
}

clearTimeout( resizing );

resizing = setTimeout( function () {
sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) );
sendAmpMessage( 'embed-size', 'height', Math.ceil( document.body.getBoundingClientRect().height ) );
}, 100 );
}

/**
* Re-get the secret when it was added later on.
*/
function getSecret() {
if ( window.self === window.top || !!secret ) {
return;
}

secret = window.location.hash.replace( /.*secret=([\d\w]{10}).*/, '$1' );

clearTimeout( secretTimeout );

secretTimeout = setTimeout( function () {
getSecret();
}, 100 );
}

if ( supportedBrowser ) {
getSecret();
document.documentElement.className = document.documentElement.className.replace( /\bno-js\b/, '' ) + ' js';
document.addEventListener( 'DOMContentLoaded', onLoad, false );
window.addEventListener( 'load', onLoad, false );
window.addEventListener( 'resize', onResize, false );
}
})( window, document );
11 changes: 11 additions & 0 deletions includes/admin/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,14 @@ function amp_story_templates() {
$story_templates = new AMP_Story_Templates();
$story_templates->init();
}


/**
* Bootstrap the Story Templates needed in editor.
*
* @since 1.?
*/
function amp_wordpress_embed_templates() {
$story_templates = new AMP_WordPress_Embed_Template();
$story_templates->init();
}
1 change: 1 addition & 0 deletions includes/class-amp-autoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class AMP_Autoloader {
'AMP_Widget_Archives' => 'includes/widgets/class-amp-widget-archives',
'AMP_Widget_Categories' => 'includes/widgets/class-amp-widget-categories',
'AMP_Widget_Text' => 'includes/widgets/class-amp-widget-text',
'AMP_WordPress_Embed_Template' => 'includes/class-amp-wordpress-embed-template',
'WPCOM_AMP_Polldaddy_Embed' => 'wpcom/class-amp-polldaddy-embed',
'AMP_Test_Stub_Sanitizer' => 'tests/stubs',
'AMP_Test_World_Sanitizer' => 'tests/stubs',
Expand Down
103 changes: 0 additions & 103 deletions includes/class-amp-story-post-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,6 @@ public static function register() {
// Enqueue the styling for the /embed endpoint.
add_action( 'embed_footer', array( __CLASS__, 'enqueue_embed_styling' ) );

// In the block editor, remove the title from above the AMP Stories embed.
add_filter( 'embed_html', array( __CLASS__, 'remove_title_from_embed' ), 10, 2 );

// Change some attributes for the AMP story embed.
add_filter( 'embed_html', array( __CLASS__, 'change_embed_iframe_attributes' ), 10, 2 );

// Override the render_callback for AMP story embeds.
add_filter( 'pre_render_block', array( __CLASS__, 'override_story_embed_callback' ), 10, 2 );

// The AJAX handler for when an image is cropped and sent via POST.
add_action( 'wp_ajax_custom-header-crop', array( __CLASS__, 'crop_featured_image' ) );

Expand Down Expand Up @@ -1129,61 +1120,6 @@ public static function enqueue_embed_styling() {
}
}

/**
* Overrides the render_callback of an AMP story post embed, when using the WordPress (embed) block.
*
* WordPress post embeds are usually wrapped in an <iframe>,
* which can cause validation and display issues in AMP.
* This overrides the embed callback in that case, replacing the <iframe> with the simple AMP story card.
*
* @param string $pre_render The pre-rendered markup, default null.
* @param array $block The block to render.
* @return string|null $rendered_markup The rendered markup, or null to not override the existing render_callback.
*/
public static function override_story_embed_callback( $pre_render, $block ) {
if ( ! isset( $block['attrs']['url'], $block['blockName'] ) || ! in_array( $block['blockName'], array( 'core-embed/wordpress', 'core/embed' ), true ) ) {
return $pre_render;
}

// Taken from url_to_postid(), ensures that the URL is from this site.
$url = $block['attrs']['url'];
$url_host = wp_parse_url( $url, PHP_URL_HOST );
$home_url_host = wp_parse_url( home_url(), PHP_URL_HOST );

// Exit if the URL isn't from this site.
if ( $url_host !== $home_url_host ) {
return $pre_render;
}

$embed_url_path = wp_parse_url( $url, PHP_URL_PATH );
$base_url_path = wp_parse_url( trailingslashit( home_url( self::REWRITE_SLUG ) ), PHP_URL_PATH );
if ( 0 !== strpos( $embed_url_path, $base_url_path ) ) {
return $pre_render;
}
$path = substr( $embed_url_path, strlen( $base_url_path ) );
$post = get_post( get_page_by_path( $path, OBJECT, self::POST_TYPE_SLUG ) );

if ( self::POST_TYPE_SLUG !== get_post_type( $post ) ) {
return $pre_render;
}

wp_enqueue_style( self::STORY_CARD_CSS_SLUG );
ob_start();
?>
<div class="amp-story-embed">
<?php
self::the_single_story_card(
array(
'post' => $post,
'size' => self::STORY_CARD_IMAGE_SIZE,
)
);
?>
</div>
<?php
return ob_get_clean();
}

/**
* Registers the dynamic block Latest Stories.
* Much of this is taken from the Core block Latest Posts.
Expand Down Expand Up @@ -1411,43 +1347,4 @@ public static function insert_attachment( $object, $cropped ) {

return $attachment_id;
}

/**
* For amp_story embeds, removes the title from above the <iframe>.
*
* @param string $output The output to filter.
* @param WP_Post $post The post for the embed.
* @return string $output The filtered output.
*/
public static function remove_title_from_embed( $output, $post ) {
if ( self::POST_TYPE_SLUG !== get_post_type( $post ) ) {
return $output;
}

return preg_replace( '/<blockquote class="wp-embedded-content">.*?<\/blockquote>/', '', $output );
}

/**
* Changes the height of the AMP Story embed <iframe>.
*
* In the block editor, this embed typically appears in an <iframe>, though on the front-end it's not in an <iframe>.
* The height of the <iframe> isn't enough to display the full story, so this increases it.
*
* @param string $output The embed output.
* @param WP_Post $post The post for the embed.
* @return string The filtered embed output.
*/
public static function change_embed_iframe_attributes( $output, $post ) {
if ( self::POST_TYPE_SLUG !== get_post_type( $post ) ) {
return $output;
}

// Add 4px more height, as the <iframe> needs that to display the full image.
$new_height = strval( ( self::STORY_LARGE_IMAGE_DIMENSION / 2 ) + 4 );
return preg_replace(
'/(<iframe sandbox="allow-scripts"[^>]*\sheight=")(\w+)("[^>]*>)/',
sprintf( '${1}%s${3}', $new_height ),
$output
);
}
}
Loading