Skip to:
Content

BuddyPress.org

Ticket #8048: 8048.2.patch

File 8048.2.patch, 89.3 KB (added by imath, 6 years ago)
  • .gitignore

    diff --git .gitignore .gitignore
    index 769dc445e..f26f47fbd 100644
     
    11.DS_Store
    22phpunit.xml
    33.idea
     4.cache
    45
    56lib-cov
    67*.seed
  • .jshintignore

    diff --git .jshintignore .jshintignore
    index c7593c293..b94772ae8 100644
     
    11// 3rd party libraries
    22src/bp-core/js/vendor/**/*
    33src/bp-messages/js/autocomplete/*
     4
     5// Blocks Scripts
     6src/**/js/blocks/*.js
     7src/**/js/block-components/*.js
     8src/**/js/block-components/**/*.js
     9src/bp-core/js/block-components.js
  • Gruntfile.js

    diff --git Gruntfile.js Gruntfile.js
    index 590b38872..a2d97b06d 100644
    module.exports = function( grunt ) { 
    1717                        '**/*.js'
    1818                ],
    1919
     20                BP_EXCLUDED_JS = [
     21                        '!**/js/blocks/*.js',
     22                        '!**/js/block-components/*.js',
     23                        '!**/js/block-components/**/*.js',
     24                        '!**/js/block-components.js'
     25                ],
     26
    2027                BP_EXCLUDED_MISC = [
    2128                ],
    2229
    module.exports = function( grunt ) { 
    164171                                expand: true
    165172                        }
    166173                },
    167                 makepot: {
    168                         target: {
    169                                 options: {
    170                                         cwd: BUILD_DIR,
    171                                         domainPath: '.',
    172                                         mainFile: 'bp-loader.php',
    173                                         potFilename: 'buddypress.pot',
    174                                         processPot: function( pot ) {
    175                                                 pot.headers['report-msgid-bugs-to'] = 'https://buddypress.trac.wordpress.org';
    176                                                 pot.headers['last-translator'] = 'JOHN JAMES JACOBY <jjj@buddypress.org>';
    177                                                 pot.headers['language-team'] = 'ENGLISH <jjj@buddypress.org>';
    178                                                 return pot;
    179                                         },
    180                                         type: 'wp-plugin'
    181                                 }
    182                         }
    183                 },
    184174                imagemin: {
    185175                        core: {
    186176                                expand: true,
    module.exports = function( grunt ) { 
    201191                                                dest: BUILD_DIR,
    202192                                                dot: true,
    203193                                                expand: true,
    204                                                 src: ['**', '!**/.{svn,git}/**'].concat( BP_EXCLUDED_MISC )
     194                                                src: ['**', '!**/.{svn,git,cache}/**', '!js/**'].concat( BP_EXCLUDED_MISC )
    205195                                        },
    206196                                        {
    207197                                                dest: BUILD_DIR,
    module.exports = function( grunt ) { 
    245235                                extDot: 'last',
    246236                                expand: true,
    247237                                ext: '.min.js',
    248                                 src: BP_JS
     238                                src: BP_JS.concat( BP_EXCLUDED_JS, BP_EXCLUDED_MISC )
    249239                        }
    250240                },
    251241                stylelint: {
    module.exports = function( grunt ) { 
    332322                                command: 'svn export --force https://github.com/buddypress/BP-REST.git/trunk bp-rest',
    333323                                cwd: BUILD_DIR,
    334324                                stdout: false
     325                        },
     326                        makepot: {
     327                                command: 'wp i18n make-pot build build/buddypress.pot --headers=\'{"Project-Id-Version": "BuddyPress", "Report-Msgid-Bugs-To": "https://buddypress.trac.wordpress.org", "Last-Translator": "JOHN JAMES JACOBY <jjj@buddypress.org>", "Language-Team": "ENGLISH <jjj@buddypress.org>"}\'',
     328                                stdout: true
     329                        },
     330                        blocks_src: {
     331                                command: 'npm run dev',
     332                                cwd: SOURCE_DIR,
     333                                stdout: true
     334                        },
     335                        blocks_build: {
     336                                command: 'npm run build',
     337                                cwd: SOURCE_DIR,
     338                                stdout: true
    335339                        }
    336340                },
    337341                jsvalidate:{
    module.exports = function( grunt ) { 
    342346                        },
    343347                        build: {
    344348                                files: {
    345                                         src: [BUILD_DIR + '/**/*.js']
     349                                        src: [BUILD_DIR + '/**/*.js'].concat( BP_EXCLUDED_JS, BP_EXCLUDED_MISC )
    346350                                }
    347351                        },
    348352                        src: {
    349353                                files: {
    350                                         src: [SOURCE_DIR + '/**/*.js'].concat( BP_EXCLUDED_MISC )
     354                                        src: [SOURCE_DIR + '/**/*.js'].concat( BP_EXCLUDED_JS, BP_EXCLUDED_MISC )
    351355                                }
    352356                        }
    353357                },
    module.exports = function( grunt ) { 
    367371         * Register tasks.
    368372         */
    369373        grunt.registerTask( 'src',     ['checkDependencies', 'jsvalidate:src', 'jshint', 'stylelint', 'sass', 'postcss', 'rtlcss'] );
     374        grunt.registerTask( 'makepot', ['exec:makepot'] );
    370375        grunt.registerTask( 'commit',  ['src', 'checktextdomain', 'imagemin', 'phplint', 'exec:phpcompat'] );
    371376        grunt.registerTask( 'bp_rest', [ 'exec:rest_api', 'copy:bp_rest_components', 'copy:bp_rest_core', 'clean:bp_rest' ] );
    372         grunt.registerTask( 'build',   ['commit', 'clean:all', 'copy:files', 'uglify', 'jsvalidate:build', 'cssmin', 'bp_rest', 'makepot', 'exec:bpdefault', 'exec:cli'] );
     377        grunt.registerTask( 'build',   ['commit', 'clean:all', 'copy:files', 'uglify:core', 'jsvalidate:build', 'exec:blocks_src', 'cssmin', 'bp_rest', 'makepot', 'exec:blocks_build', 'exec:bpdefault', 'exec:cli'] );
    373378        grunt.registerTask( 'release', ['build'] );
    374379
    375380        // Testing tasks.
  • composer.json

    diff --git composer.json composer.json
    index df0c4dbdd..216b1d8a6 100644
     
    3131                "php": ">=5.3.0"
    3232        },
    3333        "require-dev": {
    34                 "phpcompatibility/phpcompatibility-wp": "*",
     34                "phpcompatibility/phpcompatibility-wp": "*",
    3535                "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3"
    3636        }
    3737}
  • package.json

    diff --git package.json package.json
    index 5e26d7be7..9ed9a4a44 100644
     
    55        },
    66        "description": "BuddyPress adds community features to WordPress. Member Profiles, Activity Streams, Direct Messaging, Notifications, and more!",
    77        "devDependencies": {
     8                "@babel/core": "~7.8.7",
     9                "@wordpress/babel-preset-default": "~4.10.0",
    810                "@wordpress/browserslist-config": "~2.1.4",
    911                "autoprefixer": "~8.5.2",
    1012                "grunt": "~1.0.3",
     
    2628                "grunt-rtlcss": "~2.0.1",
    2729                "grunt-sass": "~2.0.0",
    2830                "grunt-stylelint": "~0.8.0",
    29                 "grunt-wp-i18n": "~1.0.2",
    3031                "matchdep": "~1.0.1",
     32                "parcel-bundler": "~1.12.4",
    3133                "postcss-scss": "~1.0.6",
    3234                "stylelint": "~7.10.1",
    3335                "stylelint-config-wordpress": "~11.0.0"
     
    3537        "engines": {
    3638                "node": ">=6.9.1"
    3739        },
     40        "scripts": {
     41                "start": "npm run dev:components && parcel watch src/js/bp-*/*s/blocks/*.js --out-dir src --no-source-maps",
     42                "dev": "npm run dev:components && parcel build src/js/bp-*/*s/blocks/*.js --out-dir src --no-source-maps --no-minify",
     43                "build": "npm run build:components && parcel build src/js/bp-*/*s/blocks/*.js --out-dir build --no-source-maps",
     44                "watch:components": "parcel watch src/js/bp-core/js/block-components/block-components.js --out-dir src/bp-core/js --out-file block-components.js --no-source-maps --global bp",
     45                "dev:components": "parcel build src/js/bp-core/js/block-components/block-components.js --out-dir src/bp-core/js --out-file block-components.js --no-source-maps --no-minify --global bp",
     46                "build:components": "parcel build src/js/bp-core/js/block-components/block-components.js --out-dir build/bp-core/js --out-file block-components.js --no-source-maps --global bp"
     47        },
    3848        "keywords": [
    3949                "activity",
    4050                "community",
     
    5363                "url": "https://buddypress.svn.wordpress.org/trunk/"
    5464        },
    5565        "version": "6.0.0-alpha",
    56         "dependencies": {},
    5766        "browserslist": [
    5867                "extends @wordpress/browserslist-config"
    5968        ]
  • src/bp-core/bp-core-actions.php

    diff --git src/bp-core/bp-core-actions.php src/bp-core/bp-core-actions.php
    index fe50c8a58..89296dd79 100644
    add_action( 'bp_init', 'bp_setup_globals', 4 ); 
    7575add_action( 'bp_init', 'bp_setup_canonical_stack',   5  );
    7676add_action( 'bp_init', 'bp_setup_nav',               6  );
    7777add_action( 'bp_init', 'bp_setup_title',             8  );
     78add_action( 'bp_init', 'bp_blocks_init',             10 );
    7879add_action( 'bp_init', 'bp_core_load_admin_bar_css', 12 );
    7980add_action( 'bp_init', 'bp_add_rewrite_tags',        20 );
    8081add_action( 'bp_init', 'bp_add_rewrite_rules',       30 );
  • new file src/bp-core/bp-core-blocks.php

    diff --git src/bp-core/bp-core-blocks.php src/bp-core/bp-core-blocks.php
    new file mode 100644
    index 000000000..7045c1d30
    - +  
     1<?php
     2/**
     3 * Core BP Blocks functions.
     4 *
     5 * @package BuddyPress
     6 * @subpackage Core
     7 * @since 6.0.0
     8 */
     9
     10// Exit if accessed directly.
     11defined( 'ABSPATH' ) || exit;
     12
     13/**
     14 * Registers the BP Block components.
     15 *
     16 * @since 6.0.0
     17 */
     18function bp_register_block_components() {
     19        wp_register_script(
     20                'bp-block-components',
     21                plugins_url( 'js/block-components.js', __FILE__ ),
     22                array(
     23                        'wp-element',
     24                        'wp-components',
     25                        'wp-i18n',
     26                        'wp-api-fetch',
     27                ),
     28                bp_get_version()
     29        );
     30}
     31add_action( 'bp_blocks_init', 'bp_register_block_components', 1 );
     32
     33/**
     34 * Filters the Block Editor settings to gather BuddyPress ones into a `bp` key.
     35 *
     36 * @since 6.0.0
     37 *
     38 * @param array $editor_settings Default editor settings.
     39 * @return array The editor settings including BP blocks specific ones.
     40 */
     41function bp_blocks_editor_settings( $editor_settings = array() ) {
     42        /**
     43         * Filter here to include your BP Blocks specific settings.
     44         *
     45         * @since 6.0.0
     46         *
     47         * @param array $bp_editor_settings BP blocks specific editor settings.
     48         */
     49        $bp_editor_settings = (array) apply_filters( 'bp_blocks_editor_settings', array() );
     50
     51        if ( $bp_editor_settings ) {
     52                $editor_settings['bp'] = $bp_editor_settings;
     53        }
     54
     55        return $editor_settings;
     56}
     57add_filter( 'block_editor_settings', 'bp_blocks_editor_settings' );
     58
     59/**
     60 * Register a BuddyPress block type.
     61 *
     62 * @since 6.0.0
     63 *
     64 * @param array $args The registration arguments for the block type.
     65 * @return BP_Block   The BuddyPress block type object.
     66 */
     67function bp_register_block( $args = array() ) {
     68        return new BP_Block( $args );
     69}
  • src/bp-core/bp-core-dependency.php

    diff --git src/bp-core/bp-core-dependency.php src/bp-core/bp-core-dependency.php
    index 6ea67ed85..cb3a3f4f5 100644
    function bp_rest_api_init() { 
    267267        do_action( 'bp_rest_api_init' );
    268268}
    269269
     270/**
     271 * BP Blocks Init hook.
     272 *
     273 * @since 6.0.0
     274 */
     275function bp_blocks_init() {
     276        /**
     277         * Hook here to register your BuddyPress blocks.
     278         *
     279         * @since 6.0.0
     280         */
     281        do_action( 'bp_blocks_init' );
     282}
     283
    270284/**
    271285 * Fire the 'bp_customize_register' action when the Customizer has loaded,
    272286 * allowing scripts and styles to be initialized.
  • new file src/bp-core/classes/class-bp-block.php

    diff --git src/bp-core/classes/class-bp-block.php src/bp-core/classes/class-bp-block.php
    new file mode 100644
    index 000000000..3f75a3644
    - +  
     1<?php
     2/**
     3 * BP Block class.
     4 *
     5 * @package BuddyPress
     6 * @subpackage Core
     7 * @since 6.0.0
     8 */
     9
     10// Exit if accessed directly.
     11if ( ! defined( 'ABSPATH' ) ) {
     12        exit;
     13}
     14
     15/**
     16 * BP Block Class.
     17 *
     18 * @since 6.0.0
     19 */
     20class BP_Block {
     21        /**
     22         * WP Block Type object.
     23         *
     24         * @since 6.0.0
     25         * @var WP_Block_Type|WP_Error
     26         */
     27        public $block;
     28
     29        /**
     30         * The script types registered.
     31         *
     32         * @since 6.0.0
     33         * @var array
     34         */
     35        private $registered_scripts;
     36
     37        /**
     38         * The style types registered.
     39         *
     40         * @since 6.0.0
     41         * @var array
     42         */
     43        private $registered_styles;
     44
     45        /**
     46         * Construct the BuddyPress Block.
     47         *
     48         * @since 6.0.0
     49         *
     50         * @param array $args The registration arguments for the BP Block.
     51         */
     52        public function __construct( $args ) {
     53                if ( ! did_action( 'bp_blocks_init' ) ) {
     54                        _doing_it_wrong( __METHOD__, esc_html__( 'BP Blocks needs to be registered hooking `bp_blocks_init`', 'buddypress' ), '6.0.0' );
     55                }
     56
     57                $min     = bp_core_get_minified_asset_suffix();
     58                $wp_args = array_intersect_key(
     59                        $args,
     60                        array(
     61                                'name'            => '',
     62                                'render_callback' => '',
     63                                'attributes'      => '',
     64                                'editor_script'   => '',
     65                                'script'          => '',
     66                                'editor_style'    => '',
     67                                'style'           => '',
     68                        )
     69                );
     70
     71                if ( ! isset( $wp_args['name'] ) || ! $wp_args['name'] || ! isset( $wp_args['editor_script'] ) || ! $wp_args['editor_script'] ) {
     72                        $this->block = new WP_Error( 'missing_parameters', __( 'The `name` or `editor_script` required keys are missing.', 'buddypress' ) );
     73                } else {
     74                        // Get specific BP Blocks arguments.
     75                        $bp_args = array_intersect_key(
     76                                $args,
     77                                array(
     78                                        'editor_script_url'  => '',
     79                                        'editor_script_deps' => array(),
     80                                        'script_url'         => '',
     81                                        'script_deps'        => array(),
     82                                        'editor_style_url'   => '',
     83                                        'editor_style_deps'  => array(),
     84                                        'style_url'          => '',
     85                                        'style_deps'         => array(),
     86                                )
     87                        );
     88
     89                        // Register the scripts.
     90                        $version                  = bp_get_version();
     91                        $this->registered_scripts = array();
     92
     93                        foreach ( array( 'editor_script', 'script' ) as $script_handle_key ) {
     94                                if ( ! isset( $wp_args[ $script_handle_key ] ) || ! $wp_args[ $script_handle_key ] ) {
     95                                        continue;
     96                                }
     97
     98                                if ( ! isset( $bp_args[ $script_handle_key . '_url' ] ) || ! $bp_args[ $script_handle_key . '_url' ] ) {
     99                                        continue;
     100                                }
     101
     102                                $deps = array();
     103                                if ( isset( $bp_args[ $script_handle_key . '_deps' ] ) && is_array( $bp_args[ $script_handle_key . '_deps' ] ) ) {
     104                                        $deps = $bp_args[ $script_handle_key . '_deps' ];
     105                                }
     106
     107                                $this->registered_scripts[ $script_handle_key ] = wp_register_script(
     108                                        $wp_args[ $script_handle_key ],
     109                                        $bp_args[ $script_handle_key . '_url' ],
     110                                        $deps,
     111                                        $version,
     112                                        true
     113                                );
     114                        }
     115
     116                        if ( ! isset( $this->registered_scripts['editor_script'] ) || ! $this->registered_scripts['editor_script'] ) {
     117                                $this->block = new WP_Error( 'script_registration_error', __( 'The required `editor_script` could not be registered.', 'buddypress' ) );
     118                        } else {
     119                                // Register the styles.
     120                                $registered_styles = array();
     121
     122                                foreach ( array( 'editor_style', 'style' ) as $style_handle_key ) {
     123                                        if ( ! isset( $wp_args[ $style_handle_key ] ) || ! $wp_args[ $style_handle_key ] ) {
     124                                                continue;
     125                                        }
     126
     127                                        if ( ! isset( $bp_args[ $style_handle_key . '_url' ] ) || ! $bp_args[ $style_handle_key . '_url' ] ) {
     128                                                continue;
     129                                        }
     130
     131                                        if ( $min ) {
     132                                                $minified_css  = str_replace( '.css', $min . '.css', $bp_args[ $style_handle_key . '_url' ] );
     133                                                $css_file_path = str_replace( content_url(), WP_CONTENT_DIR, $minified_css );
     134
     135                                                if ( file_exists( $css_file_path ) ) {
     136                                                        $bp_args[ $style_handle_key . '_url' ] = $minified_css;
     137                                                }
     138                                        }
     139
     140                                        $deps = array();
     141                                        if ( isset( $bp_args[ $style_handle_key . '_deps' ] ) && is_array( $bp_args[ $style_handle_key . '_deps' ] ) ) {
     142                                                $deps = $bp_args[ $style_handle_key . '_deps' ];
     143                                        }
     144
     145                                        $this->registered_styles[ $style_handle_key ] = wp_register_style(
     146                                                $wp_args[ $style_handle_key ],
     147                                                $bp_args[ $style_handle_key . '_url' ],
     148                                                $deps,
     149                                                $version
     150                                        );
     151
     152                                        wp_style_add_data( $wp_args[ $style_handle_key ], 'rtl', 'replace' );
     153                                        if ( $min ) {
     154                                                wp_style_add_data( $wp_args[ $style_handle_key ], 'suffix', $min );
     155                                        }
     156                                }
     157
     158                                $name = $wp_args['name'];
     159                                unset( $wp_args['name'] );
     160
     161                                // Set the Block Type.
     162                                $this->block = new WP_Block_Type( $name, $wp_args );
     163
     164                                // Register the Block Type.
     165                                register_block_type( $this->block );
     166
     167                                // Load Block translations if found.
     168                                if ( $this->block->editor_script ) {
     169                                        /**
     170                                         * Filter here to use a custom directory to look for the JSON translation file into.
     171                                         *
     172                                         * @since 6.0.0
     173                                         *
     174                                         * @param string $value         Absolute path to the directory to look for the JSON translation file into.
     175                                         * @param string $editor_script The editor's script handle.
     176                                         * @param string $name          The block's name.
     177                                         */
     178                                        $translation_dir = apply_filters( 'bp_block_translation_dir', null, $this->block->editor_script, $name );
     179
     180                                        /**
     181                                         * Filter here to use a custom domain for the JSON translation file.
     182                                         *
     183                                         * @since 6.0.0
     184                                         *
     185                                         * @param string $value         The custom domain for the JSON translation file.
     186                                         * @param string $editor_script The editor's script handle.
     187                                         * @param string $name          The block's name.
     188                                         */
     189                                        $domain = apply_filters( 'bp_block_translation_dir', 'buddypress', $this->block->editor_script, $name );
     190
     191                                        // Try to load the translation.
     192                                        $translated = wp_set_script_translations( $this->block->editor_script, $domain, $translation_dir );
     193                                }
     194                        }
     195                }
     196        }
     197}
  • src/bp-core/classes/class-bp-component.php

    diff --git src/bp-core/classes/class-bp-component.php src/bp-core/classes/class-bp-component.php
    index 6aaffc0a6..1e0d3478e 100644
    class BP_Component { 
    466466                        add_action( 'bp_rest_api_init', array( $this, 'rest_api_init' ), 10 );
    467467                }
    468468
     469                // Register BP Blocks.
     470                if ( bp_rest_api_is_available() ) {
     471                        add_action( 'bp_blocks_init', array( $this, 'blocks_init' ), 10 );
     472                }
     473
    469474                /**
    470475                 * Fires at the end of the setup_actions method inside BP_Component.
    471476                 *
    class BP_Component { 
    906911                 */
    907912                do_action( 'bp_' . $this->id . '_rest_api_init' );
    908913        }
     914
     915        /**
     916         * Register the BP Blocks.
     917         *
     918         * @since 6.0.0
     919         *
     920         * @param array $blocks The list of BP Blocks to register.
     921         */
     922        public function blocks_init( $blocks = array() ) {
     923                if ( is_array( $blocks ) && $blocks ) {
     924                        /**
     925                         * Filter here to disable all or some BP Blocks for a component.
     926                         *
     927                         * This is a dynamic hook that is based on the component string ID.
     928                         *
     929                         * @since 6.0.0
     930                         *
     931                         * @param array $blocks The list of BP Blocks for the component.
     932                         */
     933                        $blocks = (array) apply_filters( 'bp_' . $this->id . '_register_blocks', $blocks );
     934
     935                        foreach ( $blocks as $block ) {
     936                                bp_register_block( $block );
     937                        }
     938                }
     939
     940                /**
     941                 * Fires in the blocks_init method inside BP_Component.
     942                 *
     943                 * This is a dynamic hook that is based on the component string ID.
     944                 *
     945                 * @since 6.0.0
     946                 */
     947                do_action( 'bp_' . $this->id . '_blocks_init' );
     948        }
    909949}
    910950endif; // BP_Component.
  • new file src/bp-core/js/block-components.js

    diff --git src/bp-core/js/block-components.js src/bp-core/js/block-components.js
    new file mode 100644
    index 000000000..c27c7828b
    - +  
     1// modules are defined as an array
     2// [ module function, map of requires ]
     3//
     4// map of requires is short require name -> numeric require
     5//
     6// anything defined in a previous bundle is accessed via the
     7// orig method which is the require for previous bundles
     8parcelRequire = (function (modules, cache, entry, globalName) {
     9  // Save the require from previous bundle to this closure if any
     10  var previousRequire = typeof parcelRequire === 'function' && parcelRequire;
     11  var nodeRequire = typeof require === 'function' && require;
     12
     13  function newRequire(name, jumped) {
     14    if (!cache[name]) {
     15      if (!modules[name]) {
     16        // if we cannot find the module within our internal map or
     17        // cache jump to the current global require ie. the last bundle
     18        // that was added to the page.
     19        var currentRequire = typeof parcelRequire === 'function' && parcelRequire;
     20        if (!jumped && currentRequire) {
     21          return currentRequire(name, true);
     22        }
     23
     24        // If there are other bundles on this page the require from the
     25        // previous one is saved to 'previousRequire'. Repeat this as
     26        // many times as there are bundles until the module is found or
     27        // we exhaust the require chain.
     28        if (previousRequire) {
     29          return previousRequire(name, true);
     30        }
     31
     32        // Try the node require function if it exists.
     33        if (nodeRequire && typeof name === 'string') {
     34          return nodeRequire(name);
     35        }
     36
     37        var err = new Error('Cannot find module \'' + name + '\'');
     38        err.code = 'MODULE_NOT_FOUND';
     39        throw err;
     40      }
     41
     42      localRequire.resolve = resolve;
     43      localRequire.cache = {};
     44
     45      var module = cache[name] = new newRequire.Module(name);
     46
     47      modules[name][0].call(module.exports, localRequire, module, module.exports, this);
     48    }
     49
     50    return cache[name].exports;
     51
     52    function localRequire(x){
     53      return newRequire(localRequire.resolve(x));
     54    }
     55
     56    function resolve(x){
     57      return modules[name][1][x] || x;
     58    }
     59  }
     60
     61  function Module(moduleName) {
     62    this.id = moduleName;
     63    this.bundle = newRequire;
     64    this.exports = {};
     65  }
     66
     67  newRequire.isParcelRequire = true;
     68  newRequire.Module = Module;
     69  newRequire.modules = modules;
     70  newRequire.cache = cache;
     71  newRequire.parent = previousRequire;
     72  newRequire.register = function (id, exports) {
     73    modules[id] = [function (require, module) {
     74      module.exports = exports;
     75    }, {}];
     76  };
     77
     78  var error;
     79  for (var i = 0; i < entry.length; i++) {
     80    try {
     81      newRequire(entry[i]);
     82    } catch (e) {
     83      // Save first error but execute all entries
     84      if (!error) {
     85        error = e;
     86      }
     87    }
     88  }
     89
     90  if (entry.length) {
     91    // Expose entry point to Node, AMD or browser globals
     92    // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
     93    var mainExports = newRequire(entry[entry.length - 1]);
     94
     95    // CommonJS
     96    if (typeof exports === "object" && typeof module !== "undefined") {
     97      module.exports = mainExports;
     98
     99    // RequireJS
     100    } else if (typeof define === "function" && define.amd) {
     101     define(function () {
     102       return mainExports;
     103     });
     104
     105    // <script>
     106    } else if (globalName) {
     107      this[globalName] = mainExports;
     108    }
     109  }
     110
     111  // Override the current require with this new one
     112  parcelRequire = newRequire;
     113
     114  if (error) {
     115    // throw error from earlier, _after updating parcelRequire_
     116    throw error;
     117  }
     118
     119  return newRequire;
     120})({"IC7x":[function(require,module,exports) {
     121function _classCallCheck(instance, Constructor) {
     122  if (!(instance instanceof Constructor)) {
     123    throw new TypeError("Cannot call a class as a function");
     124  }
     125}
     126
     127module.exports = _classCallCheck;
     128},{}],"WiqS":[function(require,module,exports) {
     129function _defineProperties(target, props) {
     130  for (var i = 0; i < props.length; i++) {
     131    var descriptor = props[i];
     132    descriptor.enumerable = descriptor.enumerable || false;
     133    descriptor.configurable = true;
     134    if ("value" in descriptor) descriptor.writable = true;
     135    Object.defineProperty(target, descriptor.key, descriptor);
     136  }
     137}
     138
     139function _createClass(Constructor, protoProps, staticProps) {
     140  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
     141  if (staticProps) _defineProperties(Constructor, staticProps);
     142  return Constructor;
     143}
     144
     145module.exports = _createClass;
     146},{}],"xOn8":[function(require,module,exports) {
     147function _typeof(obj) {
     148  "@babel/helpers - typeof";
     149
     150  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
     151    module.exports = _typeof = function _typeof(obj) {
     152      return typeof obj;
     153    };
     154  } else {
     155    module.exports = _typeof = function _typeof(obj) {
     156      return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
     157    };
     158  }
     159
     160  return _typeof(obj);
     161}
     162
     163module.exports = _typeof;
     164},{}],"NS7G":[function(require,module,exports) {
     165function _assertThisInitialized(self) {
     166  if (self === void 0) {
     167    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
     168  }
     169
     170  return self;
     171}
     172
     173module.exports = _assertThisInitialized;
     174},{}],"oXYo":[function(require,module,exports) {
     175var _typeof = require("../helpers/typeof");
     176
     177var assertThisInitialized = require("./assertThisInitialized");
     178
     179function _possibleConstructorReturn(self, call) {
     180  if (call && (_typeof(call) === "object" || typeof call === "function")) {
     181    return call;
     182  }
     183
     184  return assertThisInitialized(self);
     185}
     186
     187module.exports = _possibleConstructorReturn;
     188},{"../helpers/typeof":"xOn8","./assertThisInitialized":"NS7G"}],"goD2":[function(require,module,exports) {
     189function _getPrototypeOf(o) {
     190  module.exports = _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
     191    return o.__proto__ || Object.getPrototypeOf(o);
     192  };
     193  return _getPrototypeOf(o);
     194}
     195
     196module.exports = _getPrototypeOf;
     197},{}],"zqo5":[function(require,module,exports) {
     198function _setPrototypeOf(o, p) {
     199  module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
     200    o.__proto__ = p;
     201    return o;
     202  };
     203
     204  return _setPrototypeOf(o, p);
     205}
     206
     207module.exports = _setPrototypeOf;
     208},{}],"RISo":[function(require,module,exports) {
     209var setPrototypeOf = require("./setPrototypeOf");
     210
     211function _inherits(subClass, superClass) {
     212  if (typeof superClass !== "function" && superClass !== null) {
     213    throw new TypeError("Super expression must either be null or a function");
     214  }
     215
     216  subClass.prototype = Object.create(superClass && superClass.prototype, {
     217    constructor: {
     218      value: subClass,
     219      writable: true,
     220      configurable: true
     221    }
     222  });
     223  if (superClass) setPrototypeOf(subClass, superClass);
     224}
     225
     226module.exports = _inherits;
     227},{"./setPrototypeOf":"zqo5"}],"W80x":[function(require,module,exports) {
     228"use strict";
     229
     230Object.defineProperty(exports, "__esModule", {
     231  value: true
     232});
     233exports.default = void 0;
     234
     235var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
     236
     237var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
     238
     239var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
     240
     241var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
     242
     243var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
     244
     245var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
     246
     247function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
     248
     249/**
     250 * WordPress dependencies.
     251 */
     252var _wp$element = wp.element,
     253    Component = _wp$element.Component,
     254    Fragment = _wp$element.Fragment,
     255    createElement = _wp$element.createElement;
     256var Popover = wp.components.Popover;
     257var _wp = wp,
     258    apiFetch = _wp.apiFetch;
     259var __ = wp.i18n.__;
     260
     261var AutoCompleter = /*#__PURE__*/function (_Component) {
     262  (0, _inherits2.default)(AutoCompleter, _Component);
     263
     264  function AutoCompleter() {
     265    var _this;
     266
     267    (0, _classCallCheck2.default)(this, AutoCompleter);
     268    _this = (0, _possibleConstructorReturn2.default)(this, (0, _getPrototypeOf2.default)(AutoCompleter).apply(this, arguments));
     269    _this.state = {
     270      search: '',
     271      items: [],
     272      error: ''
     273    };
     274    _this.searchItemName = _this.searchItemName.bind((0, _assertThisInitialized2.default)(_this));
     275    _this.selectItemName = _this.selectItemName.bind((0, _assertThisInitialized2.default)(_this));
     276    return _this;
     277  }
     278
     279  (0, _createClass2.default)(AutoCompleter, [{
     280    key: "searchItemName",
     281    value: function searchItemName(value) {
     282      var _this2 = this;
     283
     284      var search = this.state.search;
     285      var _this$props = this.props,
     286          component = _this$props.component,
     287          objectStatus = _this$props.objectStatus;
     288      this.setState({
     289        search: value
     290      });
     291
     292      if (value.length < search.length) {
     293        this.setState({
     294          items: []
     295        });
     296      }
     297
     298      var path = '/buddypress/v1/' + component;
     299
     300      if (value) {
     301        path += '?search=' + encodeURIComponent(value);
     302      }
     303
     304      if (objectStatus) {
     305        path += '&status=' + objectStatus;
     306      }
     307
     308      apiFetch({
     309        path: path
     310      }).then(function (items) {
     311        _this2.setState({
     312          items: items
     313        });
     314      }, function (error) {
     315        _this2.setState({
     316          error: error.message
     317        });
     318      });
     319    }
     320  }, {
     321    key: "selectItemName",
     322    value: function selectItemName(event, itemID) {
     323      var onSelectItem = this.props.onSelectItem;
     324      event.preventDefault();
     325      this.setState({
     326        search: '',
     327        items: [],
     328        error: ''
     329      });
     330      return onSelectItem({
     331        itemID: itemID
     332      });
     333    }
     334  }, {
     335    key: "render",
     336    value: function render() {
     337      var _this3 = this;
     338
     339      var _this$state = this.state,
     340          search = _this$state.search,
     341          items = _this$state.items;
     342      var _this$props2 = this.props,
     343          ariaLabel = _this$props2.ariaLabel,
     344          placeholder = _this$props2.placeholder,
     345          useAvatar = _this$props2.useAvatar;
     346      var itemsList;
     347
     348      if (!ariaLabel) {
     349        ariaLabel = __('Item\'s name', 'buddypress');
     350      }
     351
     352      if (!placeholder) {
     353        placeholder = __('Enter Item\'s name here…', 'buddypress');
     354      }
     355
     356      if (items.length) {
     357        itemsList = items.map(function (item) {
     358          return createElement("button", {
     359            type: "button",
     360            key: 'editor-autocompleters__item-item-' + item.id,
     361            role: "option",
     362            "aria-selected": "true",
     363            className: "components-button components-autocomplete__result editor-autocompleters__user",
     364            onClick: function onClick(event) {
     365              return _this3.selectItemName(event, item.id);
     366            }
     367          }, useAvatar && createElement("img", {
     368            key: "avatar",
     369            className: "editor-autocompleters__user-avatar",
     370            alt: "",
     371            src: item.avatar_urls.thumb
     372          }), createElement("span", {
     373            key: "name",
     374            className: "editor-autocompleters__user-name"
     375          }, item.name), item.mention_name && createElement("span", {
     376            key: "slug",
     377            className: "editor-autocompleters__user-slug"
     378          }, item.mention_name));
     379        });
     380      }
     381
     382      return createElement(Fragment, null, createElement("input", {
     383        type: "text",
     384        value: search,
     385        className: "components-placeholder__input",
     386        "aria-label": ariaLabel,
     387        placeholder: placeholder,
     388        onChange: function onChange(event) {
     389          return _this3.searchItemName(event.target.value);
     390        }
     391      }), 0 !== items.length && createElement(Popover, {
     392        className: "components-autocomplete__popover",
     393        focusOnMount: false,
     394        position: "bottom left"
     395      }, createElement("div", {
     396        className: "components-autocomplete__results"
     397      }, itemsList)));
     398    }
     399  }]);
     400  return AutoCompleter;
     401}(Component);
     402
     403var _default = AutoCompleter;
     404exports.default = _default;
     405},{"@babel/runtime/helpers/classCallCheck":"IC7x","@babel/runtime/helpers/createClass":"WiqS","@babel/runtime/helpers/possibleConstructorReturn":"oXYo","@babel/runtime/helpers/getPrototypeOf":"goD2","@babel/runtime/helpers/assertThisInitialized":"NS7G","@babel/runtime/helpers/inherits":"RISo"}],"iA92":[function(require,module,exports) {
     406"use strict";
     407
     408Object.defineProperty(exports, "__esModule", {
     409  value: true
     410});
     411exports.default = void 0;
     412
     413var _autocompleter = _interopRequireDefault(require("./autocompleter"));
     414
     415function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
     416
     417// Components
     418var _default = {
     419  AutoCompleter: _autocompleter.default
     420};
     421exports.default = _default;
     422},{"./autocompleter":"W80x"}],"Ee8M":[function(require,module,exports) {
     423"use strict";
     424
     425Object.defineProperty(exports, "__esModule", {
     426  value: true
     427});
     428Object.defineProperty(exports, "blockComponents", {
     429  enumerable: true,
     430  get: function () {
     431    return _components.default;
     432  }
     433});
     434
     435var _components = _interopRequireDefault(require("./components"));
     436
     437function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
     438},{"./components":"iA92"}]},{},["Ee8M"], "bp")
     439 No newline at end of file
  • new file src/bp-groups/bp-groups-blocks.php

    diff --git src/bp-groups/bp-groups-blocks.php src/bp-groups/bp-groups-blocks.php
    new file mode 100644
    index 000000000..cd9e41993
    - +  
     1<?php
     2/**
     3 * BP Groups Blocks Functions.
     4 *
     5 * @package BuddyPress
     6 * @subpackage GroupsBlocks
     7 * @since 6.0.0
     8 */
     9
     10// Exit if accessed directly.
     11if ( ! defined( 'ABSPATH' ) ) {
     12        exit;
     13}
     14
     15/**
     16 * Add BP Groups blocks specific settings to the BP Blocks Editor ones.
     17 *
     18 * @since 6.0.0
     19 *
     20 * @param array $bp_editor_settings BP blocks editor settings.
     21 * @return array BP Groups blocks editor settings.
     22 */
     23function bp_groups_editor_settings( $bp_editor_settings = array() ) {
     24        $bp = buddypress();
     25
     26        return array_merge(
     27                $bp_editor_settings,
     28                array(
     29                        'groups' => array(
     30                                'isAvatarEnabled'     => $bp->avatar && $bp->avatar->show_avatars && ! bp_disable_group_avatar_uploads(),
     31                                'isCoverImageEnabled' => bp_is_active( 'groups', 'cover_image' ),
     32                        ),
     33                )
     34        );
     35}
     36add_filter( 'bp_blocks_editor_settings', 'bp_groups_editor_settings' );
     37
     38/**
     39 * Callback function to render the BP Group Block.
     40 *
     41 * @since 6.0.0
     42 *
     43 * @param array $attributes The block attributes.
     44 * @return string           HTML output.
     45 */
     46function bp_groups_render_group_block( $attributes = array() ) {
     47        $bp = buddypress();
     48
     49        $block_args = wp_parse_args(
     50                $attributes,
     51                array(
     52                        'itemID'              => 0,
     53                        'avatarSize'          => 'full',
     54                        'displayDescription'  => true,
     55                        'displayActionButton' => true,
     56                        'displayCoverImage'   => true,
     57                )
     58        );
     59
     60        if ( ! $block_args['itemID'] ) {
     61                return;
     62        }
     63
     64        // Set the group ID and container classes.
     65        $group_id          = (int) $block_args['itemID'];
     66        $container_classes = array( 'bp-block-group' );
     67
     68        // Group object.
     69        $group = groups_get_group( $group_id );
     70
     71        if ( ! $group->id ) {
     72                return;
     73        }
     74
     75        // Avatar variables.
     76        $avatar           = '';
     77        $avatar_container = '';
     78
     79        // Cover image variable.
     80        $cover_image     = '';
     81        $cover_style     = '';
     82        $cover_container = '';
     83
     84        // Group name/link/description variables.
     85        $group_name        = bp_get_group_name( $group );
     86        $group_link        = bp_get_group_permalink( $group );
     87        $group_description = '';
     88        $group_content     = '';
     89
     90        // Group action button.
     91        $action_button         = '';
     92        $display_action_button = (bool) $block_args['displayActionButton'];
     93
     94        if ( $bp->avatar && $bp->avatar->show_avatars && ! bp_disable_group_avatar_uploads() && in_array( $block_args['avatarSize'], array( 'thumb', 'full' ), true ) ) {
     95                $avatar = bp_core_fetch_avatar(
     96                        array(
     97                                'item_id' => $group->id,
     98                                'object'  => 'group',
     99                                'type'    => $block_args['avatarSize'],
     100                                'html'    => false,
     101                        )
     102                );
     103
     104                $container_classes[] = 'avatar-' . $block_args['avatarSize'];
     105        } else {
     106                $container_classes[] = 'avatar-none';
     107        }
     108
     109        if ( $avatar ) {
     110                $avatar_container = sprintf(
     111                        '<div class="item-header-avatar">
     112                                <a href="%1$s">
     113                                        <img src="%2$s" alt="%3$s" class="avatar">
     114                                </a>
     115                        </div>',
     116                        esc_url( $group_link ),
     117                        esc_url( $avatar ),
     118                        // Translators: %s is the group's name.
     119                        sprintf( esc_html__( 'Group Profile photo of %s', 'buddypress' ), $group_name )
     120                );
     121        }
     122
     123        $display_cover_image = (bool) $block_args['displayCoverImage'];
     124        if ( bp_is_active( 'groups', 'cover_image' ) && $display_cover_image ) {
     125                $cover_image = bp_attachments_get_attachment(
     126                        'url',
     127                        array(
     128                                'item_id'    => $group->id,
     129                                'object_dir' => 'groups',
     130                        )
     131                );
     132
     133                if ( $cover_image ) {
     134                        $cover_style = sprintf(
     135                                ' style="background-image: url( %s );"',
     136                                esc_url( $cover_image )
     137                        );
     138                }
     139
     140                $cover_container = sprintf(
     141                        '<div class="bp-group-cover-image"%s></div>',
     142                        $cover_style
     143                );
     144
     145                $container_classes[] = 'has-cover';
     146        }
     147
     148        $display_description = (bool) $block_args['displayDescription'];
     149        if ( $display_description ) {
     150                $group_description = bp_get_group_description( $group );
     151                $group_content     = sprintf(
     152                        '<div class="group-description-content">%s</div>',
     153                        $group_description
     154                );
     155
     156                $container_classes[] = 'has-description';
     157        }
     158
     159        if ( $display_action_button ) {
     160                $action_button = sprintf(
     161                        '<div class="bp-profile-button">
     162                                <a href="%1$s" class="button large primary button-primary" role="button">%2$s</a>
     163                        </div>',
     164                        esc_url( $group_link ),
     165                        esc_html__( 'Visit Group', 'buddypress' )
     166                );
     167        }
     168
     169        $output = sprintf(
     170                '<div class="%1$s">
     171                        %2$s
     172                        <div class="group-content">
     173                                %3$s
     174                                <div class="group-description">
     175                                        <strong><a href="%4$s">%5$s</a></strong>
     176                                        %6$s
     177                                        %7$s
     178                                </div>
     179                        </div>
     180                </div>',
     181                implode( ' ', array_map( 'sanitize_html_class', $container_classes ) ),
     182                $cover_container,
     183                $avatar_container,
     184                esc_url( $group_link ),
     185                esc_html( $group_name ),
     186                $group_content,
     187                $action_button
     188        );
     189
     190        // Compact all interesting parameters.
     191        $params = array_merge( $block_args, compact( 'group_name', 'group_link', 'group_description', 'avatar', 'cover_image' ) );
     192
     193        /**
     194         * Filter here to edit the output of the single group block.
     195         *
     196         * @since 6.0.0
     197         *
     198         * @param string          $output The HTML output of the block.
     199         * @param BP_Groups_Group $group  The group object.
     200         * @param array           $params The block extended parameters.
     201         */
     202        return apply_filters( 'bp_groups_render_group_block_output', $output, $group, $params );
     203}
  • src/bp-groups/classes/class-bp-groups-component.php

    diff --git src/bp-groups/classes/class-bp-groups-component.php src/bp-groups/classes/class-bp-groups-component.php
    index b53835ea0..2f07fceae 100644
    class BP_Groups_Component extends BP_Component { 
    130130                        'functions',
    131131                        'notifications',
    132132                        'cssjs',
     133                        'blocks',
    133134                );
    134135
    135136                // Conditional includes.
    class BP_Groups_Component extends BP_Component { 
    940941                        'BP_REST_Attachments_Group_Avatar_Endpoint',
    941942                ) );
    942943        }
     944
     945        /**
     946         * Register the BP Groups Blocks.
     947         *
     948         * @since 6.0.0
     949         *
     950         * @param array $blocks Optional. See BP_Component::blocks_init() for
     951         *                      description.
     952         */
     953        public function blocks_init( $blocks = array() ) {
     954                parent::blocks_init(
     955                        array(
     956                                'bp/group' => array(
     957                                        'name'               => 'bp/group',
     958                                        'editor_script'      => 'bp-group-block',
     959                                        'editor_script_url'  => plugins_url( 'js/blocks/group.js', dirname(  __FILE__ ) ),
     960                                        'editor_script_deps' => array(
     961                                                'wp-blocks',
     962                                                'wp-element',
     963                                                'wp-components',
     964                                                'wp-i18n',
     965                                                'wp-editor',
     966                                                'wp-compose',
     967                                                'wp-data',
     968                                                'wp-block-editor',
     969                                                'bp-block-components',
     970                                        ),
     971                                        'style'              => 'bp-group-block',
     972                                        'style_url'          => plugins_url( 'css/blocks/group.css', dirname( __FILE__ ) ),
     973                                        'render_callback'    => 'bp_groups_render_group_block',
     974                                        'attributes'         => array(
     975                                                'itemID'              => array(
     976                                                        'type'    => 'integer',
     977                                                        'default' => 0,
     978                                                ),
     979                                                'avatarSize'          => array(
     980                                                        'type'    => 'string',
     981                                                        'default' => 'full',
     982                                                ),
     983                                                'displayDescription'  => array(
     984                                                        'type'    => 'boolean',
     985                                                        'default' => true,
     986                                                ),
     987                                                'displayActionButton' => array(
     988                                                        'type'    => 'boolean',
     989                                                        'default' => true,
     990                                                ),
     991                                                'displayCoverImage'   => array(
     992                                                        'type'    => 'boolean',
     993                                                        'default' => true,
     994                                                ),
     995                                        ),
     996                                ),
     997                        )
     998                );
     999        }
    9431000}
  • new file src/bp-groups/css/blocks/group-rtl.css

    diff --git src/bp-groups/css/blocks/group-rtl.css src/bp-groups/css/blocks/group-rtl.css
    new file mode 100644
    index 000000000..dce932ac8
    - +  
     1/* CSS for the bp/group block */
     2.bp-block-group {
     3        position: relative;
     4}
     5
     6.bp-block-group .group-content {
     7        display: flex;
     8}
     9
     10.bp-block-group.has-cover .group-content,
     11.bp-block-group.has-cover .item-header-avatar,
     12.bp-block-group.has-cover .group-description {
     13        z-index: 2;
     14}
     15
     16.bp-block-group .group-description {
     17        width: 100%;
     18}
     19
     20.bp-block-group.has-cover .group-description {
     21        padding-top: 75px;
     22}
     23
     24.bp-block-group.avatar-full .group-description {
     25        padding-right: 35px;
     26}
     27
     28.bp-block-group.has-cover .bp-group-cover-image {
     29        background-color: #c5c5c5;
     30        background-position: center top;
     31        background-repeat: no-repeat;
     32        background-size: cover;
     33        border: 0;
     34        display: block;
     35        right: 0;
     36        margin: 0;
     37        padding: 0;
     38        position: absolute;
     39        top: 0;
     40        width: 100%;
     41        z-index: 1;
     42        height: 150px;
     43}
     44
     45.bp-block-group img.avatar {
     46        width: auto;
     47        height: auto;
     48}
     49
     50.bp-block-group.avatar-none .item-header-avatar {
     51        display: none;
     52}
     53
     54.bp-block-group.avatar-none.has-cover {
     55        min-height: 200px;
     56}
     57
     58.bp-block-group.avatar-full {
     59        min-height: 150px;
     60}
     61
     62.bp-block-group.avatar-full.has-cover {
     63        min-height: 300px;
     64}
     65
     66.bp-block-group.avatar-full .item-header-avatar {
     67        width: 180px;
     68}
     69
     70.bp-block-group.has-cover.avatar-full .item-header-avatar {
     71        width: 200px;
     72}
     73
     74.bp-block-group.has-cover.avatar-full img.avatar {
     75        border: solid 2px #fff;
     76        background: rgba(255, 255, 255, 0.8);
     77        margin-right: 20px;
     78}
     79
     80.bp-block-group.has-cover .group-content {
     81        padding-top: 75px;
     82}
     83
     84.bp-block-group.avatar-thumb .item-header-avatar img.avatar {
     85        margin-top: 15px;
     86}
     87
     88.bp-block-group.avatar-thumb:not(.has-description) .group-content {
     89        min-height: 50px;
     90        align-items: center;
     91}
     92
     93.bp-block-group .group-description-content {
     94        width: 100%;
     95        margin-bottom: 18px;
     96}
     97
     98.bp-block-group.avatar-thumb .item-header-avatar {
     99        width: 70px;
     100}
     101
     102.bp-block-group.avatar-thumb.has-cover .item-header-avatar {
     103        padding-top: 75px;
     104}
     105
     106.bp-block-group .bp-profile-button {
     107        width: 100%;
     108        overflow: hidden;
     109}
     110
     111.bp-block-group .bp-profile-button a.button {
     112        margin: 18px 0 0;
     113}
     114
     115.bp-block-group.has-description .bp-profile-button a.button {
     116        display: block;
     117        float: left;
     118}
  • new file src/bp-groups/css/blocks/group.css

    diff --git src/bp-groups/css/blocks/group.css src/bp-groups/css/blocks/group.css
    new file mode 100644
    index 000000000..502b70b69
    - +  
     1/* CSS for the bp/group block */
     2.bp-block-group {
     3        position: relative;
     4}
     5
     6.bp-block-group .group-content {
     7        display: flex;
     8}
     9
     10.bp-block-group.has-cover .group-content,
     11.bp-block-group.has-cover .item-header-avatar,
     12.bp-block-group.has-cover .group-description {
     13        z-index: 2;
     14}
     15
     16.bp-block-group .group-description {
     17        width: 100%;
     18}
     19
     20.bp-block-group.has-cover .group-description {
     21        padding-top: 75px;
     22}
     23
     24.bp-block-group.avatar-full .group-description {
     25        padding-left: 35px;
     26}
     27
     28.bp-block-group.has-cover .bp-group-cover-image {
     29        background-color: #c5c5c5;
     30        background-position: center top;
     31        background-repeat: no-repeat;
     32        background-size: cover;
     33        border: 0;
     34        display: block;
     35        left: 0;
     36        margin: 0;
     37        padding: 0;
     38        position: absolute;
     39        top: 0;
     40        width: 100%;
     41        z-index: 1;
     42        height: 150px;
     43}
     44
     45.bp-block-group img.avatar {
     46        width: auto;
     47        height: auto;
     48}
     49
     50.bp-block-group.avatar-none .item-header-avatar {
     51        display: none;
     52}
     53
     54.bp-block-group.avatar-none.has-cover {
     55        min-height: 200px;
     56}
     57
     58.bp-block-group.avatar-full {
     59        min-height: 150px;
     60}
     61
     62.bp-block-group.avatar-full.has-cover {
     63        min-height: 300px;
     64}
     65
     66.bp-block-group.avatar-full .item-header-avatar {
     67        width: 180px;
     68}
     69
     70.bp-block-group.has-cover.avatar-full .item-header-avatar {
     71        width: 200px;
     72}
     73
     74.bp-block-group.has-cover.avatar-full img.avatar {
     75        border: solid 2px #fff;
     76        background: rgba(255, 255, 255, 0.8);
     77        margin-left: 20px;
     78}
     79
     80.bp-block-group.has-cover .group-content {
     81        padding-top: 75px;
     82}
     83
     84.bp-block-group.avatar-thumb .item-header-avatar img.avatar {
     85        margin-top: 15px;
     86}
     87
     88.bp-block-group.avatar-thumb:not(.has-description) .group-content {
     89        min-height: 50px;
     90        align-items: center;
     91}
     92
     93.bp-block-group .group-description-content {
     94        width: 100%;
     95        margin-bottom: 18px;
     96}
     97
     98.bp-block-group.avatar-thumb .item-header-avatar {
     99        width: 70px;
     100}
     101
     102.bp-block-group.avatar-thumb.has-cover .item-header-avatar {
     103        padding-top: 75px;
     104}
     105
     106.bp-block-group .bp-profile-button {
     107        width: 100%;
     108        overflow: hidden;
     109}
     110
     111.bp-block-group .bp-profile-button a.button {
     112        margin: 18px 0 0;
     113}
     114
     115.bp-block-group.has-description .bp-profile-button a.button {
     116        display: block;
     117        float: right;
     118}
  • new file src/bp-groups/js/blocks/group.js

    diff --git src/bp-groups/js/blocks/group.js src/bp-groups/js/blocks/group.js
    new file mode 100644
    index 000000000..c10b7e6bd
    - +  
     1// modules are defined as an array
     2// [ module function, map of requires ]
     3//
     4// map of requires is short require name -> numeric require
     5//
     6// anything defined in a previous bundle is accessed via the
     7// orig method which is the require for previous bundles
     8parcelRequire = (function (modules, cache, entry, globalName) {
     9  // Save the require from previous bundle to this closure if any
     10  var previousRequire = typeof parcelRequire === 'function' && parcelRequire;
     11  var nodeRequire = typeof require === 'function' && require;
     12
     13  function newRequire(name, jumped) {
     14    if (!cache[name]) {
     15      if (!modules[name]) {
     16        // if we cannot find the module within our internal map or
     17        // cache jump to the current global require ie. the last bundle
     18        // that was added to the page.
     19        var currentRequire = typeof parcelRequire === 'function' && parcelRequire;
     20        if (!jumped && currentRequire) {
     21          return currentRequire(name, true);
     22        }
     23
     24        // If there are other bundles on this page the require from the
     25        // previous one is saved to 'previousRequire'. Repeat this as
     26        // many times as there are bundles until the module is found or
     27        // we exhaust the require chain.
     28        if (previousRequire) {
     29          return previousRequire(name, true);
     30        }
     31
     32        // Try the node require function if it exists.
     33        if (nodeRequire && typeof name === 'string') {
     34          return nodeRequire(name);
     35        }
     36
     37        var err = new Error('Cannot find module \'' + name + '\'');
     38        err.code = 'MODULE_NOT_FOUND';
     39        throw err;
     40      }
     41
     42      localRequire.resolve = resolve;
     43      localRequire.cache = {};
     44
     45      var module = cache[name] = new newRequire.Module(name);
     46
     47      modules[name][0].call(module.exports, localRequire, module, module.exports, this);
     48    }
     49
     50    return cache[name].exports;
     51
     52    function localRequire(x){
     53      return newRequire(localRequire.resolve(x));
     54    }
     55
     56    function resolve(x){
     57      return modules[name][1][x] || x;
     58    }
     59  }
     60
     61  function Module(moduleName) {
     62    this.id = moduleName;
     63    this.bundle = newRequire;
     64    this.exports = {};
     65  }
     66
     67  newRequire.isParcelRequire = true;
     68  newRequire.Module = Module;
     69  newRequire.modules = modules;
     70  newRequire.cache = cache;
     71  newRequire.parent = previousRequire;
     72  newRequire.register = function (id, exports) {
     73    modules[id] = [function (require, module) {
     74      module.exports = exports;
     75    }, {}];
     76  };
     77
     78  var error;
     79  for (var i = 0; i < entry.length; i++) {
     80    try {
     81      newRequire(entry[i]);
     82    } catch (e) {
     83      // Save first error but execute all entries
     84      if (!error) {
     85        error = e;
     86      }
     87    }
     88  }
     89
     90  if (entry.length) {
     91    // Expose entry point to Node, AMD or browser globals
     92    // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
     93    var mainExports = newRequire(entry[entry.length - 1]);
     94
     95    // CommonJS
     96    if (typeof exports === "object" && typeof module !== "undefined") {
     97      module.exports = mainExports;
     98
     99    // RequireJS
     100    } else if (typeof define === "function" && define.amd) {
     101     define(function () {
     102       return mainExports;
     103     });
     104
     105    // <script>
     106    } else if (globalName) {
     107      this[globalName] = mainExports;
     108    }
     109  }
     110
     111  // Override the current require with this new one
     112  parcelRequire = newRequire;
     113
     114  if (error) {
     115    // throw error from earlier, _after updating parcelRequire_
     116    throw error;
     117  }
     118
     119  return newRequire;
     120})({"pvse":[function(require,module,exports) {
     121/**
     122 * WordPress dependencies.
     123 */
     124var registerBlockType = wp.blocks.registerBlockType;
     125var _wp$element = wp.element,
     126    createElement = _wp$element.createElement,
     127    Fragment = _wp$element.Fragment;
     128var _wp$components = wp.components,
     129    Placeholder = _wp$components.Placeholder,
     130    Disabled = _wp$components.Disabled,
     131    PanelBody = _wp$components.PanelBody,
     132    SelectControl = _wp$components.SelectControl,
     133    ToggleControl = _wp$components.ToggleControl,
     134    Toolbar = _wp$components.Toolbar,
     135    ToolbarButton = _wp$components.ToolbarButton;
     136var _wp$blockEditor = wp.blockEditor,
     137    InspectorControls = _wp$blockEditor.InspectorControls,
     138    BlockControls = _wp$blockEditor.BlockControls;
     139var withSelect = wp.data.withSelect;
     140var compose = wp.compose.compose;
     141var ServerSideRender = wp.editor.ServerSideRender;
     142var __ = wp.i18n.__;
     143/**
     144 * BuddyPress dependencies.
     145 */
     146
     147var AutoCompleter = bp.blockComponents.AutoCompleter;
     148var AVATAR_SIZES = [{
     149  label: __('None', 'buddypress'),
     150  value: 'none'
     151}, {
     152  label: __('Thumb', 'buddypress'),
     153  value: 'thumb'
     154}, {
     155  label: __('Full', 'buddypress'),
     156  value: 'full'
     157}];
     158
     159var editGroup = function editGroup(_ref) {
     160  var attributes = _ref.attributes,
     161      setAttributes = _ref.setAttributes,
     162      bpSettings = _ref.bpSettings;
     163  var isAvatarEnabled = bpSettings.isAvatarEnabled,
     164      isCoverImageEnabled = bpSettings.isCoverImageEnabled;
     165  var avatarSize = attributes.avatarSize,
     166      displayDescription = attributes.displayDescription,
     167      displayActionButton = attributes.displayActionButton,
     168      displayCoverImage = attributes.displayCoverImage;
     169
     170  if (!attributes.itemID) {
     171    return createElement(Placeholder, {
     172      icon: "buddicons-groups",
     173      label: __('BuddyPress Group', 'buddypress'),
     174      instructions: __('Start typing the name of the group you want to feature into this post.', 'buddypress')
     175    }, createElement(AutoCompleter, {
     176      component: "groups",
     177      objectStatus: "public",
     178      ariaLabel: __('Group\'s name', 'buddypress'),
     179      placeholder: __('Enter Group\'s name here…', 'buddypress'),
     180      onSelectItem: setAttributes,
     181      useAvatar: isAvatarEnabled
     182    }));
     183  }
     184
     185  return createElement(Fragment, null, createElement(BlockControls, null, createElement(Toolbar, null, createElement(ToolbarButton, {
     186    icon: "edit",
     187    title: __('Select another group', 'buddypress'),
     188    onClick: function onClick() {
     189      setAttributes({
     190        itemID: 0
     191      });
     192    }
     193  }))), createElement(InspectorControls, null, createElement(PanelBody, {
     194    title: __('Group\'s home button settings', 'buddypress'),
     195    initialOpen: true
     196  }, createElement(ToggleControl, {
     197    label: __('Display Group\'s home button', 'buddypress'),
     198    checked: !!displayActionButton,
     199    onChange: function onChange() {
     200      setAttributes({
     201        displayActionButton: !displayActionButton
     202      });
     203    },
     204    help: displayActionButton ? __('Include a link to the group\'s home page under their name.', 'buddypress') : __('Toggle to display a link to the group\'s home page under their name.', 'buddypress')
     205  })), createElement(PanelBody, {
     206    title: __('Description settings', 'buddypress'),
     207    initialOpen: false
     208  }, createElement(ToggleControl, {
     209    label: __('Display group\'s description', 'buddypress'),
     210    checked: !!displayDescription,
     211    onChange: function onChange() {
     212      setAttributes({
     213        displayDescription: !displayDescription
     214      });
     215    },
     216    help: displayDescription ? __('Include the group\'s description under their name.', 'buddypress') : __('Toggle to display the group\'s description under their name.', 'buddypress')
     217  })), isAvatarEnabled && createElement(PanelBody, {
     218    title: __('Avatar settings', 'buddypress'),
     219    initialOpen: false
     220  }, createElement(SelectControl, {
     221    label: __('Size', 'buddypress'),
     222    value: avatarSize,
     223    options: AVATAR_SIZES,
     224    onChange: function onChange(option) {
     225      setAttributes({
     226        avatarSize: option
     227      });
     228    }
     229  })), isCoverImageEnabled && createElement(PanelBody, {
     230    title: __('Cover image settings', 'buddypress'),
     231    initialOpen: false
     232  }, createElement(ToggleControl, {
     233    label: __('Display Cover Image', 'buddypress'),
     234    checked: !!displayCoverImage,
     235    onChange: function onChange() {
     236      setAttributes({
     237        displayCoverImage: !displayCoverImage
     238      });
     239    },
     240    help: displayCoverImage ? __('Include the group\'s cover image over their name.', 'buddypress') : __('Toggle to display the group\'s cover image over their name.', 'buddypress')
     241  }))), createElement(Disabled, null, createElement(ServerSideRender, {
     242    block: "bp/group",
     243    attributes: attributes
     244  })));
     245};
     246
     247var editGroupBlock = compose([withSelect(function (select) {
     248  var editorSettings = select('core/editor').getEditorSettings();
     249  return {
     250    bpSettings: editorSettings.bp.groups || {}
     251  };
     252})])(editGroup);
     253registerBlockType('bp/group', {
     254  title: __('Group', 'buddypress'),
     255  description: __('BuddyPress Group.', 'buddypress'),
     256  icon: 'buddicons-groups',
     257  category: 'buddypress',
     258  attributes: {
     259    itemID: {
     260      type: 'integer',
     261      default: 0
     262    },
     263    avatarSize: {
     264      type: 'string',
     265      default: 'full'
     266    },
     267    displayDescription: {
     268      type: 'boolean',
     269      default: true
     270    },
     271    displayActionButton: {
     272      type: 'boolean',
     273      default: true
     274    },
     275    displayCoverImage: {
     276      type: 'boolean',
     277      default: true
     278    }
     279  },
     280  edit: editGroupBlock
     281});
     282},{}]},{},["pvse"], null)
     283 No newline at end of file
  • new file src/bp-members/bp-members-blocks.php

    diff --git src/bp-members/bp-members-blocks.php src/bp-members/bp-members-blocks.php
    new file mode 100644
    index 000000000..8835625ef
    - +  
     1<?php
     2/**
     3 * BP Members Blocks Functions.
     4 *
     5 * @package BuddyPress
     6 * @subpackage MembersBlocks
     7 * @since 6.0.0
     8 */
     9
     10// Exit if accessed directly.
     11if ( ! defined( 'ABSPATH' ) ) {
     12        exit;
     13}
     14
     15/**
     16 * Add BP Members blocks specific settings to the BP Blocks Editor ones.
     17 *
     18 * @since 6.0.0
     19 *
     20 * @param array $bp_editor_settings BP blocks editor settings.
     21 * @return array BP Members blocks editor settings.
     22 */
     23function bp_members_editor_settings( $bp_editor_settings = array() ) {
     24        $bp = buddypress();
     25
     26        return array_merge(
     27                $bp_editor_settings,
     28                array(
     29                        'members' => array(
     30                                'isMentionEnabled'    => bp_is_active( 'activity' ) && bp_activity_do_mentions(),
     31                                'isAvatarEnabled'     => $bp->avatar && $bp->avatar->show_avatars,
     32                                'isCoverImageEnabled' => bp_is_active( 'members', 'cover_image' ),
     33                        ),
     34                )
     35        );
     36}
     37add_filter( 'bp_blocks_editor_settings', 'bp_members_editor_settings' );
     38
     39/**
     40 * Callback function to render the BP Member Block.
     41 *
     42 * @since 6.0.0
     43 *
     44 * @param array $attributes The block attributes.
     45 * @return string           HTML output.
     46 */
     47function bp_members_render_member_block( $attributes = array() ) {
     48        $bp = buddypress();
     49
     50        $block_args = wp_parse_args(
     51                $attributes,
     52                array(
     53                        'itemID'              => 0,
     54                        'avatarSize'          => 'full',
     55                        'displayMentionSlug'  => true,
     56                        'displayActionButton' => true,
     57                        'displayCoverImage'   => true,
     58                )
     59        );
     60
     61        if ( ! $block_args['itemID'] ) {
     62                return;
     63        }
     64
     65        // Set the member ID and container classes.
     66        $member_id         = (int) $block_args['itemID'];
     67        $container_classes = array( 'bp-block-member' );
     68
     69        // Mention variables.
     70        $username   = bp_core_get_username( $member_id );
     71        $at_mention = '';
     72
     73        // Avatar variables.
     74        $avatar           = '';
     75        $avatar_container = '';
     76
     77        // Cover image variable.
     78        $cover_image     = '';
     79        $cover_style     = '';
     80        $cover_container = '';
     81
     82        // Member name variables.
     83        $display_name = bp_core_get_user_displayname( $member_id );
     84        $member_link  = bp_core_get_user_domain( $member_id );
     85
     86        // Member action button.
     87        $action_button         = '';
     88        $display_action_button = (bool) $block_args['displayActionButton'];
     89
     90        if ( $bp->avatar && $bp->avatar->show_avatars && in_array( $block_args['avatarSize'], array( 'thumb', 'full' ), true ) ) {
     91                $avatar = bp_core_fetch_avatar(
     92                        array(
     93                                'item_id' => $member_id,
     94                                'object'  => 'user',
     95                                'type'    => $block_args['avatarSize'],
     96                                'html'    => false,
     97                        )
     98                );
     99
     100                $container_classes[] = 'avatar-' . $block_args['avatarSize'];
     101        } else {
     102                $container_classes[] = 'avatar-none';
     103        }
     104
     105        if ( $avatar ) {
     106                $avatar_container = sprintf(
     107                        '<div class="item-header-avatar">
     108                                <a href="%1$s">
     109                                        <img src="%2$s" alt="%3$s" class="avatar">
     110                                </a>
     111                        </div>',
     112                        esc_url( $member_link ),
     113                        esc_url( $avatar ),
     114                        // Translators: %s is the member's display name.
     115                        sprintf( esc_html__( 'Profile photo of %s', 'buddypress' ), $display_name )
     116                );
     117        }
     118
     119        $display_cover_image = (bool) $block_args['displayCoverImage'];
     120        if ( bp_is_active( 'members', 'cover_image' ) && $display_cover_image ) {
     121                $cover_image = bp_attachments_get_attachment(
     122                        'url',
     123                        array(
     124                                'item_id' => $member_id,
     125                        )
     126                );
     127
     128                if ( $cover_image ) {
     129                        $cover_style = sprintf(
     130                                ' style="background-image: url( %s );"',
     131                                esc_url( $cover_image )
     132                        );
     133                }
     134
     135                $cover_container = sprintf(
     136                        '<div class="bp-member-cover-image"%s></div>',
     137                        $cover_style
     138                );
     139
     140                $container_classes[] = 'has-cover';
     141        }
     142
     143        $display_mention_slug = (bool) $block_args['displayMentionSlug'];
     144        if ( bp_is_active( 'activity' ) && bp_activity_do_mentions() && $display_mention_slug ) {
     145                $at_mention = sprintf(
     146                        '<span class="user-nicename">@%s</span>',
     147                        esc_html( $username )
     148                );
     149        }
     150
     151        if ( $display_action_button ) {
     152                $action_button = sprintf(
     153                        '<div class="bp-profile-button">
     154                                <a href="%1$s" class="button large primary button-primary" role="button">%2$s</a>
     155                        </div>',
     156                        esc_url( $member_link ),
     157                        esc_html__( 'View Profile', 'buddypress' )
     158                );
     159        }
     160
     161        $output = sprintf(
     162                '<div class="%1$s">
     163                        %2$s
     164                        <div class="member-content">
     165                                %3$s
     166                                <div class="member-description">
     167                                        <strong><a href="%4$s">%5$s</a></strong>
     168                                        %6$s
     169                                        %7$s
     170                                </div>
     171                        </div>
     172                </div>',
     173                implode( ' ', array_map( 'sanitize_html_class', $container_classes ) ),
     174                $cover_container,
     175                $avatar_container,
     176                esc_url( $member_link ),
     177                esc_html( $display_name ),
     178                $at_mention,
     179                $action_button
     180        );
     181
     182        // Compact all interesting parameters.
     183        $params = array_merge( $block_args, compact( 'username', 'display_name', 'member_link', 'avatar', 'cover_image' ) );
     184
     185        /**
     186         * Filter here to edit the output of the single member block.
     187         *
     188         * @since 6.0.0
     189         *
     190         * @param string          $output The HTML output of the block.
     191         * @param array           $params The block extended parameters.
     192         */
     193        return apply_filters( 'bp_members_render_member_block_output', $output, $params );
     194}
  • src/bp-members/classes/class-bp-members-component.php

    diff --git src/bp-members/classes/class-bp-members-component.php src/bp-members/classes/class-bp-members-component.php
    index 29576b52f..25818244e 100644
    class BP_Members_Component extends BP_Component { 
    6161                        'template',
    6262                        'adminbar',
    6363                        'functions',
     64                        'blocks',
    6465                        'widgets',
    6566                        'cache',
    6667                );
    class BP_Members_Component extends BP_Component { 
    668669                        'BP_REST_Attachments_Member_Avatar_Endpoint',
    669670                ) );
    670671        }
     672
     673        /**
     674         * Register the BP Members Blocks.
     675         *
     676         * @since 6.0.0
     677         *
     678         * @param array $blocks Optional. See BP_Component::blocks_init() for
     679         *                      description.
     680         */
     681        public function blocks_init( $blocks = array() ) {
     682                parent::blocks_init(
     683                        array(
     684                                'bp/member' => array(
     685                                        'name'               => 'bp/member',
     686                                        'editor_script'      => 'bp-member-block',
     687                                        'editor_script_url'  => plugins_url( 'js/blocks/member.js', dirname( __FILE__ ) ),
     688                                        'editor_script_deps' => array(
     689                                                'wp-blocks',
     690                                                'wp-element',
     691                                                'wp-components',
     692                                                'wp-i18n',
     693                                                'wp-editor',
     694                                                'wp-compose',
     695                                                'wp-data',
     696                                                'wp-block-editor',
     697                                                'bp-block-components',
     698                                        ),
     699                                        'style'              => 'bp-member-block',
     700                                        'style_url'          => plugins_url( 'css/blocks/member.css', dirname( __FILE__ ) ),
     701                                        'render_callback'    => 'bp_members_render_member_block',
     702                                        'attributes'         => array(
     703                                                'itemID'              => array(
     704                                                        'type'    => 'integer',
     705                                                        'default' => 0,
     706                                                ),
     707                                                'avatarSize'          => array(
     708                                                        'type'    => 'string',
     709                                                        'default' => 'full',
     710                                                ),
     711                                                'displayMentionSlug'  => array(
     712                                                        'type'    => 'boolean',
     713                                                        'default' => true,
     714                                                ),
     715                                                'displayActionButton' => array(
     716                                                        'type'    => 'boolean',
     717                                                        'default' => true,
     718                                                ),
     719                                                'displayCoverImage'   => array(
     720                                                        'type'    => 'boolean',
     721                                                        'default' => true,
     722                                                ),
     723                                        ),
     724                                ),
     725                        )
     726                );
     727        }
    671728}
  • new file src/bp-members/css/blocks/member-rtl.css

    diff --git src/bp-members/css/blocks/member-rtl.css src/bp-members/css/blocks/member-rtl.css
    new file mode 100644
    index 000000000..ff9de024c
    - +  
     1/* CSS for the bp/member block */
     2.bp-block-member {
     3        position: relative;
     4}
     5
     6.bp-block-member .member-content {
     7        display: flex;
     8}
     9
     10.bp-block-member.has-cover .member-content,
     11.bp-block-member.has-cover .item-header-avatar,
     12.bp-block-member.has-cover .member-description {
     13        z-index: 2;
     14}
     15
     16.bp-block-member.has-cover .member-description {
     17        padding-top: 75px;
     18}
     19
     20.bp-block-member.has-cover .bp-member-cover-image {
     21        background-color: #c5c5c5;
     22        background-position: center top;
     23        background-repeat: no-repeat;
     24        background-size: cover;
     25        border: 0;
     26        display: block;
     27        right: 0;
     28        margin: 0;
     29        padding: 0;
     30        position: absolute;
     31        top: 0;
     32        width: 100%;
     33        z-index: 1;
     34        height: 150px;
     35}
     36
     37.bp-block-member img.avatar {
     38        width: auto;
     39        height: auto;
     40}
     41
     42.bp-block-member.avatar-none .item-header-avatar {
     43        display: none;
     44}
     45
     46.bp-block-member.avatar-none.has-cover {
     47        min-height: 200px;
     48}
     49
     50.bp-block-member.avatar-full {
     51        min-height: 150px;
     52}
     53
     54.bp-block-member.avatar-full.has-cover {
     55        min-height: 300px;
     56}
     57
     58.bp-block-member.avatar-full .item-header-avatar {
     59        width: 180px;
     60}
     61
     62.bp-block-member.has-cover.avatar-full .item-header-avatar {
     63        width: 200px;
     64}
     65
     66.bp-block-member.has-cover.avatar-full img.avatar {
     67        border: solid 2px #fff;
     68        background: rgba(255, 255, 255, 0.8);
     69        margin-right: 20px;
     70}
     71
     72.bp-block-member.has-cover .member-content {
     73        padding-top: 75px;
     74}
     75
     76.bp-block-member.avatar-thumb .member-content {
     77        min-height: 50px;
     78        align-items: center;
     79}
     80
     81.bp-block-member.avatar-thumb .item-header-avatar {
     82        width: 70px;
     83}
     84
     85.bp-block-member.avatar-thumb.has-cover .item-header-avatar {
     86        padding-top: 75px;
     87}
     88
     89.bp-block-member .user-nicename {
     90        display: block;
     91}
     92
     93.bp-block-member .user-nicename a,
     94.entry .entry-content .bp-block-member .user-nicename a {
     95        color: currentColor;
     96        text-decoration: none;
     97        border: none;
     98}
     99
     100.bp-block-member .bp-profile-button {
     101        width: 100%;
     102}
     103
     104.bp-block-member .bp-profile-button a.button {
     105        position: absolute;
     106        bottom: 10px;
     107        left: 0;
     108        display: inline-block;
     109        margin: 18px 0 0;
     110}
  • new file src/bp-members/css/blocks/member.css

    diff --git src/bp-members/css/blocks/member.css src/bp-members/css/blocks/member.css
    new file mode 100644
    index 000000000..b03ea8687
    - +  
     1/* CSS for the bp/member block */
     2.bp-block-member {
     3        position: relative;
     4}
     5
     6.bp-block-member .member-content {
     7        display: flex;
     8}
     9
     10.bp-block-member.has-cover .member-content,
     11.bp-block-member.has-cover .item-header-avatar,
     12.bp-block-member.has-cover .member-description {
     13        z-index: 2;
     14}
     15
     16.bp-block-member.has-cover .member-description {
     17        padding-top: 75px;
     18}
     19
     20.bp-block-member.has-cover .bp-member-cover-image {
     21        background-color: #c5c5c5;
     22        background-position: center top;
     23        background-repeat: no-repeat;
     24        background-size: cover;
     25        border: 0;
     26        display: block;
     27        left: 0;
     28        margin: 0;
     29        padding: 0;
     30        position: absolute;
     31        top: 0;
     32        width: 100%;
     33        z-index: 1;
     34        height: 150px;
     35}
     36
     37.bp-block-member img.avatar {
     38        width: auto;
     39        height: auto;
     40}
     41
     42.bp-block-member.avatar-none .item-header-avatar {
     43        display: none;
     44}
     45
     46.bp-block-member.avatar-none.has-cover {
     47        min-height: 200px;
     48}
     49
     50.bp-block-member.avatar-full {
     51        min-height: 150px;
     52}
     53
     54.bp-block-member.avatar-full.has-cover {
     55        min-height: 300px;
     56}
     57
     58.bp-block-member.avatar-full .item-header-avatar {
     59        width: 180px;
     60}
     61
     62.bp-block-member.has-cover.avatar-full .item-header-avatar {
     63        width: 200px;
     64}
     65
     66.bp-block-member.has-cover.avatar-full img.avatar {
     67        border: solid 2px #fff;
     68        background: rgba(255, 255, 255, 0.8);
     69        margin-left: 20px;
     70}
     71
     72.bp-block-member.has-cover .member-content {
     73        padding-top: 75px;
     74}
     75
     76.bp-block-member.avatar-thumb .member-content {
     77        min-height: 50px;
     78        align-items: center;
     79}
     80
     81.bp-block-member.avatar-thumb .item-header-avatar {
     82        width: 70px;
     83}
     84
     85.bp-block-member.avatar-thumb.has-cover .item-header-avatar {
     86        padding-top: 75px;
     87}
     88
     89.bp-block-member .user-nicename {
     90        display: block;
     91}
     92
     93.bp-block-member .user-nicename a,
     94.entry .entry-content .bp-block-member .user-nicename a {
     95        color: currentColor;
     96        text-decoration: none;
     97        border: none;
     98}
     99
     100.bp-block-member .bp-profile-button {
     101        width: 100%;
     102}
     103
     104.bp-block-member .bp-profile-button a.button {
     105        position: absolute;
     106        bottom: 10px;
     107        right: 0;
     108        display: inline-block;
     109        margin: 18px 0 0;
     110}
  • new file src/bp-members/js/blocks/member.js

    diff --git src/bp-members/js/blocks/member.js src/bp-members/js/blocks/member.js
    new file mode 100644
    index 000000000..4e7f56941
    - +  
     1// modules are defined as an array
     2// [ module function, map of requires ]
     3//
     4// map of requires is short require name -> numeric require
     5//
     6// anything defined in a previous bundle is accessed via the
     7// orig method which is the require for previous bundles
     8parcelRequire = (function (modules, cache, entry, globalName) {
     9  // Save the require from previous bundle to this closure if any
     10  var previousRequire = typeof parcelRequire === 'function' && parcelRequire;
     11  var nodeRequire = typeof require === 'function' && require;
     12
     13  function newRequire(name, jumped) {
     14    if (!cache[name]) {
     15      if (!modules[name]) {
     16        // if we cannot find the module within our internal map or
     17        // cache jump to the current global require ie. the last bundle
     18        // that was added to the page.
     19        var currentRequire = typeof parcelRequire === 'function' && parcelRequire;
     20        if (!jumped && currentRequire) {
     21          return currentRequire(name, true);
     22        }
     23
     24        // If there are other bundles on this page the require from the
     25        // previous one is saved to 'previousRequire'. Repeat this as
     26        // many times as there are bundles until the module is found or
     27        // we exhaust the require chain.
     28        if (previousRequire) {
     29          return previousRequire(name, true);
     30        }
     31
     32        // Try the node require function if it exists.
     33        if (nodeRequire && typeof name === 'string') {
     34          return nodeRequire(name);
     35        }
     36
     37        var err = new Error('Cannot find module \'' + name + '\'');
     38        err.code = 'MODULE_NOT_FOUND';
     39        throw err;
     40      }
     41
     42      localRequire.resolve = resolve;
     43      localRequire.cache = {};
     44
     45      var module = cache[name] = new newRequire.Module(name);
     46
     47      modules[name][0].call(module.exports, localRequire, module, module.exports, this);
     48    }
     49
     50    return cache[name].exports;
     51
     52    function localRequire(x){
     53      return newRequire(localRequire.resolve(x));
     54    }
     55
     56    function resolve(x){
     57      return modules[name][1][x] || x;
     58    }
     59  }
     60
     61  function Module(moduleName) {
     62    this.id = moduleName;
     63    this.bundle = newRequire;
     64    this.exports = {};
     65  }
     66
     67  newRequire.isParcelRequire = true;
     68  newRequire.Module = Module;
     69  newRequire.modules = modules;
     70  newRequire.cache = cache;
     71  newRequire.parent = previousRequire;
     72  newRequire.register = function (id, exports) {
     73    modules[id] = [function (require, module) {
     74      module.exports = exports;
     75    }, {}];
     76  };
     77
     78  var error;
     79  for (var i = 0; i < entry.length; i++) {
     80    try {
     81      newRequire(entry[i]);
     82    } catch (e) {
     83      // Save first error but execute all entries
     84      if (!error) {
     85        error = e;
     86      }
     87    }
     88  }
     89
     90  if (entry.length) {
     91    // Expose entry point to Node, AMD or browser globals
     92    // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
     93    var mainExports = newRequire(entry[entry.length - 1]);
     94
     95    // CommonJS
     96    if (typeof exports === "object" && typeof module !== "undefined") {
     97      module.exports = mainExports;
     98
     99    // RequireJS
     100    } else if (typeof define === "function" && define.amd) {
     101     define(function () {
     102       return mainExports;
     103     });
     104
     105    // <script>
     106    } else if (globalName) {
     107      this[globalName] = mainExports;
     108    }
     109  }
     110
     111  // Override the current require with this new one
     112  parcelRequire = newRequire;
     113
     114  if (error) {
     115    // throw error from earlier, _after updating parcelRequire_
     116    throw error;
     117  }
     118
     119  return newRequire;
     120})({"TmUL":[function(require,module,exports) {
     121/**
     122 * WordPress dependencies.
     123 */
     124var registerBlockType = wp.blocks.registerBlockType;
     125var _wp$element = wp.element,
     126    createElement = _wp$element.createElement,
     127    Fragment = _wp$element.Fragment;
     128var _wp$components = wp.components,
     129    Placeholder = _wp$components.Placeholder,
     130    Disabled = _wp$components.Disabled,
     131    PanelBody = _wp$components.PanelBody,
     132    SelectControl = _wp$components.SelectControl,
     133    ToggleControl = _wp$components.ToggleControl,
     134    Toolbar = _wp$components.Toolbar,
     135    ToolbarButton = _wp$components.ToolbarButton;
     136var _wp$blockEditor = wp.blockEditor,
     137    InspectorControls = _wp$blockEditor.InspectorControls,
     138    BlockControls = _wp$blockEditor.BlockControls;
     139var withSelect = wp.data.withSelect;
     140var compose = wp.compose.compose;
     141var ServerSideRender = wp.editor.ServerSideRender;
     142var __ = wp.i18n.__;
     143/**
     144 * BuddyPress dependencies.
     145 */
     146
     147var AutoCompleter = bp.blockComponents.AutoCompleter;
     148var AVATAR_SIZES = [{
     149  label: __('None', 'buddypress'),
     150  value: 'none'
     151}, {
     152  label: __('Thumb', 'buddypress'),
     153  value: 'thumb'
     154}, {
     155  label: __('Full', 'buddypress'),
     156  value: 'full'
     157}];
     158
     159var editMember = function editMember(_ref) {
     160  var attributes = _ref.attributes,
     161      setAttributes = _ref.setAttributes,
     162      bpSettings = _ref.bpSettings;
     163  var isAvatarEnabled = bpSettings.isAvatarEnabled,
     164      isMentionEnabled = bpSettings.isMentionEnabled,
     165      isCoverImageEnabled = bpSettings.isCoverImageEnabled;
     166  var avatarSize = attributes.avatarSize,
     167      displayMentionSlug = attributes.displayMentionSlug,
     168      displayActionButton = attributes.displayActionButton,
     169      displayCoverImage = attributes.displayCoverImage;
     170
     171  if (!attributes.itemID) {
     172    return createElement(Placeholder, {
     173      icon: "admin-users",
     174      label: __('BuddyPress Member', 'buddypress'),
     175      instructions: __('Start typing the name of the member you want to feature into this post.', 'buddypress')
     176    }, createElement(AutoCompleter, {
     177      component: "members",
     178      ariaLabel: __('Member\'s username', 'buddypress'),
     179      placeholder: __('Enter Member\'s username here…', 'buddypress'),
     180      onSelectItem: setAttributes,
     181      useAvatar: isAvatarEnabled
     182    }));
     183  }
     184
     185  return createElement(Fragment, null, createElement(BlockControls, null, createElement(Toolbar, null, createElement(ToolbarButton, {
     186    icon: "edit",
     187    title: __('Select another member', 'buddypress'),
     188    onClick: function onClick() {
     189      setAttributes({
     190        itemID: 0
     191      });
     192    }
     193  }))), createElement(InspectorControls, null, createElement(PanelBody, {
     194    title: __('Profile button settings', 'buddypress'),
     195    initialOpen: true
     196  }, createElement(ToggleControl, {
     197    label: __('Display Profile button', 'buddypress'),
     198    checked: !!displayActionButton,
     199    onChange: function onChange() {
     200      setAttributes({
     201        displayActionButton: !displayActionButton
     202      });
     203    },
     204    help: displayActionButton ? __('Include a link to the user\'s profile page under their display name.', 'buddypress') : __('Toggle to display a link to the user\'s profile page under their display name.', 'buddypress')
     205  })), isAvatarEnabled && createElement(PanelBody, {
     206    title: __('Avatar settings', 'buddypress'),
     207    initialOpen: false
     208  }, createElement(SelectControl, {
     209    label: __('Size', 'buddypress'),
     210    value: avatarSize,
     211    options: AVATAR_SIZES,
     212    onChange: function onChange(option) {
     213      setAttributes({
     214        avatarSize: option
     215      });
     216    }
     217  })), isCoverImageEnabled && createElement(PanelBody, {
     218    title: __('Cover image settings', 'buddypress'),
     219    initialOpen: false
     220  }, createElement(ToggleControl, {
     221    label: __('Display Cover Image', 'buddypress'),
     222    checked: !!displayCoverImage,
     223    onChange: function onChange() {
     224      setAttributes({
     225        displayCoverImage: !displayCoverImage
     226      });
     227    },
     228    help: displayCoverImage ? __('Include the user\'s cover image over their display name.', 'buddypress') : __('Toggle to display the user\'s cover image over their display name.', 'buddypress')
     229  })), isMentionEnabled && createElement(PanelBody, {
     230    title: __('Mention settings', 'buddypress'),
     231    initialOpen: false
     232  }, createElement(ToggleControl, {
     233    label: __('Display Mention slug', 'buddypress'),
     234    checked: !!displayMentionSlug,
     235    onChange: function onChange() {
     236      setAttributes({
     237        displayMentionSlug: !displayMentionSlug
     238      });
     239    },
     240    help: displayMentionSlug ? __('Include the user\'s mention name under their display name.', 'buddypress') : __('Toggle to display the user\'s mention name under their display name.', 'buddypress')
     241  }))), createElement(Disabled, null, createElement(ServerSideRender, {
     242    block: "bp/member",
     243    attributes: attributes
     244  })));
     245};
     246
     247var editMemberBlock = compose([withSelect(function (select) {
     248  var editorSettings = select('core/editor').getEditorSettings();
     249  return {
     250    bpSettings: editorSettings.bp.members || {}
     251  };
     252})])(editMember);
     253registerBlockType('bp/member', {
     254  title: __('Member', 'buddypress'),
     255  description: __('BuddyPress Member.', 'buddypress'),
     256  icon: 'admin-users',
     257  category: 'buddypress',
     258  attributes: {
     259    itemID: {
     260      type: 'integer',
     261      default: 0
     262    },
     263    avatarSize: {
     264      type: 'string',
     265      default: 'full'
     266    },
     267    displayMentionSlug: {
     268      type: 'boolean',
     269      default: true
     270    },
     271    displayActionButton: {
     272      type: 'boolean',
     273      default: true
     274    },
     275    displayCoverImage: {
     276      type: 'boolean',
     277      default: true
     278    }
     279  },
     280  edit: editMemberBlock
     281});
     282},{}]},{},["TmUL"], null)
     283 No newline at end of file
  • src/class-buddypress.php

    diff --git src/class-buddypress.php src/class-buddypress.php
    index a5d449ebd..e4e559eae 100644
    class BuddyPress { 
    488488                require( $this->plugin_dir . 'bp-core/bp-core-loader.php'           );
    489489                require( $this->plugin_dir . 'bp-core/bp-core-customizer-email.php' );
    490490                require( $this->plugin_dir . 'bp-core/bp-core-rest-api.php'         );
     491                require( $this->plugin_dir . 'bp-core/bp-core-blocks.php'           );
    491492
    492493                // Maybe load deprecated functionality (this double negative is proof positive!).
    493494                if ( ! bp_get_option( '_bp_ignore_deprecated_code', ! $this->load_deprecated ) ) {
    class BuddyPress { 
    554555                        'BP_Attachment_Cover_Image'                  => 'core',
    555556                        'BP_Attachment'                              => 'core',
    556557                        'BP_Button'                                  => 'core',
     558                        'BP_Block'                                   => 'core',
    557559                        'BP_Component'                               => 'core',
    558560                        'BP_Customizer_Control_Range'                => 'core',
    559561                        'BP_Date_Query'                              => 'core',
  • new file src/js/bp-core/js/block-components/.babelrc

    diff --git src/js/bp-core/js/block-components/.babelrc src/js/bp-core/js/block-components/.babelrc
    new file mode 100644
    index 000000000..ab258a69c
    - +  
     1{
     2        "presets": ["@wordpress/default"]
     3}
  • new file src/js/bp-core/js/block-components/block-components.js

    diff --git src/js/bp-core/js/block-components/block-components.js src/js/bp-core/js/block-components/block-components.js
    new file mode 100644
    index 000000000..32c0026cd
    - +  
     1export { default as blockComponents } from './components';
  • new file src/js/bp-core/js/block-components/components/autocompleter.js

    diff --git src/js/bp-core/js/block-components/components/autocompleter.js src/js/bp-core/js/block-components/components/autocompleter.js
    new file mode 100644
    index 000000000..30d0c7c2d
    - +  
     1/**
     2 * WordPress dependencies.
     3 */
     4const { Component, Fragment, createElement } = wp.element;
     5const { Popover } = wp.components;
     6const { apiFetch } = wp;
     7const { __ } = wp.i18n;
     8
     9class AutoCompleter extends Component {
     10        constructor() {
     11                super( ...arguments );
     12
     13                this.state = {
     14                        search: '',
     15                        items: [],
     16                        error: '',
     17                };
     18
     19                this.searchItemName = this.searchItemName.bind( this );
     20                this.selectItemName = this.selectItemName.bind( this );
     21        }
     22
     23        searchItemName( value ) {
     24                const { search } = this.state;
     25                const { component, objectStatus } = this.props;
     26                this.setState( { search: value } );
     27
     28                if ( value.length < search.length ) {
     29                        this.setState( { items: [] } );
     30                }
     31
     32                let path= '/buddypress/v1/' + component;
     33
     34                if ( value ) {
     35                        path += '?search=' + encodeURIComponent( value );
     36                }
     37
     38                if ( objectStatus ) {
     39                        path += '&status=' + objectStatus;
     40                }
     41
     42                apiFetch( { path:  path } ).then( items => {
     43                        this.setState( { items: items } );
     44                }, error => {
     45                        this.setState( { error: error.message } );
     46                } );
     47        }
     48
     49        selectItemName( event, itemID ) {
     50                const { onSelectItem } = this.props;
     51                event.preventDefault();
     52
     53                this.setState( {
     54                        search: '',
     55                        items: [],
     56                        error: '',
     57                } );
     58
     59                return onSelectItem( { itemID: itemID } );
     60        }
     61
     62        render() {
     63                const { search, items } = this.state;
     64                let { ariaLabel, placeholder, useAvatar } = this.props;
     65                let itemsList;
     66
     67                if ( ! ariaLabel ) {
     68                        ariaLabel = __( 'Item\'s name', 'buddypress' );
     69                }
     70
     71                if ( ! placeholder ) {
     72                        placeholder = __( 'Enter Item\'s name here…', 'buddypress' );
     73                }
     74
     75                if ( items.length ) {
     76                        itemsList = items.map( ( item ) => {
     77                                return (
     78                                        <button
     79                                                type="button" key={ 'editor-autocompleters__item-item-' + item.id }
     80                                                role="option"
     81                                                aria-selected="true"
     82                                                className="components-button components-autocomplete__result editor-autocompleters__user"
     83                                                onClick={ ( event ) => this.selectItemName( event, item.id ) }
     84                                        >
     85                                                { useAvatar && (
     86                                                        <img key="avatar" className="editor-autocompleters__user-avatar" alt="" src={ item.avatar_urls.thumb } />
     87                                                ) }
     88                                                <span key="name" className="editor-autocompleters__user-name">{ item.name }</span>
     89
     90                                                { item.mention_name && (
     91                                                        <span key="slug" className="editor-autocompleters__user-slug">{ item.mention_name }</span>
     92                                                ) }
     93                                        </button>
     94                                );
     95                        } );
     96                }
     97
     98                return (
     99                        <Fragment>
     100                                <input
     101                                        type="text"
     102                                        value={ search }
     103                                        className="components-placeholder__input"
     104                                        aria-label={ ariaLabel }
     105                                        placeholder={ placeholder }
     106                                        onChange={ ( event ) => this.searchItemName( event.target.value ) }
     107                                />
     108                                { 0 !== items.length &&
     109                                        <Popover
     110                                                className="components-autocomplete__popover"
     111                                                focusOnMount={ false }
     112                                                position="bottom left"
     113                                        >
     114                                                <div className="components-autocomplete__results">
     115                                                        { itemsList }
     116                                                </div>
     117                                        </Popover>
     118                                }
     119                        </Fragment>
     120                );
     121        }
     122}
     123
     124export default AutoCompleter;
  • new file src/js/bp-core/js/block-components/components/index.js

    diff --git src/js/bp-core/js/block-components/components/index.js src/js/bp-core/js/block-components/components/index.js
    new file mode 100644
    index 000000000..e255e9376
    - +  
     1// Components
     2import AutoCompleter from './autocompleter'
     3
     4export default {
     5        AutoCompleter
     6};
  • new file src/js/bp-groups/js/blocks/.babelrc

    diff --git src/js/bp-groups/js/blocks/.babelrc src/js/bp-groups/js/blocks/.babelrc
    new file mode 100644
    index 000000000..ab258a69c
    - +  
     1{
     2        "presets": ["@wordpress/default"]
     3}
  • new file src/js/bp-groups/js/blocks/group.js

    diff --git src/js/bp-groups/js/blocks/group.js src/js/bp-groups/js/blocks/group.js
    new file mode 100644
    index 000000000..6a1c4c869
    - +  
     1/**
     2 * WordPress dependencies.
     3 */
     4const { registerBlockType } = wp.blocks;
     5const { createElement, Fragment } = wp.element;
     6const { Placeholder, Disabled, PanelBody, SelectControl, ToggleControl, Toolbar, ToolbarButton } = wp.components;
     7const { InspectorControls, BlockControls } = wp.blockEditor;
     8const { withSelect } = wp.data;
     9const { compose } = wp.compose;
     10const { ServerSideRender } = wp.editor;
     11const { __ } = wp.i18n;
     12
     13/**
     14 * BuddyPress dependencies.
     15 */
     16const { AutoCompleter } = bp.blockComponents;
     17
     18const AVATAR_SIZES = [
     19        {
     20                label: __( 'None', 'buddypress' ),
     21                value: 'none',
     22        },
     23        {
     24                label: __( 'Thumb', 'buddypress' ),
     25                value: 'thumb',
     26        },
     27        {
     28                label: __( 'Full', 'buddypress' ),
     29                value: 'full',
     30        },
     31];
     32
     33const editGroup = ( { attributes, setAttributes, bpSettings } ) => {
     34        const { isAvatarEnabled, isCoverImageEnabled } = bpSettings;
     35        const { avatarSize, displayDescription, displayActionButton, displayCoverImage } = attributes;
     36
     37        if ( ! attributes.itemID ) {
     38                return (
     39                        <Placeholder
     40                                icon="buddicons-groups"
     41                                label={ __( 'BuddyPress Group', 'buddypress' ) }
     42                                instructions={ __( 'Start typing the name of the group you want to feature into this post.', 'buddypress' ) }
     43                        >
     44                                <AutoCompleter
     45                                        component="groups"
     46                                        objectStatus="public"
     47                                        ariaLabel={ __( 'Group\'s name', 'buddypress' ) }
     48                                        placeholder={ __( 'Enter Group\'s name here…', 'buddypress' ) }
     49                                        onSelectItem={ setAttributes }
     50                                        useAvatar={ isAvatarEnabled }
     51                                />
     52                        </Placeholder>
     53                );
     54        }
     55
     56        return (
     57                <Fragment>
     58                        <BlockControls>
     59                                <Toolbar>
     60                                        <ToolbarButton
     61                                                icon="edit"
     62                                                title={ __( 'Select another group', 'buddypress' ) }
     63                                                onClick={ () =>{
     64                                                        setAttributes( { itemID: 0 } );
     65                                                } }
     66                                        />
     67                                </Toolbar>
     68                        </BlockControls>
     69                        <InspectorControls>
     70                                <PanelBody title={ __( 'Group\'s home button settings', 'buddypress' ) } initialOpen={ true }>
     71                                        <ToggleControl
     72                                                label={ __( 'Display Group\'s home button', 'buddypress' ) }
     73                                                checked={ !! displayActionButton }
     74                                                onChange={ () => {
     75                                                        setAttributes( { displayActionButton: ! displayActionButton } );
     76                                                } }
     77                                                help={
     78                                                        displayActionButton
     79                                                                ? __( 'Include a link to the group\'s home page under their name.', 'buddypress' )
     80                                                                : __( 'Toggle to display a link to the group\'s home page under their name.', 'buddypress' )
     81                                                }
     82                                        />
     83                                </PanelBody>
     84                                <PanelBody title={ __( 'Description settings', 'buddypress' ) } initialOpen={ false }>
     85                                        <ToggleControl
     86                                                label={ __( 'Display group\'s description', 'buddypress' ) }
     87                                                checked={ !! displayDescription }
     88                                                onChange={ () => {
     89                                                        setAttributes( { displayDescription: ! displayDescription } );
     90                                                } }
     91                                                help={
     92                                                        displayDescription
     93                                                                ? __( 'Include the group\'s description under their name.', 'buddypress' )
     94                                                                : __( 'Toggle to display the group\'s description under their name.', 'buddypress' )
     95                                                }
     96                                        />
     97                                </PanelBody>
     98                                { isAvatarEnabled && (
     99                                        <PanelBody title={ __( 'Avatar settings', 'buddypress' ) } initialOpen={ false }>
     100                                                <SelectControl
     101                                                        label={ __( 'Size', 'buddypress' ) }
     102                                                        value={ avatarSize }
     103                                                        options={ AVATAR_SIZES }
     104                                                        onChange={ ( option ) => {
     105                                                                setAttributes( { avatarSize: option } );
     106                                                        } }
     107                                                />
     108                                        </PanelBody>
     109                                ) }
     110                                { isCoverImageEnabled && (
     111                                        <PanelBody title={ __( 'Cover image settings', 'buddypress' ) } initialOpen={ false }>
     112                                                <ToggleControl
     113                                                        label={ __( 'Display Cover Image', 'buddypress' ) }
     114                                                        checked={ !! displayCoverImage }
     115                                                        onChange={ () => {
     116                                                                setAttributes( { displayCoverImage: ! displayCoverImage } );
     117                                                        } }
     118                                                        help={
     119                                                                displayCoverImage
     120                                                                        ? __( 'Include the group\'s cover image over their name.', 'buddypress' )
     121                                                                        : __( 'Toggle to display the group\'s cover image over their name.', 'buddypress' )
     122                                                        }
     123                                                />
     124                                        </PanelBody>
     125                                ) }
     126                        </InspectorControls>
     127                        <Disabled>
     128                                <ServerSideRender block="bp/group" attributes={ attributes } />
     129                        </Disabled>
     130                </Fragment>
     131        );
     132};
     133
     134const editGroupBlock = compose( [
     135        withSelect( ( select ) => {
     136                const editorSettings = select( 'core/editor' ).getEditorSettings();
     137                return {
     138                        bpSettings: editorSettings.bp.groups || {},
     139                };
     140        } ),
     141] )( editGroup );
     142
     143registerBlockType( 'bp/group', {
     144        title: __( 'Group', 'buddypress' ),
     145
     146        description: __( 'BuddyPress Group.', 'buddypress' ),
     147
     148        icon: 'buddicons-groups',
     149
     150        category: 'buddypress',
     151
     152        attributes: {
     153                itemID: {
     154                        type: 'integer',
     155                        default: 0,
     156                },
     157                avatarSize: {
     158                        type: 'string',
     159                        default: 'full',
     160                },
     161                displayDescription: {
     162                        type: 'boolean',
     163                        default: true,
     164                },
     165                displayActionButton: {
     166                        type: 'boolean',
     167                        default: true,
     168                },
     169                displayCoverImage: {
     170                        type: 'boolean',
     171                        default: true,
     172                },
     173        },
     174
     175        edit: editGroupBlock,
     176} );
  • new file src/js/bp-members/js/blocks/.babelrc

    diff --git src/js/bp-members/js/blocks/.babelrc src/js/bp-members/js/blocks/.babelrc
    new file mode 100644
    index 000000000..ab258a69c
    - +  
     1{
     2        "presets": ["@wordpress/default"]
     3}
  • new file src/js/bp-members/js/blocks/member.js

    diff --git src/js/bp-members/js/blocks/member.js src/js/bp-members/js/blocks/member.js
    new file mode 100644
    index 000000000..1d0e650ec
    - +  
     1/**
     2 * WordPress dependencies.
     3 */
     4const { registerBlockType } = wp.blocks;
     5const { createElement, Fragment } = wp.element;
     6const { Placeholder, Disabled, PanelBody, SelectControl, ToggleControl, Toolbar, ToolbarButton } = wp.components;
     7const { InspectorControls, BlockControls } = wp.blockEditor;
     8const { withSelect } = wp.data;
     9const { compose } = wp.compose;
     10const { ServerSideRender } = wp.editor;
     11const { __ } = wp.i18n;
     12
     13/**
     14 * BuddyPress dependencies.
     15 */
     16const { AutoCompleter } = bp.blockComponents;
     17
     18const AVATAR_SIZES = [
     19        {
     20                label: __( 'None', 'buddypress' ),
     21                value: 'none',
     22        },
     23        {
     24                label: __( 'Thumb', 'buddypress' ),
     25                value: 'thumb',
     26        },
     27        {
     28                label: __( 'Full', 'buddypress' ),
     29                value: 'full',
     30        },
     31];
     32
     33const editMember = ( { attributes, setAttributes, bpSettings } ) => {
     34        const { isAvatarEnabled, isMentionEnabled, isCoverImageEnabled } = bpSettings;
     35        const { avatarSize, displayMentionSlug, displayActionButton, displayCoverImage } = attributes;
     36
     37        if ( ! attributes.itemID ) {
     38                return (
     39                        <Placeholder
     40                                icon="admin-users"
     41                                label={ __( 'BuddyPress Member', 'buddypress' ) }
     42                                instructions={ __( 'Start typing the name of the member you want to feature into this post.', 'buddypress' ) }
     43                        >
     44                                <AutoCompleter
     45                                        component="members"
     46                                        ariaLabel={ __( 'Member\'s username', 'buddypress' ) }
     47                                        placeholder={ __( 'Enter Member\'s username here…', 'buddypress' ) }
     48                                        onSelectItem={ setAttributes }
     49                                        useAvatar={ isAvatarEnabled }
     50                                />
     51                        </Placeholder>
     52                );
     53        }
     54
     55        return (
     56                <Fragment>
     57                        <BlockControls>
     58                                <Toolbar>
     59                                        <ToolbarButton
     60                                                icon="edit"
     61                                                title={ __( 'Select another member', 'buddypress' ) }
     62                                                onClick={ () =>{
     63                                                        setAttributes( { itemID: 0 } );
     64                                                } }
     65                                        />
     66                                </Toolbar>
     67                        </BlockControls>
     68                        <InspectorControls>
     69                                <PanelBody title={ __( 'Profile button settings', 'buddypress' ) } initialOpen={ true }>
     70                                        <ToggleControl
     71                                                label={ __( 'Display Profile button', 'buddypress' ) }
     72                                                checked={ !! displayActionButton }
     73                                                onChange={ () => {
     74                                                        setAttributes( { displayActionButton: ! displayActionButton } );
     75                                                } }
     76                                                help={
     77                                                        displayActionButton
     78                                                                ? __( 'Include a link to the user\'s profile page under their display name.', 'buddypress' )
     79                                                                : __( 'Toggle to display a link to the user\'s profile page under their display name.', 'buddypress' )
     80                                                }
     81                                        />
     82                                </PanelBody>
     83                                { isAvatarEnabled && (
     84                                        <PanelBody title={ __( 'Avatar settings', 'buddypress' ) } initialOpen={ false }>
     85                                                <SelectControl
     86                                                        label={ __( 'Size', 'buddypress' ) }
     87                                                        value={ avatarSize }
     88                                                        options={ AVATAR_SIZES }
     89                                                        onChange={ ( option ) => {
     90                                                                setAttributes( { avatarSize: option } );
     91                                                        } }
     92                                                />
     93                                        </PanelBody>
     94                                ) }
     95                                { isCoverImageEnabled && (
     96                                        <PanelBody title={ __( 'Cover image settings', 'buddypress' ) } initialOpen={ false }>
     97                                                <ToggleControl
     98                                                        label={ __( 'Display Cover Image', 'buddypress' ) }
     99                                                        checked={ !! displayCoverImage }
     100                                                        onChange={ () => {
     101                                                                setAttributes( { displayCoverImage: ! displayCoverImage } );
     102                                                        } }
     103                                                        help={
     104                                                                displayCoverImage
     105                                                                        ? __( 'Include the user\'s cover image over their display name.', 'buddypress' )
     106                                                                        : __( 'Toggle to display the user\'s cover image over their display name.', 'buddypress' )
     107                                                        }
     108                                                />
     109                                        </PanelBody>
     110                                ) }
     111                                { isMentionEnabled && (
     112                                        <PanelBody title={ __( 'Mention settings', 'buddypress' ) } initialOpen={ false }>
     113                                                <ToggleControl
     114                                                        label={ __( 'Display Mention slug', 'buddypress' ) }
     115                                                        checked={ !! displayMentionSlug }
     116                                                        onChange={ () => {
     117                                                                setAttributes( { displayMentionSlug: ! displayMentionSlug } );
     118                                                        } }
     119                                                        help={
     120                                                                displayMentionSlug
     121                                                                        ? __( 'Include the user\'s mention name under their display name.', 'buddypress' )
     122                                                                        : __( 'Toggle to display the user\'s mention name under their display name.', 'buddypress' )
     123                                                        }
     124                                                />
     125                                        </PanelBody>
     126                                ) }
     127                        </InspectorControls>
     128                        <Disabled>
     129                                <ServerSideRender block="bp/member" attributes={ attributes } />
     130                        </Disabled>
     131                </Fragment>
     132        );
     133};
     134
     135const editMemberBlock = compose( [
     136        withSelect( ( select ) => {
     137                const editorSettings = select( 'core/editor' ).getEditorSettings();
     138                return {
     139                        bpSettings: editorSettings.bp.members || {},
     140                };
     141        } ),
     142] )( editMember );
     143
     144registerBlockType( 'bp/member', {
     145        title: __( 'Member', 'buddypress' ),
     146
     147        description: __( 'BuddyPress Member.', 'buddypress' ),
     148
     149        icon: 'admin-users',
     150
     151        category: 'buddypress',
     152
     153        attributes: {
     154                itemID: {
     155                        type: 'integer',
     156                        default: 0,
     157                },
     158                avatarSize: {
     159                        type: 'string',
     160                        default: 'full',
     161                },
     162                displayMentionSlug: {
     163                        type: 'boolean',
     164                        default: true,
     165                },
     166                displayActionButton: {
     167                        type: 'boolean',
     168                        default: true,
     169                },
     170                displayCoverImage: {
     171                        type: 'boolean',
     172                        default: true,
     173                },
     174        },
     175
     176        edit: editMemberBlock,
     177} );