From 03789b32c90dac9df78dd6ce0cc3010569e354ce Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Mon, 31 Jul 2017 11:35:36 +0300 Subject: [PATCH 01/16] Add Categories block with dropdown, counts and hierarchy options --- blocks/library/categories/block.scss | 8 + blocks/library/categories/data.js | 13 ++ blocks/library/categories/index.js | 241 +++++++++++++++++++++++++++ blocks/library/categories/style.scss | 11 ++ 4 files changed, 273 insertions(+) create mode 100644 blocks/library/categories/block.scss create mode 100644 blocks/library/categories/data.js create mode 100644 blocks/library/categories/index.js create mode 100644 blocks/library/categories/style.scss diff --git a/blocks/library/categories/block.scss b/blocks/library/categories/block.scss new file mode 100644 index 00000000000000..cb490a56e167fe --- /dev/null +++ b/blocks/library/categories/block.scss @@ -0,0 +1,8 @@ +.wp-block-categories { + &.alignleft { + margin-right: 2em; + } + &.alignright { + margin-left: 2em; + } +} diff --git a/blocks/library/categories/data.js b/blocks/library/categories/data.js new file mode 100644 index 00000000000000..26e763efb8fe90 --- /dev/null +++ b/blocks/library/categories/data.js @@ -0,0 +1,13 @@ +/** + * Returns a Promise with the categories or an error on failure. + * + * @returns {wp.api.collections.Categories} Returns a Promise with all categories. + */ +export function getCategories() { + const categoriesCollection = new wp.api.collections.Categories(); + + const categories = categoriesCollection.fetch(); + + return categories; +} + diff --git a/blocks/library/categories/index.js b/blocks/library/categories/index.js new file mode 100644 index 00000000000000..baa9c4568ea4ae --- /dev/null +++ b/blocks/library/categories/index.js @@ -0,0 +1,241 @@ +/** + * WordPress dependencies + */ +import { Component } from 'element'; +import { Placeholder, Spinner } from 'components'; +import { __ } from 'i18n'; + +/** + * Internal dependencies + */ +import './style.scss'; +import './block.scss'; +import { registerBlockType } from '../../api'; +import { getCategories } from './data.js'; +import InspectorControls from '../../inspector-controls'; +import ToggleControl from '../../inspector-controls/toggle-control'; +import BlockDescription from '../../block-description'; +import BlockControls from '../../block-controls'; +import BlockAlignmentToolbar from '../../block-alignment-toolbar'; + +registerBlockType( 'core/categories', { + title: __( 'Categories' ), + + icon: 'list-view', + + category: 'widgets', + + defaultAttributes: { + showPostCounts: false, + displayAsDropdown: false, + showHierarchy: false, + }, + + getEditWrapperProps( attributes ) { + const { align } = attributes; + if ( 'left' === align || 'right' === align || 'full' === align ) { + return { 'data-align': align }; + } + }, + + edit: class extends Component { + constructor() { + super( ...arguments ); + + this.state = { + categories: [], + }; + + this.categoriesRequest = getCategories(); + + this.categoriesRequest + .then( categories => this.setState( { categories } ) ); + + this.toggleDisplayAsDropdown = this.toggleDisplayAsDropdown.bind( this ); + this.toggleShowPostCounts = this.toggleShowPostCounts.bind( this ); + this.toggleShowHierarchy = this.toggleShowHierarchy.bind( this ); + } + + toggleDisplayAsDropdown() { + const { displayAsDropdown } = this.props.attributes; + const { setAttributes } = this.props; + + setAttributes( { displayAsDropdown: ! displayAsDropdown } ); + } + + toggleShowPostCounts() { + const { showPostCounts } = this.props.attributes; + const { setAttributes } = this.props; + + setAttributes( { showPostCounts: ! showPostCounts } ); + } + + toggleShowHierarchy() { + const { showHierarchy } = this.props.attributes; + const { setAttributes } = this.props; + + setAttributes( { showHierarchy: ! showHierarchy } ); + } + + getCategories( parentId = null ) { + const { categories } = this.state; + if ( ! categories.length ) { + return []; + } + + if ( parentId === null ) { + return categories; + } + + return categories.filter( category => category.parent === parentId ); + } + + getCategoryListClassName( level ) { + const { className } = this.props; + return `${ className }__list ${ className }__list-level-${ level }`; + } + + renderCategoryList() { + const { showHierarchy } = this.props.attributes; + const parentId = showHierarchy ? 0 : null; + const categories = this.getCategories( parentId ); + + return ( + + ); + } + + renderCategoryListItem( category, level ) { + const { showHierarchy, showPostCounts } = this.props.attributes; + const childCategories = this.getCategories( category.id ); + + return ( +
  • + { category.name.trim() || __( '(Untitled)' ) } + { showPostCounts && + + { ' ' }({ category.count }) + + } + + { + showHierarchy && + !! childCategories.length && ( + + ) + } +
  • + ); + } + + renderCategoryDropdown() { + const { showHierarchy } = this.props.attributes; + const parentId = showHierarchy ? 0 : null; + const categories = this.getCategories( parentId ); + + return ( + + ); + } + + renderCategoryDropdownItem( category, level ) { + const { showHierarchy, showPostCounts } = this.props.attributes; + const childCategories = this.getCategories( category.id ); + + return [ + , + showHierarchy && + !! childCategories.length && ( + childCategories.map( childCategory => this.renderCategoryDropdownItem( childCategory, level + 1 ) ) + ), + ]; + } + + render() { + const { setAttributes } = this.props; + const categories = this.getCategories(); + + if ( ! categories.length ) { + return ( + + + + ); + } + + const { focus } = this.props; + const { align, displayAsDropdown, showHierarchy, showPostCounts } = this.props.attributes; + + return [ + focus && ( + + { + setAttributes( { align: nextAlign } ); + } } + controls={ [ 'left', 'center', 'right', 'full' ] } + /> + + ), + focus && ( + + +

    { __( 'Shows a list of your site\'s categories.' ) }

    +
    +

    { __( 'Categories Settings' ) }

    + + + +
    + ), +
    + { + displayAsDropdown + ? this.renderCategoryDropdown() + : this.renderCategoryList() + } +
    , + ]; + } + + componentWillUnmount() { + if ( this.categoriesRequest.state() === 'pending' ) { + this.categoriesRequest.abort(); + } + } + }, + + save() { + return null; + }, +} ); diff --git a/blocks/library/categories/style.scss b/blocks/library/categories/style.scss new file mode 100644 index 00000000000000..d40346c0b43f9d --- /dev/null +++ b/blocks/library/categories/style.scss @@ -0,0 +1,11 @@ + +.editor-visual-editor__block[data-type="core/categories"] { + + .wp-block-categories ul { + padding-left: 2.5em; + + ul { + margin-top: 6px; + } + } +} From 6f49fc2b9c4c9641f866c526cff88968b4dad3ce Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Mon, 31 Jul 2017 11:38:03 +0300 Subject: [PATCH 02/16] Add server-side rendering for Categories block --- lib/blocks/categories.php | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 lib/blocks/categories.php diff --git a/lib/blocks/categories.php b/lib/blocks/categories.php new file mode 100644 index 00000000000000..2f7fc04b5ef179 --- /dev/null +++ b/lib/blocks/categories.php @@ -0,0 +1,52 @@ + false, + 'hierarchical' => ! empty( $attributes['showHierarchy'] ), + 'orderby' => 'name', + 'show_count' => ! empty( $attributes['showPostCounts'] ), + 'title_li' => '', + ); + + if ( ! empty( $attributes['displayAsDropdown'] ) ) { + $wrapper_markup = '
    %2$s
    '; + $items_markup = wp_dropdown_categories( $args ); + $type = 'dropdown'; + } else { + $wrapper_markup = '
      %2$s
    '; + $items_markup = wp_list_categories( $args ); + $type = 'list'; + } + + $class = "wp-block-categories wp-block-categories-{$type} align{$align}"; + + $block_content = sprintf( + $wrapper_markup, + esc_attr( $class ), + $items_markup + ); + + return $block_content; +} + +register_block_type( 'core/categories', array( + 'render_callback' => 'gutenberg_render_block_core_categories', +) ); From 3f546d0a509ba05face4a028a4081d3a9e211d7d Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Mon, 31 Jul 2017 11:38:35 +0300 Subject: [PATCH 03/16] Hook up the new Categories block --- blocks/library/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/blocks/library/index.js b/blocks/library/index.js index d6ca6eccc3bac6..46e0c00d390fa6 100644 --- a/blocks/library/index.js +++ b/blocks/library/index.js @@ -15,6 +15,7 @@ import './code'; import './html'; import './freeform'; import './latest-posts'; +import './categories'; import './cover-image'; import './cover-text'; import './verse'; From 28936079046ad680cac3fb9d4beebe0faa66e6a5 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Mon, 31 Jul 2017 11:39:39 +0300 Subject: [PATCH 04/16] Add Categories block to full content tests --- blocks/test/fixtures/core__categories.html | 1 + blocks/test/fixtures/core__categories.json | 12 ++++++++++++ blocks/test/fixtures/core__categories.parsed.json | 15 +++++++++++++++ .../fixtures/core__categories.serialized.html | 1 + 4 files changed, 29 insertions(+) create mode 100644 blocks/test/fixtures/core__categories.html create mode 100644 blocks/test/fixtures/core__categories.json create mode 100644 blocks/test/fixtures/core__categories.parsed.json create mode 100644 blocks/test/fixtures/core__categories.serialized.html diff --git a/blocks/test/fixtures/core__categories.html b/blocks/test/fixtures/core__categories.html new file mode 100644 index 00000000000000..89dd1be4ab254e --- /dev/null +++ b/blocks/test/fixtures/core__categories.html @@ -0,0 +1 @@ + diff --git a/blocks/test/fixtures/core__categories.json b/blocks/test/fixtures/core__categories.json new file mode 100644 index 00000000000000..af3b5167615ef3 --- /dev/null +++ b/blocks/test/fixtures/core__categories.json @@ -0,0 +1,12 @@ +[ + { + "uid": "_uid_0", + "name": "core/categories", + "isValid": true, + "attributes": { + "showPostCounts": false, + "displayAsDropdown": false, + "showHierarchy": false + } + } +] diff --git a/blocks/test/fixtures/core__categories.parsed.json b/blocks/test/fixtures/core__categories.parsed.json new file mode 100644 index 00000000000000..389788dbf0b385 --- /dev/null +++ b/blocks/test/fixtures/core__categories.parsed.json @@ -0,0 +1,15 @@ +[ + { + "blockName": "core/categories", + "attrs": { + "showPostCounts": false, + "displayAsDropdown": false, + "showHierarchy": false + }, + "rawContent": "" + }, + { + "attrs": {}, + "rawContent": "\n" + } +] diff --git a/blocks/test/fixtures/core__categories.serialized.html b/blocks/test/fixtures/core__categories.serialized.html new file mode 100644 index 00000000000000..5b9e6d885657af --- /dev/null +++ b/blocks/test/fixtures/core__categories.serialized.html @@ -0,0 +1 @@ + From 2dfb411860810fbfd9dc66218780e731976111bf Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Mon, 31 Jul 2017 15:26:35 +0300 Subject: [PATCH 05/16] Add frontend scripts for the Categories block in dropdown mode --- lib/blocks/categories.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/blocks/categories.php b/lib/blocks/categories.php index 2f7fc04b5ef179..b7e0f023e915d6 100644 --- a/lib/blocks/categories.php +++ b/lib/blocks/categories.php @@ -27,9 +27,15 @@ function gutenberg_render_block_core_categories( $attributes ) { ); if ( ! empty( $attributes['displayAsDropdown'] ) ) { + $id = 'wp-block-categories-' . wp_rand(); + $args['id'] = $id; $wrapper_markup = '
    %2$s
    '; $items_markup = wp_dropdown_categories( $args ); $type = 'dropdown'; + + if ( ! is_admin() ) { + $wrapper_markup .= gutenberg_build_dropdown_script_block_core_categories( $id ); + } } else { $wrapper_markup = '
      %2$s
    '; $items_markup = wp_list_categories( $args ); @@ -47,6 +53,33 @@ function gutenberg_render_block_core_categories( $attributes ) { return $block_content; } +/** + * Generates the inline script for a categories dropdown field. + * + * @param string $dropdown_id ID of the dropdown field. + * + * @return string Returns the dropdown onChange redirection script. + */ +function gutenberg_build_dropdown_script_block_core_categories( $dropdown_id ) { + ob_start(); + ?> + + 'gutenberg_render_block_core_categories', ) ); From 2cc28049631994c33248990bb0c97af3d9e3dd1d Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Mon, 31 Jul 2017 15:47:19 +0300 Subject: [PATCH 06/16] Add an empty Select Category option to Categories block dropdown --- lib/blocks/categories.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/blocks/categories.php b/lib/blocks/categories.php index b7e0f023e915d6..54a011cd313056 100644 --- a/lib/blocks/categories.php +++ b/lib/blocks/categories.php @@ -29,6 +29,7 @@ function gutenberg_render_block_core_categories( $attributes ) { if ( ! empty( $attributes['displayAsDropdown'] ) ) { $id = 'wp-block-categories-' . wp_rand(); $args['id'] = $id; + $args['show_option_none'] = __( 'Select Category', 'gutenberg' ); $wrapper_markup = '
    %2$s
    '; $items_markup = wp_dropdown_categories( $args ); $type = 'dropdown'; From 86afe7aa1e2ac2931137599cf59286c98a53f5ca Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Thu, 3 Aug 2017 16:19:06 +0300 Subject: [PATCH 07/16] Fix categories data fetching description comment --- blocks/library/categories/data.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/library/categories/data.js b/blocks/library/categories/data.js index 26e763efb8fe90..0d56bd4a7a854d 100644 --- a/blocks/library/categories/data.js +++ b/blocks/library/categories/data.js @@ -1,7 +1,7 @@ /** - * Returns a Promise with the categories or an error on failure. + * Returns a jqXHR object with the categories or an error on failure. * - * @returns {wp.api.collections.Categories} Returns a Promise with all categories. + * @returns {wp.api.collections.Categories} Returns a jqXHR object with all categories. */ export function getCategories() { const categoriesCollection = new wp.api.collections.Categories(); From 515dad4b892179877f231b654accf03b41f9c033 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Thu, 3 Aug 2017 16:25:58 +0300 Subject: [PATCH 08/16] Update npm scope of WP imports --- blocks/library/categories/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blocks/library/categories/index.js b/blocks/library/categories/index.js index baa9c4568ea4ae..2acfa981a691b5 100644 --- a/blocks/library/categories/index.js +++ b/blocks/library/categories/index.js @@ -1,9 +1,9 @@ /** * WordPress dependencies */ -import { Component } from 'element'; -import { Placeholder, Spinner } from 'components'; -import { __ } from 'i18n'; +import { Component } from '@wordpress/element'; +import { Placeholder, Spinner } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies From 30ba95ea5e701489b08bc2d2f8b42ae49ae759c3 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Thu, 3 Aug 2017 16:34:23 +0300 Subject: [PATCH 09/16] Clean up props destructuring in Categories block --- blocks/library/categories/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/blocks/library/categories/index.js b/blocks/library/categories/index.js index 2acfa981a691b5..97b59a48230217 100644 --- a/blocks/library/categories/index.js +++ b/blocks/library/categories/index.js @@ -57,22 +57,22 @@ registerBlockType( 'core/categories', { } toggleDisplayAsDropdown() { - const { displayAsDropdown } = this.props.attributes; - const { setAttributes } = this.props; + const { attributes, setAttributes } = this.props; + const { displayAsDropdown } = attributes; setAttributes( { displayAsDropdown: ! displayAsDropdown } ); } toggleShowPostCounts() { - const { showPostCounts } = this.props.attributes; - const { setAttributes } = this.props; + const { attributes, setAttributes } = this.props; + const { showPostCounts } = attributes; setAttributes( { showPostCounts: ! showPostCounts } ); } toggleShowHierarchy() { - const { showHierarchy } = this.props.attributes; - const { setAttributes } = this.props; + const { attributes, setAttributes } = this.props; + const { showHierarchy } = attributes; setAttributes( { showHierarchy: ! showHierarchy } ); } From 0fa3f2936c3a66442de9f8019659e713e416efe8 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Thu, 3 Aug 2017 16:35:49 +0300 Subject: [PATCH 10/16] Cleanup in Categories block data retrieval --- blocks/library/categories/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/library/categories/index.js b/blocks/library/categories/index.js index 97b59a48230217..db7d113862482f 100644 --- a/blocks/library/categories/index.js +++ b/blocks/library/categories/index.js @@ -80,7 +80,7 @@ registerBlockType( 'core/categories', { getCategories( parentId = null ) { const { categories } = this.state; if ( ! categories.length ) { - return []; + return categories; } if ( parentId === null ) { From 428d7913fa20c21e35133bb378f8439828c97c1a Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Thu, 3 Aug 2017 16:50:40 +0300 Subject: [PATCH 11/16] Unescape HTML entities in category names --- blocks/library/categories/index.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/blocks/library/categories/index.js b/blocks/library/categories/index.js index db7d113862482f..ab5553acc2f9ee 100644 --- a/blocks/library/categories/index.js +++ b/blocks/library/categories/index.js @@ -4,6 +4,7 @@ import { Component } from '@wordpress/element'; import { Placeholder, Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { unescape } from 'lodash'; /** * Internal dependencies @@ -95,6 +96,14 @@ registerBlockType( 'core/categories', { return `${ className }__list ${ className }__list-level-${ level }`; } + renderCategoryName( category ) { + if ( ! category.name ) { + return __( '(Untitled)' ); + } + + return unescape( category.name ).trim(); + } + renderCategoryList() { const { showHierarchy } = this.props.attributes; const parentId = showHierarchy ? 0 : null; @@ -113,7 +122,7 @@ registerBlockType( 'core/categories', { return (
  • - { category.name.trim() || __( '(Untitled)' ) } + { this.renderCategoryName( category ) } { showPostCounts && { ' ' }({ category.count }) @@ -151,7 +160,7 @@ registerBlockType( 'core/categories', { return [