diff --git a/BLOCKS.md b/BLOCKS.md deleted file mode 100644 index 60d32fb49..000000000 --- a/BLOCKS.md +++ /dev/null @@ -1,127 +0,0 @@ -# Block Editor Compatibility - -This file describes steps to start developing Blocks for Edit Flow. -Currently, the following Edit Flow modules are compatible with Gutenberg Block Editor: - -* Custom Status - -### Setup - -*Note:* This document assumes you have a working knowledge of modern JavaScript and its tooling, including: npm, Webpack, and, of course, React. - -Prerequisites: `npm`, `yarn` (optionally). - -From the plugin's folder run: - -``` -npm i -``` - -or - -``` -yarn -``` - -This should leave you with everything you need for development, including local copy of Webpack and webpack-cli. - - -## Anatomy of an Edit Flow block. - -There are two parts for Block Editor compatibility implementation for each module. - -#### PHP - -**TL;DR;** check out -[Custom Status Module](modules/custom-status/custom-status.php) and its corresponding [Block Editor Compat](modules/custom-status/compat/block-editor.php) for the working example. - -On the PHP side, in the module's folder create a `compat` sub-folder, and in it, create a file named `block-editor.php`. - -That file has to contain the class ${EF_Module_Class_Name}_Block_Editor_Compat. -E.g., for the `Custom Status` Module, which class name is `EF_Custom_Status`, the compat class name has to be `EF_Custom_Status_Block_Editor_Compat`. - - -Here's a contrived example of a fictional module: - -`modules/fictional-module/fictional-module.php`: - -```php - 'module_admin_scripts' - ]; - - function module_admin_scripts() { - // something-something, not compatible with Gutenberg - } -} -``` - -`modules/fictional-module/compat/block-editor.php`: - -```php -ef_module->do_something_with_module(); - } -} -``` - -**Important** - -To avoid class inheritance complexities, Edit Flow compat files use a trait [Block_Editor_Compatible](common/php/trait-block-editor-compatible.php). Be sure to check it out before implementing compatibility for other modules. - -##### How does it work? - -Each Edit Flow module follows the same pattern: attaching the necessary hooks for actions and filters on instantiation. - -We have modified the loader logic in the main Edit_Flow class to try to instantiate the Block_Editor_Compat for corresponding module. - -This way the code for existing modules doesn't need to be modified, except adding the `protected $compat_hooks` property. - -On the instantiation of the module's compatibility class, we'll iterate over `$compat_hooks` and remove the hooks registered by the module, and add ones defined in the compat class. - -#### JavaScript - -##### Development - -To start the Webpack in watch mode: - -```npm run dev``` - -##### Build for production - -To generate optimized/minified production-ready files: - -```npm run build``` - -##### File Structure - -``` -blocks/ - # Source files: - src/ - module-slug/ - block.js # Gutenberg Block code for the module - editor.scss # Editor styles - style.scss # Front-end styles - # Build - dist/ - module-slug.build.js # Built block js - module-slug.editor.build.css # Built editor CSS - module-slug.style.build.css # Built front-end CSS -``` - -The files from `dist/` should be enqueued in the compat class for the module. - -See [Custom Statuses Compatibility Class](modules/custom-status/compat/block-editor.php) for implementation details. - -**Please note:** this is a Work-In-Progress, most likely there will be major changes in the near future. \ No newline at end of file diff --git a/blocks/BLOCKS.md b/blocks/BLOCKS.md new file mode 100644 index 000000000..ccadde955 --- /dev/null +++ b/blocks/BLOCKS.md @@ -0,0 +1,61 @@ +# Block Editor Compatibility + +This file describes steps to start developing Blocks for Edit Flow. +Currently, the following Edit Flow modules are compatible with Gutenberg Block Editor: + +* Custom Statuses + +### Setup + +*Note:* This document assumes you have a working knowledge of modern JavaScript and its tooling. Including npm, Webpack, and React. + +Prerequisites: `npm`. + +From the plugin's folder run: + +``` +npm i +``` + +This should leave you with everything you need for development, including a local copy of Webpack and webpack-cli. + +## Anatomy of an Edit Flow block. + +There are two parts for adding Block Editor compatibility to modules. + +#### PHP + +On the PHP side, we mostly just need to make sure the block assets are enqueued when they are needed. There is a [Block_Editor_Compatible](common/php/trait-block-editor-compatible.php) trait that gives access to helpful methods related to the block editor when used within modules. + +#### JavaScript + +##### Development + +To start the Webpack in watch mode: + +```npm run dev``` + +##### Build for production + +To generate optimized/minified production-ready files: + +```npm run build``` + +##### File Structure + +``` +blocks/ + # Source files: + src/ + module-slug/ + block.js # Gutenberg Block code for the module + editor.scss # Editor styles + style.scss # Front-end styles + # Build + dist/ + module-slug.build.js # Built block js + module-slug.editor.build.css # Built editor CSS + module-slug.style.build.css # Built front-end CSS +``` + +The files from `dist/` should be enqueued in the compat class for the module. diff --git a/blocks/dist/calendar.build.js b/blocks/dist/calendar.build.js deleted file mode 100644 index 7d63fdc1d..000000000 --- a/blocks/dist/calendar.build.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);n(2),n(4)},,function(e,t){},,function(e,t){}]); -//# sourceMappingURL=calendar.build.js.map \ No newline at end of file diff --git a/blocks/dist/calendar.editor.build.css b/blocks/dist/calendar.editor.build.css deleted file mode 100644 index bef982a53..000000000 --- a/blocks/dist/calendar.editor.build.css +++ /dev/null @@ -1,2 +0,0 @@ - -/*# sourceMappingURL=calendar.editor.build.css.map*/ \ No newline at end of file diff --git a/blocks/dist/calendar.style.build.css b/blocks/dist/calendar.style.build.css deleted file mode 100644 index d8a6ed9c3..000000000 --- a/blocks/dist/calendar.style.build.css +++ /dev/null @@ -1,2 +0,0 @@ - -/*# sourceMappingURL=calendar.style.build.css.map*/ \ No newline at end of file diff --git a/blocks/src/calendar/block.js b/blocks/src/calendar/block.js deleted file mode 100644 index 02ede0a80..000000000 --- a/blocks/src/calendar/block.js +++ /dev/null @@ -1,2 +0,0 @@ -import './editor.scss'; -import './style.scss'; \ No newline at end of file diff --git a/blocks/src/calendar/editor.scss b/blocks/src/calendar/editor.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/blocks/src/calendar/style.scss b/blocks/src/calendar/style.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/common/php/class-module.php b/common/php/class-module.php index 25c94dc53..5bd251057 100644 --- a/common/php/class-module.php +++ b/common/php/class-module.php @@ -15,16 +15,6 @@ class EF_Module { 'private', ); - /** - * Associative array of hook_name => callback_name - * This is used for Gutenberg-compat initialization - * [ - * 'init' => 'init_callback_on_module_instance' - * ] - * @var array - */ - protected $compat_hooks = []; - function __construct() {} /** @@ -598,15 +588,6 @@ function upgrade_074_term_descriptions( $taxonomy ) { wp_update_term( $term->term_id, $taxonomy, array( 'description' => $new_description ) ); } } - - /** - * Return compatibility hooks for the current instance - * - * @return array - */ - function get_compat_hooks() { - return isset( $this->compat_hooks ) && is_array( $this->compat_hooks ) ? $this->compat_hooks : []; - } - } + } diff --git a/common/php/trait-block-editor-compatible.php b/common/php/trait-block-editor-compatible.php index f73eac2c4..7e49df87d 100644 --- a/common/php/trait-block-editor-compatible.php +++ b/common/php/trait-block-editor-compatible.php @@ -6,129 +6,26 @@ // phpcs:disable WordPressVIPMinimum.Variables.VariableAnalysis.SelfOutsideClass trait Block_Editor_Compatible { - /** - * Holds the reference to the Module's object - * - * @var EF_Module - */ - protected $ef_module; - /** - * Holds associative array of hooks and their respective callbacks - * - * @var array - */ - protected $hooks = []; - protected static $active_plugins; - - /** - * This method handles init of Module Compat - * - * @param EF_Module $module_instance - * @param array $hooks associative array of hooks and their respective callbacks - * - * - * @return void - */ - function __construct( $module_instance, $hooks = [] ) { - $this->ef_module = $module_instance; - $this->hooks = $hooks; - - if ( is_admin() ) { - - add_action( 'init', [ $this, 'action_init_for_admin' ], 15 ); - } - } - - /** - * Unhook the module's hooks and use the module's Compat hooks instead. - * - * This is currently run on init action, but only when `is_admin` is true. - * - * @since 0.9 - * - * @return void - */ - function action_init_for_admin() { - if ( ! $this->ef_module->is_enabled() ) { - return; - } - - $this->check_active_plugins(); - - if ( $this->should_apply_compat() ) { - foreach ( $this->hooks as $hook => $callback ) { - if ( is_callable( [ $this, $callback ] ) ) { - remove_action( $hook, array( $this->ef_module, $callback ) ); - add_action( $hook, array( $this, $callback ) ); - } - } - } - } - - function check_active_plugins() { - include_once( ABSPATH . 'wp-admin/includes/plugin.php' ); - - self::$active_plugins = [ - 'classic-editor' => is_plugin_active( 'classic-editor' ), - 'gutenberg' => is_plugin_active( 'gutenberg' ), - ]; - } /** * Helper function to determine whether we're running WP 5.0. * * @return boolean */ - public static function is_at_least_50() { + private function is_at_least_wp_50() { return version_compare( get_bloginfo( 'version' ), '5.0', '>=' ); } /** - * Helper to determine whether either Gutenberg plugin or Classic Editor plugin is loaded. + * Whether or not we are in the block editor. * - * @param string $slug * @return boolean */ - public static function is_plugin_active( $slug = '' ) { - return isset( self::$active_plugins[ $slug ] ) && self::$active_plugins[ $slug ]; - } - - /** - * Detect whether we should load compatability for the module. - * This runs very early during request lifecycle and may not be precise. It's better to use `get_current_screen()->is_block_editor()` if it's available. - * - * However, Block Editor can be enabled/disabled on a granular basis via the filters for the core and the plugin versions. - * - * use_block_editor_for_post - * use_block_editor_for_post_type - * gutenberg_can_edit_post_type - * gutenberg_can_edit_post - * - * - * This needs to be handled in the compat hook callback. - * - * If any of $conditions evaluates to TRUE, we should apply compat hooks. - * - * @return boolean - */ - protected function should_apply_compat() { - $conditions = [ - /** - * 5.0: - * - * Classic editor either disabled or enabled (either via an option or with GET argument). - * It's a hairy conditional :( - */ - // phpcs:ignore WordPress.VIP.SuperGlobalInputUsage.AccessDetected, WordPress.Security.NonceVerification.NoNonceVerification - self::is_at_least_50() && ! self::is_plugin_active( 'classic-editor' ), - self::is_at_least_50() && self::is_plugin_active( 'classic-editor' ) && ( get_option( 'classic-editor-replace' ) === 'block' && ! isset( $_GET[ 'classic-editor__forget' ] ) ), - self::is_at_least_50() && self::is_plugin_active( 'classic-editor' ) && ( get_option( 'classic-editor-replace' ) === 'classic' && isset( $_GET[ 'classic-editor__forget' ] ) ), - /** - * < 5.0 but Gutenberg plugin is active. - */ - ! self::is_at_least_50() && self::is_plugin_active( 'gutenberg' ), - ]; + public function is_block_editor() { + if ( self::is_at_least_wp_50() && function_exists( 'get_current_screen' ) ) { + return get_current_screen()->is_block_editor(); + } - return count( array_filter( $conditions, function( $c ) { return (bool) $c; } ) ) > 0; + return false; } } diff --git a/edit_flow.php b/edit_flow.php index 72d55f8da..7b1435553 100644 --- a/edit_flow.php +++ b/edit_flow.php @@ -121,10 +121,6 @@ private function load_modules() { if ( file_exists( EDIT_FLOW_ROOT . "/modules/{$module_dir}/$module_dir.php" ) ) { include_once( EDIT_FLOW_ROOT . "/modules/{$module_dir}/$module_dir.php" ); - // Try to load Gutenberg compat files - if ( file_exists( EDIT_FLOW_ROOT . "/modules/{$module_dir}/compat/block-editor.php" ) ) { - include_once( EDIT_FLOW_ROOT . "/modules/{$module_dir}/compat/block-editor.php" ); - } // Prepare the class name because it should be standardized $tmp = explode( '-', $module_dir ); $class_name = ''; @@ -150,10 +146,6 @@ private function load_modules() { foreach( $class_names as $slug => $class_name ) { if ( class_exists( $class_name ) ) { $this->$slug = new $class_name(); - $compat_class_name = "{$class_name}_Block_Editor_Compat"; - if ( class_exists( $compat_class_name ) ) { - $this->$slug->compat = new $compat_class_name( $this->$slug, $this->$slug->get_compat_hooks() ); - } } } diff --git a/modules/custom-status/compat/block-editor.php b/modules/custom-status/compat/block-editor.php deleted file mode 100644 index d1ae15fa3..000000000 --- a/modules/custom-status/compat/block-editor.php +++ /dev/null @@ -1,37 +0,0 @@ -ef_module->disable_custom_statuses_for_post_type() ) { - return; - } - - /** - * WP_Screen::is_block_editor only available in 5.0. If it's available and is false it's safe to say we should only pass through to the module. - */ - if ( Block_Editor_Compatible::is_at_least_50() && ! get_current_screen()->is_block_editor() ) { - return $this->ef_module->action_admin_enqueue_scripts(); - } - - wp_enqueue_style( 'edit-flow-block-custom-status', EDIT_FLOW_URL . 'blocks/dist/custom-status.editor.build.css', false, EDIT_FLOW_VERSION ); - wp_enqueue_script( 'edit-flow-block-custom-status', EDIT_FLOW_URL . 'blocks/dist/custom-status.build.js', array( 'wp-blocks', 'wp-element', 'wp-edit-post', 'wp-plugins', 'wp-components' ), EDIT_FLOW_VERSION ); - - wp_localize_script( 'edit-flow-block-custom-status', 'EditFlowCustomStatuses', $this->get_custom_statuses() ); - } - - /** - * Just a wrapper to make sure we're have simple array instead of associative one. - * - * @return array Custom statuses. - */ - function get_custom_statuses() { - return array_values( $this->ef_module->get_custom_statuses() ); - } -} diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index 2a55e0fb1..79a34ef3b 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -13,20 +13,12 @@ if ( !class_exists( 'EF_Custom_Status' ) ) { class EF_Custom_Status extends EF_Module { + use Block_Editor_Compatible; var $module; private $custom_statuses_cache = array(); - /** - * Define the hooks that need to be unhooked/rehooked to make the module Gutenberg-ready. - * - * @var array - */ - protected $compat_hooks = [ - 'admin_enqueue_scripts' => 'action_admin_enqueue_scripts', - ]; - // This is taxonomy name used to store all our custom statuses const taxonomy_key = 'post_status'; @@ -291,12 +283,18 @@ function disable_custom_statuses_for_post_type( $post_type = null ) { * - We have other custom code for Quick Edit and JS niceties */ function action_admin_enqueue_scripts() { - global $pagenow; - if ( $this->disable_custom_statuses_for_post_type() ) { return; } + // Load block editor assets and return early. + if ( $this->is_block_editor() ) { + wp_enqueue_style( 'edit-flow-block-custom-status-styles', EDIT_FLOW_URL . 'blocks/dist/custom-status.editor.build.css', false, EDIT_FLOW_VERSION ); + wp_enqueue_script( 'edit-flow-block-custom-status-script', EDIT_FLOW_URL . 'blocks/dist/custom-status.build.js', array( 'wp-blocks', 'wp-element', 'wp-edit-post', 'wp-plugins', 'wp-components' ), EDIT_FLOW_VERSION ); + wp_localize_script( 'edit-flow-block-custom-status-script', 'EditFlowCustomStatuses', array_values( $this->get_custom_statuses() ) ); + return; + } + // Load Javascript we need to use on the configuration views (jQuery Sortable and Quick Edit) if ( $this->is_whitelisted_settings_view( $this->module->name ) ) { wp_enqueue_script( 'jquery-ui-sortable' );