-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Asynchronously load tinyMCE the first time a classic block is edited #21684
Closed
sarayourfriend
wants to merge
15
commits into
WordPress:master
from
sarayourfriend:add/lazy-classic-block
Closed
Changes from 11 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
f334112
Add LazyLoad component
sarayourfriend a2e071e
WIP: Lazy load tineMCE for the classic block
sarayourfriend bac6771
Only print TinyMCE scripts outside of the block editor screen
sarayourfriend 99c7af2
Inline a window function for TinyMCE i18n init
sarayourfriend 26a3b7c
Add script and style REST APIs
spacedmonkey 288519c
Fix PHP
spacedmonkey d57e03f
Fix lints
spacedmonkey 594cf96
Update lib/class-wp-rest-dependencies-controller.php
spacedmonkey a33ed9e
Allow passing multiple dependency handles
sarayourfriend f706903
Use the actual object's handles rather than duplicating its initializ…
sarayourfriend 0d55d9a
Use dependencies REST API to fetch style and script URIs
sarayourfriend 7d370f1
Guarantee script execution order
sarayourfriend 3980857
Fetch TinyMCE translations from REST API
sarayourfriend 6f0575d
Prevent onLoaded for previously loaded deps
sarayourfriend 0f90624
Add document to LazyLoad component
sarayourfriend File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -159,3 +159,25 @@ function gutenberg_rest_nonce() { | |
exit( wp_create_nonce( 'wp_rest' ) ); | ||
} | ||
add_action( 'wp_ajax_gutenberg_rest_nonce', 'gutenberg_rest_nonce' ); | ||
|
||
/** | ||
* Prints TinyMCE scripts when we're outside of the block editor. | ||
* Otherwise, use the default TinyMCE script printing behavior. | ||
* | ||
* @since 7.9.1 | ||
*/ | ||
function gutenberg_print_tinymce_scripts() { | ||
$current_screen = get_current_screen(); | ||
if ( ! $current_screen->is_block_editor() ) { | ||
// maybe also need to check a setting/feature flag? Put this behind phase 2? | ||
wp_print_tinymce_scripts(); | ||
} else { | ||
echo "<!-- Skipping TinyMCE in favor of loading async -->"; | ||
echo "<script type='text/javascript'>\n" . | ||
"window.wpMceTranslation = function() {\n" . | ||
_WP_Editors::wp_mce_translation() . | ||
"\n};\n</script>\n"; | ||
} | ||
} | ||
remove_action( 'print_tinymce_scripts', 'wp_print_tinymce_scripts' ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This action needs to be added in core, example PR here: WordPress/wordpress-develop#232 |
||
add_action( 'print_tinymce_scripts', 'gutenberg_print_tinymce_scripts' ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,288 @@ | ||
<?php | ||
/** | ||
* Dependencies controller. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
/** | ||
* Extendable dependency class. | ||
* | ||
* Class WP_REST_Dependencies_Controller | ||
* | ||
* @see WP_REST_Controller | ||
*/ | ||
class WP_REST_Dependencies_Controller extends WP_REST_Controller { | ||
|
||
/** | ||
* Dependencies core object. | ||
* | ||
* @var Object | ||
*/ | ||
protected $object; | ||
|
||
|
||
/** | ||
* $editor_block_dependency | ||
* | ||
* @var string | ||
*/ | ||
protected $editor_block_dependency = ''; | ||
|
||
|
||
/** | ||
* $block_dependency | ||
* | ||
* @var string | ||
*/ | ||
protected $block_dependency = ''; | ||
|
||
/** | ||
* Register routes. | ||
*/ | ||
public function register_routes() { | ||
register_rest_route( | ||
$this->namespace, | ||
'/' . $this->rest_base, | ||
array( | ||
array( | ||
'methods' => WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_items' ), | ||
'permission_callback' => array( $this, 'get_items_permissions_check' ), | ||
'args' => $this->get_collection_params(), | ||
), | ||
'schema' => array( $this, 'get_item_schema' ), | ||
) | ||
); | ||
|
||
$get_item_args = array( | ||
'context' => $this->get_context_param( array( 'default' => 'view' ) ), | ||
); | ||
|
||
register_rest_route( | ||
$this->namespace, | ||
'/' . $this->rest_base . '/(?P<handle>[\w-]+)', | ||
array( | ||
'args' => array( | ||
'handle' => array( | ||
'description' => __( 'Unique identifier for the object.', 'gutenberg' ), | ||
'type' => 'string', | ||
), | ||
), | ||
array( | ||
'methods' => WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_item' ), | ||
'permission_callback' => array( $this, 'get_item_permissions_check' ), | ||
'args' => $get_item_args, | ||
), | ||
'schema' => array( $this, 'get_public_item_schema' ), | ||
) | ||
); | ||
} | ||
|
||
/** | ||
* Get list of dependencies. | ||
* | ||
* @param WP_REST_Request $request Request. | ||
* | ||
* @return array|WP_Error|WP_REST_Response | ||
*/ | ||
public function get_items( $request ) { | ||
$data = []; | ||
$handle = $request['dependency']; | ||
$filter = []; | ||
if ( $handle ) { | ||
$this->object->all_deps( $handle ); | ||
$filter = $this->object->to_do; | ||
} | ||
|
||
if ( $handle ) { | ||
foreach ( $filter as $dependency_handle ) { | ||
foreach ( $this->object->registered as $dependency ) { | ||
if ( $dependency_handle === $dependency->handle ) { | ||
$item = $this->prepare_item_for_response( $dependency, $request ); | ||
$data[] = $this->prepare_response_for_collection( $item ); | ||
} | ||
} | ||
} | ||
} else { | ||
foreach ( $this->object->registered as $dependency ) { | ||
$item = $this->prepare_item_for_response( $dependency, $request ); | ||
$data[] = $this->prepare_response_for_collection( $item ); | ||
} | ||
} | ||
|
||
return $data; | ||
} | ||
|
||
/** | ||
* Get a single dependency. | ||
* | ||
* @param WP_REST_Request $request Request. | ||
* | ||
* @return array|mixed|WP_Error|WP_REST_Response | ||
*/ | ||
public function get_item( $request ) { | ||
if ( ! isset( $this->object->registered[ $request['handle'] ] ) ) { | ||
return []; | ||
} | ||
$dependency = $this->object->registered[ $request['handle'] ]; | ||
$data = $this->prepare_item_for_response( $dependency, $request ); | ||
|
||
return $data; | ||
} | ||
|
||
/** | ||
* Prepare item for response. | ||
* | ||
* @param mixed $dependency Dependency. | ||
* @param WP_REST_Request $request Request. | ||
* | ||
* @return mixed|WP_Error|WP_REST_Response | ||
*/ | ||
public function prepare_item_for_response( $dependency, $request ) { | ||
$dependency->url = $this->get_url( $dependency->src, $dependency->ver, $dependency->handle ); | ||
$response = rest_ensure_response( (array) $dependency ); | ||
$dependencies = $this->prepare_links( $dependency ); | ||
$response->add_links( $dependencies ); | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Permission check. | ||
* | ||
* @param WP_REST_Request $request Request. | ||
* | ||
* @return bool|true|WP_Error | ||
*/ | ||
public function get_items_permissions_check( $request ) { | ||
if ( $this->check_handle( $request['dependency'] ) ) { | ||
return true; | ||
} | ||
|
||
return current_user_can( 'manage_options' ); | ||
} | ||
|
||
/** | ||
* Permission check. | ||
* | ||
* @param WP_REST_Request $request Request. | ||
* | ||
* @return bool|true|WP_Error | ||
*/ | ||
public function get_item_permissions_check( $request ) { | ||
if ( $this->check_handle( $request['handle'] ) ) { | ||
return true; | ||
} | ||
|
||
return current_user_can( 'manage_options' ); | ||
} | ||
|
||
/** | ||
* Prepare links. | ||
* | ||
* @param object $dependency Dependency. | ||
* | ||
* @return array | ||
*/ | ||
protected function prepare_links( $dependency ) { | ||
$base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); | ||
// Entity meta. | ||
$links = array( | ||
'self' => array( | ||
'href' => rest_url( trailingslashit( $base ) . $dependency->handle ), | ||
), | ||
'collection' => array( | ||
'href' => rest_url( $base ), | ||
), | ||
'deps' => array( | ||
'href' => rest_url( trailingslashit( $base ) . '?dependency=' . $dependency->handle ), | ||
), | ||
); | ||
|
||
return $links; | ||
} | ||
|
||
/** | ||
* Get collection params. | ||
* | ||
* @return array | ||
*/ | ||
public function get_collection_params() { | ||
$query_params = parent::get_collection_params(); | ||
$query_params['context']['default'] = 'view'; | ||
|
||
$query_params['dependency'] = array( | ||
'description' => __( 'Dependency.', 'gutenberg' ), | ||
'type' => 'array', | ||
); | ||
|
||
return $query_params; | ||
} | ||
|
||
/** | ||
* Check handle exists and is viewable. | ||
* | ||
* @param string $handle script / style handle. | ||
* | ||
* @return bool | ||
*/ | ||
protected function check_handle( $handle ) { | ||
|
||
if ( ! $handle ) { | ||
return false; | ||
} | ||
|
||
// All core assets should be public. | ||
if ( in_array( $handle, $this->get_core_assets(), true ) ) { | ||
return true; | ||
} | ||
|
||
// All block public assets should also be public. | ||
if ( in_array( $handle, $this->block_asset( $this->block_dependency ), true ) ) { | ||
return true; | ||
} | ||
|
||
// All block edit assets should check if user is logged in and has the ability to using the editor. | ||
if ( in_array( $handle, $this->block_asset( $this->editor_block_dependency ), true ) ) { | ||
return current_user_can( 'edit_posts' ); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Get core assets. | ||
* | ||
* @return array | ||
*/ | ||
public function get_core_assets() { | ||
/* translators: %s: Method name. */ | ||
_doing_it_wrong( sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'gutenberg' ), __METHOD__ ), 'x.x' ); | ||
|
||
return array(); | ||
} | ||
|
||
/** | ||
* Block asset. | ||
* | ||
* @param string $field Field to pluck from list of objects. | ||
* | ||
* @return array | ||
*/ | ||
protected function block_asset( $field ) { | ||
if ( ! $field ) { | ||
return array(); | ||
} | ||
|
||
$block_registry = WP_Block_Type_Registry::get_instance(); | ||
$blocks = $block_registry->get_all_registered(); | ||
$handles = wp_list_pluck( $blocks, $field ); | ||
$handles = array_values( $handles ); | ||
$handles = array_filter( $handles ); | ||
|
||
return $handles; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<?php | ||
/** | ||
* Scripts controller. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
/** | ||
* Class WP_REST_Scripts_Controller | ||
*/ | ||
class WP_REST_Scripts_Controller extends WP_REST_Dependencies_Controller { | ||
/** | ||
* WP_REST_Scripts_Controller constructor. | ||
*/ | ||
public function __construct() { | ||
$this->namespace = 'wp/v2'; | ||
$this->rest_base = 'scripts'; | ||
$this->editor_block_dependency = 'editor_script'; | ||
$this->block_dependency = 'script'; | ||
$this->object = wp_scripts(); | ||
} | ||
|
||
/** | ||
* Helper to get Script URL. | ||
* | ||
* @param string $src Script URL. | ||
* @param string $ver Version URL. | ||
* @param string $handle Handle name. | ||
* | ||
* @return string | ||
*/ | ||
public function get_url( $src, $ver, $handle ) { | ||
if ( ! is_bool( $src ) && ! preg_match( '|^(https?:)?//|', $src ) && ! ( $this->object->content_url && 0 === strpos( $src, $this->object->content_url ) ) ) { | ||
$src = $this->object->base_url . $src; | ||
} | ||
if ( ! empty( $ver ) ) { | ||
$src = add_query_arg( 'ver', $ver, $src ); | ||
} | ||
|
||
/** This filter is documented in wp-includes/class.wp-scripts.php */ | ||
$src = esc_url( apply_filters( 'script_loader_src', $src, $handle ) ); | ||
|
||
return esc_url( $src ); | ||
} | ||
|
||
/** | ||
* Get core assets. | ||
* | ||
* @return array | ||
*/ | ||
public function get_core_assets() { | ||
$handles = wp_list_pluck( $this->object->registered, 'handle' ); | ||
$handles = array_values( $handles ); | ||
|
||
return $handles; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be part of the REST API response in this async scenario?
I don't think it can be as simple as that for the block editor. Plugins still can register Meta Boxes that use TinyMCE heavily. AFC is a great example to test with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Definitely an open question to me... I guess we can create a filter? The issue is the endpoint returns just the URI for the dependency, not the dependency script itself, so we can’t just concatenate the translation bit to it.
An alternative to this is to just have a
tinymce-i18n
endpoint to just return the json needed, the asynconLoaded
prop is suitable for that.