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

Asynchronously load tinyMCE the first time a classic block is edited #21684

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 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
22 changes: 22 additions & 0 deletions gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -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" .
Copy link
Member

@gziolo gziolo Apr 27, 2020

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.

Copy link
Contributor Author

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 async onLoaded prop is suitable for that.

_WP_Editors::wp_mce_translation() .
"\n};\n</script>\n";
}
}
remove_action( 'print_tinymce_scripts', 'wp_print_tinymce_scripts' );
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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' );
288 changes: 288 additions & 0 deletions lib/class-wp-rest-dependencies-controller.php
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;
}

}
57 changes: 57 additions & 0 deletions lib/class-wp-rest-scripts-controller.php
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;
}
}
Loading