From 4a583f22b09b452fbfc44fe76c1e8e1df95b92a3 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sat, 23 Nov 2019 19:33:09 -0600 Subject: [PATCH 01/28] Begin the proof of concept for the refactoring It looks like it is possible to edit the post content directly from a meta box. Now, this meta box is rendered with React. There's still a lot of work left, including porting much of the PHP and JS into this. --- .eslintignore | 2 +- .gitignore | 2 +- css/admin.block-post.css | 5 + js/src/components/block-lab-editor.js | 49 +++++ js/src/components/index.js | 1 + js/src/edit-block.js | 19 ++ js/src/index.js | 1 - php/post-types/class-block-post.php | 178 +----------------- .../unit/post-types/test-class-block-post.php | 9 +- webpack.config.js | 2 +- 10 files changed, 83 insertions(+), 185 deletions(-) create mode 100644 js/src/components/block-lab-editor.js create mode 100644 js/src/components/index.js create mode 100644 js/src/edit-block.js delete mode 100644 js/src/index.js diff --git a/.eslintignore b/.eslintignore index 5497a6199..0b3a60ed8 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,2 @@ js/editor.blocks.js -js/scripts.js +js/admin.edit-block.js diff --git a/.gitignore b/.gitignore index 7d92936ad..ec6bfdf66 100644 --- a/.gitignore +++ b/.gitignore @@ -114,6 +114,6 @@ coverage/html/ # Builds js/editor.blocks.js -js/scripts.js +js/admin.edit-block.js css/blocks.editor.css package/ diff --git a/css/admin.block-post.css b/css/admin.block-post.css index 9a8dc2f92..082d11468 100644 --- a/css/admin.block-post.css +++ b/css/admin.block-post.css @@ -1,3 +1,8 @@ +/* Hide the main block editing area, as that does not apply to this. */ +.post-type-block_lab .editor-block-list__layout { + display: none; +} + #minor-publishing-actions, #misc-publishing-actions #visibility, #misc-publishing-actions .curtime { diff --git a/js/src/components/block-lab-editor.js b/js/src/components/block-lab-editor.js new file mode 100644 index 000000000..68b511051 --- /dev/null +++ b/js/src/components/block-lab-editor.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies + */ +const { blocks } = wp; +const { TextControl } = wp.components; +const { compose } = wp.compose; +const { withDispatch, withSelect } = wp.data; +const { Component } = wp.element; +const { __ } = wp.i18n; + +/** + * The Block Lab field editor. + */ +class BlockLabEditor extends Component { + /** + * Renders the editor. + * + * @return {Function} The rendered component. + */ + render() { + const { content, onChange } = this.props; + + return ( + + ); + } +} + +export default compose( [ + withSelect( ( select ) => { + return { + content: select( 'core/editor' ).getEditedPostContent(), + }; + } ), + withDispatch( ( dispatch ) => { + const store = dispatch( 'core/editor' ); + + return { + onChange( content ) { + store.editPost( { content } ); + store.resetEditorBlocks( blocks.parse( content ) ); + }, + }; + } ), +] )( BlockLabEditor ); diff --git a/js/src/components/index.js b/js/src/components/index.js new file mode 100644 index 000000000..ccd1858fa --- /dev/null +++ b/js/src/components/index.js @@ -0,0 +1 @@ +export { default as BlockLabEditor } from './block-lab-editor'; diff --git a/js/src/edit-block.js b/js/src/edit-block.js new file mode 100644 index 000000000..74a32440c --- /dev/null +++ b/js/src/edit-block.js @@ -0,0 +1,19 @@ +/** + * WordPress dependencies + */ +const { domReady } = wp; +const { dispatch } = wp.data; +const { render } = wp.element; + +/** + * Internal dependencies + */ +import { BlockLabEditor } from './components'; + +domReady( () => { + dispatch( 'core/editor' ).updateEditorSettings( { richEditingEnabled: false } ); + render( + , + document.getElementById( 'bl-block-editor' ), + ); +} ); diff --git a/js/src/index.js b/js/src/index.js deleted file mode 100644 index 236bc077c..000000000 --- a/js/src/index.js +++ /dev/null @@ -1 +0,0 @@ -// Import components. diff --git a/php/post-types/class-block-post.php b/php/post-types/class-block-post.php index e52a3fb16..1d410c591 100644 --- a/php/post-types/class-block-post.php +++ b/php/post-types/class-block-post.php @@ -214,7 +214,8 @@ public function register_post_type() { 'hierarchical' => true, 'capabilities' => $this->get_capabilities(), 'map_meta_cap' => true, - 'supports' => array( 'title' ), + 'supports' => array( 'title', 'editor' ), + 'show_in_rest' => true, ); register_post_type( $this->slug, $args ); @@ -281,8 +282,8 @@ public function enqueue_scripts() { wp_enqueue_script( 'block-post', - $this->plugin->get_url( 'js/admin.block-post.js' ), - array( 'jquery', 'jquery-ui-sortable', 'wp-util', 'wp-blocks' ), + $this->plugin->get_url( 'js/admin.edit-block.js' ), + array( 'wp-blocks', 'wp-compose', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n' ), $this->plugin->get_version(), false ); @@ -519,67 +520,9 @@ public function render_properties_meta_box() { * @return void */ public function render_fields_meta_box() { - $post = get_post(); - $block = new Block( $post->ID ); - do_action( 'block_lab_before_fields_list' ); ?> -
- - - - - - - - - - - - - - -
- - - - - -
-
-

- Add Field below to add your first field.', 'block-lab' ) ); ?> -

- fields ) > 0 ) { - foreach ( $block->fields as $field ) { - $this->render_fields_meta_box_row( $field, uniqid() ); - } - } - ?> -
-
-
-
- - - -
+
keywords = $keywords; } - // Block fields. - if ( isset( $_POST['block-fields-name'] ) && is_array( $_POST['block-fields-name'] ) ) { - // We loop through this array and sanitize its content according to the content type. - $fields = wp_unslash( $_POST['block-fields-name'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - foreach ( $fields as $key => $name ) { - // Field name and order. - $field_config = array( 'name' => sanitize_key( $name ) ); - - // Field label. - if ( isset( $_POST['block-fields-label'][ $key ] ) ) { - $field_config['label'] = sanitize_text_field( - wp_unslash( $_POST['block-fields-label'][ $key ] ) - ); - } - - // Field control. - if ( isset( $_POST['block-fields-control'][ $key ] ) ) { - $field_config['control'] = sanitize_text_field( - wp_unslash( $_POST['block-fields-control'][ $key ] ) - ); - } - - // Field type. - if ( isset( $field_config['control'] ) && isset( $this->controls[ $field_config['control'] ] ) ) { - $field_config['type'] = $this->controls[ $field_config['control'] ]->type; - } - - /* - * Field settings. - * If the field is a pro field that's no longer available, re-save the previous value of that field. - * This allows saving other new fields, while retaining the previous pro field value in case the user reactivates the license. - */ - if ( ! empty( $_POST['block-is-disabled-pro-field'][ $key ] ) ) { - $previous_block = new Block( $post_id ); - foreach ( $previous_block->fields as $previous_field ) { - if ( $name === $previous_field->name ) { - $field = $previous_field; - break; - } - } - } elseif ( isset( $field_config['control'] ) && isset( $this->controls[ $field_config['control'] ] ) ) { - $control = $this->controls[ $field_config['control'] ]; - foreach ( $control->settings as $setting ) { - $value = false; // This is a good default, it allows us to pick up on unchecked checkboxes. - - if ( isset( $_POST['block-fields-settings'][ $key ][ $setting->name ] ) ) { - $value = $_POST['block-fields-settings'][ $key ][ $setting->name ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - $value = wp_unslash( $value ); - } - - // Sanitize the field options according to their type. - if ( is_callable( $setting->sanitize ) ) { - $value = call_user_func( $setting->sanitize, $value ); - } - - // Validate the field options according to their type. - if ( is_callable( $setting->validate ) ) { - $value = call_user_func( - $setting->validate, - $value, - $field_config['settings'] - ); - } - - $field_config['settings'][ $setting->name ] = $value; - - $field = new Field( $field_config ); - } - } else { - $field = new Field( $field_config ); - } - - /* - * Sub-Fields - * If there's a "block-fields-parent" input, include the current field in a "sub-fields" field setting - * for the specified parent. - */ - if ( ! empty( $_POST['block-fields-parent'][ $key ] ) ) { - $parent_uid = sanitize_key( $_POST['block-fields-parent'][ $key ] ); - - // The parent's name should have been submitted. - if ( ! isset( $fields[ $parent_uid ] ) ) { - continue; - } - - $parent = $fields[ $parent_uid ]; - - // The parent field should be set by now. We expect it to always precede the child field. - if ( ! isset( $block->fields[ $parent ] ) ) { - continue; - } - - if ( ! isset( $block->fields[ $parent ]->settings['sub_fields'] ) ) { - $block->fields[ $parent ]->settings['sub_fields'] = array(); - } - - $field->settings['parent'] = $parent; - $field->order = count( - $block->fields[ $parent ]->settings['sub_fields'] - ); - - $block->fields[ $parent ]->settings['sub_fields'][ $name ] = $field; - } else { - $field->order = count( $block->fields ); - - $block->fields[ $name ] = $field; - } - } - } - $data['post_content'] = wp_slash( $block->to_json() ); return $data; } diff --git a/tests/php/unit/post-types/test-class-block-post.php b/tests/php/unit/post-types/test-class-block-post.php index fae5c60e1..7f9cfc16e 100644 --- a/tests/php/unit/post-types/test-class-block-post.php +++ b/tests/php/unit/post-types/test-class-block-post.php @@ -213,16 +213,9 @@ public function test_render_properties_meta_box() { * @covers \Block_Lab\Post_Types\Block_Post::render_fields_meta_box() */ public function test_render_fields_meta_box() { - $this->load_dummy_block(); - ob_start(); $this->instance->render_fields_meta_box(); - $fields_meta_box = ob_get_clean(); - - $this->assertNotEmpty( $fields_meta_box ); - $this->assertGreaterThan( 0, strpos( $fields_meta_box, 'block-fields-list' ) ); - $this->assertGreaterThan( 0, strpos( $fields_meta_box, 'block-fields-actions-add-field' ) ); - $this->assertGreaterThan( 0, strpos( $fields_meta_box, 'block_lab_fields_nonce' ) ); + $this->assertContains( '
', ob_get_clean() ); } /** diff --git a/webpack.config.js b/webpack.config.js index 1ea61f54b..57824640c 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -40,7 +40,7 @@ const extractConfig = { module.exports = { entry: { './js/editor.blocks': './js/blocks/index.js', - './js/scripts': './js/src/index.js', + './js/admin.edit-block': './js/src/edit-block.js', }, output: { path: path.resolve( __dirname ), From dae00a86a83f191cd86b1f00ed276c0295304326 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sat, 23 Nov 2019 19:47:05 -0600 Subject: [PATCH 02/28] At least for now, delete PHP saving of Block Properties meta box This can overwrite what's saved in the main block fields meta box. Also, the Block Properties meta box might have to be rendered by React, as the 'Slug' field gets its initial value from the post title. --- php/post-types/class-block-post.php | 125 ------------------ .../unit/post-types/test-class-block-post.php | 1 - 2 files changed, 126 deletions(-) diff --git a/php/post-types/class-block-post.php b/php/post-types/class-block-post.php index 1d410c591..8d949e0b6 100644 --- a/php/post-types/class-block-post.php +++ b/php/post-types/class-block-post.php @@ -70,7 +70,6 @@ public function register_hooks() { add_filter( 'enter_title_here', array( $this, 'post_title_placeholder' ) ); add_action( 'post_submitbox_misc_actions', array( $this, 'post_type_condition' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); - add_action( 'wp_insert_post_data', array( $this, 'save_block' ), 10, 2 ); add_action( 'init', array( $this, 'register_controls' ) ); add_filter( 'block_lab_field_value', array( $this, 'get_field_value' ), 10, 3 ); add_filter( 'block_lab_sub_field_value', array( $this, 'get_field_value' ), 10, 3 ); @@ -897,130 +896,6 @@ public function ajax_field_settings() { wp_send_json_success( $data ); } - /** - * Save block meta boxes as a json blob in post content. - * - * @param array $data An array of slashed post data. - * - * @return array - */ - public function save_block( $data ) { - if ( ! isset( $_POST['post_ID'] ) ) { - return $data; - } - - $post_id = sanitize_key( $_POST['post_ID'] ); - - // Exits script depending on save status. - if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) { - return $data; - } - - // Exits script if not the right post type. - if ( $this->slug !== $data['post_type'] ) { - return $data; - } - - check_admin_referer( 'block_lab_save_properties', 'block_lab_properties_nonce' ); - - // Strip encoded special characters, like 🖖 (%f0%9f%96%96). - $data['post_name'] = preg_replace( '/%[a-f|0-9][a-f|0-9]/', '', $data['post_name'] ); - - // sanitize_title() allows underscores, but register_block_type doesn't. - $data['post_name'] = str_replace( '_', '-', $data['post_name'] ); - - // If only special characters were used, it's possible the post_name is now empty. - if ( '' === $data['post_name'] ) { - $data['post_name'] = $post_id; - } - - // register_block_type doesn't allow slugs starting with a number. - if ( is_numeric( $data['post_name'][0] ) ) { - $data['post_name'] = 'block-' . $data['post_name']; - } - - // Make sure the block slug is still unique. - $data['post_name'] = wp_unique_post_slug( - $data['post_name'], - $post_id, - $data['post_status'], - $data['post_type'], - $data['post_parent'] - ); - - $block = new Block(); - - // Block name. - $block->name = sanitize_key( $data['post_name'] ); - if ( '' === $block->name ) { - $block->name = $post_id; - } - - // Block title. - $block->title = sanitize_text_field( - wp_unslash( $data['post_title'] ) - ); - if ( '' === $block->title ) { - $block->title = $post_id; - } - - // Block excluded post type. - if ( isset( $_POST['block-excluded-post-types'] ) ) { - $excluded = sanitize_text_field( - wp_unslash( $_POST['block-excluded-post-types'] ) - ); - if ( ! empty( $excluded ) ) { - $block->excluded = explode( ',', $excluded ); - } - } - - // Block icon. - if ( isset( $_POST['block-properties-icon'] ) ) { - $block->icon = sanitize_key( $_POST['block-properties-icon'] ); - } - - // Block category. - if ( isset( $_POST['block-properties-category'] ) ) { - $category_slug = sanitize_key( $_POST['block-properties-category'] ); - $categories = get_block_categories( the_post() ); - - if ( '__custom' === $category_slug && isset( $_POST['block-properties-category-name'] ) ) { - $category = array( - 'slug' => sanitize_key( $_POST['block-properties-category-name'] ), - 'title' => sanitize_text_field( - wp_unslash( $_POST['block-properties-category-name'] ) - ), - 'icon' => null, - ); - } else { - $category_slugs = wp_list_pluck( $categories, 'slug' ); - $category_key = array_search( $category_slug, $category_slugs, true ); - $category = $categories[ $category_key ]; - } - - if ( ! $category ) { - $category = isset( $categories[0] ) ? $categories[0] : ''; - } - - $block->category = $category; - } - - // Block keywords. - if ( isset( $_POST['block-properties-keywords'] ) ) { - $keywords = sanitize_text_field( - wp_unslash( $_POST['block-properties-keywords'] ) - ); - $keywords = explode( ',', $keywords ); - $keywords = array_map( 'trim', $keywords ); - $keywords = array_slice( $keywords, 0, 3 ); - - $block->keywords = $keywords; - } - - $data['post_content'] = wp_slash( $block->to_json() ); - return $data; - } - /** * Change the default "Enter Title Here" placeholder on the edit post screen. * diff --git a/tests/php/unit/post-types/test-class-block-post.php b/tests/php/unit/post-types/test-class-block-post.php index 7f9cfc16e..59831291b 100644 --- a/tests/php/unit/post-types/test-class-block-post.php +++ b/tests/php/unit/post-types/test-class-block-post.php @@ -58,7 +58,6 @@ public function test_register_hooks() { $this->assertEquals( 10, has_action( 'post_submitbox_start', array( $this->instance, 'save_draft_button' ) ) ); $this->assertEquals( 10, has_action( 'enter_title_here', array( $this->instance, 'post_title_placeholder' ) ) ); $this->assertEquals( 10, has_action( 'admin_enqueue_scripts', array( $this->instance, 'enqueue_scripts' ) ) ); - $this->assertEquals( 10, has_action( 'wp_insert_post_data', array( $this->instance, 'save_block' ) ) ); $this->assertEquals( 10, has_action( 'init', array( $this->instance, 'register_controls' ) ) ); $this->assertEquals( 10, has_action( 'block_lab_field_value', array( $this->instance, 'get_field_value' ) ) ); From 558fdf706673dd05d422236b9b398c95572f7fa0 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sat, 23 Nov 2019 20:09:57 -0600 Subject: [PATCH 03/28] Port PHP rendering to React, mainly copying Port some of the PHP from render_fields_meta_box() into the React component. Still, it doesn't actuall add fields, and doesn't save anything. --- js/src/components/block-lab-editor.js | 50 ++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/js/src/components/block-lab-editor.js b/js/src/components/block-lab-editor.js index 68b511051..f0bee1138 100644 --- a/js/src/components/block-lab-editor.js +++ b/js/src/components/block-lab-editor.js @@ -2,11 +2,11 @@ * WordPress dependencies */ const { blocks } = wp; -const { TextControl } = wp.components; +const { Button } = wp.components; const { compose } = wp.compose; const { withDispatch, withSelect } = wp.data; const { Component } = wp.element; -const { __ } = wp.i18n; +const { __, sprintf } = wp.i18n; /** * The Block Lab field editor. @@ -18,14 +18,46 @@ class BlockLabEditor extends Component { * @return {Function} The rendered component. */ render() { - const { content, onChange } = this.props; - return ( - +
+
+ + + + + + + + + + + + + + +
+ { __( 'Field Label', 'block-lab' ) } + + { __( 'Field Name', 'block-lab' ) } + + { __( 'Field Type', 'block-lab' ) } +
+
+

+ { sprintf( __( 'Click %sAdd Field%s below to add your first field.', 'block-lab' ), '', ' + { /* @todo: implement render_fields_meta_box_row() here. */ } +

+
+
+
+ + { /* @todo: reimplement render_fields_meta_box_row and render_fields_sub_rows() */ } +
+
); } } From f85c315e66826af46365f09db07f5e80cd396a99 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 24 Nov 2019 01:27:24 -0600 Subject: [PATCH 04/28] Render basic field rows from the post content Add a helper function to get the post content. Port render_fields_meta_box_row() into the component , and render a row. --- js/src/components/block-lab-editor.js | 21 ++- js/src/components/field-row.js | 180 ++++++++++++++++++++++++++ js/src/components/index.js | 1 + js/src/helpers/index.js | 15 +++ 4 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 js/src/components/field-row.js create mode 100644 js/src/helpers/index.js diff --git a/js/src/components/block-lab-editor.js b/js/src/components/block-lab-editor.js index f0bee1138..95114a95f 100644 --- a/js/src/components/block-lab-editor.js +++ b/js/src/components/block-lab-editor.js @@ -6,7 +6,13 @@ const { Button } = wp.components; const { compose } = wp.compose; const { withDispatch, withSelect } = wp.data; const { Component } = wp.element; -const { __, sprintf } = wp.i18n; +const { __ } = wp.i18n; + +/** + * Internal dependencies + */ +import { getBlockFromContent } from '../helpers'; +import { FieldRow } from '.'; /** * The Block Lab field editor. @@ -18,6 +24,10 @@ class BlockLabEditor extends Component { * @return {Function} The rendered component. */ render() { + const { content } = this.props; + const parsedBlock = getBlockFromContent( content ); + const { fields } = parsedBlock; + return (
@@ -41,9 +51,13 @@ class BlockLabEditor extends Component {

- { sprintf( __( 'Click %sAdd Field%s below to add your first field.', 'block-lab' ), '', ' - { /* @todo: implement render_fields_meta_box_row() here. */ } + { + !! fields && Object.values( fields ).map( ( field, index ) => { + return ; + } ) + }

@@ -55,7 +69,6 @@ class BlockLabEditor extends Component { { __( 'Add Field', 'block-lab' ) } - { /* @todo: reimplement render_fields_meta_box_row and render_fields_sub_rows() */ }
); diff --git a/js/src/components/field-row.js b/js/src/components/field-row.js new file mode 100644 index 000000000..106f99587 --- /dev/null +++ b/js/src/components/field-row.js @@ -0,0 +1,180 @@ +/** + * WordPress dependencies + */ +const { Button, TextControl } = wp.components; +const { Component } = wp.element; +const { __ } = wp.i18n; + +/** + * The Block Lab field editor. + */ +export default class FieldRow extends Component { + /** + * Renders the editor. + * + * @return {Function} The rendered component. + */ + render() { + const { field, uid } = this.props; + const isFieldDisabled = false; + + return ( +
+
+
+ +
+
+ +
+ + + +
+
+
+ { field.name } +
+
+ { + /* controls[ $field->control ] ) ) : + echo esc_html( $this->controls[ $field->control ]->label ); + else : + + + + sprintf( + __( 'This %1$s field requires an active pro license.', 'block-lab' ), + field.control, + 'https://example.com' + ) + + + endif; + */ + } +
+
+
+ + + + + + + + + + + + + + + + + { /* render_field_settings( $field, $uid ); ?> */ } + + + + + +
+ +

+ { __( 'A label describing your block\'s custom field.', 'block-lab' ) } +

+
+ +
+ +

+ { __( 'Single word, no spaces.', 'block-lab' ) } +

+
+ +
+ + + +
+ + +
+
+ + { + /* + @todo: reimpliment this in JS. + if ( 'repeater' === field.control ) { + if ( ! field.settings.sub_fields ) { + field.settings.sub_fields = []; + } + $this->render_fields_sub_rows( $field->settings['sub_fields'], $uid ); + } + */ + } +
+ ); + } +} diff --git a/js/src/components/index.js b/js/src/components/index.js index ccd1858fa..d6939eb89 100644 --- a/js/src/components/index.js +++ b/js/src/components/index.js @@ -1 +1,2 @@ export { default as BlockLabEditor } from './block-lab-editor'; +export { default as FieldRow } from './field-row'; diff --git a/js/src/helpers/index.js b/js/src/helpers/index.js new file mode 100644 index 000000000..1635d276e --- /dev/null +++ b/js/src/helpers/index.js @@ -0,0 +1,15 @@ +/** + * Parses the block from the post content into an object. + * + * @param {string} content The post content, probably containing JSON. + * @return {Object|boolean} The block parsed into an object. + */ +export const getBlockFromContent = ( content ) => { + try { + const parsedContent = JSON.parse( content ); + const values = Object.values( parsedContent ); + return values[ 0 ] ? values[ 0 ] : false; + } catch ( e ) { + return false; + } +}; From 8c6212ad9658743b491a438a384ea5890db4fa83 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 24 Nov 2019 11:41:18 -0600 Subject: [PATCH 05/28] Correct DocBlocks in FieldRow I copied these from BlockLabEditor, so update them for the new component. --- js/src/components/field-row.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/components/field-row.js b/js/src/components/field-row.js index 106f99587..6d4b95546 100644 --- a/js/src/components/field-row.js +++ b/js/src/components/field-row.js @@ -6,11 +6,11 @@ const { Component } = wp.element; const { __ } = wp.i18n; /** - * The Block Lab field editor. + * A field row. */ export default class FieldRow extends Component { /** - * Renders the editor. + * Renders the field row. * * @return {Function} The rendered component. */ From 32aa1d9ab8005fdb76f2109ccd9a78b7cafdb854 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Mon, 16 Dec 2019 21:46:06 -0600 Subject: [PATCH 06/28] Add a basic JS unit test setup, modeled after AMP plugin tests/js/jest.config.js is mainly copied from the AMP plugin, though several values are removed. This should begin the work for Jest unit tests, and new helper functions should all have tests. --- .travis.yml | 6 +++++ js/src/components/block-lab-editor.js | 2 +- .../{index.js => getBlockFromContent.js} | 6 +++-- js/src/helpers/test/getBlockFromContent.js | 24 +++++++++++++++++++ package.json | 7 ++++-- tests/js/jest.config.js | 17 +++++++++++++ 6 files changed, 57 insertions(+), 5 deletions(-) rename js/src/helpers/{index.js => getBlockFromContent.js} (68%) create mode 100644 js/src/helpers/test/getBlockFromContent.js create mode 100644 tests/js/jest.config.js diff --git a/.travis.yml b/.travis.yml index ea90695ef..43ad2b974 100755 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,8 @@ matrix: env: WP_VERSION=trunk - php: 5.6 env: WP_TRAVISCI=lint + - php: 7.3 + env: WP_TRAVISCI=test-js install: - composer install @@ -58,6 +60,10 @@ before_script: npm install npm run lint:js fi + - | + if [[ "$WP_TRAVISCI" == "test-js" ]] ; then + npm run test:js + fi script: - | diff --git a/js/src/components/block-lab-editor.js b/js/src/components/block-lab-editor.js index 95114a95f..90b86f701 100644 --- a/js/src/components/block-lab-editor.js +++ b/js/src/components/block-lab-editor.js @@ -11,7 +11,7 @@ const { __ } = wp.i18n; /** * Internal dependencies */ -import { getBlockFromContent } from '../helpers'; +import getBlockFromContent from '../helpers/getBlockFromContent'; import { FieldRow } from '.'; /** diff --git a/js/src/helpers/index.js b/js/src/helpers/getBlockFromContent.js similarity index 68% rename from js/src/helpers/index.js rename to js/src/helpers/getBlockFromContent.js index 1635d276e..279610b33 100644 --- a/js/src/helpers/index.js +++ b/js/src/helpers/getBlockFromContent.js @@ -2,9 +2,9 @@ * Parses the block from the post content into an object. * * @param {string} content The post content, probably containing JSON. - * @return {Object|boolean} The block parsed into an object. + * @return {Object|boolean} The block parsed into an object, or false. */ -export const getBlockFromContent = ( content ) => { +const getBlockFromContent = ( content ) => { try { const parsedContent = JSON.parse( content ); const values = Object.values( parsedContent ); @@ -13,3 +13,5 @@ export const getBlockFromContent = ( content ) => { return false; } }; + +export default getBlockFromContent; diff --git a/js/src/helpers/test/getBlockFromContent.js b/js/src/helpers/test/getBlockFromContent.js new file mode 100644 index 000000000..16d8bb241 --- /dev/null +++ b/js/src/helpers/test/getBlockFromContent.js @@ -0,0 +1,24 @@ +/** + * Internal dependencies + */ +import getBlockFromContent from '../getBlockFromContent'; + +describe( 'getBlockFromContent', () => { + const mockValue = { foo: 'example', bar: 'baz' }; + + it( 'should return false if the content is an empty string', () => { + expect( getBlockFromContent( '' ) ).toStrictEqual( false ); + } ); + + it( 'should return false when passed invalid JSON, and not throw an error', () => { + expect( getBlockFromContent( '{"0: "example value"' ) ).toStrictEqual( false ); + } ); + + it( 'should return false if there is no value at the "0" index', () => { + expect( getBlockFromContent( JSON.stringify( { 1: mockValue } ) ) ).toStrictEqual( mockValue ); + } ); + + it( 'should return the value at the "0" index', () => { + expect( getBlockFromContent( JSON.stringify( { 0: mockValue } ) ) ).toStrictEqual( mockValue ); + } ); +} ); diff --git a/package.json b/package.json index 61a16b256..56351a9ef 100644 --- a/package.json +++ b/package.json @@ -14,13 +14,15 @@ "lint:js": "eslint js", "lint:js:fix": "eslint js --fix", "lint:php": "vendor/bin/phpcs", - "lint:php:fix": "vendor/bin/phpcbf" + "lint:php:fix": "vendor/bin/phpcbf", + "test:js": "wp-scripts test-unit-js --config=tests/js/jest.config.js" }, "author": "Block Lab", "license": "GPL-2.0+", "devDependencies": { - "@wordpress/babel-preset-default": "^1.2.0", + "@wordpress/babel-preset-default": "4.7.0", "@wordpress/eslint-plugin": "3.0.0", + "@wordpress/scripts": "5.1.0", "babel-core": "^6.26.3", "babel-eslint": "^8.2.3", "babel-loader": "^7.1.4", @@ -35,6 +37,7 @@ "gulp": "^4.0.0", "gulp-run": "^1.7.1", "gulp-string-replace": "^1.1.1", + "jest-silent-reporter": "0.1.2", "merge-stream": "^1.0.1", "node-sass": "^4.9.0", "npm-run-all": "4.1.5", diff --git a/tests/js/jest.config.js b/tests/js/jest.config.js new file mode 100644 index 000000000..93146de18 --- /dev/null +++ b/tests/js/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + rootDir: '../../', + ...require( '@wordpress/scripts/config/jest-unit.config' ), + transform: { + '^.+\\.[jt]sx?$': '/node_modules/@wordpress/scripts/config/babel-transform', + }, + testPathIgnorePatterns: [ + '/.git', + '/node_modules', + ], + coveragePathIgnorePatterns: [ + '/node_modules', + ], + coverageReporters: [ 'lcov' ], + coverageDirectory: '/coverage', + reporters: [ [ 'jest-silent-reporter', { useDots: true } ] ], +}; From a0c85106e882a05891d812205798c9ecfd984876 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Mon, 16 Dec 2019 22:05:12 -0600 Subject: [PATCH 07/28] Attempt to fix failed Travis build Do npm install before running npm run lint:js. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 43ad2b974..402db2f0b 100755 --- a/.travis.yml +++ b/.travis.yml @@ -62,6 +62,7 @@ before_script: fi - | if [[ "$WP_TRAVISCI" == "test-js" ]] ; then + npm install npm run test:js fi From 3486c27968da8e64ff6acf9ea1572da72c8e24e6 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Mon, 16 Dec 2019 22:57:10 -0600 Subject: [PATCH 08/28] Move comment to the relevant file, do npm run lint:js:fix There's a comment for loader/index.js. But that probably fits better in that file. --- js/admin.block-post.js | 2 +- js/blocks/components/advanced-controls.js | 2 +- js/blocks/components/fetch-input.js | 2 +- js/blocks/components/repeater-rows.js | 2 +- js/blocks/i18n.js | 1 + js/blocks/index.js | 3 --- js/blocks/loader/fields.js | 2 +- 7 files changed, 6 insertions(+), 8 deletions(-) diff --git a/js/admin.block-post.js b/js/admin.block-post.js index 2c0d80baa..db9a7502e 100644 --- a/js/admin.block-post.js +++ b/js/admin.block-post.js @@ -352,7 +352,7 @@ const postType = $( input ).val(); if ( -1 === excludedPostTypes.indexOf( postType ) ) { displayList.push( - $( input ).next( 'label' ).text() + $( input ).next( 'label' ).text(), ); } } diff --git a/js/blocks/components/advanced-controls.js b/js/blocks/components/advanced-controls.js index 8da000120..be58f2998 100644 --- a/js/blocks/components/advanced-controls.js +++ b/js/blocks/components/advanced-controls.js @@ -20,7 +20,7 @@ const AdvancedControls = ( { block } ) => { const tip = sprintf( __( 'The Additional CSS Class can be included in your block template with the %1$s field.', 'block-lab' ), - 'className' + 'className', ); return ( diff --git a/js/blocks/components/fetch-input.js b/js/blocks/components/fetch-input.js index 37134cda4..b0f680728 100644 --- a/js/blocks/components/fetch-input.js +++ b/js/blocks/components/fetch-input.js @@ -133,7 +133,7 @@ class FetchInput extends Component { '%d result found, use up and down arrow keys to navigate.', '%d results found, use up and down arrow keys to navigate.', results.length, - 'block-lab' + 'block-lab', ), results.length ), 'assertive' ); if ( null === this.state.selectedSuggestion && '' !== this.getInputValue() ) { diff --git a/js/blocks/components/repeater-rows.js b/js/blocks/components/repeater-rows.js index ed875e389..5c44d0dd1 100644 --- a/js/blocks/components/repeater-rows.js +++ b/js/blocks/components/repeater-rows.js @@ -122,7 +122,7 @@ class RepeaterRows extends Component { setTimeout( () => { /* eslint-disable-line @wordpress/react-no-unsafe-timeout */ rowRefTo.classList.remove( 'row-to' ); rowRefFrom.classList.remove( 'row-from' ); - }, 1000 ) + }, 1000 ), ); scrollContainer.scroll( { top: scrollTop, behavior: 'smooth' } ); diff --git a/js/blocks/i18n.js b/js/blocks/i18n.js index 92a213fb8..807290a27 100644 --- a/js/blocks/i18n.js +++ b/js/blocks/i18n.js @@ -1 +1,2 @@ +// Needed for for global context. wp.i18n.setLocaleData( { '': {} }, 'block-lab' ); diff --git a/js/blocks/index.js b/js/blocks/index.js index 4c78b0493..33c8a046d 100644 --- a/js/blocks/index.js +++ b/js/blocks/index.js @@ -1,8 +1,5 @@ -// Import localization. Need to import for global context. /** * Internal dependencies */ import './i18n'; - -// Import Block Lab loader. import './loader'; diff --git a/js/blocks/loader/fields.js b/js/blocks/loader/fields.js index c7c759353..5c75cce15 100644 --- a/js/blocks/loader/fields.js +++ b/js/blocks/loader/fields.js @@ -38,7 +38,7 @@ const simplifiedFields = ( fields ) => { { ...field, name: fieldName, - } + }, ); } From 95384cfca5748a911c45ae3e2a696ee79e0605ab Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sat, 21 Dec 2019 15:30:58 -0600 Subject: [PATCH 09/28] Restructure js files into js/src There are now 2 directories in that:: block-editor/ block-lab-editor/ This might be changed, but I think it's a first step in making the directories a little easier to understand. --- .eslintignore | 4 +- .gitignore | 3 +- gulpfile.js | 1 - js/admin.block-post.js | 527 ------------------ js/blocks/i18n.js | 2 - js/blocks/index.js | 5 - .../components/advanced-controls.js | 0 .../components/block-lab-inspector.js | 6 +- .../components/content-control.js | 0 .../components}/controls/checkbox.js | 0 .../components}/controls/classic-text.js | 2 +- .../components}/controls/color.js | 0 .../components}/controls/email.js | 0 .../components}/controls/image.js | 2 +- .../components}/controls/index.js | 0 .../components}/controls/multiselect.js | 0 .../components}/controls/number.js | 0 .../block-editor/components}/controls/post.js | 2 +- .../components}/controls/radio.js | 0 .../components}/controls/range.js | 0 .../components}/controls/repeater.js | 2 +- .../components}/controls/rich-text.js | 0 .../components}/controls/select.js | 0 .../components}/controls/taxonomy.js | 2 +- .../block-editor/components}/controls/text.js | 0 .../components}/controls/textarea.js | 0 .../components}/controls/toggle.js | 0 .../block-editor/components}/controls/url.js | 0 .../block-editor/components}/controls/user.js | 2 +- .../block-editor}/components/edit.js | 2 +- .../block-editor}/components/fetch-input.js | 0 .../block-editor}/components/fields.js | 6 +- .../block-editor}/components/form-controls.js | 0 .../block-editor}/components/image.js | 0 .../block-editor}/components/index.js | 0 .../block-editor}/components/repeater-rows.js | 0 .../block-editor}/components/tiny-mce.js | 0 .../helpers/getBlockAttributes.js} | 0 .../helpers/getSimplifiedFields.js} | 4 +- .../block-editor/helpers/registerBlocks.js} | 7 +- js/src/block-editor/index.js | 12 + .../components/block-lab-editor.js | 0 .../components/field-row.js | 0 .../components/index.js | 0 .../helpers/getBlockFromContent.js | 0 .../helpers/test/getBlockFromContent.js | 0 .../index.js} | 0 php/blocks/class-loader.php | 4 +- php/post-types/class-block-post.php | 2 +- tests/php/unit/blocks/test-class-loader.php | 4 +- webpack.config.js | 5 +- 51 files changed, 40 insertions(+), 566 deletions(-) delete mode 100644 js/admin.block-post.js delete mode 100644 js/blocks/i18n.js delete mode 100644 js/blocks/index.js rename js/{blocks => src/block-editor}/components/advanced-controls.js (100%) rename js/{blocks => src/block-editor}/components/block-lab-inspector.js (89%) rename js/{blocks => src/block-editor}/components/content-control.js (100%) rename js/{blocks => src/block-editor/components}/controls/checkbox.js (100%) rename js/{blocks => src/block-editor/components}/controls/classic-text.js (95%) rename js/{blocks => src/block-editor/components}/controls/color.js (100%) rename js/{blocks => src/block-editor/components}/controls/email.js (100%) rename js/{blocks => src/block-editor/components}/controls/image.js (80%) rename js/{blocks => src/block-editor/components}/controls/index.js (100%) rename js/{blocks => src/block-editor/components}/controls/multiselect.js (100%) rename js/{blocks => src/block-editor/components}/controls/number.js (100%) rename js/{blocks => src/block-editor/components}/controls/post.js (92%) rename js/{blocks => src/block-editor/components}/controls/radio.js (100%) rename js/{blocks => src/block-editor/components}/controls/range.js (100%) rename js/{blocks => src/block-editor/components}/controls/repeater.js (97%) rename js/{blocks => src/block-editor/components}/controls/rich-text.js (100%) rename js/{blocks => src/block-editor/components}/controls/select.js (100%) rename js/{blocks => src/block-editor/components}/controls/taxonomy.js (91%) rename js/{blocks => src/block-editor/components}/controls/text.js (100%) rename js/{blocks => src/block-editor/components}/controls/textarea.js (100%) rename js/{blocks => src/block-editor/components}/controls/toggle.js (100%) rename js/{blocks => src/block-editor/components}/controls/url.js (100%) rename js/{blocks => src/block-editor/components}/controls/user.js (96%) rename js/{blocks => src/block-editor}/components/edit.js (96%) rename js/{blocks => src/block-editor}/components/fetch-input.js (100%) rename js/{blocks => src/block-editor}/components/fields.js (96%) rename js/{blocks => src/block-editor}/components/form-controls.js (100%) rename js/{blocks => src/block-editor}/components/image.js (100%) rename js/{blocks => src/block-editor}/components/index.js (100%) rename js/{blocks => src/block-editor}/components/repeater-rows.js (100%) rename js/{blocks => src/block-editor}/components/tiny-mce.js (100%) rename js/{blocks/loader/attributes.js => src/block-editor/helpers/getBlockAttributes.js} (100%) rename js/{blocks/loader/fields.js => src/block-editor/helpers/getSimplifiedFields.js} (91%) rename js/{blocks/loader/index.js => src/block-editor/helpers/registerBlocks.js} (89%) create mode 100644 js/src/block-editor/index.js rename js/src/{ => block-lab-editor}/components/block-lab-editor.js (100%) rename js/src/{ => block-lab-editor}/components/field-row.js (100%) rename js/src/{ => block-lab-editor}/components/index.js (100%) rename js/src/{ => block-lab-editor}/helpers/getBlockFromContent.js (100%) rename js/src/{ => block-lab-editor}/helpers/test/getBlockFromContent.js (100%) rename js/src/{edit-block.js => block-lab-editor/index.js} (100%) diff --git a/.eslintignore b/.eslintignore index 0b3a60ed8..e857f46be 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,2 @@ -js/editor.blocks.js -js/admin.edit-block.js +js/build/block-editor.js +js/build/block-lab-editor.js diff --git a/.gitignore b/.gitignore index ec6bfdf66..402b8a08d 100644 --- a/.gitignore +++ b/.gitignore @@ -113,7 +113,6 @@ tests/includes/ coverage/html/ # Builds -js/editor.blocks.js -js/admin.edit-block.js +js/build/* css/blocks.editor.css package/ diff --git a/gulpfile.js b/gulpfile.js index b48d939e8..f426323c6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -75,7 +75,6 @@ gulp.task( 'clean:bundle', function () { 'package/trunk/package', 'package/trunk/assets/wporg', 'package/trunk/coverage', - 'package/trunk/js/blocks', 'package/trunk/js/src', 'package/trunk/bin', 'package/trunk/node_modules', diff --git a/js/admin.block-post.js b/js/admin.block-post.js deleted file mode 100644 index db9a7502e..000000000 --- a/js/admin.block-post.js +++ /dev/null @@ -1,527 +0,0 @@ -/** - * Used for editing Blocks. - * - * @package Block_Lab - * @copyright Copyright(c) 2019, Block Lab - * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0) - */ - -/* global blockLab, jQuery */ - -( function( $ ) { - $( function() { - blockTitleInit(); - blockIconInit(); - blockFieldInit(); - blockPostTypesInit(); - - $( '#block-add-field' ).on( 'click', function() { - const template = wp.template( 'field-repeater' ), - data = { uid: new Date().getTime() }, - row = $( template( data ) ), - edit = row.find( '.block-fields-actions-edit' ), - label = row.find( '.block-fields-edit-label input' ); - - incrementRow( row ); - - $( '.block-fields-rows' ).append( row ); - $( '.block-no-fields' ).hide(); - $( '.block-lab-add-fields' ).hide(); - - edit.trigger( 'click' ); - label.data( 'defaultValue', label.val() ); - label.trigger( 'change' ); - label.select(); - } ); - - $( '#block_fields' ).on( 'click', '#block-add-sub-field', function() { - const template = wp.template( 'field-repeater' ), - data = { uid: new Date().getTime() }, - row = $( template( data ) ), - parent = $( this ).closest( '.block-fields-row' ), - edit = row.find( '.block-fields-actions-edit' ), - label = row.find( '.block-fields-edit-label input' ); - - incrementRow( row ); - - // Prevents adding a repeater, in a repeater, in a repeater… - row.find( '.block-fields-edit-control option[value="repeater"]' ).remove(); - - // Don't render the location or width settings for sub-fields. - row.find( '.block-fields-edit-location-settings' ).remove(); - row.find( '.block-fields-edit-width-settings' ).remove(); - - // Add parent UID as a hidden input - const parentInput = $( '' ).attr( { - type: 'hidden', - name: 'block-fields-parent[' + data.uid + ']', - value: parent.data( 'uid' ), - } ); - row.append( parentInput ); - - $( '.block-fields-sub-rows', parent ).append( row ); - $( '.repeater-no-fields', parent ).hide(); - $( '.repeater-has-fields', parent ).show(); - - edit.trigger( 'click' ); - label.data( 'defaultValue', label.val() ); - label.trigger( 'change' ); - label.select(); - } ); - - $( '.block-lab-pub-section .edit-post-types' ).on( 'click', function() { - const excludedPostTypes = $( '#block-excluded-post-types' ).val().split( ',' ).filter( Boolean ); - - $( '.post-types-select-items input' ).prop( 'checked', true ); - - for ( const postType of excludedPostTypes ) { - $( '.post-types-select-items input[value="' + postType + '"]' ).prop( 'checked', false ); - } - - $( '.block-lab-pub-section .post-types-select' ).slideDown( 200 ); - $( this ).hide(); - } ); - - $( '.block-lab-pub-section .save-post-types' ).on( 'click', function() { - const checked = $( '.post-types-select-items input:not(:checked)' ), - postTypes = []; - for ( const input of checked ) { - postTypes.push( $( input ).val() ); - } - - $( '#block-excluded-post-types' ).val( postTypes.join( ',' ) ); - - blockPostTypesInit(); - - $( '.block-lab-pub-section .post-types-select' ).slideUp( 200 ); - $( '.block-lab-pub-section .edit-post-types' ).show(); - } ); - - $( '.block-lab-pub-section .button-cancel' ).on( 'click', function() { - $( '.block-lab-pub-section .post-types-select' ).slideUp( 200 ); - $( '.block-lab-pub-section .edit-post-types' ).show(); - } ); - - $( '#block_properties .block-properties-icon-select span' ).on( 'click', function() { - const svg = $( 'svg', this ).clone(); - $( '#block_properties .block-properties-icon-select span.selected' ).removeClass( 'selected' ); - $( this ).addClass( 'selected' ); - $( '#block-properties-icon' ).val( $( this ).data( 'value' ) ); - $( '#block-properties-icon-current' ).html( svg ); - } ); - - $( '#block_properties .block-properties-category' ).on( 'change', function() { - if ( '__custom' === $( this ).val() ) { - $( this ).next( '.block-properties-category-custom' ).css( 'display', 'block' ); - } else { - $( this ).next( '.block-properties-category-custom' ).hide(); - } - } ); - - $( '#block_template .template-location a.filename' ).on( 'click', function( event ) { - event.preventDefault(); - - const copy = $( '#block_template .template-location .click-to-copy' ), - input = $( 'input', copy ), - width = $( this ).width() + input.outerWidth( false ) - input.width(); - - copy.show(); - input.outerWidth( width ).focus().select(); - - const copied = document.execCommand( 'copy' ); - - if ( copied ) { - copy.attr( 'data-tooltip', blockLab.copySuccessMessage ); - } else { - copy.attr( 'data-tooltip', blockLab.copyFailMessage ); - } - - $( this ).hide(); - } ); - - $( '#block_template .template-location .click-to-copy input' ).on( 'blur', function() { - $( '#block_template .template-location a.filename' ).show(); - $( this ).parent().hide(); - } ); - - $( '.block-fields-rows' ) - .on( 'click', '.block-fields-actions-delete', function() { - const subRows = $( this ).closest( '.block-fields-sub-rows' ); - $( this ).closest( '.block-fields-row' ).remove(); - if ( 0 === $( '.block-fields-rows' ).children( '.block-fields-row' ).length ) { - $( '.block-no-fields' ).show(); - } - if ( 0 !== subRows.length && 0 === $( '.block-fields-row', subRows ).length ) { - subRows.parent().find( '.repeater-no-fields' ).show(); - subRows.parent().find( '.repeater-has-fields' ).hide(); - } - } ) - .on( 'click', '.block-fields-actions-duplicate', function() { - const row = $( this ).closest( '.block-fields-row' ), - newRow = cloneRow( row ); - - // Expand the duplicated row. - if ( newRow.hasClass( 'block-fields-row-active' ) ) { - row.find( '.block-fields-actions-edit' ).eq( 0 ).trigger( 'click' ); - } else { - newRow.find( '.block-fields-actions-edit' ).eq( 0 ).trigger( 'click' ); - } - - // Select the label field of the new row. - const label = newRow.find( '.block-fields-edit-label input' ); - label.trigger( 'change' ); - label.select(); - } ) - .on( 'click', '.block-fields-actions-edit, a.row-title', function() { - const currentRow = $( this ).closest( '.block-fields-row' ); - - // If we're expanding this row, first collapse all other rows and scroll this row into view. - if ( ! currentRow.hasClass( 'block-fields-row-active' ) ) { - const editRow = $( '.block-fields-rows .block-fields-edit' ); - - scrollRowIntoView( currentRow ); - editRow.slideUp(); - - $( '.block-fields-rows .block-fields-row-active' ).removeClass( 'block-fields-row-active' ); - } - - currentRow.toggleClass( 'block-fields-row-active' ); - currentRow.find( '.block-fields-edit' ).first().slideToggle(); - - // Fetch field settings if field is active and there are no settings. - if ( $( this ).closest( '.block-fields-row' ).hasClass( 'block-fields-row-active' ) ) { - const fieldRow = $( this ).closest( '.block-fields-row' ); - if ( 0 === fieldRow.find( '.block-fields-edit-settings' ).length ) { - const fieldControl = fieldRow.find( '.block-fields-edit-control select' ).val(); - fetchFieldSettings( fieldRow, fieldControl ); - } - } - } ) - .on( 'click', '.block-fields-edit-actions-close a.button', function() { - const fieldRow = $( this ).closest( '.block-fields-row' ); - fieldRow.removeClass( 'block-fields-row-active' ); - $( '.block-fields-edit', fieldRow ).slideUp(); - } ) - .on( 'change keyup', '.block-fields-edit input', function() { - const sync = $( this ).data( 'sync' ); - $( '#' + sync ).text( $( this ).val() ); - } ) - .on( 'change keyup', '.block-fields-edit select', function() { - const sync = $( this ).data( 'sync' ), - option = $( 'option:selected', $( this ) ).text(); - $( '#' + sync ).text( option ); - } ) - .on( 'change', '.block-fields-edit-control select', function() { - const fieldRow = $( this ).closest( '.block-fields-row' ); - fetchFieldSettings( fieldRow, $( this ).val() ); - - if ( 'repeater' === $( this ).val() ) { - const subRows = wp.template( 'sub-field-rows' ); - fieldRow.append( subRows ); - blockFieldSubRowsInit( $( '.block-fields-sub-rows', fieldRow ) ); - } else { - $( '.block-fields-sub-rows,.block-fields-sub-rows-actions', fieldRow ).remove(); - } - } ) - .on( 'change', '.block-fields-edit-location-settings select', function() { - blockFieldWidthInit( $( this ).closest( '.block-fields-row' ) ); - } ) - .on( 'change keyup', '.block-fields-edit-label input', function() { - const slug = $( this ) - .closest( '.block-fields-edit' ) - .find( '.block-fields-edit-name input' ); - - if ( 'false' !== slug.data( 'autoslug' ) ) { - slug - .val( slugify( $( this ).val() ) ) - .trigger( 'change' ); - } - } ) - .on( 'blur', '.block-fields-edit-label input', function() { - // If the value hasn't changed from default, don't turn off autoslug. - if ( $( this ).data( 'defaultValue' ) === $( this ).val() ) { - return; - } - $( this ) - .closest( '.block-fields-edit' ) - .find( '.block-fields-edit-name input' ) - .data( 'autoslug', 'false' ); - } ) - .on( 'mouseenter', '.block-fields-row div:not(.block-fields-edit,.block-fields-sub-rows,.block-fields-sub-rows-actions)', function() { - $( this ).parent().addClass( 'hover' ); - } ) - .on( 'mouseleave', '.block-fields-row div', function() { - $( this ).parent().removeClass( 'hover' ); - } ) - .sortable( { - axis: 'y', - cursor: 'grabbing', - handle: '> .block-fields-row-columns .block-fields-sort-handle', - containment: 'parent', - tolerance: 'pointer', - } ); - } ); - - const blockTitleInit = function() { - const title = $( '#title' ), - slug = $( '#block-properties-slug' ); - - // If this is a new block, then enable auto-generated slugs. - if ( '' === title.val() && '' === slug.val() ) { - // If auto-generated slugs are enabled, set the slug based on the title. - title.on( 'change keyup', function() { - if ( 'false' !== slug.data( 'autoslug' ) ) { - slug.val( slugify( title.val() ) ); - } - } ); - - // Turn auto-generated slugs off once a title has been set. - title.on( 'blur', function() { - if ( '' !== title.val() ) { - slug.data( 'autoslug', 'false' ); - } - } ); - } - }; - - const blockIconInit = function() { - const iconsContainer = $( '.block-properties-icon-select' ), - selectedIcon = $( '.selected', iconsContainer ); - if ( 0 !== iconsContainer.length && 0 !== selectedIcon.length ) { - iconsContainer.scrollTop( selectedIcon.position().top ); - } - }; - - const blockFieldInit = function() { - if ( 0 === $( '.block-fields-rows' ).children( '.block-fields-row' ).length ) { - $( '.block-no-fields' ).show(); - } - $( '.block-fields-edit-name input' ).data( 'autoslug', 'false' ); - $( '.block-fields-sub-rows' ).each( function() { - blockFieldSubRowsInit( $( this ) ); - } ); - }; - - const blockFieldSubRowsInit = function( subRows ) { - subRows.sortable( { - axis: 'y', - cursor: 'grabbing', - handle: '> .block-fields-row-columns .block-fields-sort-handle', - containment: 'parent', - tolerance: 'pointer', - } ); - }; - - const blockFieldWidthInit = function( fieldRow ) { - const widthSettings = fieldRow.find( '.block-fields-edit-width-settings' ), - locationSettings = fieldRow.find( '.block-fields-edit-location-settings' ); - - if ( 'editor' !== $( 'select', locationSettings ).val() ) { - widthSettings.hide(); - } else { - widthSettings.show(); - } - }; - - const blockPostTypesInit = function() { - if ( 0 === $( '.block-lab-pub-section' ).length ) { - return; - } - - const display = $( '.post-types-display' ); - - const excludedPostTypes = $( '#block-excluded-post-types' ) - .val() - .split( ',' ) - .filter( Boolean ); - - if ( 0 === excludedPostTypes.length ) { - display.text( blockLab.postTypes.all ); - return; - } - - const inputs = $( '.post-types-select-items input' ); - - if ( excludedPostTypes.length === inputs.length ) { - display.text( blockLab.postTypes.none ); - return; - } - - const displayList = []; - for ( const input of inputs ) { - const postType = $( input ).val(); - if ( -1 === excludedPostTypes.indexOf( postType ) ) { - displayList.push( - $( input ).next( 'label' ).text(), - ); - } - } - - display.text( displayList.join( ', ' ) ); - }; - - const fetchFieldSettings = function( fieldRow, fieldControl ) { - if ( ! blockLab.hasOwnProperty( 'fieldSettingsNonce' ) ) { - return; - } - - const loadingRow = '' + - '' + - ' ' + - ' ' + - ' ' + - ''; - - $( '.block-fields-edit-settings', fieldRow ).remove(); - $( '.block-fields-edit-control', fieldRow ).after( $( loadingRow ) ); - - const data = { - control: fieldControl, - uid: fieldRow.data( 'uid' ), - nonce: blockLab.fieldSettingsNonce, - }; - - // If this is a sub-field, pass along the parent UID as well. - if ( fieldRow.parent( '.block-fields-sub-rows' ).length > 0 ) { - data.parent = fieldRow.closest( '.block-fields-row' ).data( 'uid' ); - } - - wp.ajax.send( 'fetch_field_settings', { - success( result ) { - $( '.block-fields-edit-loading', fieldRow ).remove(); - - if ( ! result.hasOwnProperty( 'html' ) ) { - return; - } - const settingsRows = $( result.html ); - $( '.block-fields-edit-control', fieldRow ).after( settingsRows ); - blockFieldWidthInit( fieldRow ); - scrollRowIntoView( fieldRow ); - }, - error() { - $( '.block-fields-edit-loading', fieldRow ).remove(); - }, - data, - } ); - }; - - const scrollRowIntoView = function( row ) { - let scrollTop = 0; - - $( '.block-fields-rows .block-fields-row' ).each( function() { - // Add the height of all previous rows to the target scrollTop position. - if ( $( this ).is( row ) ) { - return false; - } - - const height = $( this ).children().first().outerHeight(); - scrollTop += height; - } ); - - $( 'body' ).animate( { scrollTop } ); - }; - - /** - * Clone a row, used for row duplication. - * - * @param {jQuery} row The row to clone. - * @param {boolean} append Whether to append the cloned row, or just return it. - * @return {jQuery} The cloned row. - */ - const cloneRow = function( row, append = true ) { - const uid = row.data( 'uid' ), - newUid = Math.floor( Math.random() * 1000000000000 ), - newRow = row.clone(), - subRows = newRow.find( '.block-fields-sub-rows' ).children(); - - // Remove the sub rows (we'll add them back again later). - if ( subRows.length > 0 ) { - subRows.remove(); - } - - // Replace all the UIDs. - newRow.attr( 'data-uid', newUid ); - newRow.html( function( index, html ) { - return html.replace( new RegExp( uid, 'g' ), newUid ); - } ); - - // Set the values manually. jQuery's clone method doesn't work for dynamic data. - row.find( '[name*="[' + uid + ']"]' ).each( function() { - const newRowName = $( this ).attr( 'name' ).replace( uid, newUid ), - newRowInput = newRow.find( '[name="' + newRowName + '"]' ); - - // Radio and Checkbox inputs are unique in that multiple can exist with the same name. - if ( $( this ).is( '[type="radio"],[type="checkbox"]' ) ) { - newRowInput.parent().find( '[value="' + $( this ).val() + '"]' ).prop( 'checked', $( this ).prop( 'checked' ) ); - } else { - newRowInput.val( $( this ).val() ); - } - } ); - - incrementRow( newRow ); - - // Insert the new row. - if ( append ) { - newRow.insertAfter( row ); - } - - subRows.each( function() { - $( this ).find( 'input[name^="block-fields-parent"]' ).val( newUid ); - newRow.find( '.block-fields-sub-rows' ).append( cloneRow( $( this ), false ) ); - } ); - - return newRow; - }; - - const incrementRow = function( row ) { - const label = row.find( '.block-fields-edit-label input' ).eq( 0 ), - name = row.find( '.block-fields-edit-name input' ).eq( 0 ), - baseName = name.val().replace( /-\d+$/, '' ), - baseLabel = label.val().replace( / \d+$/, '' ), - nameMatchRegex = new RegExp( '^' + baseName + '(-\\d+)?$' ), - matchedNames = $( 'input[name^="block-fields-name"]' ).filter( function() { - // Get all other rows that have the same base name. - return $( this ).val().match( nameMatchRegex ); - } ); - - let numbers = []; - - // Get the number of each row, then sort them. - matchedNames.each( function() { - numbers.push( $( this ).val().match( /\d*$/ )[ 0 ] ); - } ); - - // Filter out duplicate numbers. - numbers = numbers.filter( function( value, index, self ) { - return self.indexOf( value ) === index; - } ); - - // Put the numbers in ascending order. - numbers = numbers.sort( function( a, b ) { - return b - a; - } ); - - // Assign the new names. - if ( numbers.length > 1 ) { - const newNumber = parseInt( numbers[ 0 ] ) + 1; - name.val( baseName + '-' + newNumber ); - label.val( baseLabel + ' ' + newNumber ); - } else if ( 1 === numbers.length ) { - name.val( name.val() + '-1' ); - label.val( label.val() + ' 1' ); - } else { - name.val( name.val() ); - label.val( label.val() ); - } - - label.data( 'defaultValue', label.val() ); - }; - - const slugify = function( text ) { - return text - .toLowerCase() - .replace( /[^\w ]+/g, '' ) - .replace( / +/g, '-' ) - .replace( /_+/g, '-' ); - }; -}( jQuery ) ); diff --git a/js/blocks/i18n.js b/js/blocks/i18n.js deleted file mode 100644 index 807290a27..000000000 --- a/js/blocks/i18n.js +++ /dev/null @@ -1,2 +0,0 @@ -// Needed for for global context. -wp.i18n.setLocaleData( { '': {} }, 'block-lab' ); diff --git a/js/blocks/index.js b/js/blocks/index.js deleted file mode 100644 index 33c8a046d..000000000 --- a/js/blocks/index.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Internal dependencies - */ -import './i18n'; -import './loader'; diff --git a/js/blocks/components/advanced-controls.js b/js/src/block-editor/components/advanced-controls.js similarity index 100% rename from js/blocks/components/advanced-controls.js rename to js/src/block-editor/components/advanced-controls.js diff --git a/js/blocks/components/block-lab-inspector.js b/js/src/block-editor/components/block-lab-inspector.js similarity index 89% rename from js/blocks/components/block-lab-inspector.js rename to js/src/block-editor/components/block-lab-inspector.js index 51d1c4c91..517cfb9e6 100644 --- a/js/blocks/components/block-lab-inspector.js +++ b/js/src/block-editor/components/block-lab-inspector.js @@ -8,8 +8,8 @@ const { applyFilters } = wp.hooks; /** * Internal dependencies */ -import simplifiedFields from '../loader/fields'; -import controls from '../controls'; +import getSimplifiedFields from '../helpers/getSimplifiedFields'; +import controls from './controls'; /** * Gets the rendered controls for the Inspector Controls, based on the field values. @@ -20,7 +20,7 @@ import controls from '../controls'; * @return {Function|null} The inspector controls. */ const BlockLabInspector = ( { blockProps, block } ) => { - const fields = simplifiedFields( block.fields ).map( ( field ) => { + const fields = getSimplifiedFields( block.fields ).map( ( field ) => { // If it's not meant for the inspector, continue (return null). if ( ! field.location || ! field.location.includes( 'inspector' ) ) { return null; diff --git a/js/blocks/components/content-control.js b/js/src/block-editor/components/content-control.js similarity index 100% rename from js/blocks/components/content-control.js rename to js/src/block-editor/components/content-control.js diff --git a/js/blocks/controls/checkbox.js b/js/src/block-editor/components/controls/checkbox.js similarity index 100% rename from js/blocks/controls/checkbox.js rename to js/src/block-editor/components/controls/checkbox.js diff --git a/js/blocks/controls/classic-text.js b/js/src/block-editor/components/controls/classic-text.js similarity index 95% rename from js/blocks/controls/classic-text.js rename to js/src/block-editor/components/controls/classic-text.js index 57f2823cc..c0fbb665c 100644 --- a/js/blocks/controls/classic-text.js +++ b/js/src/block-editor/components/controls/classic-text.js @@ -6,7 +6,7 @@ const { BaseControl } = wp.components; /** * Internal dependencies */ -import { TinyMCE } from '../components'; +import { TinyMCE } from '../'; const BlockLabClassicTextControl = ( props ) => { const { field, getValue, instanceId, onChange, parentBlockProps, rowIndex } = props; diff --git a/js/blocks/controls/color.js b/js/src/block-editor/components/controls/color.js similarity index 100% rename from js/blocks/controls/color.js rename to js/src/block-editor/components/controls/color.js diff --git a/js/blocks/controls/email.js b/js/src/block-editor/components/controls/email.js similarity index 100% rename from js/blocks/controls/email.js rename to js/src/block-editor/components/controls/email.js diff --git a/js/blocks/controls/image.js b/js/src/block-editor/components/controls/image.js similarity index 80% rename from js/blocks/controls/image.js rename to js/src/block-editor/components/controls/image.js index 0e93de218..bcdeba29a 100644 --- a/js/blocks/controls/image.js +++ b/js/src/block-editor/components/controls/image.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { Image } from '../components'; +import { Image } from '../'; const BlockLabImageControl = ( props ) => { return ( diff --git a/js/blocks/controls/index.js b/js/src/block-editor/components/controls/index.js similarity index 100% rename from js/blocks/controls/index.js rename to js/src/block-editor/components/controls/index.js diff --git a/js/blocks/controls/multiselect.js b/js/src/block-editor/components/controls/multiselect.js similarity index 100% rename from js/blocks/controls/multiselect.js rename to js/src/block-editor/components/controls/multiselect.js diff --git a/js/blocks/controls/number.js b/js/src/block-editor/components/controls/number.js similarity index 100% rename from js/blocks/controls/number.js rename to js/src/block-editor/components/controls/number.js diff --git a/js/blocks/controls/post.js b/js/src/block-editor/components/controls/post.js similarity index 92% rename from js/blocks/controls/post.js rename to js/src/block-editor/components/controls/post.js index fafd76255..fee07a924 100644 --- a/js/blocks/controls/post.js +++ b/js/src/block-editor/components/controls/post.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { ContentControl } from '../components'; +import { ContentControl } from '../'; const BlockLabPostControl = ( props ) => { /** diff --git a/js/blocks/controls/radio.js b/js/src/block-editor/components/controls/radio.js similarity index 100% rename from js/blocks/controls/radio.js rename to js/src/block-editor/components/controls/radio.js diff --git a/js/blocks/controls/range.js b/js/src/block-editor/components/controls/range.js similarity index 100% rename from js/blocks/controls/range.js rename to js/src/block-editor/components/controls/range.js diff --git a/js/blocks/controls/repeater.js b/js/src/block-editor/components/controls/repeater.js similarity index 97% rename from js/blocks/controls/repeater.js rename to js/src/block-editor/components/controls/repeater.js index 9a47b3ff8..26f776bcb 100644 --- a/js/blocks/controls/repeater.js +++ b/js/src/block-editor/components/controls/repeater.js @@ -7,7 +7,7 @@ const { BaseControl, IconButton } = wp.components; /** * Internal dependencies */ -import { RepeaterRows } from '../components'; +import { RepeaterRows } from '../'; const BlockLabRepeaterControl = ( props ) => { const { field, instanceId, onChange, parentBlock, parentBlockProps } = props; diff --git a/js/blocks/controls/rich-text.js b/js/src/block-editor/components/controls/rich-text.js similarity index 100% rename from js/blocks/controls/rich-text.js rename to js/src/block-editor/components/controls/rich-text.js diff --git a/js/blocks/controls/select.js b/js/src/block-editor/components/controls/select.js similarity index 100% rename from js/blocks/controls/select.js rename to js/src/block-editor/components/controls/select.js diff --git a/js/blocks/controls/taxonomy.js b/js/src/block-editor/components/controls/taxonomy.js similarity index 91% rename from js/blocks/controls/taxonomy.js rename to js/src/block-editor/components/controls/taxonomy.js index d77b17b97..224b87ee6 100644 --- a/js/blocks/controls/taxonomy.js +++ b/js/src/block-editor/components/controls/taxonomy.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { ContentControl } from '../components'; +import { ContentControl } from '../'; const BlockLabTaxonomyControl = ( props ) => { /** diff --git a/js/blocks/controls/text.js b/js/src/block-editor/components/controls/text.js similarity index 100% rename from js/blocks/controls/text.js rename to js/src/block-editor/components/controls/text.js diff --git a/js/blocks/controls/textarea.js b/js/src/block-editor/components/controls/textarea.js similarity index 100% rename from js/blocks/controls/textarea.js rename to js/src/block-editor/components/controls/textarea.js diff --git a/js/blocks/controls/toggle.js b/js/src/block-editor/components/controls/toggle.js similarity index 100% rename from js/blocks/controls/toggle.js rename to js/src/block-editor/components/controls/toggle.js diff --git a/js/blocks/controls/url.js b/js/src/block-editor/components/controls/url.js similarity index 100% rename from js/blocks/controls/url.js rename to js/src/block-editor/components/controls/url.js diff --git a/js/blocks/controls/user.js b/js/src/block-editor/components/controls/user.js similarity index 96% rename from js/blocks/controls/user.js rename to js/src/block-editor/components/controls/user.js index 87aa0883d..fc2d5167a 100644 --- a/js/blocks/controls/user.js +++ b/js/src/block-editor/components/controls/user.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { FetchInput } from '../components'; +import { FetchInput } from '../'; const BlockLabUserControl = ( props ) => { const { field, getValue, onChange } = props; diff --git a/js/blocks/components/edit.js b/js/src/block-editor/components/edit.js similarity index 96% rename from js/blocks/components/edit.js rename to js/src/block-editor/components/edit.js index 2fa427ff2..5d29b57a2 100644 --- a/js/blocks/components/edit.js +++ b/js/src/block-editor/components/edit.js @@ -8,7 +8,7 @@ const { Fragment } = wp.element; * Internal dependencies */ import { AdvancedControls, BlockLabInspector, FormControls } from './'; -import icons from '../../../assets/icons.json'; +import icons from '../../../../assets/icons.json'; /** * The Edit function for the block. diff --git a/js/blocks/components/fetch-input.js b/js/src/block-editor/components/fetch-input.js similarity index 100% rename from js/blocks/components/fetch-input.js rename to js/src/block-editor/components/fetch-input.js diff --git a/js/blocks/components/fields.js b/js/src/block-editor/components/fields.js similarity index 96% rename from js/blocks/components/fields.js rename to js/src/block-editor/components/fields.js index d31244aa1..d0f78a9c2 100644 --- a/js/blocks/components/fields.js +++ b/js/src/block-editor/components/fields.js @@ -7,8 +7,8 @@ const { select } = wp.data; /** * Internal dependencies */ -import simplifiedFields from '../loader/fields'; -import controls from '../controls'; +import getSimplifiedFields from '../helpers/getSimplifiedFields'; +import controls from './controls'; /** * Gets the control function for the field. @@ -48,7 +48,7 @@ const getClassName = ( field ) => { * @return {Function} fields The rendered fields. */ const Fields = ( { fields, parentBlockProps, parentBlock, rowIndex } ) => { - return simplifiedFields( fields ).map( ( field ) => { + return getSimplifiedFields( fields ).map( ( field ) => { if ( field.location && ! field.location.includes( 'editor' ) ) { return null; // This is not meant for the editor. } diff --git a/js/blocks/components/form-controls.js b/js/src/block-editor/components/form-controls.js similarity index 100% rename from js/blocks/components/form-controls.js rename to js/src/block-editor/components/form-controls.js diff --git a/js/blocks/components/image.js b/js/src/block-editor/components/image.js similarity index 100% rename from js/blocks/components/image.js rename to js/src/block-editor/components/image.js diff --git a/js/blocks/components/index.js b/js/src/block-editor/components/index.js similarity index 100% rename from js/blocks/components/index.js rename to js/src/block-editor/components/index.js diff --git a/js/blocks/components/repeater-rows.js b/js/src/block-editor/components/repeater-rows.js similarity index 100% rename from js/blocks/components/repeater-rows.js rename to js/src/block-editor/components/repeater-rows.js diff --git a/js/blocks/components/tiny-mce.js b/js/src/block-editor/components/tiny-mce.js similarity index 100% rename from js/blocks/components/tiny-mce.js rename to js/src/block-editor/components/tiny-mce.js diff --git a/js/blocks/loader/attributes.js b/js/src/block-editor/helpers/getBlockAttributes.js similarity index 100% rename from js/blocks/loader/attributes.js rename to js/src/block-editor/helpers/getBlockAttributes.js diff --git a/js/blocks/loader/fields.js b/js/src/block-editor/helpers/getSimplifiedFields.js similarity index 91% rename from js/blocks/loader/fields.js rename to js/src/block-editor/helpers/getSimplifiedFields.js index 5c75cce15..b74b908d3 100644 --- a/js/blocks/loader/fields.js +++ b/js/src/block-editor/helpers/getSimplifiedFields.js @@ -21,7 +21,7 @@ const compare = ( a, b ) => { * @param {Array} fields The fields to simplify. * @return {Array} The simplified fields. */ -const simplifiedFields = ( fields ) => { +const getSimplifiedFields = ( fields ) => { const fieldList = []; for ( const fieldName in fields ) { @@ -47,4 +47,4 @@ const simplifiedFields = ( fields ) => { return fieldList; }; -export default simplifiedFields; +export default getSimplifiedFields; diff --git a/js/blocks/loader/index.js b/js/src/block-editor/helpers/registerBlocks.js similarity index 89% rename from js/blocks/loader/index.js rename to js/src/block-editor/helpers/registerBlocks.js index c2285bb1f..b3aa6e636 100644 --- a/js/blocks/loader/index.js +++ b/js/src/block-editor/helpers/registerBlocks.js @@ -8,10 +8,9 @@ const { registerBlockType } = wp.blocks; /** * Internal dependencies */ -import icons from '../../../assets/icons.json'; -import getBlockAttributes from './attributes'; +import icons from '../../../../assets/icons.json'; +import getBlockAttributes from './getBlockAttributes'; import { Edit } from '../components'; -import '../../../css/src/editor.scss'; const registerBlocks = () => { // Loop through all the blocks. @@ -57,4 +56,4 @@ const registerBlocks = () => { } }; -export default registerBlocks(); +export default registerBlocks; diff --git a/js/src/block-editor/index.js b/js/src/block-editor/index.js new file mode 100644 index 000000000..157814b54 --- /dev/null +++ b/js/src/block-editor/index.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +const { i18n } = wp; + +/** + * Internal dependencies + */ +import registerBlocks from './helpers/registerBlocks'; + +i18n.setLocaleData( { '': {} }, 'block-lab' ); +registerBlocks(); diff --git a/js/src/components/block-lab-editor.js b/js/src/block-lab-editor/components/block-lab-editor.js similarity index 100% rename from js/src/components/block-lab-editor.js rename to js/src/block-lab-editor/components/block-lab-editor.js diff --git a/js/src/components/field-row.js b/js/src/block-lab-editor/components/field-row.js similarity index 100% rename from js/src/components/field-row.js rename to js/src/block-lab-editor/components/field-row.js diff --git a/js/src/components/index.js b/js/src/block-lab-editor/components/index.js similarity index 100% rename from js/src/components/index.js rename to js/src/block-lab-editor/components/index.js diff --git a/js/src/helpers/getBlockFromContent.js b/js/src/block-lab-editor/helpers/getBlockFromContent.js similarity index 100% rename from js/src/helpers/getBlockFromContent.js rename to js/src/block-lab-editor/helpers/getBlockFromContent.js diff --git a/js/src/helpers/test/getBlockFromContent.js b/js/src/block-lab-editor/helpers/test/getBlockFromContent.js similarity index 100% rename from js/src/helpers/test/getBlockFromContent.js rename to js/src/block-lab-editor/helpers/test/getBlockFromContent.js diff --git a/js/src/edit-block.js b/js/src/block-lab-editor/index.js similarity index 100% rename from js/src/edit-block.js rename to js/src/block-lab-editor/index.js diff --git a/php/blocks/class-loader.php b/php/blocks/class-loader.php index 333af2193..5359c124a 100644 --- a/php/blocks/class-loader.php +++ b/php/blocks/class-loader.php @@ -45,11 +45,11 @@ class Loader extends Component_Abstract { public function init() { $this->assets = [ 'path' => [ - 'entry' => $this->plugin->get_path( 'js/editor.blocks.js' ), + 'entry' => $this->plugin->get_path( 'js/build/block-editor.js' ), 'editor_style' => $this->plugin->get_path( 'css/blocks.editor.css' ), ], 'url' => [ - 'entry' => $this->plugin->get_url( 'js/editor.blocks.js' ), + 'entry' => $this->plugin->get_url( 'js/build/block-editor.js' ), 'editor_style' => $this->plugin->get_url( 'css/blocks.editor.css' ), ], ]; diff --git a/php/post-types/class-block-post.php b/php/post-types/class-block-post.php index d29896a1e..da5b65323 100644 --- a/php/post-types/class-block-post.php +++ b/php/post-types/class-block-post.php @@ -281,7 +281,7 @@ public function enqueue_scripts() { wp_enqueue_script( 'block-post', - $this->plugin->get_url( 'js/admin.edit-block.js' ), + $this->plugin->get_url( 'js/build/block-lab-editor.js' ), [ 'wp-blocks', 'wp-compose', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n' ], $this->plugin->get_version(), false diff --git a/tests/php/unit/blocks/test-class-loader.php b/tests/php/unit/blocks/test-class-loader.php index 1a1766f42..0948c7b9a 100644 --- a/tests/php/unit/blocks/test-class-loader.php +++ b/tests/php/unit/blocks/test-class-loader.php @@ -56,9 +56,9 @@ public function test_init() { $this->instance->init(); $assets = $this->get_protected_property( 'assets' ); $this->assertEquals( 'Block_Lab\\Blocks\\Loader', get_class( $this->instance->init() ) ); - $this->assertContains( 'js/editor.blocks.js', $assets['path']['entry'] ); + $this->assertContains( 'js/build/block-editor.js', $assets['path']['entry'] ); $this->assertContains( 'css/blocks.editor.css', $assets['path']['editor_style'] ); - $this->assertContains( 'js/editor.blocks.js', $assets['url']['entry'] ); + $this->assertContains( 'js/build/block-editor.js', $assets['url']['entry'] ); $this->assertContains( 'css/blocks.editor.css', $assets['url']['editor_style'] ); } diff --git a/webpack.config.js b/webpack.config.js index 57824640c..3b2f14fd7 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -36,11 +36,10 @@ const extractConfig = { ], }; - module.exports = { entry: { - './js/editor.blocks': './js/blocks/index.js', - './js/admin.edit-block': './js/src/edit-block.js', + './js/build/block-lab-editor': './js/src/block-editor/index.js', + './js/build/block-editor': './js/src/block-lab-editor/index.js', }, output: { path: path.resolve( __dirname ), From 4d667fd06f1617d5d3b9e75fb3a4227f5c4546a9 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 22 Dec 2019 13:55:11 -0600 Subject: [PATCH 10/28] Add the build/ directory to the ignored test paths There's no need to look for Jest tests in the js/build directory. --- tests/js/jest.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/js/jest.config.js b/tests/js/jest.config.js index 93146de18..5d563c826 100644 --- a/tests/js/jest.config.js +++ b/tests/js/jest.config.js @@ -7,9 +7,11 @@ module.exports = { testPathIgnorePatterns: [ '/.git', '/node_modules', + '/js/build', ], coveragePathIgnorePatterns: [ '/node_modules', + '/js/build', ], coverageReporters: [ 'lcov' ], coverageDirectory: '/coverage', From 8bdaf0987cc3f051fa9d5ce07eb1d45916fada2a Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 22 Dec 2019 14:22:24 -0600 Subject: [PATCH 11/28] Rename function to getBlockLabAttributes, add unit test There's already a getBlockAttributes() function in Gutenberg, so give this a differernt name. --- ...Attributes.js => getBlockLabAttributes.js} | 4 +- js/src/block-editor/helpers/registerBlocks.js | 4 +- .../helpers/test/getBlockLabAttributes.js | 40 +++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) rename js/src/block-editor/helpers/{getBlockAttributes.js => getBlockLabAttributes.js} (87%) create mode 100644 js/src/block-editor/helpers/test/getBlockLabAttributes.js diff --git a/js/src/block-editor/helpers/getBlockAttributes.js b/js/src/block-editor/helpers/getBlockLabAttributes.js similarity index 87% rename from js/src/block-editor/helpers/getBlockAttributes.js rename to js/src/block-editor/helpers/getBlockLabAttributes.js index eefc499d9..14b6daa06 100644 --- a/js/src/block-editor/helpers/getBlockAttributes.js +++ b/js/src/block-editor/helpers/getBlockLabAttributes.js @@ -4,7 +4,7 @@ * @param {Object} fields The fields to get the attributes from. * @return {Object} attributes The attributes for the fields. */ -const getBlockAttributes = ( fields ) => { +const getBlockLabAttributes = ( fields ) => { const attributes = {}; for ( const fieldName in fields ) { @@ -28,4 +28,4 @@ const getBlockAttributes = ( fields ) => { return attributes; }; -export default getBlockAttributes; +export default getBlockLabAttributes; diff --git a/js/src/block-editor/helpers/registerBlocks.js b/js/src/block-editor/helpers/registerBlocks.js index b3aa6e636..36c61d263 100644 --- a/js/src/block-editor/helpers/registerBlocks.js +++ b/js/src/block-editor/helpers/registerBlocks.js @@ -9,7 +9,7 @@ const { registerBlockType } = wp.blocks; * Internal dependencies */ import icons from '../../../../assets/icons.json'; -import getBlockAttributes from './getBlockAttributes'; +import getBlockLabAttributes from './getBlockLabAttributes'; import { Edit } from '../components'; const registerBlocks = () => { @@ -45,7 +45,7 @@ const registerBlocks = () => { category: 'object' === typeof block.category ? block.category.slug : block.category, icon, keywords: block.keywords, - attributes: getBlockAttributes( block.fields ), + attributes: getBlockLabAttributes( block.fields ), edit: ( props ) => { return ; }, diff --git a/js/src/block-editor/helpers/test/getBlockLabAttributes.js b/js/src/block-editor/helpers/test/getBlockLabAttributes.js new file mode 100644 index 000000000..8e80aabf9 --- /dev/null +++ b/js/src/block-editor/helpers/test/getBlockLabAttributes.js @@ -0,0 +1,40 @@ +/** + * Internal dependencies + */ +import getBlockLabAttributes from '../getBlockLabAttributes'; + +describe( 'getBlockFromContent', () => { + const mockFields = { + example_text: { + type: 'text', + default: 'Here is some text', + help: 'This is the help text', + location: 'editor', + }, + example_url: { + type: 'url', + default: 'https://example.com/go-here', + help: 'Here is the help text', + location: 'inspector', + }, + }; + + const expectedAttributes = { + example_text: { + type: 'text', + default: 'Here is some text', + }, + example_url: { + type: 'url', + default: 'https://example.com/go-here', + }, + }; + + it( 'should return an empty object if passed an empty object', () => { + expect( getBlockLabAttributes( {} ) ).toStrictEqual( {} ); + } ); + + it( 'should return only the attributes of the fields', () => { + expect( getBlockLabAttributes( mockFields ) ).toStrictEqual( expectedAttributes ); + } ); +} ); From a94739585b25c38f0ab5b70b98e58cb15567fb6d Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 22 Dec 2019 14:27:43 -0600 Subject: [PATCH 12/28] Remove .gitkeep, as the directory isn't empty anymore It looks like .gitkeep is only so that GIT keeps the directory, even though it's empty. Now that jest.config.js is in the directory, it's not empty anymore. --- tests/js/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/js/.gitkeep diff --git a/tests/js/.gitkeep b/tests/js/.gitkeep deleted file mode 100644 index e69de29bb..000000000 From 4e7ef2803b37079c9b41a27fe179d14b1c967988 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 22 Dec 2019 17:46:24 -0600 Subject: [PATCH 13/28] Add a Jest unit test for getSimplifiedFields() @todo: consider if the compare function is still needed. --- .../helpers/getBlockLabAttributes.js | 4 +- .../helpers/getSimplifiedFields.js | 2 +- .../helpers/test/getBlockLabAttributes.js | 55 +++++++++------ .../helpers/test/getSimplifiedFields.js | 70 +++++++++++++++++++ 4 files changed, 105 insertions(+), 26 deletions(-) create mode 100644 js/src/block-editor/helpers/test/getSimplifiedFields.js diff --git a/js/src/block-editor/helpers/getBlockLabAttributes.js b/js/src/block-editor/helpers/getBlockLabAttributes.js index 14b6daa06..75c39888c 100644 --- a/js/src/block-editor/helpers/getBlockLabAttributes.js +++ b/js/src/block-editor/helpers/getBlockLabAttributes.js @@ -16,11 +16,11 @@ const getBlockLabAttributes = ( fields ) => { attributes[ fieldName ] = {}; - if ( field.type ) { + if ( field.hasOwnProperty( 'type' ) ) { attributes[ fieldName ].type = field.type; } - if ( field.default ) { + if ( field.hasOwnProperty( 'default' ) ) { attributes[ fieldName ].default = field.default; } } diff --git a/js/src/block-editor/helpers/getSimplifiedFields.js b/js/src/block-editor/helpers/getSimplifiedFields.js index b74b908d3..cc26c3387 100644 --- a/js/src/block-editor/helpers/getSimplifiedFields.js +++ b/js/src/block-editor/helpers/getSimplifiedFields.js @@ -42,7 +42,7 @@ const getSimplifiedFields = ( fields ) => { ); } - fieldList.sort( compare ); + fieldList.sort( compare ); // @todo: is this needed? Even then, it should only affect the Block Lab editor UI. return fieldList; }; diff --git a/js/src/block-editor/helpers/test/getBlockLabAttributes.js b/js/src/block-editor/helpers/test/getBlockLabAttributes.js index 8e80aabf9..6863943cc 100644 --- a/js/src/block-editor/helpers/test/getBlockLabAttributes.js +++ b/js/src/block-editor/helpers/test/getBlockLabAttributes.js @@ -4,29 +4,12 @@ import getBlockLabAttributes from '../getBlockLabAttributes'; describe( 'getBlockFromContent', () => { - const mockFields = { - example_text: { - type: 'text', - default: 'Here is some text', - help: 'This is the help text', - location: 'editor', + const fieldsWithOnlyType = { + example_email: { + type: 'email', }, - example_url: { - type: 'url', - default: 'https://example.com/go-here', - help: 'Here is the help text', - location: 'inspector', - }, - }; - - const expectedAttributes = { - example_text: { - type: 'text', - default: 'Here is some text', - }, - example_url: { - type: 'url', - default: 'https://example.com/go-here', + example_post: { + type: 'post', }, }; @@ -34,7 +17,33 @@ describe( 'getBlockFromContent', () => { expect( getBlockLabAttributes( {} ) ).toStrictEqual( {} ); } ); + it( 'should not throw an error if certain attributes are not present', () => { + expect( getBlockLabAttributes( fieldsWithOnlyType ) ).toStrictEqual( fieldsWithOnlyType ); + } ); + it( 'should return only the attributes of the fields', () => { - expect( getBlockLabAttributes( mockFields ) ).toStrictEqual( expectedAttributes ); + expect( getBlockLabAttributes( { + example_text: { + type: 'text', + default: 'Here is some text', + help: 'This is the help text', + location: 'editor', + }, + example_url: { + type: 'url', + default: 'https://example.com/go-here', + help: 'Here is the help text', + location: 'inspector', + }, + } ) ).toStrictEqual( { + example_text: { + type: 'text', + default: 'Here is some text', + }, + example_url: { + type: 'url', + default: 'https://example.com/go-here', + }, + } ); } ); } ); diff --git a/js/src/block-editor/helpers/test/getSimplifiedFields.js b/js/src/block-editor/helpers/test/getSimplifiedFields.js new file mode 100644 index 000000000..a40628a1a --- /dev/null +++ b/js/src/block-editor/helpers/test/getSimplifiedFields.js @@ -0,0 +1,70 @@ +/** + * Internal dependencies + */ +import getSimplifiedFields from '../getSimplifiedFields'; + +describe( 'getBlockFromContent', () => { + it( 'should return an empty array if passed an empty object', () => { + expect( getSimplifiedFields( {} ) ).toStrictEqual( [] ); + } ); + + it( 'should return simplified fields for an object of 3 fields', () => { + expect( getSimplifiedFields( { + example_post: { + type: 'post', + help: 'This is some example help text', + location: 'editor', + post_type: 'posts', + width: '100', + }, + example_classic_text: { + type: 'classic_text', + default: 'https://example.com/go-here', + help: 'Here is the help text', + location: 'editor', + }, + example_user: { + type: 'user', + default: 'https://example.com/go-here', + help: 'Here is the help text', + location: 'inspector', + }, + } ) ).toStrictEqual( [ + { + name: 'example_post', + type: 'post', + help: 'This is some example help text', + location: 'editor', + post_type: 'posts', + width: '100', + }, + { + name: 'example_classic_text', + type: 'classic_text', + default: 'https://example.com/go-here', + help: 'Here is the help text', + location: 'editor', + }, + { + name: 'example_user', + type: 'user', + default: 'https://example.com/go-here', + help: 'Here is the help text', + location: 'inspector', + }, + ] ); + } ); + + it( 'should still include falsey values in the simplified fields', () => { + expect( getSimplifiedFields( { + test_taxonomy: { + default: '', + }, + } ) ).toStrictEqual( [ + { + name: 'test_taxonomy', + default: '', + }, + ] ); + } ); +} ); From 84755bbff703556fb07c822a85eebad923725ed9 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Wed, 25 Dec 2019 22:38:44 -0600 Subject: [PATCH 14/28] Add a unit test for getBlockLabAttributes() There wasn't a test for this before. Also, add parameters for it. --- .../block-editor/helpers/getBlockLabAttributes.js | 1 - js/src/block-editor/helpers/registerBlocks.js | 15 +++++++++------ .../block-editor/helpers/test/registerBlocks.js | 14 ++++++++++++++ js/src/block-editor/index.js | 5 ++++- tests/js/jest.config.js | 3 +++ tests/js/setup-globals.js | 5 +++++ 6 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 js/src/block-editor/helpers/test/registerBlocks.js create mode 100644 tests/js/setup-globals.js diff --git a/js/src/block-editor/helpers/getBlockLabAttributes.js b/js/src/block-editor/helpers/getBlockLabAttributes.js index 75c39888c..b069a6963 100644 --- a/js/src/block-editor/helpers/getBlockLabAttributes.js +++ b/js/src/block-editor/helpers/getBlockLabAttributes.js @@ -13,7 +13,6 @@ const getBlockLabAttributes = ( fields ) => { } const field = fields[ fieldName ]; - attributes[ fieldName ] = {}; if ( field.hasOwnProperty( 'type' ) ) { diff --git a/js/src/block-editor/helpers/registerBlocks.js b/js/src/block-editor/helpers/registerBlocks.js index 36c61d263..2d33f4628 100644 --- a/js/src/block-editor/helpers/registerBlocks.js +++ b/js/src/block-editor/helpers/registerBlocks.js @@ -1,4 +1,3 @@ -/* global blockLab, blockLabBlocks */ /** * WordPress dependencies @@ -10,11 +9,15 @@ const { registerBlockType } = wp.blocks; */ import icons from '../../../../assets/icons.json'; import getBlockLabAttributes from './getBlockLabAttributes'; -import { Edit } from '../components'; -const registerBlocks = () => { - // Loop through all the blocks. - // Note: This is not guaranteed to be sequential. +/** + * Loops through all of the blocks, but not guaranteed to be sequential. + * + * @param {Object} blockLab Block Lab properties, available via wp_localize_script(). + * @param {Object} blockLabBlocks The registered Block Lab blocks, available via wp_add_inline_script(). + * @param {Function} EditComponent The edit component to render the blocks. + */ +const registerBlocks = ( blockLab, blockLabBlocks, EditComponent ) => { for ( const blockName in blockLabBlocks ) { // Avoid weird inheritance issues. Which should not happen because the backend is safe. if ( ! blockLabBlocks.hasOwnProperty( blockName ) ) { @@ -47,7 +50,7 @@ const registerBlocks = () => { keywords: block.keywords, attributes: getBlockLabAttributes( block.fields ), edit: ( props ) => { - return ; + return ; }, save() { return null; diff --git a/js/src/block-editor/helpers/test/registerBlocks.js b/js/src/block-editor/helpers/test/registerBlocks.js new file mode 100644 index 000000000..c1a83e332 --- /dev/null +++ b/js/src/block-editor/helpers/test/registerBlocks.js @@ -0,0 +1,14 @@ +/** + * Internal dependencies + */ +import registerBlocks from '../registerBlocks'; +const mockRegisterBlockType = jest.fn(); + +describe( 'registerBlocks', () => { + const Edit = () => {}; + + it( 'should not register any block if there are no Block Lab blocks passed', () => { + registerBlocks( {}, {}, Edit ); + expect( mockRegisterBlockType ).toHaveBeenCalledTimes( 0 ); + } ); +} ); diff --git a/js/src/block-editor/index.js b/js/src/block-editor/index.js index 157814b54..fb46d04f2 100644 --- a/js/src/block-editor/index.js +++ b/js/src/block-editor/index.js @@ -1,3 +1,5 @@ +/* global blockLab, blockLabBlocks */ + /** * WordPress dependencies */ @@ -7,6 +9,7 @@ const { i18n } = wp; * Internal dependencies */ import registerBlocks from './helpers/registerBlocks'; +import { Edit } from './components'; i18n.setLocaleData( { '': {} }, 'block-lab' ); -registerBlocks(); +registerBlocks( blockLab, blockLabBlocks, Edit ); diff --git a/tests/js/jest.config.js b/tests/js/jest.config.js index 5d563c826..1234618de 100644 --- a/tests/js/jest.config.js +++ b/tests/js/jest.config.js @@ -1,6 +1,9 @@ module.exports = { rootDir: '../../', ...require( '@wordpress/scripts/config/jest-unit.config' ), + setupFiles: [ + '/tests/js/setup-globals', + ], transform: { '^.+\\.[jt]sx?$': '/node_modules/@wordpress/scripts/config/babel-transform', }, diff --git a/tests/js/setup-globals.js b/tests/js/setup-globals.js new file mode 100644 index 000000000..9da45920a --- /dev/null +++ b/tests/js/setup-globals.js @@ -0,0 +1,5 @@ +// Mock wp object. + +global.wp = { + blocks: {}, +}; From ce9c437723ce2b1e5c59a7b96a9fdc5223597adf Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Thu, 26 Dec 2019 23:17:59 -0600 Subject: [PATCH 15/28] Add a unit test for registerBlocks() Test not registering any block, registering 1, and registering 2. --- js/src/block-editor/helpers/registerBlocks.js | 2 +- .../helpers/test/registerBlocks.js | 58 ++++++++++++++++++- tests/js/setup-globals.js | 2 +- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/js/src/block-editor/helpers/registerBlocks.js b/js/src/block-editor/helpers/registerBlocks.js index 2d33f4628..00874b9ec 100644 --- a/js/src/block-editor/helpers/registerBlocks.js +++ b/js/src/block-editor/helpers/registerBlocks.js @@ -49,7 +49,7 @@ const registerBlocks = ( blockLab, blockLabBlocks, EditComponent ) => { icon, keywords: block.keywords, attributes: getBlockLabAttributes( block.fields ), - edit: ( props ) => { + edit( props ) { return ; }, save() { diff --git a/js/src/block-editor/helpers/test/registerBlocks.js b/js/src/block-editor/helpers/test/registerBlocks.js index c1a83e332..4b86148f0 100644 --- a/js/src/block-editor/helpers/test/registerBlocks.js +++ b/js/src/block-editor/helpers/test/registerBlocks.js @@ -2,13 +2,67 @@ * Internal dependencies */ import registerBlocks from '../registerBlocks'; -const mockRegisterBlockType = jest.fn(); describe( 'registerBlocks', () => { const Edit = () => {}; it( 'should not register any block if there are no Block Lab blocks passed', () => { registerBlocks( {}, {}, Edit ); - expect( mockRegisterBlockType ).toHaveBeenCalledTimes( 0 ); + expect( global.wp.blocks.registerBlockType ).toHaveBeenCalledTimes( 0 ); + } ); + + it( 'should register a single block', () => { + const blockName = 'test_post'; + const blockLabBlocks = {}; + blockLabBlocks[ blockName ] = { + title: 'example-post', + category: 'widget', + keywords: [ 'foobaz', 'example' ], + }; + + registerBlocks( {}, blockLabBlocks, Edit ); + expect( global.wp.blocks.registerBlockType ).toHaveBeenCalledWith( + blockName, + expect.objectContaining( { + title: expect.any( String ), + category: expect.any( String ), + icon: expect.any( String ), + keywords: expect.any( Array ), + attributes: expect.any( Object ), + edit: expect.any( Function ), + save: expect.any( Function ), + } ) + ); + } ); + + it( 'should register two blocks', () => { + const firstBlockName = 'test_post'; + const secondBlockName = 'test_email'; + const blockLabBlocks = {}; + blockLabBlocks[ firstBlockName ] = { + title: 'example-post', + category: 'widget', + keywords: [ 'foobaz', 'example' ], + }; + blockLabBlocks[ firstBlockName ] = { + title: 'example-email', + category: 'widget', + keywords: [ 'example-keyword', 'another' ], + }; + + registerBlocks( {}, blockLabBlocks, Edit ); + expect( global.wp.blocks.registerBlockType ).toHaveBeenNthCalledWith( + 2, + expect.any( String ), + expect.objectContaining( { + title: expect.any( String ), + category: expect.any( String ), + icon: expect.any( String ), + keywords: expect.any( Array ), + attributes: expect.any( Object ), + edit: expect.any( Function ), + save: expect.any( Function ), + } ) + ); } ); } ); diff --git a/tests/js/setup-globals.js b/tests/js/setup-globals.js index 9da45920a..580eff218 100644 --- a/tests/js/setup-globals.js +++ b/tests/js/setup-globals.js @@ -1,5 +1,5 @@ // Mock wp object. global.wp = { - blocks: {}, + blocks: { registerBlockType: jest.fn() }, }; From 89b15e03c15e7c7a7c55adad5b91e9a68297ca73 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Thu, 26 Dec 2019 23:33:16 -0600 Subject: [PATCH 16/28] Add more unit tests for registerBlocks() Test not registering any block, registering 1, and registering 2. --- .../helpers/test/registerBlocks.js | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/js/src/block-editor/helpers/test/registerBlocks.js b/js/src/block-editor/helpers/test/registerBlocks.js index 4b86148f0..5f7362be2 100644 --- a/js/src/block-editor/helpers/test/registerBlocks.js +++ b/js/src/block-editor/helpers/test/registerBlocks.js @@ -36,21 +36,23 @@ describe( 'registerBlocks', () => { } ); it( 'should register two blocks', () => { - const firstBlockName = 'test_post'; - const secondBlockName = 'test_email'; - const blockLabBlocks = {}; - blockLabBlocks[ firstBlockName ] = { - title: 'example-post', - category: 'widget', - keywords: [ 'foobaz', 'example' ], - }; - blockLabBlocks[ firstBlockName ] = { - title: 'example-email', - category: 'widget', - keywords: [ 'example-keyword', 'another' ], - }; + registerBlocks( + {}, + { + test_text: { + title: 'example-post', + category: 'widget', + keywords: [ 'foobaz', 'example' ], + }, + test_email: { + title: 'example-email', + category: 'widget', + keywords: [ 'example-keyword', 'another' ], + }, + }, + Edit + ); - registerBlocks( {}, blockLabBlocks, Edit ); expect( global.wp.blocks.registerBlockType ).toHaveBeenNthCalledWith( 2, expect.any( String ), From 1648af26e0815b294731d083f45aa165ca7fe0b1 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Thu, 26 Dec 2019 23:48:16 -0600 Subject: [PATCH 17/28] Use the singular for no block In this case, there is no need to pluralize this. --- js/src/block-editor/helpers/test/registerBlocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/block-editor/helpers/test/registerBlocks.js b/js/src/block-editor/helpers/test/registerBlocks.js index 5f7362be2..5b4b53864 100644 --- a/js/src/block-editor/helpers/test/registerBlocks.js +++ b/js/src/block-editor/helpers/test/registerBlocks.js @@ -6,7 +6,7 @@ import registerBlocks from '../registerBlocks'; describe( 'registerBlocks', () => { const Edit = () => {}; - it( 'should not register any block if there are no Block Lab blocks passed', () => { + it( 'should not register any block if there is no Block Lab block passed', () => { registerBlocks( {}, {}, Edit ); expect( global.wp.blocks.registerBlockType ).toHaveBeenCalledTimes( 0 ); } ); From 422f024417ad9317534e5647157f17d45c98de56 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Fri, 27 Dec 2019 16:47:04 -0600 Subject: [PATCH 18/28] Apply Rob's HTML and CSS for the new UI Copy Rob's markup for the fields, and the CSS file. --- css/admin.block-post.css | 311 +++++++++++++++++- .../components/block-lab-editor.js | 94 ------ js/src/block-lab-editor/components/editor.js | 79 +++++ .../block-lab-editor/components/field-edit.js | 123 +++++++ .../block-lab-editor/components/field-row.js | 180 ---------- js/src/block-lab-editor/components/field.js | 53 +++ js/src/block-lab-editor/components/index.js | 5 +- js/src/block-lab-editor/index.js | 4 +- php/blocks/class-loader.php | 2 +- 9 files changed, 571 insertions(+), 280 deletions(-) delete mode 100644 js/src/block-lab-editor/components/block-lab-editor.js create mode 100644 js/src/block-lab-editor/components/editor.js create mode 100644 js/src/block-lab-editor/components/field-edit.js delete mode 100644 js/src/block-lab-editor/components/field-row.js create mode 100644 js/src/block-lab-editor/components/field.js diff --git a/css/admin.block-post.css b/css/admin.block-post.css index 082d11468..95c0e8c10 100644 --- a/css/admin.block-post.css +++ b/css/admin.block-post.css @@ -1,8 +1,317 @@ /* Hide the main block editing area, as that does not apply to this. */ -.post-type-block_lab .editor-block-list__layout { +.post-type-block_lab .editor-post-text-editor { display: none; } +/* Copied from Rob's work in react-ui repo */ +:root { + --color-body: #545E6A; + --color-heading: #32373C; + --color-blue: #027CBA; + --color-blue-light: #DAECF5; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + color: var(--color-body ); + font-size: 14px; + font-weight: 500; +} + +.wp-admin { + display: grid; + grid-template-columns: 160px 1fr; + min-height: 100vh; +} + +.wp-side-bar { + height: 100%; + background-color: #23282D; +} + +.block-builder { + display: grid; + grid-template-columns: 1fr 280px; + grid-template-rows: 56px 1fr; +} + +.header { + height: 100%; + grid-column: 1 / 3; + grid-row: 1 / 2; + border-bottom: 1px solid #e2e4e7; + display: flex; + justify-content: space-between; + align-items: stretch; +} + +.header-nav { + height: 100%; + display: flex; + align-self: stretch; +} + +.header-nav-item { + height: 100%; + display: flex; + align-items: center; + padding-left: 30px; + padding-right: 30px; + text-decoration: none; + color: var( --color-heading ); +} + +.header-nav-item--active { + font-weight: 700; + border-bottom: 3px solid var( --color-blue ); +} + +.main { + grid-column: 1 / 2; + grid-row: 2 / 3; + display: flex; + justify-content: center; + align-items: flex-start; + padding-top: 50px; +} + +.side { + grid-column: 2 / 3; + grid-row: 2 / 3; + border-left: 1px solid #e2e4e7; +} + +.fields { + width: 610px; + background-color: #F3F3F4; + padding: 20px; + display: grid; + grid-template-columns: repeat( 4, calc( 25% - 15px ) ); + grid-gap: 20px; +} + +.field-selector { + display: none; +} + +.field:not(.dragging) .field-selector:checked + .field-container { + box-shadow: 0 0 0 2px var( --color-blue ); +} + +.field:not(.dragging) .field-selector:checked + .field-container .field-icon-container { + background-color: var( --color-blue-light ); +} + +.field:not(.dragging) .field-selector:checked + .field-container .field-icon{ + fill: var( --color-blue ); +} + +.field { + position: relative; + grid-column: 1 / 5; + min-height: 40px; + cursor: w-resize; +} + +.field--1\/4 { + grid-column: span 1; + cursor: e-resize; +} + +.field--1\/2 { + grid-column: span 2; + cursor: ew-resize; +} + +.field--3\/4 { + grid-column: span 3; + cursor: ew-resize; +} + +.field:not(.sub-field)::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + right: -10px; + width: 20px; +} + +.field-container { + position: relative; + cursor: pointer; + display: flex; + flex-wrap: wrap; + min-height: 40px; + justify-content: flex-start; + align-items: center; + background-color: #fff; + padding: 10px 12px 10px 30px; + border-radius: 3px; + overflow: hidden; +} + +.field.dragging .field-container { + background: transparent; + padding: 9px 11px 9px 29px; + border: 1px dashed var( --color-body ); +} + +.field-icon-container { + position: absolute; + left: 0; + top: 0; + bottom: 0; + flex-shrink: 0; + height: 100%; + width: 30px; + display: flex; + justify-content: center; + align-items: center; + background-color: #E4E4E7; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.field:not(.dragging) > .field-selector:not(:checked) + .field-container:not(.child-hover):hover { + box-shadow: 0 0 0 2px var( --color-blue-light ); +} + +.field:not(.dragging) > .field-container:not(.child-hover):hover > .field-icon-container { + background-color: var( --color-blue-light ); +} + +.field:not(.dragging) > .field-container:not(.child-hover):hover > .field-icon-container .field-icon { + fill: var( --color-blue ); +} + +.field-icon-container:hover { + cursor: grab; +} + +.field-icon-container:active { + cursor: grabbing; +} + +.field-icon { + width: 16px; + fill: var( --color-body ); +} + +.field-grab-indicator { + width: 18px; + fill: var( --color-blue ); + display: none; + transform: rotate( 90deg ); +} + +.field:not(.dragging) .field-icon-container:hover .field-icon { + display: none; +} + +.field:not(.dragging) .field-icon-container:hover .field-grab-indicator { + display: block; +} + +.field-title { + margin-left: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1 1 0; +} + +.field-copy-pill { + position: relative; + height: 20px; + display: flex; + align-items: center; + padding-left: 6px; + padding-right: 4px; + background-color: var( --color-blue-light ); + margin-left: auto; + border-radius: 3px; +} + +.field-copy-pill:hover { + cursor: pointer; +} + +.field-copy-pill span { + font-family: monospace; + color: var( --color-blue ); +} + +.field--1\/4 .field-copy-pill { + padding-left: 4px; +} + +.field--1\/4 .field-copy-pill span { + display: none; +} + +.field--1\/4 .field-copy-pill svg { + margin-left: 0; +} + +.field-copy-pill svg { + width: 12px; + fill: var( --color-blue); + margin-left: 6px; +} + +.sub-fields { + flex: 1 1 100%; + padding: 12px 10px 0 20px +} + +.sub-fields .field { + cursor: default; + display: block; + padding: 5px 0; +} + +.sub-fields .field:last-of-type { + margin-bottom: 8px; +} + +.sub-fields .field-container { + background: #f3f3f4; +} + +.add-field-container { + grid-column: 1 / 5; + display: flex; + justify-content: center; +} + +.add-field-container svg { + fill: var( --color-blue ); + width: 20px; + margin-right: 4px; +} + +.add-field-container button { + display: flex; + align-items: center; + font-size: 13px; + border: none; + background-color: transparent; + font-weight: 700; + color: var( --color-blue ); +} + +.add-field-container button:hover { + cursor: pointer; + filter: brightness( 0.8 ); +} + +/* @todo: reconsider this, as it's from the old UI */ #minor-publishing-actions, #misc-publishing-actions #visibility, #misc-publishing-actions .curtime { diff --git a/js/src/block-lab-editor/components/block-lab-editor.js b/js/src/block-lab-editor/components/block-lab-editor.js deleted file mode 100644 index 90b86f701..000000000 --- a/js/src/block-lab-editor/components/block-lab-editor.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * WordPress dependencies - */ -const { blocks } = wp; -const { Button } = wp.components; -const { compose } = wp.compose; -const { withDispatch, withSelect } = wp.data; -const { Component } = wp.element; -const { __ } = wp.i18n; - -/** - * Internal dependencies - */ -import getBlockFromContent from '../helpers/getBlockFromContent'; -import { FieldRow } from '.'; - -/** - * The Block Lab field editor. - */ -class BlockLabEditor extends Component { - /** - * Renders the editor. - * - * @return {Function} The rendered component. - */ - render() { - const { content } = this.props; - const parsedBlock = getBlockFromContent( content ); - const { fields } = parsedBlock; - - return ( -
-
- - - - - - - - - - - - - - -
- { __( 'Field Label', 'block-lab' ) } - - { __( 'Field Name', 'block-lab' ) } - - { __( 'Field Type', 'block-lab' ) } -
-
-

- { __( 'Click Add Field below to add your first field.', 'block-lab' ) } -

- { - !! fields && Object.values( fields ).map( ( field, index ) => { - return ; - } ) - } -
-
-
-
- -
-
- ); - } -} - -export default compose( [ - withSelect( ( select ) => { - return { - content: select( 'core/editor' ).getEditedPostContent(), - }; - } ), - withDispatch( ( dispatch ) => { - const store = dispatch( 'core/editor' ); - - return { - onChange( content ) { - store.editPost( { content } ); - store.resetEditorBlocks( blocks.parse( content ) ); - }, - }; - } ), -] )( BlockLabEditor ); diff --git a/js/src/block-lab-editor/components/editor.js b/js/src/block-lab-editor/components/editor.js new file mode 100644 index 000000000..7add21c54 --- /dev/null +++ b/js/src/block-lab-editor/components/editor.js @@ -0,0 +1,79 @@ +/** + * WordPress dependencies + */ +const { blocks } = wp; +const { Button } = wp.components; +const { compose } = wp.compose; +const { withDispatch, withSelect } = wp.data; +const { Component } = wp.element; +const { __ } = wp.i18n; + +/** + * Internal dependencies + */ +import getBlockFromContent from '../helpers/getBlockFromContent'; +import { Field } from '.'; + +/** + * The Block Lab field editor. + */ +class Editor extends Component { + /** + * Renders the editor. + * + * @return {Function} The rendered component. + */ + render() { + const { content } = this.props; + const parsedBlock = getBlockFromContent( content ); + const { fields } = parsedBlock; + + return ( +
+
+
+ + +
+
+
+
+ { + !! fields && Object.values( fields ).map( ( field, index ) => { + return ; + } ) + } +
+ +
+
+
+
+
+ ); + } +} + +export default compose( [ + withSelect( ( select ) => { + return { + content: select( 'core/editor' ).getEditedPostContent(), + }; + } ), + withDispatch( ( dispatch ) => { + const store = dispatch( 'core/editor' ); + + return { + onChange( content ) { + store.editPost( { content } ); + store.resetEditorBlocks( blocks.parse( content ) ); + }, + }; + } ), +] )( Editor ); diff --git a/js/src/block-lab-editor/components/field-edit.js b/js/src/block-lab-editor/components/field-edit.js new file mode 100644 index 000000000..0cea090f4 --- /dev/null +++ b/js/src/block-lab-editor/components/field-edit.js @@ -0,0 +1,123 @@ +/** + * WordPress dependencies + */ +const { __ } = wp.i18n; +const { Button, TextControl } = wp.components; +const { Component } = wp.element; + +/** + * A field row. + */ +class FieldEdit extends Component { + /** + * Renders the field row. + * + * @return {Function} The rendered component. + */ + render() { + const { field, uid } = this.props; + const isFieldDisabled = false; + + return ( +
+ + + + + + + + + + + + + + + + + { /* render_field_settings( $field, $uid ); ?> */ } + + + + + +
+ +

+ { __( 'A label describing your block\'s custom field.', 'block-lab' ) } +

+
+ +
+ +

+ { __( 'Single word, no spaces.', 'block-lab' ) } +

+
+ +
+ + + +
+ + +
+
+ ); + } +} + +export default FieldEdit; diff --git a/js/src/block-lab-editor/components/field-row.js b/js/src/block-lab-editor/components/field-row.js deleted file mode 100644 index 6d4b95546..000000000 --- a/js/src/block-lab-editor/components/field-row.js +++ /dev/null @@ -1,180 +0,0 @@ -/** - * WordPress dependencies - */ -const { Button, TextControl } = wp.components; -const { Component } = wp.element; -const { __ } = wp.i18n; - -/** - * A field row. - */ -export default class FieldRow extends Component { - /** - * Renders the field row. - * - * @return {Function} The rendered component. - */ - render() { - const { field, uid } = this.props; - const isFieldDisabled = false; - - return ( -
-
-
- -
-
- -
- - - -
-
-
- { field.name } -
-
- { - /* controls[ $field->control ] ) ) : - echo esc_html( $this->controls[ $field->control ]->label ); - else : - - - - sprintf( - __( 'This %1$s field requires an active pro license.', 'block-lab' ), - field.control, - 'https://example.com' - ) - - - endif; - */ - } -
-
-
- - - - - - - - - - - - - - - - - { /* render_field_settings( $field, $uid ); ?> */ } - - - - - -
- -

- { __( 'A label describing your block\'s custom field.', 'block-lab' ) } -

-
- -
- -

- { __( 'Single word, no spaces.', 'block-lab' ) } -

-
- -
- - - -
- - -
-
- - { - /* - @todo: reimpliment this in JS. - if ( 'repeater' === field.control ) { - if ( ! field.settings.sub_fields ) { - field.settings.sub_fields = []; - } - $this->render_fields_sub_rows( $field->settings['sub_fields'], $uid ); - } - */ - } -
- ); - } -} diff --git a/js/src/block-lab-editor/components/field.js b/js/src/block-lab-editor/components/field.js new file mode 100644 index 000000000..15c4a3860 --- /dev/null +++ b/js/src/block-lab-editor/components/field.js @@ -0,0 +1,53 @@ +/** + * WordPress dependencies + */ +const { Component } = wp.element; + +/** + * Internal dependencies + */ +import { FieldEdit } from './'; + +/** + * A field row. + */ +class Field extends Component { + /** + * Renders the field row. + * + * @return {Function} The rendered component. + */ + render() { + const { field } = this.props; + + return ( +
+
+
+ + + + + + + + +
+ + { field.name } + +
+ { field.label } + + + + +
+
+ +
+ ); + } +} + +export default Field; diff --git a/js/src/block-lab-editor/components/index.js b/js/src/block-lab-editor/components/index.js index d6939eb89..f0ce63095 100644 --- a/js/src/block-lab-editor/components/index.js +++ b/js/src/block-lab-editor/components/index.js @@ -1,2 +1,3 @@ -export { default as BlockLabEditor } from './block-lab-editor'; -export { default as FieldRow } from './field-row'; +export { default as Editor } from './editor'; +export { default as Field } from './field'; +export { default as FieldEdit } from './field-edit'; diff --git a/js/src/block-lab-editor/index.js b/js/src/block-lab-editor/index.js index 74a32440c..63a10e7b0 100644 --- a/js/src/block-lab-editor/index.js +++ b/js/src/block-lab-editor/index.js @@ -8,12 +8,12 @@ const { render } = wp.element; /** * Internal dependencies */ -import { BlockLabEditor } from './components'; +import { Editor } from './components'; domReady( () => { dispatch( 'core/editor' ).updateEditorSettings( { richEditingEnabled: false } ); render( - , + , document.getElementById( 'bl-block-editor' ), ); } ); diff --git a/php/blocks/class-loader.php b/php/blocks/class-loader.php index 5359c124a..3744439bb 100644 --- a/php/blocks/class-loader.php +++ b/php/blocks/class-loader.php @@ -135,7 +135,7 @@ protected function editor_assets() { wp_enqueue_script( 'block-lab-blocks', $this->assets['url']['entry'], - [ 'wp-i18n', 'wp-element', 'wp-blocks', 'wp-components', 'wp-api-fetch' ], + [ 'wp-i18n', 'wp-editor', 'wp-element', 'wp-blocks', 'wp-components', 'wp-api-fetch' ], $this->plugin->get_version(), true ); From 8350ee6c04a1e02f0156b2147f966b3d8a6b26f8 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 29 Dec 2019 19:21:56 -0600 Subject: [PATCH 19/28] Add a function to save a new field value Also, add a unit test for that. @todo: test the 'happy path' of that function. --- js/src/block-lab-editor/components/editor.js | 2 +- .../block-lab-editor/components/field-edit.js | 33 +++++--- js/src/block-lab-editor/components/field.js | 4 +- .../helpers/saveFieldValue.js | 40 ++++++++++ .../helpers/test/saveFieldValue.js | 21 +++++ package.json | 2 +- php/blocks/class-loader.php | 6 ++ tests/js/setup-globals.js | 16 +++- webpack.config.js | 78 ++++++++----------- 9 files changed, 139 insertions(+), 63 deletions(-) create mode 100644 js/src/block-lab-editor/helpers/saveFieldValue.js create mode 100644 js/src/block-lab-editor/helpers/test/saveFieldValue.js diff --git a/js/src/block-lab-editor/components/editor.js b/js/src/block-lab-editor/components/editor.js index 7add21c54..5929c239f 100644 --- a/js/src/block-lab-editor/components/editor.js +++ b/js/src/block-lab-editor/components/editor.js @@ -40,7 +40,7 @@ class Editor extends Component {
{ !! fields && Object.values( fields ).map( ( field, index ) => { - return ; + return ; } ) }
diff --git a/js/src/block-lab-editor/components/field-edit.js b/js/src/block-lab-editor/components/field-edit.js index 0cea090f4..7cfc7e79d 100644 --- a/js/src/block-lab-editor/components/field-edit.js +++ b/js/src/block-lab-editor/components/field-edit.js @@ -6,16 +6,21 @@ const { Button, TextControl } = wp.components; const { Component } = wp.element; /** - * A field row. + * Internal dependencies + */ +import saveFieldValue from '../helpers/saveFieldValue'; + +/** + * A field's editing section. */ class FieldEdit extends Component { /** - * Renders the field row. + * Renders the field's editing section. * * @return {Function} The rendered component. */ render() { - const { field, uid } = this.props; + const { field, uiud } = this.props; const isFieldDisabled = false; return ( @@ -24,7 +29,7 @@ class FieldEdit extends Component { -