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

Full site editor: load content in iframe #25775

Merged
merged 1 commit into from Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 37 additions & 0 deletions lib/client-assets.php
Expand Up @@ -669,3 +669,40 @@ function gutenberg_extend_block_editor_settings_with_fse_theme_flag( $settings )
return $settings;
}
add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_settings_with_fse_theme_flag' );

/**
* Sets the editor styles to be consumed by JS.
*/
function gutenberg_extend_block_editor_styles_html() {
$handles = array(
'wp-block-editor',
'wp-block-library',
'wp-edit-blocks',
);

$block_registry = WP_Block_Type_Registry::get_instance();

foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) {
if ( ! empty( $block_type->style ) ) {
$handles[] = $block_type->style;
}

if ( ! empty( $block_type->editor_style ) ) {
$handles[] = $block_type->editor_style;
}
}

$handles = array_unique( $handles );
$done = wp_styles()->done;

ob_start();

wp_styles()->done = array();
wp_styles()->do_items( $handles );
wp_styles()->done = $done;

$editor_styles = wp_json_encode( array( 'html' => ob_get_clean() ) );

echo "<script>window.__editorStyles = $editor_styles</script>";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better to do that without the new variable. Maybe by just reusing the "styles" property in the editor settings?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can look into that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to do this, but the styles are not available yet at the time when the editor scripts are registered.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not suggesting at the script registration, I was more suggesting block_editor_settings filter for post editor and gutenberg_edit_site_init for the site editor.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I mean. The block_editor_settings filter is run at script registration, which is too early. The settings are passed as an inline script initialising the editor.

}
add_action( 'admin_footer-toplevel_page_gutenberg-edit-site', 'gutenberg_extend_block_editor_styles_html' );
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/block-editor/package.json
Expand Up @@ -60,6 +60,7 @@
"lodash": "^4.17.19",
"memize": "^1.1.0",
"react-autosize-textarea": "^7.1.0",
"react-merge-refs": "^1.0.0",
"react-spring": "^8.0.19",
"reakit": "1.3.4",
"redux-multi": "^0.1.12",
Expand Down
Expand Up @@ -167,7 +167,13 @@ function BlockPopover( {
: 'top right left';
const stickyBoundaryElement = showEmptyBlockSideInserter
? undefined
: getScrollContainer( node ) || ownerDocument.body;
: // The sticky boundary element should be the boundary at which the
// the block toolbar becomes sticky when the block scolls out of view.
// In case of an iframe, this should be the iframe boundary, otherwise
// the scroll container.
ownerDocument.defaultView.frameElement ||
getScrollContainer( node ) ||
ownerDocument.body;
youknowriad marked this conversation as resolved.
Show resolved Hide resolved

return (
<Popover
Expand Down
Expand Up @@ -223,6 +223,12 @@ function BlockSelectionButton( { clientId, rootClientId, blockElement } ) {

if ( navigateDown ) {
nextTabbable = focus.tabbable.findNext( blockElement );

if ( ! nextTabbable ) {
nextTabbable =
blockElement.ownerDocument.defaultView.frameElement;
nextTabbable = focus.tabbable.findNext( nextTabbable );
}
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
} else {
nextTabbable = focus.tabbable.findPrevious( blockElement );
}
Expand Down
189 changes: 189 additions & 0 deletions packages/block-editor/src/components/iframe/index.js
@@ -0,0 +1,189 @@
/**
* External dependencies
*/
import mergeRefs from 'react-merge-refs';

/**
* WordPress dependencies
*/
import {
useState,
createPortal,
useCallback,
forwardRef,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';

const BODY_CLASS_NAME = 'editor-styles-wrapper';
const BLOCK_PREFIX = 'wp-block';

/**
* Clones stylesheets targetting the editor canvas to the given document. A
* stylesheet is considered targetting the editor a canvas if it contains the
* `editor-styles-wrapper`, `wp-block`, or `wp-block-*` class selectors.
*
* Ideally, this hook should be removed in the future and styles should be added
* explicitly as editor styles.
*
* @param {Document} doc The document to append cloned stylesheets to.
*/
function styleSheetsCompat( doc ) {
// Search the document for stylesheets targetting the editor canvas.
Array.from( document.styleSheets ).forEach( ( styleSheet ) => {
try {
// May fail for external styles.
// eslint-disable-next-line no-unused-expressions
styleSheet.cssRules;
} catch ( e ) {
return;
}

const { ownerNode, cssRules } = styleSheet;

if ( ! cssRules ) {
return;
}

const isMatch = Array.from( cssRules ).find(
( { selectorText } ) =>
selectorText &&
( selectorText.includes( `.${ BODY_CLASS_NAME }` ) ||
selectorText.includes( `.${ BLOCK_PREFIX }` ) )
);

if ( isMatch && ! doc.getElementById( ownerNode.id ) ) {
doc.head.appendChild( ownerNode.cloneNode( true ) );
}
} );
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant for stylesheets from themes and plugins not using the "official" editor styles way of enqueuing things? Should we add a deprecated call when we find one of these?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct! We could do that, yes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's still some of our own stylesheets left as well. Maybe we should attempt this in a follow-up?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, definitely, what are our own stylesheets that fall here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of the site editor (I need to check for the post editor, but I think there are several), it's the edit-site package setting some default styles:

.wp-block {
  max-width: 840px;
}
.wp-block[data-align=wide] {
  max-width: 1100px;
}
.wp-block[data-align=full] {
  max-width: none;
}

It seems that there styles need to more in some sort of default stylesheet. They don't belong in the package I think.


/**
* Bubbles some event types (keydown, keypress, and dragover) to parent document
* document to ensure that the keyboard shortcuts and drag and drop work.
*
* Ideally, we should remove event bubbling in the future. Keyboard shortcuts
* should be context dependent, e.g. actions on blocks like Cmd+A should not
* work globally outside the block editor.
*
* @param {Document} doc Document to attach listeners to.
*/
function bubbleEvents( doc ) {
const { defaultView } = doc;
const { frameElement } = defaultView;

function bubbleEvent( event ) {
const prototype = Object.getPrototypeOf( event );
const constructorName = prototype.constructor.name;
const Constructor = window[ constructorName ];

const init = {};

for ( const key in event ) {
init[ key ] = event[ key ];
}

if ( event instanceof defaultView.MouseEvent ) {
const rect = frameElement.getBoundingClientRect();
init.clientX += rect.left;
init.clientY += rect.top;
}

const newEvent = new Constructor( event.type, init );
const cancelled = ! frameElement.dispatchEvent( newEvent );

if ( cancelled ) {
event.preventDefault();
}
}

const eventTypes = [ 'keydown', 'keypress', 'dragover' ];

for ( const name of eventTypes ) {
doc.addEventListener( name, bubbleEvent );
}
}

/**
* Sets the document direction.
*
* Sets the `editor-styles-wrapper` class name on the body.
*
* Copies the `admin-color-*` class name to the body so that the admin color
* scheme applies to components in the iframe.
*
* @param {Document} doc Document to add class name to.
*/
function setBodyClassName( doc ) {
doc.dir = document.dir;
doc.body.className = BODY_CLASS_NAME;

for ( const name of document.body.classList ) {
if ( name.startsWith( 'admin-color-' ) ) {
doc.body.classList.add( name );
}
}
}

/**
* Sets the document head and default styles.
*
* @param {Document} doc Document to set the head for.
* @param {string} head HTML to set as the head.
*/
function setHead( doc, head ) {
doc.head.innerHTML =
// Body margin must be overridable by themes.
'<style>body{margin:0}</style>' + head;
}

function Iframe( { contentRef, children, head, ...props }, ref ) {
const [ iframeDocument, setIframeDocument ] = useState();

const setRef = useCallback( ( node ) => {
if ( ! node ) {
return;
}

function setDocumentIfReady() {
const { contentDocument } = node;
const { readyState } = contentDocument;

if ( readyState !== 'interactive' && readyState !== 'complete' ) {
return false;
}

setIframeDocument( contentDocument );
setHead( contentDocument, head );
setBodyClassName( contentDocument );
styleSheetsCompat( contentDocument );
bubbleEvents( contentDocument );
setBodyClassName( contentDocument );
contentRef.current = contentDocument.body;

return true;
}

if ( setDocumentIfReady() ) {
return;
}

// Document is not immediately loaded in Firefox.
node.addEventListener( 'load', () => {
setDocumentIfReady();
} );
}, [] );

return (
<iframe
{ ...props }
ref={ useCallback( mergeRefs( [ ref, setRef ] ), [] ) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure, the useCallback here is useless.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not, see #27686.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting :)

tabIndex="0"
title={ __( 'Editor canvas' ) }
name="editor-canvas"
>
{ iframeDocument && createPortal( children, iframeDocument.body ) }
</iframe>
);
}

export default forwardRef( Iframe );
2 changes: 2 additions & 0 deletions packages/block-editor/src/components/index.js
Expand Up @@ -108,6 +108,7 @@ export { default as NavigableToolbar } from './navigable-toolbar';
export {
default as ObserveTyping,
useTypingObserver as __unstableUseTypingObserver,
useMouseMoveTypingReset as __unstableUseMouseMoveTypingReset,
} from './observe-typing';
export { default as PreserveScrollInReorder } from './preserve-scroll-in-reorder';
export { default as SkipToSelectedBlock } from './skip-to-selected-block';
Expand All @@ -119,6 +120,7 @@ export { default as Warning } from './warning';
export { default as WritingFlow } from './writing-flow';
export { useCanvasClickRedirect as __unstableUseCanvasClickRedirect } from './use-canvas-click-redirect';
export { default as useBlockDisplayInformation } from './use-block-display-information';
export { default as __unstableIframe } from './iframe';

/*
* State Related Components
Expand Down