├── .editorconfig ├── README.md ├── .gitignore ├── scripts └── archive.sh └── block-editor-assets-endpoint.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | 16 | [*.{yml,yaml}] 17 | indent_style = space 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # block-editor-assets-endpoint 2 | 3 | An exploration defining a new WordPress REST API endpoint returning all block editor assets needed for a block editor embedded within the mobile app. The approach is based on the [`_gutenberg_get_iframed_editor_assets_6_4`](https://github.com/WordPress/gutenberg/blob/ae20515b20d9c9e31408c4aecaffb3991c0fe31a/lib/compat/wordpress-6.4/script-loader.php#L8-L103) utility, but extended to include all assets used by the block editor. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Coverage directory used by tools like istanbul 9 | coverage 10 | 11 | # Compiled binary addons (https://nodejs.org/api/addons.html) 12 | build/Release 13 | 14 | # Dependency directories 15 | node_modules/ 16 | 17 | # Optional npm cache directory 18 | .npm 19 | 20 | # Optional eslint cache 21 | .eslintcache 22 | 23 | # Output of `npm pack` 24 | *.tgz 25 | 26 | # Output of `wp-scripts plugin-zip` 27 | *.zip 28 | 29 | # dotenv environment variables file 30 | .env 31 | 32 | # Build output 33 | **/build/ 34 | -------------------------------------------------------------------------------- /scripts/archive.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the source directory and ZIP file name 4 | SOURCE_DIR="$(dirname "$(dirname "$0")")" 5 | ZIP_FILE="block-editor-assets-endpoint.zip" 6 | 7 | # Create a temporary directory to store the files to be archived 8 | TEMP_DIR=$(mktemp -d) 9 | 10 | # Copy all files and directories from the source directory to the temporary directory, 11 | # excluding the node_modules directory and .gitignore file 12 | rsync -av \ 13 | --exclude='.DS_Store' \ 14 | --exclude='node_modules' \ 15 | --exclude='.gitignore' \ 16 | --exclude='.git' \ 17 | --exclude='bin' \ 18 | --exclude='.editorconfig' \ 19 | --exclude='package-lock.json' \ 20 | --exclude='scripts' \ 21 | "$SOURCE_DIR/" "$TEMP_DIR/" \ 22 | > /dev/null 23 | 24 | # Change to the temporary directory 25 | pushd "$TEMP_DIR" > /dev/null 26 | 27 | # Create the ZIP file 28 | zip -r "$TEMP_DIR/$ZIP_FILE" ./* > /dev/null 29 | 30 | # Change back to the original directory 31 | popd > /dev/null 32 | 33 | # Move the ZIP file to the desired location 34 | mv -f "$TEMP_DIR/$ZIP_FILE" "$SOURCE_DIR" 35 | 36 | # Clean up the temporary directory 37 | rm -rf "$TEMP_DIR" 38 | 39 | echo "Archive created successfully at $(realpath "$SOURCE_DIR/$ZIP_FILE")" 40 | -------------------------------------------------------------------------------- /block-editor-assets-endpoint.php: -------------------------------------------------------------------------------- 1 | namespace = '__experimental/wp-block-editor/v1'; 33 | $this->rest_base = 'editor-assets'; 34 | } 35 | 36 | /** 37 | * Registers the controller routes. 38 | */ 39 | public function register_routes() { 40 | register_rest_route( 41 | $this->namespace, 42 | '/' . $this->rest_base, 43 | array( 44 | array( 45 | 'methods' => WP_REST_Server::READABLE, 46 | 'callback' => array( $this, 'get_items' ), 47 | 'permission_callback' => array( $this, 'get_items_permissions_check' ), 48 | ), 49 | 'schema' => array( $this, 'get_public_item_schema' ), 50 | ) 51 | ); 52 | } 53 | 54 | /** 55 | * Retrieves a collection of items. 56 | * 57 | * @param WP_REST_Request $request The request object. 58 | * 59 | * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 60 | */ 61 | public function get_items( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable 62 | global $wp_styles, $wp_scripts; 63 | 64 | $current_wp_styles = $wp_styles; 65 | $current_wp_scripts = $wp_scripts; 66 | 67 | $wp_styles = new WP_Styles(); 68 | $wp_scripts = new WP_Scripts(); 69 | 70 | // Trigger an action frequently used by plugins to enqueue assets. 71 | do_action( 'wp_loaded' ); 72 | 73 | // We generally do not need reset styles for the block editor. However, if 74 | // it's a classic theme, margins will be added to every block, which is 75 | // reset specifically for list items, so classic themes rely on these 76 | // reset styles. 77 | $wp_styles->done = 78 | wp_theme_has_theme_json() ? array( 'wp-reset-editor-styles' ) : array(); 79 | 80 | wp_enqueue_script( 'wp-polyfill' ); 81 | // Enqueue the `editorStyle` handles for all core block, and dependencies. 82 | wp_enqueue_style( 'wp-edit-blocks' ); 83 | 84 | if ( current_theme_supports( 'wp-block-styles' ) ) { 85 | wp_enqueue_style( 'wp-block-library-theme' ); 86 | } 87 | 88 | // Enqueue frequent dependent, admin-only `dashicon` asset. 89 | wp_enqueue_style( 'dashicons' ); 90 | 91 | // Enqueue the admin-only `postbox` asset required for the block editor. 92 | $suffix = wp_scripts_get_suffix(); 93 | wp_enqueue_script( 'postbox', "/wp-admin/js/postbox$suffix.js", array( 'jquery-ui-sortable', 'wp-a11y' ), false, 1 ); 94 | 95 | // Enqueue foundational post editor assets. 96 | wp_enqueue_script( 'wp-edit-post' ); 97 | wp_enqueue_style( 'wp-edit-post' ); 98 | 99 | // Ensure the block editor scripts and styles are enqueued. 100 | add_filter( 'should_load_block_editor_scripts_and_styles', '__return_true' ); 101 | do_action( 'enqueue_block_assets' ); 102 | do_action( 'enqueue_block_editor_assets' ); 103 | remove_filter( 'should_load_block_editor_scripts_and_styles', '__return_true' ); 104 | 105 | // Additionally, enqueue `editorStyle` and `editorScript` assets for all 106 | // blocks, which contains editor-only styling for blocks (editor content). 107 | $block_registry = WP_Block_Type_Registry::get_instance(); 108 | foreach ( $block_registry->get_all_registered() as $block_type ) { 109 | if ( isset( $block_type->editor_style_handles ) && is_array( $block_type->editor_style_handles ) ) { 110 | foreach ( $block_type->editor_style_handles as $style_handle ) { 111 | wp_enqueue_style( $style_handle ); 112 | } 113 | } 114 | if ( isset( $block_type->editor_script_handles ) && is_array( $block_type->editor_script_handles ) ) { 115 | foreach ( $block_type->editor_script_handles as $script_handle ) { 116 | wp_enqueue_script( $script_handle ); 117 | } 118 | } 119 | } 120 | 121 | // Remove the deprecated `print_emoji_styles` handler. It avoids breaking 122 | // style generation with a deprecation message. 123 | $has_emoji_styles = has_action( 'wp_print_styles', 'print_emoji_styles' ); 124 | if ( $has_emoji_styles ) { 125 | remove_action( 'wp_print_styles', 'print_emoji_styles' ); 126 | } 127 | 128 | ob_start(); 129 | wp_print_styles(); 130 | $styles = ob_get_clean(); 131 | 132 | if ( $has_emoji_styles ) { 133 | add_action( 'wp_print_styles', 'print_emoji_styles' ); 134 | } 135 | 136 | ob_start(); 137 | wp_print_head_scripts(); 138 | wp_print_footer_scripts(); 139 | $scripts = ob_get_clean(); 140 | 141 | $wp_styles = $current_wp_styles; 142 | $wp_scripts = $current_wp_scripts; 143 | 144 | return array( 145 | 'styles' => $styles, 146 | 'scripts' => $scripts, 147 | ); 148 | } 149 | 150 | /** 151 | * Checks the permissions for retrieving items. 152 | * 153 | * @param WP_REST_Request $request The REST request object. 154 | * 155 | * @return bool|WP_Error True if the request has permission, WP_Error object otherwise. 156 | */ 157 | public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable 158 | if ( current_user_can( 'edit_posts' ) ) { 159 | return true; 160 | } 161 | 162 | foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { 163 | if ( current_user_can( $post_type->cap->edit_posts ) ) { 164 | return true; 165 | } 166 | } 167 | 168 | return new WP_Error( 169 | 'rest_cannot_read_block_editor_assets', 170 | __( 'Sorry, you are not allowed to read the block editor assets.', 'gutenberg' ), 171 | array( 'status' => rest_authorization_required_code() ) 172 | ); 173 | } 174 | 175 | /** 176 | * Retrieves the block editor assets schema, conforming to JSON Schema. 177 | * 178 | * @return array Item schema data. 179 | */ 180 | public function get_item_schema() { 181 | if ( $this->schema ) { 182 | return $this->add_additional_fields_schema( $this->schema ); 183 | } 184 | 185 | $schema = array( 186 | 'type' => 'object', 187 | 'properties' => array( 188 | 'styles' => array( 189 | 'description' => esc_html__( 'Style link tags for the block editor.', 'gutenberg' ), 190 | 'type' => 'string', 191 | ), 192 | 'scripts' => array( 193 | 'description' => esc_html__( 'Script tags for the block editor.', 'gutenberg' ), 194 | 'type' => 'string', 195 | ), 196 | ), 197 | ); 198 | 199 | $this->schema = $schema; 200 | 201 | return $this->add_additional_fields_schema( $this->schema ); 202 | } 203 | } 204 | } 205 | 206 | /** 207 | * Registers the block editor assets REST API route. 208 | */ 209 | function gutenberg_register_block_editor_assets() { 210 | $editor_assets = new WP_REST_Block_Editor_Assets_Controller(); 211 | $editor_assets->register_routes(); 212 | } 213 | add_action( 'rest_api_init', 'gutenberg_register_block_editor_assets' ); 214 | --------------------------------------------------------------------------------